diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 2454b46..87140b1 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,7 +5,21 @@ module.exports = { node: true, }, extends: ['eslint:recommended', 'plugin:import/recommended'], - overrides: [], + overrides: [ + { + files: ['**/*.test.js', 'test/**/*.js'], + env: { + // technically, we are using vitest, but that's pretty similar to jest + jest: true, + }, + settings: { + 'import/ignore': [ + // for some reason, `import {it} from "vitest";` throws an error + /node_modules\/vitest\/dist\/index\.js$/.source, + ], + }, + }, + ], parserOptions: { sourceType: 'module', }, diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 162b984..6e2bf8d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,11 +5,11 @@ on: pull_request: jobs: - lint: + test: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x] + node-version: [18.x, 20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v3 @@ -18,11 +18,13 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: 'npm' - - run: npm ci # throws an error if the package-lock.json file is out of sync + - run: npm ci - name: Check for linting errors (run `npm run lint:fix` to fix) run: npm run lint - name: Check for prettier errors (run `npm run format` to fix) run: npx prettier --check . + - name: Run unit tests + run: npm test - name: Build the npm package to publish run: npm pack - uses: actions/upload-artifact@v3 diff --git a/.gitignore b/.gitignore index 434a2a5..12591a1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ node_modules dist .vscode *.d.ts -*.tgz +tsconfig.tsbuildinfo \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..7e80190 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,23 @@ +Original dagre-d3 copyright: Copyright (c) 2013 Chris Pettitt +Original dagre copyright: Copyright (c) 2012-2014 Chris Pettitt +Original graphlib copyright: Copyright (c) 2012-2014 Chris Pettitt + +Copyright (c) 2022-2024 Thibaut Lassalle, David Newell, Alois Klink, Sidharth Vinod and dagre-es contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 14541fd..df6cf1d 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,26 @@

- - dagre-d3-es on npm -   + dagre-d3-es on npm  + dagre-d3-es on npm

# dagre-d3-es -The [dagre-d3](https://github.com/dagrejs) library is not maintained anymore. +[dagre-d3-es](https://www.npmjs.com/package/dagre-d3-es) is a fork of [dagre-d3](https://github.com/dagrejs) using the more modern ES6 javascript syntax. -[dagre-d3-es](https://www.npmjs.com/package/dagre-d3-es) is a fork using the more modern ES6 javascript syntax. It uses [ES](https://262.ecma-international.org/6.0/) modules, thus the name [dagre-d3-es](https://www.npmjs.com/package/dagre-d3-es). [dagre-d3-es](https://www.npmjs.com/package/dagre-d3-es) follows [d3](https://www.npmjs.com/package/d3) versions. Ex: dagre-d3-es version 7 depends on [d3](https://www.npmjs.com/package/d3) version 7. ## Demos -[Simple graph demo](https://codesandbox.io/s/dagre-d3-es-tree-9ywg9) using react, dagre-d3-es, [d3](https://www.npmjs.com/package/d3) version 7.2. +[Simple graph demo](https://codesandbox.io/s/dagre-d3-es-tree-9ywg9) using react, dagre-d3-es. + +Clone and run [dagre-es-example](https://github.com/tbo47/dagre-es-example) using angular. ## Install ``` -npm install dagre-d3-es --save +npm install dagre-d3-es ``` ## Code example diff --git a/package-lock.json b/package-lock.json index 817201c..51ce0cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,34 +1,575 @@ { "name": "dagre-d3-es", - "version": "7.0.10", + "version": "7.0.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dagre-d3-es", - "version": "7.0.10", + "version": "7.0.11", "license": "MIT", "dependencies": { - "d3": "^7.8.2", + "d3": "^7.9.0", "lodash-es": "^4.17.21" }, "devDependencies": { - "@types/d3": "^7.4.0", - "@types/lodash-es": "^4.17.6", - "eslint": "^8.34.0", - "eslint-plugin-import": "^2.27.5", - "prettier": "^2.8.4", - "typescript": "^4.9.5" + "@types/d3": "^7.4.3", + "@types/jest": "^29.5.13", + "@types/lodash-es": "^4.17.12", + "eslint-plugin-import": "^2.30.0", + "prettier": "^3.3.3", + "typescript": "^5.6.2", + "vitest": "^2.1.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "1.4.1", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -43,13 +584,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -58,8 +614,11 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=12.22" }, @@ -69,14 +628,72 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -87,16 +704,22 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 8" } }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -105,8 +728,248 @@ "node": ">= 8" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.2.tgz", + "integrity": "sha512-8Ao+EDmTPjZ1ZBABc1ohN7Ylx7UIYcjReZinigedTOnGFhIctyGPxY2II+hJ6gD2/vkDKZTyQ0e7++kwv6wDrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.2.tgz", + "integrity": "sha512-I+B1v0a4iqdS9DvYt1RJZ3W+Oh9EVWjbY6gp79aAYipIbxSLEoQtFQlZEnUuwhDXCqMxJ3hluxKAdPD+GiluFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.2.tgz", + "integrity": "sha512-BTHO7rR+LC67OP7I8N8GvdvnQqzFujJYWo7qCQ8fGdQcb8Gn6EQY+K1P+daQLnDCuWKbZ+gHAQZuKiQkXkqIYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.2.tgz", + "integrity": "sha512-1esGwDNFe2lov4I6GsEeYaAMHwkqk0IbuGH7gXGdBmd/EP9QddJJvTtTF/jv+7R8ZTYPqwcdLpMTxK8ytP6k6Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.2.tgz", + "integrity": "sha512-GBHuY07x96OTEM3OQLNaUSUwrOhdMea/LDmlFHi/HMonrgF6jcFrrFFwJhhe84XtA1oK/Qh4yFS+VMREf6dobg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.2.tgz", + "integrity": "sha512-Dbfa9Sc1G1lWxop0gNguXOfGhaXQWAGhZUcqA0Vs6CnJq8JW/YOw/KvyGtQFmz4yDr0H4v9X248SM7bizYj4yQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.2.tgz", + "integrity": "sha512-Z1YpgBvFYhZIyBW5BoopwSg+t7yqEhs5HCei4JbsaXnhz/eZehT18DaXl957aaE9QK7TRGFryCAtStZywcQe1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.2.tgz", + "integrity": "sha512-66Zszr7i/JaQ0u/lefcfaAw16wh3oT72vSqubIMQqWzOg85bGCPhoeykG/cC5uvMzH80DQa2L539IqKht6twVA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.2.tgz", + "integrity": "sha512-HpJCMnlMTfEhwo19bajvdraQMcAq3FX08QDx3OfQgb+414xZhKNf3jNvLFYKbbDSGBBrQh5yNwWZrdK0g0pokg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.2.tgz", + "integrity": "sha512-/egzQzbOSRef2vYCINKITGrlwkzP7uXRnL+xU2j75kDVp3iPdcF0TIlfwTRF8woBZllhk3QaxNOEj2Ogh3t9hg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.2.tgz", + "integrity": "sha512-qgYbOEbrPfEkH/OnUJd1/q4s89FvNJQIUldx8X2F/UM5sEbtkqZpf2s0yly2jSCKr1zUUOY1hnTP2J1WOzMAdA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.2.tgz", + "integrity": "sha512-a0lkvNhFLhf+w7A95XeBqGQaG0KfS3hPFJnz1uraSdUe/XImkp/Psq0Ca0/UdD5IEAGoENVmnYrzSC9Y2a2uKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.2.tgz", + "integrity": "sha512-sSWBVZgzwtsuG9Dxi9kjYOUu/wKW+jrbzj4Cclabqnfkot8Z3VEHcIgyenA3lLn/Fu11uDviWjhctulkhEO60g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.2.tgz", + "integrity": "sha512-t/YgCbZ638R/r7IKb9yCM6nAek1RUvyNdfU0SHMDLOf6GFe/VG1wdiUAsxTWHKqjyzkRGg897ZfCpdo1bsCSsA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.2.tgz", + "integrity": "sha512-kTmX5uGs3WYOA+gYDgI6ITkZng9SP71FEMoHNkn+cnmb9Zuyyay8pf0oO5twtTwSjNGy1jlaWooTIr+Dw4tIbw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.2.tgz", + "integrity": "sha512-Yy8So+SoRz8I3NS4Bjh91BICPOSVgdompTIPYTByUqU66AXSIOgmW3Lv1ke3NORPqxdF+RdrZET+8vYai6f4aA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/d3": { - "version": "7.4.0", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", "dev": true, "license": "MIT", "dependencies": { @@ -143,12 +1006,16 @@ } }, "node_modules/@types/d3-array": { - "version": "3.0.3", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-axis": { - "version": "3.0.1", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", "dev": true, "license": "MIT", "dependencies": { @@ -156,7 +1023,9 @@ } }, "node_modules/@types/d3-brush": { - "version": "3.0.1", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "dev": true, "license": "MIT", "dependencies": { @@ -164,17 +1033,23 @@ } }, "node_modules/@types/d3-chord": { - "version": "3.0.1", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-color": { - "version": "3.1.0", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-contour": { - "version": "3.0.1", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", "dev": true, "license": "MIT", "dependencies": { @@ -183,17 +1058,23 @@ } }, "node_modules/@types/d3-delaunay": { - "version": "6.0.1", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-dispatch": { - "version": "3.0.1", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-drag": { - "version": "3.0.1", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -201,17 +1082,23 @@ } }, "node_modules/@types/d3-dsv": { - "version": "3.0.0", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-ease": { - "version": "3.0.0", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-fetch": { - "version": "3.0.1", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", "dev": true, "license": "MIT", "dependencies": { @@ -219,17 +1106,23 @@ } }, "node_modules/@types/d3-force": { - "version": "3.0.3", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-format": { - "version": "3.0.1", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-geo": { - "version": "3.0.2", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", "dev": true, "license": "MIT", "dependencies": { @@ -237,12 +1130,16 @@ } }, "node_modules/@types/d3-hierarchy": { - "version": "3.1.0", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-interpolate": { - "version": "3.0.1", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "dev": true, "license": "MIT", "dependencies": { @@ -250,27 +1147,37 @@ } }, "node_modules/@types/d3-path": { - "version": "3.0.0", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-polygon": { - "version": "3.0.0", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-quadtree": { - "version": "3.0.2", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-random": { - "version": "3.0.1", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-scale": { - "version": "4.0.2", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -278,17 +1185,23 @@ } }, "node_modules/@types/d3-scale-chromatic": { - "version": "3.0.0", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-selection": { - "version": "3.0.3", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", + "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-shape": { - "version": "3.1.0", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", "dev": true, "license": "MIT", "dependencies": { @@ -296,64 +1209,286 @@ } }, "node_modules/@types/d3-time": { - "version": "3.0.0", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-time-format": { - "version": "4.0.0", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-timer": { - "version": "3.0.0", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "dev": true, "license": "MIT" }, "node_modules/@types/d3-transition": { - "version": "3.0.2", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", + "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.13", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz", + "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "22.5.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", + "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/@vitest/expect": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.1.tgz", + "integrity": "sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.1", + "@vitest/utils": "2.1.1", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.1.tgz", + "integrity": "sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "^2.1.0-beta.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.11" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/spy": "2.1.1", + "msw": "^2.3.5", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.1.tgz", + "integrity": "sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.1.tgz", + "integrity": "sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==", "dev": true, "license": "MIT", "dependencies": { - "@types/d3-selection": "*" + "@vitest/utils": "2.1.1", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/d3-zoom": { - "version": "3.0.1", + "node_modules/@vitest/snapshot": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.1.tgz", + "integrity": "sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==", "dev": true, "license": "MIT", "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" + "@vitest/pretty-format": "2.1.1", + "magic-string": "^0.30.11", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/geojson": { - "version": "7946.0.10", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.14.191", + "node_modules/@vitest/spy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.1.tgz", + "integrity": "sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/@types/lodash-es": { - "version": "4.17.6", + "node_modules/@vitest/utils": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.1.tgz", + "integrity": "sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/lodash": "*" + "@vitest/pretty-format": "2.1.1", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/acorn": { - "version": "8.8.2", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -363,16 +1498,22 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -386,14 +1527,19 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -408,18 +1554,41 @@ }, "node_modules/argparse": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "Python-2.0" + "license": "Python-2.0", + "peer": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/array-includes": { - "version": "3.1.6", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -429,14 +1598,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { - "version": "1.3.1", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -447,13 +1639,15 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.1", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -463,13 +1657,66 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { @@ -477,13 +1724,44 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { - "version": "1.0.2", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -491,14 +1769,36 @@ }, "node_modules/callsites": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -512,8 +1812,36 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -525,11 +1853,15 @@ }, "node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/commander": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "engines": { "node": ">= 10" @@ -537,13 +1869,18 @@ }, "node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -554,7 +1891,9 @@ } }, "node_modules/d3": { - "version": "7.8.2", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", "license": "ISC", "dependencies": { "d3-array": "3", @@ -593,7 +1932,9 @@ } }, "node_modules/d3-array": { - "version": "3.2.1", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", "dependencies": { "internmap": "1 - 2" @@ -604,6 +1945,8 @@ }, "node_modules/d3-axis": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", "license": "ISC", "engines": { "node": ">=12" @@ -611,6 +1954,8 @@ }, "node_modules/d3-brush": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -625,6 +1970,8 @@ }, "node_modules/d3-chord": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", "license": "ISC", "dependencies": { "d3-path": "1 - 3" @@ -635,13 +1982,17 @@ }, "node_modules/d3-color": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/d3-contour": { - "version": "4.0.0", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", "license": "ISC", "dependencies": { "d3-array": "^3.2.0" @@ -651,7 +2002,9 @@ } }, "node_modules/d3-delaunay": { - "version": "6.0.2", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "license": "ISC", "dependencies": { "delaunator": "5" @@ -662,6 +2015,8 @@ }, "node_modules/d3-dispatch": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", "license": "ISC", "engines": { "node": ">=12" @@ -669,6 +2024,8 @@ }, "node_modules/d3-drag": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -680,6 +2037,8 @@ }, "node_modules/d3-dsv": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "license": "ISC", "dependencies": { "commander": "7", @@ -703,6 +2062,8 @@ }, "node_modules/d3-ease": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", "license": "BSD-3-Clause", "engines": { "node": ">=12" @@ -710,6 +2071,8 @@ }, "node_modules/d3-fetch": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "license": "ISC", "dependencies": { "d3-dsv": "1 - 3" @@ -720,6 +2083,8 @@ }, "node_modules/d3-force": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -732,13 +2097,17 @@ }, "node_modules/d3-format": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/d3-geo": { - "version": "3.0.1", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", "license": "ISC", "dependencies": { "d3-array": "2.5.0 - 3" @@ -749,6 +2118,8 @@ }, "node_modules/d3-hierarchy": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", "license": "ISC", "engines": { "node": ">=12" @@ -756,6 +2127,8 @@ }, "node_modules/d3-interpolate": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "license": "ISC", "dependencies": { "d3-color": "1 - 3" @@ -765,7 +2138,9 @@ } }, "node_modules/d3-path": { - "version": "3.0.1", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "license": "ISC", "engines": { "node": ">=12" @@ -773,6 +2148,8 @@ }, "node_modules/d3-polygon": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", "license": "ISC", "engines": { "node": ">=12" @@ -780,6 +2157,8 @@ }, "node_modules/d3-quadtree": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", "license": "ISC", "engines": { "node": ">=12" @@ -787,6 +2166,8 @@ }, "node_modules/d3-random": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", "license": "ISC", "engines": { "node": ">=12" @@ -794,6 +2175,8 @@ }, "node_modules/d3-scale": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "license": "ISC", "dependencies": { "d3-array": "2.10.0 - 3", @@ -807,7 +2190,9 @@ } }, "node_modules/d3-scale-chromatic": { - "version": "3.0.0", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -819,16 +2204,20 @@ }, "node_modules/d3-selection": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/d3-shape": { - "version": "3.1.0", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "license": "ISC", "dependencies": { - "d3-path": "1 - 3" + "d3-path": "^3.1.0" }, "engines": { "node": ">=12" @@ -836,6 +2225,8 @@ }, "node_modules/d3-time": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "license": "ISC", "dependencies": { "d3-array": "2 - 3" @@ -846,6 +2237,8 @@ }, "node_modules/d3-time-format": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "license": "ISC", "dependencies": { "d3-time": "1 - 3" @@ -856,6 +2249,8 @@ }, "node_modules/d3-timer": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "license": "ISC", "engines": { "node": ">=12" @@ -863,6 +2258,8 @@ }, "node_modules/d3-transition": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -880,6 +2277,8 @@ }, "node_modules/d3-zoom": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -892,12 +2291,68 @@ "node": ">=12" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { - "version": "4.3.4", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -908,16 +2363,50 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, - "node_modules/define-properties": { + "node_modules/define-data-property": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "license": "MIT", "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -929,16 +2418,31 @@ } }, "node_modules/delaunator": { - "version": "5.0.0", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", "license": "ISC", "dependencies": { - "robust-predicates": "^3.0.0" + "robust-predicates": "^3.0.2" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/doctrine": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -947,35 +2451,58 @@ } }, "node_modules/es-abstract": { - "version": "1.20.5", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", + "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "unbox-primitive": "^1.0.2" + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -984,16 +2511,71 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-shim-unscopables": { + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "license": "MIT", "dependencies": { @@ -1002,16 +2584,58 @@ "is-symbol": "^1.0.2" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escape-string-regexp": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -1020,49 +2644,50 @@ } }, "node_modules/eslint": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", - "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@eslint/eslintrc": "^1.4.1", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -1076,17 +2701,21 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1094,7 +2723,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.7.4", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.0.tgz", + "integrity": "sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1111,6 +2742,8 @@ }, "node_modules/eslint-module-utils/node_modules/debug": { "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1118,25 +2751,30 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.27.5", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "dev": true, "license": "MIT", "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.9.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -1147,6 +2785,8 @@ }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1155,6 +2795,8 @@ }, "node_modules/eslint-plugin-import/node_modules/doctrine": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1165,58 +2807,48 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/espree": { - "version": "9.4.1", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1226,9 +2858,12 @@ } }, "node_modules/esquery": { - "version": "1.4.0", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -1238,8 +2873,11 @@ }, "node_modules/esrecurse": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -1249,47 +2887,94 @@ }, "node_modules/estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fastq": { - "version": "1.14.0", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "reusify": "^1.0.4" } }, "node_modules/file-entry-cache": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -1297,10 +2982,26 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -1313,11 +3014,15 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { @@ -1325,29 +3030,67 @@ } }, "node_modules/flatted": { - "version": "3.2.7", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } }, "node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } }, "node_modules/function-bind": { - "version": "1.1.1", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { - "version": "1.1.5", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" }, "engines": { "node": ">= 0.4" @@ -1358,32 +3101,54 @@ }, "node_modules/functions-have-names": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { - "version": "1.1.3", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-symbol-description": { - "version": "1.0.0", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -1394,8 +3159,12 @@ }, "node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1413,8 +3182,11 @@ }, "node_modules/glob-parent": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -1423,9 +3195,12 @@ } }, "node_modules/globals": { - "version": "13.20.0", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -1436,8 +3211,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gopd": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, "license": "MIT", "dependencies": { @@ -1447,24 +3241,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/has": { - "version": "1.0.3", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } + "peer": true }, "node_modules/has-bigints": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, "license": "MIT", "funding": { @@ -1473,6 +3268,8 @@ }, "node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -1480,11 +3277,26 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1492,6 +3304,8 @@ }, "node_modules/has-symbols": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true, "license": "MIT", "engines": { @@ -1502,11 +3316,13 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -1515,8 +3331,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -1526,17 +3357,23 @@ } }, "node_modules/ignore": { - "version": "5.2.4", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -1550,16 +3387,23 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.19" } }, "node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1567,16 +3411,21 @@ }, "node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/internal-slot": { - "version": "1.0.4", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "hasown": "^2.0.0", "side-channel": "^1.0.4" }, "engines": { @@ -1585,13 +3434,34 @@ }, "node_modules/internmap": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", "license": "ISC", "engines": { "node": ">=12" } }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-bigint": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "license": "MIT", "dependencies": { @@ -1603,6 +3473,8 @@ }, "node_modules/is-boolean-object": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "license": "MIT", "dependencies": { @@ -1618,6 +3490,8 @@ }, "node_modules/is-callable": { "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", "engines": { @@ -1628,11 +3502,32 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", "dev": true, "license": "MIT", "dependencies": { - "has": "^1.0.3" + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1640,6 +3535,8 @@ }, "node_modules/is-date-object": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1654,6 +3551,8 @@ }, "node_modules/is-extglob": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -1662,6 +3561,8 @@ }, "node_modules/is-glob": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -1672,7 +3573,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "license": "MIT", "engines": { @@ -1682,8 +3585,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-number-object": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1698,14 +3613,19 @@ }, "node_modules/is-path-inside": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } }, "node_modules/is-regex": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "license": "MIT", "dependencies": { @@ -1720,11 +3640,16 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1732,6 +3657,8 @@ }, "node_modules/is-string": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "license": "MIT", "dependencies": { @@ -1744,49 +3671,161 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-symbol": { - "version": "1.0.4", + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-weakref": { - "version": "1.0.2", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/js-sdsl": { - "version": "4.2.0", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -1794,18 +3833,34 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json5": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "license": "MIT", "dependencies": { @@ -1815,10 +3870,24 @@ "json5": "lib/cli.js" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/levn": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -1829,8 +3898,11 @@ }, "node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -1843,15 +3915,56 @@ }, "node_modules/lodash-es": { "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true + }, + "node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } }, "node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -1862,7 +3975,9 @@ } }, "node_modules/minimist": { - "version": "1.2.7", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", "funding": { @@ -1870,25 +3985,56 @@ } }, "node_modules/ms": { - "version": "2.1.2", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/object-inspect": { - "version": "1.12.2", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "dev": true, "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object-keys": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "license": "MIT", "engines": { @@ -1896,12 +4042,14 @@ } }, "node_modules/object.assign": { - "version": "4.1.4", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, @@ -1912,14 +4060,50 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object.values": { - "version": "1.1.6", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -1930,23 +4114,29 @@ }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "wrappy": "1" } }, "node_modules/optionator": { - "version": "0.9.1", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -1954,8 +4144,11 @@ }, "node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -1968,8 +4161,11 @@ }, "node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -1982,8 +4178,11 @@ }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -1993,66 +4192,190 @@ }, "node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } }, "node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } }, "node_modules/path-parse": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "dev": true, "license": "MIT" }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, + "license": "MIT", "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/punycode": { - "version": "2.3.0", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } }, "node_modules/queue-microtask": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -2068,16 +4391,27 @@ "url": "https://feross.org/support" } ], + "license": "MIT", + "peer": true + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, "license": "MIT" }, "node_modules/regexp.prototype.flags": { - "version": "1.4.3", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -2086,23 +4420,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/resolve": { - "version": "1.22.1", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -2115,16 +4440,22 @@ }, "node_modules/resolve-from": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=4" } }, "node_modules/reusify": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -2132,8 +4463,12 @@ }, "node_modules/rimraf": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -2145,11 +4480,58 @@ } }, "node_modules/robust-predicates": { - "version": "3.0.1", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", "license": "Unlicense" }, + "node_modules/rollup": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.2.tgz", + "integrity": "sha512-JWWpTrZmqQGQWt16xvNn6KVIUz16VtZwl984TKw0dfqqRpFwtLJYYk1/4BTgplndMQKWUk/yB4uOShYmMzA2Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.2", + "@rollup/rollup-android-arm64": "4.22.2", + "@rollup/rollup-darwin-arm64": "4.22.2", + "@rollup/rollup-darwin-x64": "4.22.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.2", + "@rollup/rollup-linux-arm-musleabihf": "4.22.2", + "@rollup/rollup-linux-arm64-gnu": "4.22.2", + "@rollup/rollup-linux-arm64-musl": "4.22.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.2", + "@rollup/rollup-linux-riscv64-gnu": "4.22.2", + "@rollup/rollup-linux-s390x-gnu": "4.22.2", + "@rollup/rollup-linux-x64-gnu": "4.22.2", + "@rollup/rollup-linux-x64-musl": "4.22.2", + "@rollup/rollup-win32-arm64-msvc": "4.22.2", + "@rollup/rollup-win32-ia32-msvc": "4.22.2", + "@rollup/rollup-win32-x64-msvc": "4.22.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -2166,92 +4548,259 @@ } ], "license": "MIT", + "peer": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "queue-microtask": "^1.2.2" + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/rw": { - "version": "1.3.3", - "license": "BSD-3-Clause" + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } }, - "node_modules/safe-regex-test": { - "version": "1.0.0", + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "license": "MIT" + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" }, - "node_modules/semver": { - "version": "6.3.0", + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/shebang-command": { - "version": "2.0.0", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "escape-string-regexp": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.0.4", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimend": { - "version": "1.0.6", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.6", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2259,8 +4808,11 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2270,6 +4822,8 @@ }, "node_modules/strip-bom": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", "engines": { @@ -2278,8 +4832,11 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" }, @@ -2289,6 +4846,8 @@ }, "node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -2300,6 +4859,8 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", "engines": { @@ -2311,24 +4872,89 @@ }, "node_modules/text-table": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", + "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", "dev": true, "license": "MIT" }, + "node_modules/tinypool": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tsconfig-paths": { - "version": "3.14.1", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "node_modules/type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -2338,8 +4964,11 @@ }, "node_modules/type-fest": { "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=10" }, @@ -2347,8 +4976,87 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { - "version": "4.9.5", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2356,11 +5064,13 @@ "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "license": "MIT", "dependencies": { @@ -2373,18 +5083,178 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "punycode": "^2.1.0" } }, + "node_modules/vite": { + "version": "5.4.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", + "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.1.tgz", + "integrity": "sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.6", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.1.tgz", + "integrity": "sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.1", + "@vitest/mocker": "2.1.1", + "@vitest/pretty-format": "^2.1.1", + "@vitest/runner": "2.1.1", + "@vitest/snapshot": "2.1.1", + "@vitest/spy": "2.1.1", + "@vitest/utils": "2.1.1", + "chai": "^5.1.1", + "debug": "^4.3.6", + "magic-string": "^0.30.11", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.1", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.1", + "@vitest/ui": "2.1.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -2397,6 +5267,8 @@ }, "node_modules/which-boxed-primitive": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, "license": "MIT", "dependencies": { @@ -2410,23 +5282,69 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { - "version": "1.2.3", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index c0f831d..cc72335 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,17 @@ { "name": "dagre-d3-es", - "version": "7.0.10", + "version": "7.0.11", "description": "", - "author": "Thibaut Lassalle", "license": "MIT", "main": "src/index.js", "type": "module", "scripts": { - "generate_types": "tsc --build --clean && tsc --build && prettier --write .", + "generate_types": "find src -name '*.d.ts' -type f -delete ; tsc --build", "prepack": "npm run generate_types", "lint": "eslint .", "lint:fix": "eslint --fix .", "format": "prettier --write .", + "test": "vitest ", "my_1_npm_login": "npm login", "my_2_publish": "echo update_package_version ; git clean -xdf ; npm ci ; npm publish", "compile_js_as_ts": "tsc src/index.js --AllowJs --checkJs --outDir dist/" @@ -21,22 +21,42 @@ ], "repository": { "type": "git", - "url": "https://github.com/tbo47/dagre-es" + "url": "git+https://github.com/tbo47/dagre-es.git" }, "dependencies": { - "d3": "^7.8.2", + "d3": "^7.9.0", "lodash-es": "^4.17.21" }, "devDependencies": { - "@types/d3": "^7.4.0", - "@types/lodash-es": "^4.17.6", - "eslint": "^8.34.0", - "eslint-plugin-import": "^2.27.5", - "prettier": "^2.8.4", - "typescript": "^4.9.5" + "@types/d3": "^7.4.3", + "@types/jest": "^29.5.13", + "@types/lodash-es": "^4.17.12", + "eslint-plugin-import": "^2.30.0", + "prettier": "^3.3.3", + "typescript": "^5.6.2", + "vitest": "^2.1.1" }, + "contributors": [ + { + "name": "Thibaut Lassalle", + "url": "https://tbo47.github.io/" + }, + { + "name": "Alois Klink", + "url": "https://github.com/aloisklink" + }, + { + "name": "David Newell", + "url": "https://github.com/rustedgrail" + }, + { + "name": "Sidharth Vinod", + "url": "https://github.com/sidharthv96" + } + ], "files": [ - "src/**/*" + "src/**/*", + "!src/**/*.test.js" ], "types": "./src/index.d.ts" } diff --git a/src/dagre-js/create-edge-labels.js b/src/dagre-js/create-edge-labels.js index 214d180..66389cf 100644 --- a/src/dagre-js/create-edge-labels.js +++ b/src/dagre-js/create-edge-labels.js @@ -1,5 +1,4 @@ import * as d3 from 'd3'; -import * as _ from 'lodash-es'; import { addLabel } from './label/add-label.js'; import * as util from './util.js'; @@ -28,10 +27,10 @@ let createEdgeLabels = function (selection, g) { if (edge.labelId) { label.attr('id', edge.labelId); } - if (!_.has(edge, 'width')) { + if (!Object.prototype.hasOwnProperty.call(edge, 'width')) { edge.width = bbox.width; } - if (!_.has(edge, 'height')) { + if (!Object.prototype.hasOwnProperty.call(edge, 'height')) { edge.height = bbox.height; } }); diff --git a/src/dagre-js/create-edge-paths.js b/src/dagre-js/create-edge-paths.js index 6a613dd..80750c6 100644 --- a/src/dagre-js/create-edge-paths.js +++ b/src/dagre-js/create-edge-paths.js @@ -32,7 +32,7 @@ var createEdgePaths = function (selection, g, arrows) { util.applyClass( domEdge, edge['class'], - (domEdge.classed('update') ? 'update ' : '') + 'edgePath' + (domEdge.classed('update') ? 'update ' : '') + 'edgePath', ); }); diff --git a/src/dagre-js/create-nodes.js b/src/dagre-js/create-nodes.js index db9ef41..827453e 100644 --- a/src/dagre-js/create-nodes.js +++ b/src/dagre-js/create-nodes.js @@ -28,7 +28,7 @@ var createNodes = function (selection, g, shapes) { util.applyClass( thisGroup, node['class'], - (thisGroup.classed('update') ? 'update ' : '') + 'node' + (thisGroup.classed('update') ? 'update ' : '') + 'node', ); thisGroup.select('g.label').remove(); @@ -61,7 +61,7 @@ var createNodes = function (selection, g, shapes) { (node.paddingLeft - node.paddingRight) / 2 + ',' + (node.paddingTop - node.paddingBottom) / 2 + - ')' + ')', ); var root = d3.select(this); diff --git a/src/dagre-js/intersect/intersect-polygon.js b/src/dagre-js/intersect/intersect-polygon.js index c5400ef..075ca55 100644 --- a/src/dagre-js/intersect/intersect-polygon.js +++ b/src/dagre-js/intersect/intersect-polygon.js @@ -29,7 +29,7 @@ function intersectPolygon(node, polyPoints, point) { node, point, { x: left + p1.x, y: top + p1.y }, - { x: left + p2.x, y: top + p2.y } + { x: left + p2.x, y: top + p2.y }, ); if (intersect) { intersections.push(intersect); diff --git a/src/dagre-js/render.js b/src/dagre-js/render.js index fce88ff..302c34e 100644 --- a/src/dagre-js/render.js +++ b/src/dagre-js/render.js @@ -90,8 +90,27 @@ var EDGE_DEFAULT_ATTRS = { curve: d3.curveLinear, }; +/** + * @typedef {Object} Node + * @property {string} label - The label of the node. + * @property {number} [paddingX] - The horizontal padding of the node. + * @property {number} [paddingY] - The vertical padding of the node. + * @property {number} [padding] - The padding of the node for all directions. Overrides `paddingX` and `paddingY`. + * @property {number} [paddingLeft] - The left padding of the node. + * @property {number} [paddingRight] - The right padding of the node. + * @property {number} [_prevWidth] + * @property {number} [width] + * @property {number} [_prevHeight] + * @property {number} [height] + */ + +/** + * Pre-processes the graph by setting default labels and padding for nodes. + * @param {Object} g - The graph object. + */ function preProcessGraph(g) { g.nodes().forEach(function (v) { + /** @type {Node} */ var node = g.node(v); if (!_.has(node, 'label') && !g.children(v).length) { node.label = v; @@ -136,6 +155,7 @@ function preProcessGraph(g) { }); g.edges().forEach(function (e) { + /** @type {Node} */ var edge = g.edge(e); if (!_.has(edge, 'label')) { edge.label = ''; @@ -146,6 +166,7 @@ function preProcessGraph(g) { function postProcessGraph(g) { _.each(g.nodes(), function (v) { + /** @type {Node} */ var node = g.node(v); // Restore original dimensions diff --git a/src/dagre-js/shapes.js b/src/dagre-js/shapes.js index 9808e9a..51f483a 100644 --- a/src/dagre-js/shapes.js +++ b/src/dagre-js/shapes.js @@ -83,7 +83,7 @@ function diamond(parent, bbox, node) { .map(function (p) { return p.x + ',' + p.y; }) - .join(' ') + .join(' '), ); node.intersect = function (p) { diff --git a/src/dagre/acyclic.test.js b/src/dagre/acyclic.test.js new file mode 100644 index 0000000..2d82b36 --- /dev/null +++ b/src/dagre/acyclic.test.js @@ -0,0 +1,98 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import * as acyclic from './acyclic.js'; +import { Graph } from '../graphlib/index.js'; +import { findCycles } from '../graphlib/alg/find-cycles.js'; + +describe('acyclic', function () { + var ACYCLICERS = ['greedy', 'dfs', 'unknown-should-still-work']; + var g; + + beforeEach(function () { + g = new Graph({ multigraph: true }).setDefaultEdgeLabel(function () { + return { minlen: 1, weight: 1 }; + }); + }); + + _.forEach(ACYCLICERS, function (acyclicer) { + describe(acyclicer, function () { + beforeEach(function () { + g.setGraph({ acyclicer: acyclicer }); + }); + + describe('run', function () { + it('does not change an already acyclic graph', function () { + g.setPath(['a', 'b', 'd']); + g.setPath(['a', 'c', 'd']); + acyclic.run(g); + var results = _.map(g.edges(), stripLabel); + expect(_.sortBy(results, ['v', 'w'])).to.eql([ + { v: 'a', w: 'b' }, + { v: 'a', w: 'c' }, + { v: 'b', w: 'd' }, + { v: 'c', w: 'd' }, + ]); + }); + + it('breaks cycles in the input graph', function () { + g.setPath(['a', 'b', 'c', 'd', 'a']); + acyclic.run(g); + expect(findCycles(g)).to.eql([]); + }); + + it('creates a multi-edge where necessary', function () { + g.setPath(['a', 'b', 'a']); + acyclic.run(g); + expect(findCycles(g)).to.eql([]); + if (g.hasEdge('a', 'b')) { + expect(g.outEdges('a', 'b')).to.have.length(2); + } else { + expect(g.outEdges('b', 'a')).to.have.length(2); + } + expect(g.edgeCount()).to.equal(2); + }); + }); + + describe('undo', function () { + it('does not change edges where the original graph was acyclic', function () { + g.setEdge('a', 'b', { minlen: 2, weight: 3 }); + acyclic.run(g); + acyclic.undo(g); + expect(g.edge('a', 'b')).to.eql({ minlen: 2, weight: 3 }); + expect(g.edges()).to.have.length(1); + }); + + it('can restore previosuly reversed edges', function () { + g.setEdge('a', 'b', { minlen: 2, weight: 3 }); + g.setEdge('b', 'a', { minlen: 3, weight: 4 }); + acyclic.run(g); + acyclic.undo(g); + expect(g.edge('a', 'b')).to.eql({ minlen: 2, weight: 3 }); + expect(g.edge('b', 'a')).to.eql({ minlen: 3, weight: 4 }); + expect(g.edges()).to.have.length(2); + }); + }); + }); + }); + + describe('greedy-specific functionality', function () { + it('prefers to break cycles at low-weight edges', function () { + g.setGraph({ acyclicer: 'greedy' }); + g.setDefaultEdgeLabel(function () { + return { minlen: 1, weight: 2 }; + }); + g.setPath(['a', 'b', 'c', 'd', 'a']); + g.setEdge('c', 'd', { weight: 1 }); + acyclic.run(g); + expect(findCycles(g)).to.eql([]); + expect(g.hasEdge('c', 'd')).to.be.false; + }); + }); +}); + +function stripLabel(edge) { + var c = _.clone(edge); + delete c.label; + return c; +} diff --git a/src/dagre/add-border-segments.test.js b/src/dagre/add-border-segments.test.js new file mode 100644 index 0000000..595dd14 --- /dev/null +++ b/src/dagre/add-border-segments.test.js @@ -0,0 +1,142 @@ +import { describe, expect, it } from 'vitest'; +import { addBorderSegments } from './add-border-segments.js'; +import { Graph } from '../graphlib/index.js'; + +describe('addBorderSegments', function () { + var g; + + beforeEach(function () { + g = new Graph({ compound: true }); + }); + + it('does not add border nodes for a non-compound graph', function () { + var g = new Graph(); + g.setNode('a', { rank: 0 }); + addBorderSegments(g); + expect(g.nodeCount()).to.equal(1); + expect(g.node('a')).to.eql({ rank: 0 }); + }); + + it('does not add border nodes for a graph with no clusters', function () { + g.setNode('a', { rank: 0 }); + addBorderSegments(g); + expect(g.nodeCount()).to.equal(1); + expect(g.node('a')).to.eql({ rank: 0 }); + }); + + it('adds a border for a single-rank subgraph', function () { + g.setNode('sg', { minRank: 1, maxRank: 1 }); + addBorderSegments(g); + + var bl = g.node('sg').borderLeft[1]; + var br = g.node('sg').borderRight[1]; + expect(g.node(bl)).eqls({ + dummy: 'border', + borderType: 'borderLeft', + rank: 1, + width: 0, + height: 0, + }); + expect(g.parent(bl)).equals('sg'); + expect(g.node(br)).eqls({ + dummy: 'border', + borderType: 'borderRight', + rank: 1, + width: 0, + height: 0, + }); + expect(g.parent(br)).equals('sg'); + }); + + it('adds a border for a multi-rank subgraph', function () { + g.setNode('sg', { minRank: 1, maxRank: 2 }); + addBorderSegments(g); + + var sgNode = g.node('sg'); + var bl2 = sgNode.borderLeft[1]; + var br2 = sgNode.borderRight[1]; + expect(g.node(bl2)).eqls({ + dummy: 'border', + borderType: 'borderLeft', + rank: 1, + width: 0, + height: 0, + }); + expect(g.parent(bl2)).equals('sg'); + expect(g.node(br2)).eqls({ + dummy: 'border', + borderType: 'borderRight', + rank: 1, + width: 0, + height: 0, + }); + expect(g.parent(br2)).equals('sg'); + + var bl1 = sgNode.borderLeft[2]; + var br1 = sgNode.borderRight[2]; + expect(g.node(bl1)).eqls({ + dummy: 'border', + borderType: 'borderLeft', + rank: 2, + width: 0, + height: 0, + }); + expect(g.parent(bl1)).equals('sg'); + expect(g.node(br1)).eqls({ + dummy: 'border', + borderType: 'borderRight', + rank: 2, + width: 0, + height: 0, + }); + expect(g.parent(br1)).equals('sg'); + + expect(g.hasEdge(sgNode.borderLeft[1], sgNode.borderLeft[2])).to.be.true; + expect(g.hasEdge(sgNode.borderRight[1], sgNode.borderRight[2])).to.be.true; + }); + + it('adds borders for nested subgraphs', function () { + g.setNode('sg1', { minRank: 1, maxRank: 1 }); + g.setNode('sg2', { minRank: 1, maxRank: 1 }); + g.setParent('sg2', 'sg1'); + addBorderSegments(g); + + var bl1 = g.node('sg1').borderLeft[1]; + var br1 = g.node('sg1').borderRight[1]; + expect(g.node(bl1)).eqls({ + dummy: 'border', + borderType: 'borderLeft', + rank: 1, + width: 0, + height: 0, + }); + expect(g.parent(bl1)).equals('sg1'); + expect(g.node(br1)).eqls({ + dummy: 'border', + borderType: 'borderRight', + rank: 1, + width: 0, + height: 0, + }); + expect(g.parent(br1)).equals('sg1'); + + var bl2 = g.node('sg2').borderLeft[1]; + var br2 = g.node('sg2').borderRight[1]; + expect(g.node(bl2)).eqls({ + dummy: 'border', + borderType: 'borderLeft', + rank: 1, + width: 0, + height: 0, + }); + expect(g.parent(bl2)).equals('sg2'); + expect(g.node(br2)).eqls({ + dummy: 'border', + borderType: 'borderRight', + rank: 1, + width: 0, + height: 0, + }); + expect(g.parent(br2)).equals('sg2'); + }); +}); diff --git a/src/dagre/coordinate-system.test.js b/src/dagre/coordinate-system.test.js new file mode 100644 index 0000000..d9a1b1e --- /dev/null +++ b/src/dagre/coordinate-system.test.js @@ -0,0 +1,71 @@ +import { Graph } from '../graphlib/index.js'; +import * as coordinateSystem from './coordinate-system.js'; +import { describe, expect, it } from 'vitest'; + +describe('coordinateSystem', function () { + var g; + + beforeEach(function () { + g = new Graph(); + }); + + describe('coordinateSystem.adjust', function () { + beforeEach(function () { + g.setNode('a', { width: 100, height: 200 }); + }); + + it('does nothing to node dimensions with rankdir = TB', function () { + g.setGraph({ rankdir: 'TB' }); + coordinateSystem.adjust(g); + expect(g.node('a')).eqls({ width: 100, height: 200 }); + }); + + it('does nothing to node dimensions with rankdir = BT', function () { + g.setGraph({ rankdir: 'BT' }); + coordinateSystem.adjust(g); + expect(g.node('a')).eqls({ width: 100, height: 200 }); + }); + + it('swaps width and height for nodes with rankdir = LR', function () { + g.setGraph({ rankdir: 'LR' }); + coordinateSystem.adjust(g); + expect(g.node('a')).eqls({ width: 200, height: 100 }); + }); + + it('swaps width and height for nodes with rankdir = RL', function () { + g.setGraph({ rankdir: 'RL' }); + coordinateSystem.adjust(g); + expect(g.node('a')).eqls({ width: 200, height: 100 }); + }); + }); + + describe('coordinateSystem.undo', function () { + beforeEach(function () { + g.setNode('a', { width: 100, height: 200, x: 20, y: 40 }); + }); + + it('does nothing to points with rankdir = TB', function () { + g.setGraph({ rankdir: 'TB' }); + coordinateSystem.undo(g); + expect(g.node('a')).eqls({ x: 20, y: 40, width: 100, height: 200 }); + }); + + it('flips the y coordinate for points with rankdir = BT', function () { + g.setGraph({ rankdir: 'BT' }); + coordinateSystem.undo(g); + expect(g.node('a')).eqls({ x: 20, y: -40, width: 100, height: 200 }); + }); + + it('swaps dimensions and coordinates for points with rankdir = LR', function () { + g.setGraph({ rankdir: 'LR' }); + coordinateSystem.undo(g); + expect(g.node('a')).eqls({ x: 40, y: 20, width: 200, height: 100 }); + }); + + it('swaps dims and coords and flips x for points with rankdir = RL', function () { + g.setGraph({ rankdir: 'RL' }); + coordinateSystem.undo(g); + expect(g.node('a')).eqls({ x: -40, y: 20, width: 200, height: 100 }); + }); + }); +}); diff --git a/src/dagre/data/list.test.js b/src/dagre/data/list.test.js new file mode 100644 index 0000000..c031162 --- /dev/null +++ b/src/dagre/data/list.test.js @@ -0,0 +1,61 @@ +import { describe, expect, it } from 'vitest'; + +import { List } from './list.js'; + +describe('data.List', function () { + var list; + + beforeEach(function () { + list = new List(); + }); + + describe('dequeue', function () { + it('returns undefined with an empty list', function () { + expect(list.dequeue()).to.be.undefined; + }); + + it('unlinks and returns the first entry', function () { + var obj = {}; + list.enqueue(obj); + expect(list.dequeue()).to.equal(obj); + }); + + it('unlinks and returns multiple entries in FIFO order', function () { + var obj1 = {}; + var obj2 = {}; + list.enqueue(obj1); + list.enqueue(obj2); + + expect(list.dequeue()).to.equal(obj1); + expect(list.dequeue()).to.equal(obj2); + }); + + it('unlinks and relinks an entry if it is re-enqueued', function () { + var obj1 = {}; + var obj2 = {}; + list.enqueue(obj1); + list.enqueue(obj2); + list.enqueue(obj1); + + expect(list.dequeue()).to.equal(obj2); + expect(list.dequeue()).to.equal(obj1); + }); + + it('unlinks and relinks an entry if it is enqueued on another list', function () { + var obj = {}; + var list2 = new List(); + list.enqueue(obj); + list2.enqueue(obj); + + expect(list.dequeue()).to.be.undefined; + expect(list2.dequeue()).to.equal(obj); + }); + + it('can return a string representation', function () { + list.enqueue({ entry: 1 }); + list.enqueue({ entry: 2 }); + + expect(list.toString()).to.equal('[{"entry":1}, {"entry":2}]'); + }); + }); +}); diff --git a/src/dagre/greedy-fas.js b/src/dagre/greedy-fas.js index d36fa89..5cdd295 100644 --- a/src/dagre/greedy-fas.js +++ b/src/dagre/greedy-fas.js @@ -24,7 +24,7 @@ function greedyFAS(g, weightFn) { return _.flatten( _.map(results, function (e) { return g.outEdges(e.v, e.w); - }) + }), ); } diff --git a/src/dagre/greedy-fas.test.js b/src/dagre/greedy-fas.test.js new file mode 100644 index 0000000..1c97d8e --- /dev/null +++ b/src/dagre/greedy-fas.test.js @@ -0,0 +1,110 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graphlib/index.js'; +import { findCycles } from '../graphlib/alg/find-cycles.js'; +import { greedyFAS } from './greedy-fas.js'; + +describe('greedyFAS', function () { + var g; + + beforeEach(function () { + g = new Graph(); + }); + + it('returns the empty set for empty graphs', function () { + expect(greedyFAS(g)).to.eql([]); + }); + + it('returns the empty set for single-node graphs', function () { + g.setNode('a'); + expect(greedyFAS(g)).to.eql([]); + }); + + it('returns an empty set if the input graph is acyclic', function () { + var g = new Graph(); + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + g.setEdge('b', 'd'); + g.setEdge('a', 'e'); + expect(greedyFAS(g)).to.eql([]); + }); + + it('returns a single edge with a simple cycle', function () { + var g = new Graph(); + g.setEdge('a', 'b'); + g.setEdge('b', 'a'); + checkFAS(g, greedyFAS(g)); + }); + + it('returns a single edge in a 4-node cycle', function () { + var g = new Graph(); + g.setEdge('n1', 'n2'); + g.setPath(['n2', 'n3', 'n4', 'n5', 'n2']); + g.setEdge('n3', 'n5'); + g.setEdge('n4', 'n2'); + g.setEdge('n4', 'n6'); + checkFAS(g, greedyFAS(g)); + }); + + it('returns two edges for two 4-node cycles', function () { + var g = new Graph(); + g.setEdge('n1', 'n2'); + g.setPath(['n2', 'n3', 'n4', 'n5', 'n2']); + g.setEdge('n3', 'n5'); + g.setEdge('n4', 'n2'); + g.setEdge('n4', 'n6'); + g.setPath(['n6', 'n7', 'n8', 'n9', 'n6']); + g.setEdge('n7', 'n9'); + g.setEdge('n8', 'n6'); + g.setEdge('n8', 'n10'); + checkFAS(g, greedyFAS(g)); + }); + + it('works with arbitrarily weighted edges', function () { + // Our algorithm should also work for graphs with multi-edges, a graph + // where more than one edge can be pointing in the same direction between + // the same pair of incident nodes. We try this by assigning weights to + // our edges representing the number of edges from one node to the other. + + var g1 = new Graph(); + g1.setEdge('n1', 'n2', 2); + g1.setEdge('n2', 'n1', 1); + expect(greedyFAS(g1, weightFn(g1))).to.eql([{ v: 'n2', w: 'n1' }]); + + var g2 = new Graph(); + g2.setEdge('n1', 'n2', 1); + g2.setEdge('n2', 'n1', 2); + expect(greedyFAS(g2, weightFn(g2))).to.eql([{ v: 'n1', w: 'n2' }]); + }); + + it('works for multigraphs', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b', 5, 'foo'); + g.setEdge('b', 'a', 2, 'bar'); + g.setEdge('b', 'a', 2, 'baz'); + expect(_.sortBy(greedyFAS(g, weightFn(g)), 'name')).to.eql([ + { v: 'b', w: 'a', name: 'bar' }, + { v: 'b', w: 'a', name: 'baz' }, + ]); + }); +}); + +function checkFAS(g, fas) { + var n = g.nodeCount(); + var m = g.edgeCount(); + _.forEach(fas, function (edge) { + g.removeEdge(edge.v, edge.w); + }); + expect(findCycles(g)).to.eql([]); + // The more direct m/2 - n/6 fails for the simple cycle A <-> B, where one + // edge must be reversed, but the performance bound implies that only 2/3rds + // of an edge can be reversed. I'm using floors to acount for this. + expect(fas.length).to.be.lte(Math.floor(m / 2) - Math.floor(n / 6)); +} + +function weightFn(g) { + return function (e) { + return g.edge(e); + }; +} diff --git a/src/dagre/layout.js b/src/dagre/layout.js index 636d07a..07ac381 100644 --- a/src/dagre/layout.js +++ b/src/dagre/layout.js @@ -176,7 +176,7 @@ function buildLayoutGraph(inputGraph) { var graph = canonicalize(inputGraph.graph()); g.setGraph( - _.merge({}, graphDefaults, selectNumberAttrs(graph, graphNumAttrs), _.pick(graph, graphAttrs)) + _.merge({}, graphDefaults, selectNumberAttrs(graph, graphNumAttrs), _.pick(graph, graphAttrs)), ); _.forEach(inputGraph.nodes(), function (v) { @@ -189,7 +189,7 @@ function buildLayoutGraph(inputGraph) { var edge = canonicalize(inputGraph.edge(e)); g.setEdge( e, - _.merge({}, edgeDefaults, selectNumberAttrs(edge, edgeNumAttrs), _.pick(edge, edgeAttrs)) + _.merge({}, edgeDefaults, selectNumberAttrs(edge, edgeNumAttrs), _.pick(edge, edgeAttrs)), ); }); @@ -421,7 +421,7 @@ function insertSelfEdges(g) { e: selfEdge.e, label: selfEdge.label, }, - '_se' + '_se', ); }); delete node.selfEdges; diff --git a/src/dagre/layout.test.js b/src/dagre/layout.test.js new file mode 100644 index 0000000..d5b526f --- /dev/null +++ b/src/dagre/layout.test.js @@ -0,0 +1,304 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { layout } from './layout.js'; +import { Graph } from '../graphlib/index.js'; + +describe('layout', function () { + var g; + + beforeEach(function () { + g = new Graph({ multigraph: true, compound: true }) + .setGraph({}) + .setDefaultEdgeLabel(function () { + return {}; + }); + }); + + it('can layout a single node', function () { + g.setNode('a', { width: 50, height: 100 }); + layout(g); + expect(extractCoordinates(g)).to.eql({ + a: { x: 50 / 2, y: 100 / 2 }, + }); + expect(g.node('a').x).to.equal(50 / 2); + expect(g.node('a').y).to.equal(100 / 2); + }); + + it('can layout two nodes on the same rank', function () { + g.graph().nodesep = 200; + g.setNode('a', { width: 50, height: 100 }); + g.setNode('b', { width: 75, height: 200 }); + layout(g); + expect(extractCoordinates(g)).to.eql({ + a: { x: 50 / 2, y: 200 / 2 }, + b: { x: 50 + 200 + 75 / 2, y: 200 / 2 }, + }); + }); + + it('can layout two nodes connected by an edge', function () { + g.graph().ranksep = 300; + g.setNode('a', { width: 50, height: 100 }); + g.setNode('b', { width: 75, height: 200 }); + g.setEdge('a', 'b'); + layout(g); + expect(extractCoordinates(g)).to.eql({ + a: { x: 75 / 2, y: 100 / 2 }, + b: { x: 75 / 2, y: 100 + 300 + 200 / 2 }, + }); + + // We should not get x, y coordinates if the edge has no label + expect(g.edge('a', 'b')).to.not.have.property('x'); + expect(g.edge('a', 'b')).to.not.have.property('y'); + }); + + it('can layout an edge with a label', function () { + g.graph().ranksep = 300; + g.setNode('a', { width: 50, height: 100 }); + g.setNode('b', { width: 75, height: 200 }); + g.setEdge('a', 'b', { width: 60, height: 70, labelpos: 'c' }); + layout(g); + expect(extractCoordinates(g)).to.eql({ + a: { x: 75 / 2, y: 100 / 2 }, + b: { x: 75 / 2, y: 100 + 150 + 70 + 150 + 200 / 2 }, + }); + expect(_.pick(g.edge('a', 'b'), ['x', 'y'])).eqls({ x: 75 / 2, y: 100 + 150 + 70 / 2 }); + }); + + describe('can layout an edge with a long label, with rankdir =', function () { + _.forEach(['TB', 'BT', 'LR', 'RL'], function (rankdir) { + it(rankdir, function () { + g.graph().nodesep = g.graph().edgesep = 10; + g.graph().rankdir = rankdir; + _.forEach(['a', 'b', 'c', 'd'], function (v) { + g.setNode(v, { width: 10, height: 10 }); + }); + g.setEdge('a', 'c', { width: 2000, height: 10, labelpos: 'c' }); + g.setEdge('b', 'd', { width: 1, height: 1 }); + layout(g); + + var p1, p2; + if (rankdir === 'TB' || rankdir === 'BT') { + p1 = g.edge('a', 'c'); + p2 = g.edge('b', 'd'); + } else { + p1 = g.node('a'); + p2 = g.node('c'); + } + + expect(Math.abs(p1.x - p2.x)).gt(1000); + }); + }); + }); + + describe('can apply an offset, with rankdir =', function () { + _.forEach(['TB', 'BT', 'LR', 'RL'], function (rankdir) { + it(rankdir, function () { + g.graph().nodesep = g.graph().edgesep = 10; + g.graph().rankdir = rankdir; + _.forEach(['a', 'b', 'c', 'd'], function (v) { + g.setNode(v, { width: 10, height: 10 }); + }); + g.setEdge('a', 'b', { width: 10, height: 10, labelpos: 'l', labeloffset: 1000 }); + g.setEdge('c', 'd', { width: 10, height: 10, labelpos: 'r', labeloffset: 1000 }); + layout(g); + + if (rankdir === 'TB' || rankdir === 'BT') { + expect(g.edge('a', 'b').x - g.edge('a', 'b').points[0].x).equals(-1000 - 10 / 2); + expect(g.edge('c', 'd').x - g.edge('c', 'd').points[0].x).equals(1000 + 10 / 2); + } else { + expect(g.edge('a', 'b').y - g.edge('a', 'b').points[0].y).equals(-1000 - 10 / 2); + expect(g.edge('c', 'd').y - g.edge('c', 'd').points[0].y).equals(1000 + 10 / 2); + } + }); + }); + }); + + it('can layout a long edge with a label', function () { + g.graph().ranksep = 300; + g.setNode('a', { width: 50, height: 100 }); + g.setNode('b', { width: 75, height: 200 }); + g.setEdge('a', 'b', { width: 60, height: 70, minlen: 2, labelpos: 'c' }); + layout(g); + expect(g.edge('a', 'b').x).to.equal(75 / 2); + expect(g.edge('a', 'b').y).to.be.gt(g.node('a').y).to.be.lt(g.node('b').y); + }); + + it('can layout out a short cycle', function () { + g.graph().ranksep = 200; + g.setNode('a', { width: 100, height: 100 }); + g.setNode('b', { width: 100, height: 100 }); + g.setEdge('a', 'b', { weight: 2 }); + g.setEdge('b', 'a'); + layout(g); + expect(extractCoordinates(g)).to.eql({ + a: { x: 100 / 2, y: 100 / 2 }, + b: { x: 100 / 2, y: 100 + 200 + 100 / 2 }, + }); + // One arrow should point down, one up + expect(g.edge('a', 'b').points[1].y).gt(g.edge('a', 'b').points[0].y); + expect(g.edge('b', 'a').points[0].y).gt(g.edge('b', 'a').points[1].y); + }); + + it('adds rectangle intersects for edges', function () { + g.graph().ranksep = 200; + g.setNode('a', { width: 100, height: 100 }); + g.setNode('b', { width: 100, height: 100 }); + g.setEdge('a', 'b'); + layout(g); + var points = g.edge('a', 'b').points; + expect(points).to.have.length(3); + expect(points).eqls([ + { x: 100 / 2, y: 100 }, // intersect with bottom of a + { x: 100 / 2, y: 100 + 200 / 2 }, // point for edge label + { x: 100 / 2, y: 100 + 200 }, // intersect with top of b + ]); + }); + + it('adds rectangle intersects for edges spanning multiple ranks', function () { + g.graph().ranksep = 200; + g.setNode('a', { width: 100, height: 100 }); + g.setNode('b', { width: 100, height: 100 }); + g.setEdge('a', 'b', { minlen: 2 }); + layout(g); + var points = g.edge('a', 'b').points; + expect(points).to.have.length(5); + expect(points).eqls([ + { x: 100 / 2, y: 100 }, // intersect with bottom of a + { x: 100 / 2, y: 100 + 200 / 2 }, // bend #1 + { x: 100 / 2, y: 100 + 400 / 2 }, // point for edge label + { x: 100 / 2, y: 100 + 600 / 2 }, // bend #2 + { x: 100 / 2, y: 100 + 800 / 2 }, // intersect with top of b + ]); + }); + + describe('can layout a self loop', function () { + _.forEach(['TB', 'BT', 'LR', 'RL'], function (rankdir) { + it('in rankdir = ' + rankdir, function () { + g.graph().edgesep = 75; + g.graph().rankdir = rankdir; + g.setNode('a', { width: 100, height: 100 }); + g.setEdge('a', 'a', { width: 50, height: 50 }); + layout(g); + var nodeA = g.node('a'); + var points = g.edge('a', 'a').points; + expect(points).to.have.length(7); + _.forEach(points, function (point) { + if (rankdir !== 'LR' && rankdir !== 'RL') { + expect(point.x).gt(nodeA.x); + expect(Math.abs(point.y - nodeA.y)).lte(nodeA.height / 2); + } else { + expect(point.y).gt(nodeA.y); + expect(Math.abs(point.x - nodeA.x)).lte(nodeA.width / 2); + } + }); + }); + }); + }); + + it('can layout a graph with subgraphs', function () { + // To be expanded, this primarily ensures nothing blows up for the moment. + g.setNode('a', { width: 50, height: 50 }); + g.setParent('a', 'sg1'); + layout(g); + }); + + it('minimizes the height of subgraphs', function () { + _.forEach(['a', 'b', 'c', 'd', 'x', 'y'], function (v) { + g.setNode(v, { width: 50, height: 50 }); + }); + g.setPath(['a', 'b', 'c', 'd']); + g.setEdge('a', 'x', { weight: 100 }); + g.setEdge('y', 'd', { weight: 100 }); + g.setParent('x', 'sg'); + g.setParent('y', 'sg'); + + // We did not set up an edge (x, y), and we set up high-weight edges from + // outside of the subgraph to nodes in the subgraph. This is to try to + // force nodes x and y to be on different ranks, which we want our ranker + // to avoid. + layout(g); + expect(g.node('x').y).to.equal(g.node('y').y); + }); + + it('can layout subgraphs with different rankdirs', function () { + g.setNode('a', { width: 50, height: 50 }); + g.setNode('sg', {}); + g.setParent('a', 'sg'); + + function check(rankdir) { + expect(g.node('sg').width, 'width ' + rankdir).gt(50); + expect(g.node('sg').height, 'height ' + rankdir).gt(50); + expect(g.node('sg').x, 'x ' + rankdir).gt(50 / 2); + expect(g.node('sg').y, 'y ' + rankdir).gt(50 / 2); + } + + _.forEach(['tb', 'bt', 'lr', 'rl'], function (rankdir) { + g.graph().rankdir = rankdir; + layout(g); + check(rankdir); + }); + }); + + it('adds dimensions to the graph', function () { + g.setNode('a', { width: 100, height: 50 }); + layout(g); + expect(g.graph().width).equals(100); + expect(g.graph().height).equals(50); + }); + + describe('ensures all coordinates are in the bounding box for the graph', function () { + _.forEach(['TB', 'BT', 'LR', 'RL'], function (rankdir) { + describe(rankdir, function () { + beforeEach(function () { + g.graph().rankdir = rankdir; + }); + + it('node', function () { + g.setNode('a', { width: 100, height: 200 }); + layout(g); + expect(g.node('a').x).equals(100 / 2); + expect(g.node('a').y).equals(200 / 2); + }); + + it('edge, labelpos = l', function () { + g.setNode('a', { width: 100, height: 100 }); + g.setNode('b', { width: 100, height: 100 }); + g.setEdge('a', 'b', { + width: 1000, + height: 2000, + labelpos: 'l', + labeloffset: 0, + }); + layout(g); + if (rankdir === 'TB' || rankdir === 'BT') { + expect(g.edge('a', 'b').x).equals(1000 / 2); + } else { + expect(g.edge('a', 'b').y).equals(2000 / 2); + } + }); + }); + }); + }); + + it('treats attributes with case-insensitivity', function () { + g.graph().nodeSep = 200; // note the capital S + g.setNode('a', { width: 50, height: 100 }); + g.setNode('b', { width: 75, height: 200 }); + layout(g); + expect(extractCoordinates(g)).to.eql({ + a: { x: 50 / 2, y: 200 / 2 }, + b: { x: 50 + 200 + 75 / 2, y: 200 / 2 }, + }); + }); +}); + +function extractCoordinates(g) { + var nodes = g.nodes(); + return _.zipObject( + nodes, + _.map(nodes, function (v) { + return _.pick(g.node(v), ['x', 'y']); + }), + ); +} diff --git a/src/dagre/nesting-graph.js b/src/dagre/nesting-graph.js index 4f22d4e..c346bb6 100644 --- a/src/dagre/nesting-graph.js +++ b/src/dagre/nesting-graph.js @@ -120,7 +120,7 @@ function sumWeights(g) { function (acc, e) { return acc + g.edge(e).weight; }, - 0 + 0, ); } diff --git a/src/dagre/nesting-graph.test.js b/src/dagre/nesting-graph.test.js new file mode 100644 index 0000000..42d4f84 --- /dev/null +++ b/src/dagre/nesting-graph.test.js @@ -0,0 +1,200 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graphlib/index.js'; +import { components } from '../graphlib/alg/components.js'; +import * as nestingGraph from './nesting-graph.js'; + +describe('rank/nestingGraph', function () { + var g; + + beforeEach(function () { + g = new Graph({ compound: true }).setGraph({}).setDefaultNodeLabel(function () { + return {}; + }); + }); + + describe('run', function () { + it('connects a disconnected graph', function () { + g.setNode('a'); + g.setNode('b'); + expect(components(g)).to.have.length(2); + nestingGraph.run(g); + expect(components(g)).to.have.length(1); + expect(g.hasNode('a')); + expect(g.hasNode('b')); + }); + + it('adds border nodes to the top and bottom of a subgraph', function () { + g.setParent('a', 'sg1'); + nestingGraph.run(g); + + var borderTop = g.node('sg1').borderTop; + var borderBottom = g.node('sg1').borderBottom; + expect(borderTop).to.exist; + expect(borderBottom).to.exist; + expect(g.parent(borderTop)).to.equal('sg1'); + expect(g.parent(borderBottom)).to.equal('sg1'); + expect(g.outEdges(borderTop, 'a')).to.have.length(1); + expect(g.edge(g.outEdges(borderTop, 'a')[0]).minlen).equals(1); + expect(g.outEdges('a', borderBottom)).to.have.length(1); + expect(g.edge(g.outEdges('a', borderBottom)[0]).minlen).equals(1); + expect(g.node(borderTop)).eqls({ width: 0, height: 0, dummy: 'border' }); + expect(g.node(borderBottom)).eqls({ width: 0, height: 0, dummy: 'border' }); + }); + + it('adds edges between borders of nested subgraphs', function () { + g.setParent('sg2', 'sg1'); + g.setParent('a', 'sg2'); + nestingGraph.run(g); + + var sg1Top = g.node('sg1').borderTop; + var sg1Bottom = g.node('sg1').borderBottom; + var sg2Top = g.node('sg2').borderTop; + var sg2Bottom = g.node('sg2').borderBottom; + expect(sg1Top).to.exist; + expect(sg1Bottom).to.exist; + expect(sg2Top).to.exist; + expect(sg2Bottom).to.exist; + expect(g.outEdges(sg1Top, sg2Top)).to.have.length(1); + expect(g.edge(g.outEdges(sg1Top, sg2Top)[0]).minlen).equals(1); + expect(g.outEdges(sg2Bottom, sg1Bottom)).to.have.length(1); + expect(g.edge(g.outEdges(sg2Bottom, sg1Bottom)[0]).minlen).equals(1); + }); + + it('adds sufficient weight to border to node edges', function () { + // We want to keep subgraphs tight, so we should ensure that the weight for + // the edge between the top (and bottom) border nodes and nodes in the + // subgraph have weights exceeding anything in the graph. + g.setParent('x', 'sg'); + g.setEdge('a', 'x', { weight: 100 }); + g.setEdge('x', 'b', { weight: 200 }); + nestingGraph.run(g); + + var top = g.node('sg').borderTop; + var bot = g.node('sg').borderBottom; + expect(g.edge(top, 'x').weight).to.be.gt(300); + expect(g.edge('x', bot).weight).to.be.gt(300); + }); + + it('adds an edge from the root to the tops of top-level subgraphs', function () { + g.setParent('a', 'sg1'); + nestingGraph.run(g); + + var root = g.graph().nestingRoot; + var borderTop = g.node('sg1').borderTop; + expect(root).to.exist; + expect(borderTop).to.exist; + expect(g.outEdges(root, borderTop)).to.have.length(1); + expect(g.hasEdge(g.outEdges(root, borderTop)[0])).to.be.true; + }); + + it('adds an edge from root to each node with the correct minlen #1', function () { + g.setNode('a'); + nestingGraph.run(g); + + var root = g.graph().nestingRoot; + expect(root).to.exist; + expect(g.outEdges(root, 'a')).to.have.length(1); + expect(g.edge(g.outEdges(root, 'a')[0])).eqls({ weight: 0, minlen: 1 }); + }); + + it('adds an edge from root to each node with the correct minlen #2', function () { + g.setParent('a', 'sg1'); + nestingGraph.run(g); + + var root = g.graph().nestingRoot; + expect(root).to.exist; + expect(g.outEdges(root, 'a')).to.have.length(1); + expect(g.edge(g.outEdges(root, 'a')[0])).eqls({ weight: 0, minlen: 3 }); + }); + + it('adds an edge from root to each node with the correct minlen #3', function () { + g.setParent('sg2', 'sg1'); + g.setParent('a', 'sg2'); + nestingGraph.run(g); + + var root = g.graph().nestingRoot; + expect(root).to.exist; + expect(g.outEdges(root, 'a')).to.have.length(1); + expect(g.edge(g.outEdges(root, 'a')[0])).eqls({ weight: 0, minlen: 5 }); + }); + + it('does not add an edge from the root to itself', function () { + g.setNode('a'); + nestingGraph.run(g); + + var root = g.graph().nestingRoot; + expect(g.outEdges(root, root)).eqls([]); + }); + + it('expands inter-node edges to separate SG border and nodes #1', function () { + g.setEdge('a', 'b', { minlen: 1 }); + nestingGraph.run(g); + expect(g.edge('a', 'b').minlen).equals(1); + }); + + it('expands inter-node edges to separate SG border and nodes #2', function () { + g.setParent('a', 'sg1'); + g.setEdge('a', 'b', { minlen: 1 }); + nestingGraph.run(g); + expect(g.edge('a', 'b').minlen).equals(3); + }); + + it('expands inter-node edges to separate SG border and nodes #3', function () { + g.setParent('sg2', 'sg1'); + g.setParent('a', 'sg2'); + g.setEdge('a', 'b', { minlen: 1 }); + nestingGraph.run(g); + expect(g.edge('a', 'b').minlen).equals(5); + }); + + it('sets minlen correctly for nested SG boder to children', function () { + g.setParent('a', 'sg1'); + g.setParent('sg2', 'sg1'); + g.setParent('b', 'sg2'); + nestingGraph.run(g); + + // We expect the following layering: + // + // 0: root + // 1: empty (close sg2) + // 2: empty (close sg1) + // 3: open sg1 + // 4: open sg2 + // 5: a, b + // 6: close sg2 + // 7: close sg1 + + var root = g.graph().nestingRoot; + var sg1Top = g.node('sg1').borderTop; + var sg1Bot = g.node('sg1').borderBottom; + var sg2Top = g.node('sg2').borderTop; + var sg2Bot = g.node('sg2').borderBottom; + + expect(g.edge(root, sg1Top).minlen).equals(3); + expect(g.edge(sg1Top, sg2Top).minlen).equals(1); + expect(g.edge(sg1Top, 'a').minlen).equals(2); + expect(g.edge('a', sg1Bot).minlen).equals(2); + expect(g.edge(sg2Top, 'b').minlen).equals(1); + expect(g.edge('b', sg2Bot).minlen).equals(1); + expect(g.edge(sg2Bot, sg1Bot).minlen).equals(1); + }); + }); + + describe('cleanup', function () { + it('removes nesting graph edges', function () { + g.setParent('a', 'sg1'); + g.setEdge('a', 'b', { minlen: 1 }); + nestingGraph.run(g); + nestingGraph.cleanup(g); + expect(g.successors('a')).eqls(['b']); + }); + + it('removes the root node', function () { + g.setParent('a', 'sg1'); + nestingGraph.run(g); + nestingGraph.cleanup(g); + expect(g.nodeCount()).to.equal(4); // sg1 + sg1Top + sg1Bottom + "a" + }); + }); +}); diff --git a/src/dagre/normalize.test.js b/src/dagre/normalize.test.js new file mode 100644 index 0000000..f95ddd3 --- /dev/null +++ b/src/dagre/normalize.test.js @@ -0,0 +1,229 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import * as normalize from './normalize.js'; +import { Graph } from '../graphlib/index.js'; + +describe('normalize', function () { + var g; + + beforeEach(function () { + g = new Graph({ multigraph: true, compound: true }).setGraph({}); + }); + + describe('run', function () { + it('does not change a short edge', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 1 }); + g.setEdge('a', 'b', {}); + + normalize.run(g); + + expect(_.map(g.edges(), incidentNodes)).to.eql([{ v: 'a', w: 'b' }]); + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(1); + }); + + it('splits a two layer edge into two segments', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 2 }); + g.setEdge('a', 'b', {}); + + normalize.run(g); + + expect(g.successors('a')).to.have.length(1); + var successor = g.successors('a')[0]; + expect(g.node(successor).dummy).to.equal('edge'); + expect(g.node(successor).rank).to.equal(1); + expect(g.successors(successor)).to.eql(['b']); + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(2); + + expect(g.graph().dummyChains).to.have.length(1); + expect(g.graph().dummyChains[0]).to.equal(successor); + }); + + it('assigns width = 0, height = 0 to dummy nodes by default', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 2 }); + g.setEdge('a', 'b', { width: 10, height: 10 }); + + normalize.run(g); + + expect(g.successors('a')).to.have.length(1); + var successor = g.successors('a')[0]; + expect(g.node(successor).width).to.equal(0); + expect(g.node(successor).height).to.equal(0); + }); + + it('assigns width and height from the edge for the node on labelRank', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 4 }); + g.setEdge('a', 'b', { width: 20, height: 10, labelRank: 2 }); + + normalize.run(g); + + var labelV = g.successors(g.successors('a')[0])[0]; + var labelNode = g.node(labelV); + expect(labelNode.width).to.equal(20); + expect(labelNode.height).to.equal(10); + }); + + it('preserves the weight for the edge', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 2 }); + g.setEdge('a', 'b', { weight: 2 }); + + normalize.run(g); + + expect(g.successors('a')).to.have.length(1); + expect(g.edge('a', g.successors('a')[0]).weight).to.equal(2); + }); + }); + + describe('undo', function () { + it('reverses the run operation', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 2 }); + g.setEdge('a', 'b', {}); + + normalize.run(g); + normalize.undo(g); + + expect(_.map(g.edges(), incidentNodes)).to.eql([{ v: 'a', w: 'b' }]); + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(2); + }); + + it('restores previous edge labels', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 2 }); + g.setEdge('a', 'b', { foo: 'bar' }); + + normalize.run(g); + normalize.undo(g); + + expect(g.edge('a', 'b').foo).equals('bar'); + }); + + it("collects assigned coordinates into the 'points' attribute", function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 2 }); + g.setEdge('a', 'b', {}); + + normalize.run(g); + + var dummyLabel = g.node(g.neighbors('a')[0]); + dummyLabel.x = 5; + dummyLabel.y = 10; + + normalize.undo(g); + + expect(g.edge('a', 'b').points).eqls([{ x: 5, y: 10 }]); + }); + + it("merges assigned coordinates into the 'points' attribute", function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 4 }); + g.setEdge('a', 'b', {}); + + normalize.run(g); + + var aSucLabel = g.node(g.neighbors('a')[0]); + aSucLabel.x = 5; + aSucLabel.y = 10; + + var midLabel = g.node(g.successors(g.successors('a')[0])[0]); + midLabel.x = 20; + midLabel.y = 25; + + var bPredLabel = g.node(g.neighbors('b')[0]); + bPredLabel.x = 100; + bPredLabel.y = 200; + + normalize.undo(g); + + expect(g.edge('a', 'b').points).eqls([ + { x: 5, y: 10 }, + { x: 20, y: 25 }, + { x: 100, y: 200 }, + ]); + }); + + it('sets coords and dims for the label, if the edge has one', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 2 }); + g.setEdge('a', 'b', { width: 10, height: 20, labelRank: 1 }); + + normalize.run(g); + + var labelNode = g.node(g.successors('a')[0]); + labelNode.x = 50; + labelNode.y = 60; + labelNode.width = 20; + labelNode.height = 10; + + normalize.undo(g); + + expect(_.pick(g.edge('a', 'b'), ['x', 'y', 'width', 'height'])).eqls({ + x: 50, + y: 60, + width: 20, + height: 10, + }); + }); + + it('sets coords and dims for the label, if the long edge has one', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 4 }); + g.setEdge('a', 'b', { width: 10, height: 20, labelRank: 2 }); + + normalize.run(g); + + var labelNode = g.node(g.successors(g.successors('a')[0])[0]); + labelNode.x = 50; + labelNode.y = 60; + labelNode.width = 20; + labelNode.height = 10; + + normalize.undo(g); + + expect(_.pick(g.edge('a', 'b'), ['x', 'y', 'width', 'height'])).eqls({ + x: 50, + y: 60, + width: 20, + height: 10, + }); + }); + + it('restores multi-edges', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 2 }); + g.setEdge('a', 'b', {}, 'bar'); + g.setEdge('a', 'b', {}, 'foo'); + + normalize.run(g); + + var outEdges = _.sortBy(g.outEdges('a'), 'name'); + expect(outEdges).to.have.length(2); + + var barDummy = g.node(outEdges[0].w); + barDummy.x = 5; + barDummy.y = 10; + + var fooDummy = g.node(outEdges[1].w); + fooDummy.x = 15; + fooDummy.y = 20; + + normalize.undo(g); + + expect(g.hasEdge('a', 'b')).to.be.false; + expect(g.edge('a', 'b', 'bar').points).eqls([{ x: 5, y: 10 }]); + expect(g.edge('a', 'b', 'foo').points).eqls([{ x: 15, y: 20 }]); + }); + }); +}); + +function incidentNodes(edge) { + return { v: edge.v, w: edge.w }; +} diff --git a/src/dagre/order/add-subgraph-constraints.test.js b/src/dagre/order/add-subgraph-constraints.test.js new file mode 100644 index 0000000..7a2d75f --- /dev/null +++ b/src/dagre/order/add-subgraph-constraints.test.js @@ -0,0 +1,62 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../../graphlib/graph.js'; +import { addSubgraphConstraints } from './add-subgraph-constraints.js'; + +describe('order/addSubgraphConstraints', function () { + var g, cg; + + beforeEach(function () { + g = new Graph({ compound: true }); + cg = new Graph(); + }); + + it('does not change CG for a flat set of nodes', function () { + var vs = ['a', 'b', 'c', 'd']; + _.forEach(vs, function (v) { + g.setNode(v); + }); + addSubgraphConstraints(g, cg, vs); + expect(cg.nodeCount()).equals(0); + expect(cg.edgeCount()).equals(0); + }); + + it("doesn't create a constraint for contiguous subgraph nodes", function () { + var vs = ['a', 'b', 'c']; + _.forEach(vs, function (v) { + g.setParent(v, 'sg'); + }); + addSubgraphConstraints(g, cg, vs); + expect(cg.nodeCount()).equals(0); + expect(cg.edgeCount()).equals(0); + }); + + it('adds a constraint when the parents for adjacent nodes are different', function () { + var vs = ['a', 'b']; + g.setParent('a', 'sg1'); + g.setParent('b', 'sg2'); + addSubgraphConstraints(g, cg, vs); + expect(cg.edges()).eqls([{ v: 'sg1', w: 'sg2' }]); + }); + + it('works for multiple levels', function () { + var vs = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; + _.forEach(vs, function (v) { + g.setNode(v); + }); + g.setParent('b', 'sg2'); + g.setParent('sg2', 'sg1'); + g.setParent('c', 'sg1'); + g.setParent('d', 'sg3'); + g.setParent('sg3', 'sg1'); + g.setParent('f', 'sg4'); + g.setParent('g', 'sg5'); + g.setParent('sg5', 'sg4'); + addSubgraphConstraints(g, cg, vs); + expect(_.sortBy(cg.edges(), 'v')).eqls([ + { v: 'sg1', w: 'sg4' }, + { v: 'sg2', w: 'sg3' }, + ]); + }); +}); diff --git a/src/dagre/order/barycenter.js b/src/dagre/order/barycenter.js index 6d9c099..7dabefe 100644 --- a/src/dagre/order/barycenter.js +++ b/src/dagre/order/barycenter.js @@ -18,7 +18,7 @@ function barycenter(g, movable) { weight: acc.weight + edge.weight, }; }, - { sum: 0, weight: 0 } + { sum: 0, weight: 0 }, ); return { diff --git a/src/dagre/order/barycenter.test.js b/src/dagre/order/barycenter.test.js new file mode 100644 index 0000000..8e0cb4d --- /dev/null +++ b/src/dagre/order/barycenter.test.js @@ -0,0 +1,74 @@ +import { describe, expect, it } from 'vitest'; + +import { barycenter } from './barycenter.js'; +import { Graph } from '../../graphlib/graph.js'; + +describe('order/barycenter', function () { + var g; + + beforeEach(function () { + g = new Graph() + .setDefaultNodeLabel(function () { + return {}; + }) + .setDefaultEdgeLabel(function () { + return { weight: 1 }; + }); + }); + + it('assigns an undefined barycenter for a node with no predecessors', function () { + g.setNode('x', {}); + + var results = barycenter(g, ['x']); + expect(results).to.have.length(1); + expect(results[0]).to.eql({ v: 'x' }); + }); + + it('assigns the position of the sole predecessors', function () { + g.setNode('a', { order: 2 }); + g.setEdge('a', 'x'); + + var results = barycenter(g, ['x']); + expect(results).to.have.length(1); + expect(results[0]).eqls({ v: 'x', barycenter: 2, weight: 1 }); + }); + + it('assigns the average of multiple predecessors', function () { + g.setNode('a', { order: 2 }); + g.setNode('b', { order: 4 }); + g.setEdge('a', 'x'); + g.setEdge('b', 'x'); + + var results = barycenter(g, ['x']); + expect(results).to.have.length(1); + expect(results[0]).eqls({ v: 'x', barycenter: 3, weight: 2 }); + }); + + it('takes into account the weight of edges', function () { + g.setNode('a', { order: 2 }); + g.setNode('b', { order: 4 }); + g.setEdge('a', 'x', { weight: 3 }); + g.setEdge('b', 'x'); + + var results = barycenter(g, ['x']); + expect(results).to.have.length(1); + expect(results[0]).eqls({ v: 'x', barycenter: 2.5, weight: 4 }); + }); + + it('calculates barycenters for all nodes in the movable layer', function () { + g.setNode('a', { order: 1 }); + g.setNode('b', { order: 2 }); + g.setNode('c', { order: 4 }); + g.setEdge('a', 'x'); + g.setEdge('b', 'x'); + g.setNode('y'); + g.setEdge('a', 'z', { weight: 2 }); + g.setEdge('c', 'z'); + + var results = barycenter(g, ['x', 'y', 'z']); + expect(results).to.have.length(3); + expect(results[0]).eqls({ v: 'x', barycenter: 1.5, weight: 2 }); + expect(results[1]).eqls({ v: 'y' }); + expect(results[2]).eqls({ v: 'z', barycenter: 2, weight: 3 }); + }); +}); diff --git a/src/dagre/order/build-layer-graph.test.js b/src/dagre/order/build-layer-graph.test.js new file mode 100644 index 0000000..7a54278 --- /dev/null +++ b/src/dagre/order/build-layer-graph.test.js @@ -0,0 +1,120 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../../graphlib/graph.js'; +import { buildLayerGraph } from './build-layer-graph.js'; + +describe('order/buildLayerGraph', function () { + var g; + + beforeEach(function () { + g = new Graph({ compound: true, multigraph: true }); + }); + + it('places movable nodes with no parents under the root node', function () { + g.setNode('a', { rank: 1 }); + g.setNode('b', { rank: 1 }); + g.setNode('c', { rank: 2 }); + g.setNode('d', { rank: 3 }); + + var lg; + lg = buildLayerGraph(g, 1, 'inEdges'); + expect(lg.hasNode(lg.graph().root)); + expect(lg.children()).eqls([lg.graph().root]); + expect(lg.children(lg.graph().root)).eqls(['a', 'b']); + }); + + it('copies flat nodes from the layer to the graph', function () { + g.setNode('a', { rank: 1 }); + g.setNode('b', { rank: 1 }); + g.setNode('c', { rank: 2 }); + g.setNode('d', { rank: 3 }); + + expect(buildLayerGraph(g, 1, 'inEdges').nodes()).to.include('a'); + expect(buildLayerGraph(g, 1, 'inEdges').nodes()).to.include('b'); + expect(buildLayerGraph(g, 2, 'inEdges').nodes()).to.include('c'); + expect(buildLayerGraph(g, 3, 'inEdges').nodes()).to.include('d'); + }); + + it('uses the original node label for copied nodes', function () { + // This allows us to make updates to the original graph and have them + // be available automatically in the layer graph. + g.setNode('a', { foo: 1, rank: 1 }); + g.setNode('b', { foo: 2, rank: 2 }); + g.setEdge('a', 'b', { weight: 1 }); + + var lg = buildLayerGraph(g, 2, 'inEdges'); + + expect(lg.node('a').foo).equals(1); + g.node('a').foo = 'updated'; + expect(lg.node('a').foo).equals('updated'); + + expect(lg.node('b').foo).equals(2); + g.node('b').foo = 'updated'; + expect(lg.node('b').foo).equals('updated'); + }); + + it('copies edges incident on rank nodes to the graph (inEdges)', function () { + g.setNode('a', { rank: 1 }); + g.setNode('b', { rank: 1 }); + g.setNode('c', { rank: 2 }); + g.setNode('d', { rank: 3 }); + g.setEdge('a', 'c', { weight: 2 }); + g.setEdge('b', 'c', { weight: 3 }); + g.setEdge('c', 'd', { weight: 4 }); + + expect(buildLayerGraph(g, 1, 'inEdges').edgeCount()).to.equal(0); + expect(buildLayerGraph(g, 2, 'inEdges').edgeCount()).to.equal(2); + expect(buildLayerGraph(g, 2, 'inEdges').edge('a', 'c')).eqls({ weight: 2 }); + expect(buildLayerGraph(g, 2, 'inEdges').edge('b', 'c')).eqls({ weight: 3 }); + expect(buildLayerGraph(g, 3, 'inEdges').edgeCount()).to.equal(1); + expect(buildLayerGraph(g, 3, 'inEdges').edge('c', 'd')).eqls({ weight: 4 }); + }); + + it('copies edges incident on rank nodes to the graph (outEdges)', function () { + g.setNode('a', { rank: 1 }); + g.setNode('b', { rank: 1 }); + g.setNode('c', { rank: 2 }); + g.setNode('d', { rank: 3 }); + g.setEdge('a', 'c', { weight: 2 }); + g.setEdge('b', 'c', { weight: 3 }); + g.setEdge('c', 'd', { weight: 4 }); + + expect(buildLayerGraph(g, 1, 'outEdges').edgeCount()).to.equal(2); + expect(buildLayerGraph(g, 1, 'outEdges').edge('c', 'a')).eqls({ weight: 2 }); + expect(buildLayerGraph(g, 1, 'outEdges').edge('c', 'b')).eqls({ weight: 3 }); + expect(buildLayerGraph(g, 2, 'outEdges').edgeCount()).to.equal(1); + expect(buildLayerGraph(g, 2, 'outEdges').edge('d', 'c')).eqls({ weight: 4 }); + expect(buildLayerGraph(g, 3, 'outEdges').edgeCount()).to.equal(0); + }); + + it('collapses multi-edges', function () { + g.setNode('a', { rank: 1 }); + g.setNode('b', { rank: 2 }); + g.setEdge('a', 'b', { weight: 2 }); + g.setEdge('a', 'b', { weight: 3 }, 'multi'); + + expect(buildLayerGraph(g, 2, 'inEdges').edge('a', 'b')).eqls({ weight: 5 }); + }); + + it('preserves hierarchy for the movable layer', function () { + g.setNode('a', { rank: 0 }); + g.setNode('b', { rank: 0 }); + g.setNode('c', { rank: 0 }); + g.setNode('sg', { + minRank: 0, + maxRank: 0, + borderLeft: ['bl'], + borderRight: ['br'], + }); + _.forEach(['a', 'b'], function (v) { + g.setParent(v, 'sg'); + }); + + var lg = buildLayerGraph(g, 0, 'inEdges'); + var root = lg.graph().root; + expect(_.sortBy(lg.children(root))).eqls(['c', 'sg']); + expect(lg.parent('a')).equals('sg'); + expect(lg.parent('b')).equals('sg'); + }); +}); diff --git a/src/dagre/order/cross-count.js b/src/dagre/order/cross-count.js index 6bb439e..cf9d195 100644 --- a/src/dagre/order/cross-count.js +++ b/src/dagre/order/cross-count.js @@ -34,7 +34,7 @@ function twoLayerCrossCount(g, northLayer, southLayer) { southLayer, _.map(southLayer, function (v, i) { return i; - }) + }), ); var southEntries = _.flatten( _.map(northLayer, function (v) { @@ -42,9 +42,9 @@ function twoLayerCrossCount(g, northLayer, southLayer) { _.map(g.outEdges(v), function (e) { return { pos: southPos[e.w], weight: g.edge(e).weight }; }), - 'pos' + 'pos', ); - }) + }), ); // Build the accumulator tree @@ -75,7 +75,7 @@ function twoLayerCrossCount(g, northLayer, southLayer) { tree[index] += entry.weight; } cc += entry.weight * weightSum; - }) + }), ); return cc; diff --git a/src/dagre/order/cross-count.test.js b/src/dagre/order/cross-count.test.js new file mode 100644 index 0000000..00b5213 --- /dev/null +++ b/src/dagre/order/cross-count.test.js @@ -0,0 +1,84 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../../graphlib/graph.js'; +import { crossCount } from './cross-count.js'; + +describe('crossCount', function () { + var g; + + beforeEach(function () { + g = new Graph().setDefaultEdgeLabel(function () { + return { weight: 1 }; + }); + }); + + it('returns 0 for an empty layering', function () { + expect(crossCount(g, [])).equals(0); + }); + + it('returns 0 for a layering with no crossings', function () { + g.setEdge('a1', 'b1'); + g.setEdge('a2', 'b2'); + expect( + crossCount(g, [ + ['a1', 'a2'], + ['b1', 'b2'], + ]), + ).equals(0); + }); + + it('returns 1 for a layering with 1 crossing', function () { + g.setEdge('a1', 'b1'); + g.setEdge('a2', 'b2'); + expect( + crossCount(g, [ + ['a1', 'a2'], + ['b2', 'b1'], + ]), + ).equals(1); + }); + + it('returns a weighted crossing count for a layering with 1 crossing', function () { + g.setEdge('a1', 'b1', { weight: 2 }); + g.setEdge('a2', 'b2', { weight: 3 }); + expect( + crossCount(g, [ + ['a1', 'a2'], + ['b2', 'b1'], + ]), + ).equals(6); + }); + + it('calculates crossings across layers', function () { + g.setPath(['a1', 'b1', 'c1']); + g.setPath(['a2', 'b2', 'c2']); + expect( + crossCount(g, [ + ['a1', 'a2'], + ['b2', 'b1'], + ['c1', 'c2'], + ]), + ).equals(2); + }); + + it('works for graph #1', function () { + g.setPath(['a', 'b', 'c']); + g.setPath(['d', 'e', 'c']); + g.setPath(['a', 'f', 'i']); + g.setEdge('a', 'e'); + expect( + crossCount(g, [ + ['a', 'd'], + ['b', 'e', 'f'], + ['c', 'i'], + ]), + ).equals(1); + expect( + crossCount(g, [ + ['d', 'a'], + ['e', 'b', 'f'], + ['c', 'i'], + ]), + ).equals(0); + }); +}); diff --git a/src/dagre/order/index.test.js b/src/dagre/order/index.test.js new file mode 100644 index 0000000..6f9ba1e --- /dev/null +++ b/src/dagre/order/index.test.js @@ -0,0 +1,61 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../../graphlib/graph.js'; +import { order } from './index.js'; +import { crossCount } from './cross-count.js'; +import { buildLayerMatrix } from '../util.js'; + +describe('order', function () { + var g; + + beforeEach(function () { + g = new Graph().setDefaultEdgeLabel({ weight: 1 }); + }); + + it('does not add crossings to a tree structure', function () { + g.setNode('a', { rank: 1 }); + _.forEach(['b', 'e'], function (v) { + g.setNode(v, { rank: 2 }); + }); + _.forEach(['c', 'd', 'f'], function (v) { + g.setNode(v, { rank: 3 }); + }); + g.setPath(['a', 'b', 'c']); + g.setEdge('b', 'd'); + g.setPath(['a', 'e', 'f']); + order(g); + var layering = buildLayerMatrix(g); + expect(crossCount(g, layering)).to.equal(0); + }); + + it('can solve a simple graph', function () { + // This graph resulted in a single crossing for previous versions of dagre. + _.forEach(['a', 'd'], function (v) { + g.setNode(v, { rank: 1 }); + }); + _.forEach(['b', 'f', 'e'], function (v) { + g.setNode(v, { rank: 2 }); + }); + _.forEach(['c', 'g'], function (v) { + g.setNode(v, { rank: 3 }); + }); + order(g); + var layering = buildLayerMatrix(g); + expect(crossCount(g, layering)).to.equal(0); + }); + + it('can minimize crossings', function () { + g.setNode('a', { rank: 1 }); + _.forEach(['b', 'e', 'g'], function (v) { + g.setNode(v, { rank: 2 }); + }); + _.forEach(['c', 'f', 'h'], function (v) { + g.setNode(v, { rank: 3 }); + }); + g.setNode('d', { rank: 4 }); + order(g); + var layering = buildLayerMatrix(g); + expect(crossCount(g, layering)).to.be.lte(1); + }); +}); diff --git a/src/dagre/order/init-order.js b/src/dagre/order/init-order.js index 1efb758..60af2cb 100644 --- a/src/dagre/order/init-order.js +++ b/src/dagre/order/init-order.js @@ -21,7 +21,7 @@ function initOrder(g) { var maxRank = _.max( _.map(simpleNodes, function (v) { return g.node(v).rank; - }) + }), ); var layers = _.map(_.range(maxRank + 1), function () { return []; diff --git a/src/dagre/order/init-order.test.js b/src/dagre/order/init-order.test.js new file mode 100644 index 0000000..b9acc50 --- /dev/null +++ b/src/dagre/order/init-order.test.js @@ -0,0 +1,51 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../../graphlib/graph.js'; +import { initOrder } from './init-order.js'; + +describe('order/initOrder', function () { + var g; + + beforeEach(function () { + g = new Graph({ compound: true }).setDefaultEdgeLabel(function () { + return { weight: 1 }; + }); + }); + + it('assigns non-overlapping orders for each rank in a tree', function () { + _.forEach({ a: 0, b: 1, c: 2, d: 2, e: 1 }, function (rank, v) { + g.setNode(v, { rank: rank }); + }); + g.setPath(['a', 'b', 'c']); + g.setEdge('b', 'd'); + g.setEdge('a', 'e'); + + var layering = initOrder(g); + expect(layering[0]).to.eql(['a']); + expect(_.sortBy(layering[1])).to.eql(['b', 'e']); + expect(_.sortBy(layering[2])).to.eql(['c', 'd']); + }); + + it('assigns non-overlapping orders for each rank in a DAG', function () { + _.forEach({ a: 0, b: 1, c: 1, d: 2 }, function (rank, v) { + g.setNode(v, { rank: rank }); + }); + g.setPath(['a', 'b', 'd']); + g.setPath(['a', 'c', 'd']); + + var layering = initOrder(g); + expect(layering[0]).to.eql(['a']); + expect(_.sortBy(layering[1])).to.eql(['b', 'c']); + expect(_.sortBy(layering[2])).to.eql(['d']); + }); + + it('does not assign an order to subgraph nodes', function () { + g.setNode('a', { rank: 0 }); + g.setNode('sg1', {}); + g.setParent('a', 'sg1'); + + var layering = initOrder(g); + expect(layering).to.eql([['a']]); + }); +}); diff --git a/src/dagre/order/resolve-conflicts.js b/src/dagre/order/resolve-conflicts.js index 4767239..cca18fa 100644 --- a/src/dagre/order/resolve-conflicts.js +++ b/src/dagre/order/resolve-conflicts.js @@ -102,7 +102,7 @@ function doResolveConflicts(sourceSet) { }), function (entry) { return _.pick(entry, ['vs', 'i', 'barycenter', 'weight']); - } + }, ); } diff --git a/src/dagre/order/resolve-conflicts.test.js b/src/dagre/order/resolve-conflicts.test.js new file mode 100644 index 0000000..c1c7d46 --- /dev/null +++ b/src/dagre/order/resolve-conflicts.test.js @@ -0,0 +1,134 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../../graphlib/graph.js'; +import { resolveConflicts } from './resolve-conflicts.js'; + +describe('order/resolveConflicts', function () { + var cg; + + beforeEach(function () { + cg = new Graph(); + }); + + it('returns back nodes unchanged when no constraints exist', function () { + var input = [ + { v: 'a', barycenter: 2, weight: 3 }, + { v: 'b', barycenter: 1, weight: 2 }, + ]; + expect(_.sortBy(resolveConflicts(input, cg), 'vs')).eqls([ + { vs: ['a'], i: 0, barycenter: 2, weight: 3 }, + { vs: ['b'], i: 1, barycenter: 1, weight: 2 }, + ]); + }); + + it('returns back nodes unchanged when no conflicts exist', function () { + var input = [ + { v: 'a', barycenter: 2, weight: 3 }, + { v: 'b', barycenter: 1, weight: 2 }, + ]; + cg.setEdge('b', 'a'); + expect(_.sortBy(resolveConflicts(input, cg), 'vs')).eqls([ + { vs: ['a'], i: 0, barycenter: 2, weight: 3 }, + { vs: ['b'], i: 1, barycenter: 1, weight: 2 }, + ]); + }); + + it('coalesces nodes when there is a conflict', function () { + var input = [ + { v: 'a', barycenter: 2, weight: 3 }, + { v: 'b', barycenter: 1, weight: 2 }, + ]; + cg.setEdge('a', 'b'); + expect(_.sortBy(resolveConflicts(input, cg), 'vs')).eqls([ + { vs: ['a', 'b'], i: 0, barycenter: (3 * 2 + 2 * 1) / (3 + 2), weight: 3 + 2 }, + ]); + }); + + it('coalesces nodes when there is a conflict #2', function () { + var input = [ + { v: 'a', barycenter: 4, weight: 1 }, + { v: 'b', barycenter: 3, weight: 1 }, + { v: 'c', barycenter: 2, weight: 1 }, + { v: 'd', barycenter: 1, weight: 1 }, + ]; + cg.setPath(['a', 'b', 'c', 'd']); + expect(_.sortBy(resolveConflicts(input, cg), 'vs')).eqls([ + { vs: ['a', 'b', 'c', 'd'], i: 0, barycenter: (4 + 3 + 2 + 1) / 4, weight: 4 }, + ]); + }); + + it('works with multiple constraints for the same target #1', function () { + var input = [ + { v: 'a', barycenter: 4, weight: 1 }, + { v: 'b', barycenter: 3, weight: 1 }, + { v: 'c', barycenter: 2, weight: 1 }, + ]; + cg.setEdge('a', 'c'); + cg.setEdge('b', 'c'); + var results = resolveConflicts(input, cg); + expect(results).to.have.length(1); + expect(_.indexOf(results[0].vs, 'c')).to.be.gt(_.indexOf(results[0].vs, 'a')); + expect(_.indexOf(results[0].vs, 'c')).to.be.gt(_.indexOf(results[0].vs, 'b')); + expect(results[0].i).equals(0); + expect(results[0].barycenter).equals((4 + 3 + 2) / 3); + expect(results[0].weight).equals(3); + }); + + it('works with multiple constraints for the same target #2', function () { + var input = [ + { v: 'a', barycenter: 4, weight: 1 }, + { v: 'b', barycenter: 3, weight: 1 }, + { v: 'c', barycenter: 2, weight: 1 }, + { v: 'd', barycenter: 1, weight: 1 }, + ]; + cg.setEdge('a', 'c'); + cg.setEdge('a', 'd'); + cg.setEdge('b', 'c'); + cg.setEdge('c', 'd'); + var results = resolveConflicts(input, cg); + expect(results).to.have.length(1); + expect(_.indexOf(results[0].vs, 'c')).to.be.gt(_.indexOf(results[0].vs, 'a')); + expect(_.indexOf(results[0].vs, 'c')).to.be.gt(_.indexOf(results[0].vs, 'b')); + expect(_.indexOf(results[0].vs, 'd')).to.be.gt(_.indexOf(results[0].vs, 'c')); + expect(results[0].i).equals(0); + expect(results[0].barycenter).equals((4 + 3 + 2 + 1) / 4); + expect(results[0].weight).equals(4); + }); + + it('does nothing to a node lacking both a barycenter and a constraint', function () { + var input = [{ v: 'a' }, { v: 'b', barycenter: 1, weight: 2 }]; + expect(_.sortBy(resolveConflicts(input, cg), 'vs')).eqls([ + { vs: ['a'], i: 0 }, + { vs: ['b'], i: 1, barycenter: 1, weight: 2 }, + ]); + }); + + it('treats a node w/o a barycenter as always violating constraints #1', function () { + var input = [{ v: 'a' }, { v: 'b', barycenter: 1, weight: 2 }]; + cg.setEdge('a', 'b'); + expect(_.sortBy(resolveConflicts(input, cg), 'vs')).eqls([ + { vs: ['a', 'b'], i: 0, barycenter: 1, weight: 2 }, + ]); + }); + + it('treats a node w/o a barycenter as always violating constraints #2', function () { + var input = [{ v: 'a' }, { v: 'b', barycenter: 1, weight: 2 }]; + cg.setEdge('b', 'a'); + expect(_.sortBy(resolveConflicts(input, cg), 'vs')).eqls([ + { vs: ['b', 'a'], i: 0, barycenter: 1, weight: 2 }, + ]); + }); + + it('ignores edges not related to entries', function () { + var input = [ + { v: 'a', barycenter: 2, weight: 3 }, + { v: 'b', barycenter: 1, weight: 2 }, + ]; + cg.setEdge('c', 'd'); + expect(_.sortBy(resolveConflicts(input, cg), 'vs')).eqls([ + { vs: ['a'], i: 0, barycenter: 2, weight: 3 }, + { vs: ['b'], i: 1, barycenter: 1, weight: 2 }, + ]); + }); +}); diff --git a/src/dagre/order/sort-subgraph.js b/src/dagre/order/sort-subgraph.js index e47c2de..844caaa 100644 --- a/src/dagre/order/sort-subgraph.js +++ b/src/dagre/order/sort-subgraph.js @@ -60,7 +60,7 @@ function expandSubgraphs(entries, subgraphs) { return subgraphs[v].vs; } return v; - }) + }), ); }); } diff --git a/src/dagre/order/sort-subgraph.test.js b/src/dagre/order/sort-subgraph.test.js new file mode 100644 index 0000000..bcb178c --- /dev/null +++ b/src/dagre/order/sort-subgraph.test.js @@ -0,0 +1,153 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { sortSubgraph } from './sort-subgraph.js'; +import { Graph } from '../../graphlib/graph.js'; + +describe('order/sortSubgraph', function () { + var g, cg; + + beforeEach(function () { + g = new Graph({ compound: true }) + .setDefaultNodeLabel(function () { + return {}; + }) + .setDefaultEdgeLabel(function () { + return { weight: 1 }; + }); + _.forEach(_.range(5), function (v) { + g.setNode(v, { order: v }); + }); + cg = new Graph(); + }); + + it('sorts a flat subgraph based on barycenter', function () { + g.setEdge(3, 'x'); + g.setEdge(1, 'y', { weight: 2 }); + g.setEdge(4, 'y'); + _.forEach(['x', 'y'], function (v) { + g.setParent(v, 'movable'); + }); + + expect(sortSubgraph(g, 'movable', cg).vs).eqls(['y', 'x']); + }); + + it('preserves the pos of a node (y) w/o neighbors in a flat subgraph', function () { + g.setEdge(3, 'x'); + g.setNode('y'); + g.setEdge(1, 'z', { weight: 2 }); + g.setEdge(4, 'z'); + _.forEach(['x', 'y', 'z'], function (v) { + g.setParent(v, 'movable'); + }); + + expect(sortSubgraph(g, 'movable', cg).vs).eqls(['z', 'y', 'x']); + }); + + it('biases to the left without reverse bias', function () { + g.setEdge(1, 'x'); + g.setEdge(1, 'y'); + _.forEach(['x', 'y'], function (v) { + g.setParent(v, 'movable'); + }); + + expect(sortSubgraph(g, 'movable', cg).vs).eqls(['x', 'y']); + }); + + it('biases to the right with reverse bias', function () { + g.setEdge(1, 'x'); + g.setEdge(1, 'y'); + _.forEach(['x', 'y'], function (v) { + g.setParent(v, 'movable'); + }); + + expect(sortSubgraph(g, 'movable', cg, true).vs).eqls(['y', 'x']); + }); + + it('aggregates stats about the subgraph', function () { + g.setEdge(3, 'x'); + g.setEdge(1, 'y', { weight: 2 }); + g.setEdge(4, 'y'); + _.forEach(['x', 'y'], function (v) { + g.setParent(v, 'movable'); + }); + + var results = sortSubgraph(g, 'movable', cg); + expect(results.barycenter).to.equal(2.25); + expect(results.weight).to.equal(4); + }); + + it('can sort a nested subgraph with no barycenter', function () { + g.setNodes(['a', 'b', 'c']); + g.setParent('a', 'y'); + g.setParent('b', 'y'); + g.setParent('c', 'y'); + g.setEdge(0, 'x'); + g.setEdge(1, 'z'); + g.setEdge(2, 'y'); + _.forEach(['x', 'y', 'z'], function (v) { + g.setParent(v, 'movable'); + }); + + expect(sortSubgraph(g, 'movable', cg).vs).eqls(['x', 'z', 'a', 'b', 'c']); + }); + + it('can sort a nested subgraph with a barycenter', function () { + g.setNodes(['a', 'b', 'c']); + g.setParent('a', 'y'); + g.setParent('b', 'y'); + g.setParent('c', 'y'); + g.setEdge(0, 'a', { weight: 3 }); + g.setEdge(0, 'x'); + g.setEdge(1, 'z'); + g.setEdge(2, 'y'); + _.forEach(['x', 'y', 'z'], function (v) { + g.setParent(v, 'movable'); + }); + + expect(sortSubgraph(g, 'movable', cg).vs).eqls(['x', 'a', 'b', 'c', 'z']); + }); + + it('can sort a nested subgraph with no in-edges', function () { + g.setNodes(['a', 'b', 'c']); + g.setParent('a', 'y'); + g.setParent('b', 'y'); + g.setParent('c', 'y'); + g.setEdge(0, 'a'); + g.setEdge(1, 'b'); + g.setEdge(0, 'x'); + g.setEdge(1, 'z'); + _.forEach(['x', 'y', 'z'], function (v) { + g.setParent(v, 'movable'); + }); + + expect(sortSubgraph(g, 'movable', cg).vs).eqls(['x', 'a', 'b', 'c', 'z']); + }); + + it('sorts border nodes to the extremes of the subgraph', function () { + g.setEdge(0, 'x'); + g.setEdge(1, 'y'); + g.setEdge(2, 'z'); + g.setNode('sg1', { borderLeft: 'bl', borderRight: 'br' }); + _.forEach(['x', 'y', 'z', 'bl', 'br'], function (v) { + g.setParent(v, 'sg1'); + }); + expect(sortSubgraph(g, 'sg1', cg).vs).eqls(['bl', 'x', 'y', 'z', 'br']); + }); + + it('assigns a barycenter to a subgraph based on previous border nodes', function () { + g.setNode('bl1', { order: 0 }); + g.setNode('br1', { order: 1 }); + g.setEdge('bl1', 'bl2'); + g.setEdge('br1', 'br2'); + _.forEach(['bl2', 'br2'], function (v) { + g.setParent(v, 'sg'); + }); + g.setNode('sg', { borderLeft: 'bl2', borderRight: 'br2' }); + expect(sortSubgraph(g, 'sg', cg)).eqls({ + barycenter: 0.5, + weight: 2, + vs: ['bl2', 'br2'], + }); + }); +}); diff --git a/src/dagre/order/sort.test.js b/src/dagre/order/sort.test.js new file mode 100644 index 0000000..dc80d1c --- /dev/null +++ b/src/dagre/order/sort.test.js @@ -0,0 +1,91 @@ +import { describe, expect, it } from 'vitest'; + +import { sort } from './sort.js'; + +describe('sort', function () { + it('sorts nodes by barycenter', function () { + var input = [ + { vs: ['a'], i: 0, barycenter: 2, weight: 3 }, + { vs: ['b'], i: 1, barycenter: 1, weight: 2 }, + ]; + expect(sort(input)).eqls({ + vs: ['b', 'a'], + barycenter: (2 * 3 + 1 * 2) / (3 + 2), + weight: 3 + 2, + }); + }); + + it('can sort super-nodes', function () { + var input = [ + { vs: ['a', 'c', 'd'], i: 0, barycenter: 2, weight: 3 }, + { vs: ['b'], i: 1, barycenter: 1, weight: 2 }, + ]; + expect(sort(input)).eqls({ + vs: ['b', 'a', 'c', 'd'], + barycenter: (2 * 3 + 1 * 2) / (3 + 2), + weight: 3 + 2, + }); + }); + + it('biases to the left by default', function () { + var input = [ + { vs: ['a'], i: 0, barycenter: 1, weight: 1 }, + { vs: ['b'], i: 1, barycenter: 1, weight: 1 }, + ]; + expect(sort(input)).eqls({ + vs: ['a', 'b'], + barycenter: 1, + weight: 2, + }); + }); + + it('biases to the right if biasRight = true', function () { + var input = [ + { vs: ['a'], i: 0, barycenter: 1, weight: 1 }, + { vs: ['b'], i: 1, barycenter: 1, weight: 1 }, + ]; + expect(sort(input, true)).eqls({ + vs: ['b', 'a'], + barycenter: 1, + weight: 2, + }); + }); + + it('can sort nodes without a barycenter', function () { + var input = [ + { vs: ['a'], i: 0, barycenter: 2, weight: 1 }, + { vs: ['b'], i: 1, barycenter: 6, weight: 1 }, + { vs: ['c'], i: 2 }, + { vs: ['d'], i: 3, barycenter: 3, weight: 1 }, + ]; + expect(sort(input)).eqls({ + vs: ['a', 'd', 'c', 'b'], + barycenter: (2 + 6 + 3) / 3, + weight: 3, + }); + }); + + it('can handle no barycenters for any nodes', function () { + var input = [ + { vs: ['a'], i: 0 }, + { vs: ['b'], i: 3 }, + { vs: ['c'], i: 2 }, + { vs: ['d'], i: 1 }, + ]; + expect(sort(input)).eqls({ vs: ['a', 'd', 'c', 'b'] }); + }); + + it('can handle a barycenter of 0', function () { + var input = [ + { vs: ['a'], i: 0, barycenter: 0, weight: 1 }, + { vs: ['b'], i: 3 }, + { vs: ['c'], i: 2 }, + { vs: ['d'], i: 1 }, + ]; + expect(sort(input)).eqls({ + vs: ['a', 'd', 'c', 'b'], + barycenter: 0, + weight: 1, + }); + }); +}); diff --git a/src/dagre/parent-dummy-chains.test.js b/src/dagre/parent-dummy-chains.test.js new file mode 100644 index 0000000..fea98f4 --- /dev/null +++ b/src/dagre/parent-dummy-chains.test.js @@ -0,0 +1,148 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graphlib/index.js'; +import { parentDummyChains } from './parent-dummy-chains.js'; + +describe('parentDummyChains', function () { + var g; + + beforeEach(function () { + g = new Graph({ compound: true }).setGraph({}); + }); + + it('does not set a parent if both the tail and head have no parent', function () { + g.setNode('a'); + g.setNode('b'); + g.setNode('d1', { edgeObj: { v: 'a', w: 'b' } }); + g.graph().dummyChains = ['d1']; + g.setPath(['a', 'd1', 'b']); + + parentDummyChains(g); + expect(g.parent('d1')).to.be.undefined; + }); + + it("uses the tail's parent for the first node if it is not the root", function () { + g.setParent('a', 'sg1'); + g.setNode('sg1', { minRank: 0, maxRank: 2 }); + g.setNode('d1', { edgeObj: { v: 'a', w: 'b' }, rank: 2 }); + g.graph().dummyChains = ['d1']; + g.setPath(['a', 'd1', 'b']); + + parentDummyChains(g); + expect(g.parent('d1')).equals('sg1'); + }); + + it("uses the heads's parent for the first node if tail's is root", function () { + g.setParent('b', 'sg1'); + g.setNode('sg1', { minRank: 1, maxRank: 3 }); + g.setNode('d1', { edgeObj: { v: 'a', w: 'b' }, rank: 1 }); + g.graph().dummyChains = ['d1']; + g.setPath(['a', 'd1', 'b']); + + parentDummyChains(g); + expect(g.parent('d1')).equals('sg1'); + }); + + it('handles a long chain starting in a subgraph', function () { + g.setParent('a', 'sg1'); + g.setNode('sg1', { minRank: 0, maxRank: 2 }); + g.setNode('d1', { edgeObj: { v: 'a', w: 'b' }, rank: 2 }); + g.setNode('d2', { rank: 3 }); + g.setNode('d3', { rank: 4 }); + g.graph().dummyChains = ['d1']; + g.setPath(['a', 'd1', 'd2', 'd3', 'b']); + + parentDummyChains(g); + expect(g.parent('d1')).equals('sg1'); + expect(g.parent('d2')).to.be.undefined; + expect(g.parent('d3')).to.be.undefined; + }); + + it('handles a long chain ending in a subgraph', function () { + g.setParent('b', 'sg1'); + g.setNode('sg1', { minRank: 3, maxRank: 5 }); + g.setNode('d1', { edgeObj: { v: 'a', w: 'b' }, rank: 1 }); + g.setNode('d2', { rank: 2 }); + g.setNode('d3', { rank: 3 }); + g.graph().dummyChains = ['d1']; + g.setPath(['a', 'd1', 'd2', 'd3', 'b']); + + parentDummyChains(g); + expect(g.parent('d1')).to.be.undefined; + expect(g.parent('d2')).to.be.undefined; + expect(g.parent('d3')).equals('sg1'); + }); + + it('handles nested subgraphs', function () { + g.setParent('a', 'sg2'); + g.setParent('sg2', 'sg1'); + g.setNode('sg1', { minRank: 0, maxRank: 4 }); + g.setNode('sg2', { minRank: 1, maxRank: 3 }); + g.setParent('b', 'sg4'); + g.setParent('sg4', 'sg3'); + g.setNode('sg3', { minRank: 6, maxRank: 10 }); + g.setNode('sg4', { minRank: 7, maxRank: 9 }); + for (var i = 0; i < 5; ++i) { + g.setNode('d' + (i + 1), { rank: i + 3 }); + } + g.node('d1').edgeObj = { v: 'a', w: 'b' }; + g.graph().dummyChains = ['d1']; + g.setPath(['a', 'd1', 'd2', 'd3', 'd4', 'd5', 'b']); + + parentDummyChains(g); + expect(g.parent('d1')).equals('sg2'); + expect(g.parent('d2')).equals('sg1'); + expect(g.parent('d3')).to.be.undefined; + expect(g.parent('d4')).equals('sg3'); + expect(g.parent('d5')).equals('sg4'); + }); + + it('handles overlapping rank ranges', function () { + g.setParent('a', 'sg1'); + g.setNode('sg1', { minRank: 0, maxRank: 3 }); + g.setParent('b', 'sg2'); + g.setNode('sg2', { minRank: 2, maxRank: 6 }); + g.setNode('d1', { edgeObj: { v: 'a', w: 'b' }, rank: 2 }); + g.setNode('d2', { rank: 3 }); + g.setNode('d3', { rank: 4 }); + g.graph().dummyChains = ['d1']; + g.setPath(['a', 'd1', 'd2', 'd3', 'b']); + + parentDummyChains(g); + expect(g.parent('d1')).equals('sg1'); + expect(g.parent('d2')).equals('sg1'); + expect(g.parent('d3')).equals('sg2'); + }); + + it('handles an LCA that is not the root of the graph #1', function () { + g.setParent('a', 'sg1'); + g.setParent('sg2', 'sg1'); + g.setNode('sg1', { minRank: 0, maxRank: 6 }); + g.setParent('b', 'sg2'); + g.setNode('sg2', { minRank: 3, maxRank: 5 }); + g.setNode('d1', { edgeObj: { v: 'a', w: 'b' }, rank: 2 }); + g.setNode('d2', { rank: 3 }); + g.graph().dummyChains = ['d1']; + g.setPath(['a', 'd1', 'd2', 'b']); + + parentDummyChains(g); + expect(g.parent('d1')).equals('sg1'); + expect(g.parent('d2')).equals('sg2'); + }); + + it('handles an LCA that is not the root of the graph #2', function () { + g.setParent('a', 'sg2'); + g.setParent('sg2', 'sg1'); + g.setNode('sg1', { minRank: 0, maxRank: 6 }); + g.setParent('b', 'sg1'); + g.setNode('sg2', { minRank: 1, maxRank: 3 }); + g.setNode('d1', { edgeObj: { v: 'a', w: 'b' }, rank: 3 }); + g.setNode('d2', { rank: 4 }); + g.graph().dummyChains = ['d1']; + g.setPath(['a', 'd1', 'd2', 'b']); + + parentDummyChains(g); + expect(g.parent('d1')).equals('sg2'); + expect(g.parent('d2')).equals('sg1'); + }); +}); diff --git a/src/dagre/position/bk.test.js b/src/dagre/position/bk.test.js new file mode 100644 index 0000000..0964d69 --- /dev/null +++ b/src/dagre/position/bk.test.js @@ -0,0 +1,678 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { buildLayerMatrix } from '../util.js'; +import { + findType1Conflicts, + findType2Conflicts, + addConflict, + hasConflict, + verticalAlignment, + horizontalCompaction, + alignCoordinates, + balance, + findSmallestWidthAlignment, + positionX, +} from './bk.js'; +import { Graph } from '../../graphlib/graph.js'; + +describe('position/bk', function () { + var g; + + beforeEach(function () { + g = new Graph().setGraph({}); + }); + + describe('findType1Conflicts', function () { + var layering; + + beforeEach(function () { + g.setDefaultEdgeLabel(function () { + return {}; + }) + .setNode('a', { rank: 0, order: 0 }) + .setNode('b', { rank: 0, order: 1 }) + .setNode('c', { rank: 1, order: 0 }) + .setNode('d', { rank: 1, order: 1 }) + // Set up crossing + .setEdge('a', 'd') + .setEdge('b', 'c'); + + layering = buildLayerMatrix(g); + }); + + it('does not mark edges that have no conflict', function () { + g.removeEdge('a', 'd'); + g.removeEdge('b', 'c'); + g.setEdge('a', 'c'); + g.setEdge('b', 'd'); + + var conflicts = findType1Conflicts(g, layering); + expect(hasConflict(conflicts, 'a', 'c')).to.be.false; + expect(hasConflict(conflicts, 'b', 'd')).to.be.false; + }); + + it('does not mark type-0 conflicts (no dummies)', function () { + var conflicts = findType1Conflicts(g, layering); + expect(hasConflict(conflicts, 'a', 'd')).to.be.false; + expect(hasConflict(conflicts, 'b', 'c')).to.be.false; + }); + + _.forEach(['a', 'b', 'c', 'd'], function (v) { + it('does not mark type-0 conflicts (' + v + ' is dummy)', function () { + g.node(v).dummy = true; + + var conflicts = findType1Conflicts(g, layering); + expect(hasConflict(conflicts, 'a', 'd')).to.be.false; + expect(hasConflict(conflicts, 'b', 'c')).to.be.false; + }); + }); + + _.forEach(['a', 'b', 'c', 'd'], function (v) { + it('does mark type-1 conflicts (' + v + ' is non-dummy)', function () { + _.forEach(['a', 'b', 'c', 'd'], function (w) { + if (v !== w) { + g.node(w).dummy = true; + } + }); + + var conflicts = findType1Conflicts(g, layering); + if (v === 'a' || v === 'd') { + expect(hasConflict(conflicts, 'a', 'd')).to.be.true; + expect(hasConflict(conflicts, 'b', 'c')).to.be.false; + } else { + expect(hasConflict(conflicts, 'a', 'd')).to.be.false; + expect(hasConflict(conflicts, 'b', 'c')).to.be.true; + } + }); + }); + + it('does not mark type-2 conflicts (all dummies)', function () { + _.forEach(['a', 'b', 'c', 'd'], function (v) { + g.node(v).dummy = true; + }); + + var conflicts = findType1Conflicts(g, layering); + expect(hasConflict(conflicts, 'a', 'd')).to.be.false; + expect(hasConflict(conflicts, 'b', 'c')).to.be.false; + findType1Conflicts(g, layering); + }); + }); + + describe('findType2Conflicts', function () { + var layering; + + beforeEach(function () { + g.setDefaultEdgeLabel(function () { + return {}; + }) + .setNode('a', { rank: 0, order: 0 }) + .setNode('b', { rank: 0, order: 1 }) + .setNode('c', { rank: 1, order: 0 }) + .setNode('d', { rank: 1, order: 1 }) + // Set up crossing + .setEdge('a', 'd') + .setEdge('b', 'c'); + + layering = buildLayerMatrix(g); + }); + + it('marks type-2 conflicts favoring border segments #1', function () { + _.forEach(['a', 'd'], function (v) { + g.node(v).dummy = true; + }); + + _.forEach(['b', 'c'], function (v) { + g.node(v).dummy = 'border'; + }); + + var conflicts = findType2Conflicts(g, layering); + expect(hasConflict(conflicts, 'a', 'd')).to.be.true; + expect(hasConflict(conflicts, 'b', 'c')).to.be.false; + findType1Conflicts(g, layering); + }); + + it('marks type-2 conflicts favoring border segments #2', function () { + _.forEach(['b', 'c'], function (v) { + g.node(v).dummy = true; + }); + + _.forEach(['a', 'd'], function (v) { + g.node(v).dummy = 'border'; + }); + + var conflicts = findType2Conflicts(g, layering); + expect(hasConflict(conflicts, 'a', 'd')).to.be.false; + expect(hasConflict(conflicts, 'b', 'c')).to.be.true; + findType1Conflicts(g, layering); + }); + }); + + describe('hasConflict', function () { + it('can test for a type-1 conflict regardless of edge orientation', function () { + var conflicts = {}; + addConflict(conflicts, 'b', 'a'); + expect(hasConflict(conflicts, 'a', 'b')).to.be.true; + expect(hasConflict(conflicts, 'b', 'a')).to.be.true; + }); + + it('works for multiple conflicts with the same node', function () { + var conflicts = {}; + addConflict(conflicts, 'a', 'b'); + addConflict(conflicts, 'a', 'c'); + expect(hasConflict(conflicts, 'a', 'b')).to.be.true; + expect(hasConflict(conflicts, 'a', 'c')).to.be.true; + }); + }); + + describe('verticalAlignment', function () { + it('Aligns with itself if the node has no adjacencies', function () { + g.setNode('a', { rank: 0, order: 0 }); + g.setNode('b', { rank: 1, order: 0 }); + + var layering = buildLayerMatrix(g); + var conflicts = {}; + + var result = verticalAlignment(g, layering, conflicts, g.predecessors.bind(g)); + expect(result).to.eql({ + root: { a: 'a', b: 'b' }, + align: { a: 'a', b: 'b' }, + }); + }); + + it('Aligns with its sole adjacency', function () { + g.setNode('a', { rank: 0, order: 0 }); + g.setNode('b', { rank: 1, order: 0 }); + g.setEdge('a', 'b'); + + var layering = buildLayerMatrix(g); + var conflicts = {}; + + var result = verticalAlignment(g, layering, conflicts, g.predecessors.bind(g)); + expect(result).to.eql({ + root: { a: 'a', b: 'a' }, + align: { a: 'b', b: 'a' }, + }); + }); + + it('aligns with its left median when possible', function () { + g.setNode('a', { rank: 0, order: 0 }); + g.setNode('b', { rank: 0, order: 1 }); + g.setNode('c', { rank: 1, order: 0 }); + g.setEdge('a', 'c'); + g.setEdge('b', 'c'); + + var layering = buildLayerMatrix(g); + var conflicts = {}; + + var result = verticalAlignment(g, layering, conflicts, g.predecessors.bind(g)); + expect(result).to.eql({ + root: { a: 'a', b: 'b', c: 'a' }, + align: { a: 'c', b: 'b', c: 'a' }, + }); + }); + + it('aligns correctly even regardless of node name / insertion order', function () { + // This test ensures that we're actually properly sorting nodes by + // position when searching for candidates. Many of these tests previously + // passed because the node insertion order matched the order of the nodes + // in the layering. + g.setNode('b', { rank: 0, order: 1 }); + g.setNode('c', { rank: 1, order: 0 }); + g.setNode('z', { rank: 0, order: 0 }); + g.setEdge('z', 'c'); + g.setEdge('b', 'c'); + + var layering = buildLayerMatrix(g); + var conflicts = {}; + + var result = verticalAlignment(g, layering, conflicts, g.predecessors.bind(g)); + expect(result).to.eql({ + root: { z: 'z', b: 'b', c: 'z' }, + align: { z: 'c', b: 'b', c: 'z' }, + }); + }); + + it('aligns with its right median when left is unavailable', function () { + g.setNode('a', { rank: 0, order: 0 }); + g.setNode('b', { rank: 0, order: 1 }); + g.setNode('c', { rank: 1, order: 0 }); + g.setEdge('a', 'c'); + g.setEdge('b', 'c'); + + var layering = buildLayerMatrix(g); + var conflicts = {}; + + addConflict(conflicts, 'a', 'c'); + + var result = verticalAlignment(g, layering, conflicts, g.predecessors.bind(g)); + expect(result).to.eql({ + root: { a: 'a', b: 'b', c: 'b' }, + align: { a: 'a', b: 'c', c: 'b' }, + }); + }); + + it('aligns with neither median if both are unavailable', function () { + g.setNode('a', { rank: 0, order: 0 }); + g.setNode('b', { rank: 0, order: 1 }); + g.setNode('c', { rank: 1, order: 0 }); + g.setNode('d', { rank: 1, order: 1 }); + g.setEdge('a', 'd'); + g.setEdge('b', 'c'); + g.setEdge('b', 'd'); + + var layering = buildLayerMatrix(g); + var conflicts = {}; + + var result = verticalAlignment(g, layering, conflicts, g.predecessors.bind(g)); + // c will align with b, so d will not be able to align with a, because + // (a,d) and (c,b) cross. + expect(result).to.eql({ + root: { a: 'a', b: 'b', c: 'b', d: 'd' }, + align: { a: 'a', b: 'c', c: 'b', d: 'd' }, + }); + }); + + it('aligns with the single median for an odd number of adjacencies', function () { + g.setNode('a', { rank: 0, order: 0 }); + g.setNode('b', { rank: 0, order: 1 }); + g.setNode('c', { rank: 0, order: 2 }); + g.setNode('d', { rank: 1, order: 0 }); + g.setEdge('a', 'd'); + g.setEdge('b', 'd'); + g.setEdge('c', 'd'); + + var layering = buildLayerMatrix(g); + var conflicts = {}; + + var result = verticalAlignment(g, layering, conflicts, g.predecessors.bind(g)); + expect(result).to.eql({ + root: { a: 'a', b: 'b', c: 'c', d: 'b' }, + align: { a: 'a', b: 'd', c: 'c', d: 'b' }, + }); + }); + + it('aligns blocks across multiple layers', function () { + g.setNode('a', { rank: 0, order: 0 }); + g.setNode('b', { rank: 1, order: 0 }); + g.setNode('c', { rank: 1, order: 1 }); + g.setNode('d', { rank: 2, order: 0 }); + g.setPath(['a', 'b', 'd']); + g.setPath(['a', 'c', 'd']); + + var layering = buildLayerMatrix(g); + var conflicts = {}; + + var result = verticalAlignment(g, layering, conflicts, g.predecessors.bind(g)); + expect(result).to.eql({ + root: { a: 'a', b: 'a', c: 'c', d: 'a' }, + align: { a: 'b', b: 'd', c: 'c', d: 'a' }, + }); + }); + }); + + describe('horizonalCompaction', function () { + it('places the center of a single node graph at origin (0,0)', function () { + var root = { a: 'a' }; + var align = { a: 'a' }; + g.setNode('a', { rank: 0, order: 0 }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(0); + }); + + it('separates adjacent nodes by specified node separation', function () { + var root = { a: 'a', b: 'b' }; + var align = { a: 'a', b: 'b' }; + g.graph().nodesep = 100; + g.setNode('a', { rank: 0, order: 0, width: 100 }); + g.setNode('b', { rank: 0, order: 1, width: 200 }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(0); + expect(xs.b).to.equal(100 / 2 + 100 + 200 / 2); + }); + + it('separates adjacent edges by specified node separation', function () { + var root = { a: 'a', b: 'b' }; + var align = { a: 'a', b: 'b' }; + g.graph().edgesep = 20; + g.setNode('a', { rank: 0, order: 0, width: 100, dummy: true }); + g.setNode('b', { rank: 0, order: 1, width: 200, dummy: true }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(0); + expect(xs.b).to.equal(100 / 2 + 20 + 200 / 2); + }); + + it('aligns the centers of nodes in the same block', function () { + var root = { a: 'a', b: 'a' }; + var align = { a: 'b', b: 'a' }; + g.setNode('a', { rank: 0, order: 0, width: 100 }); + g.setNode('b', { rank: 1, order: 0, width: 200 }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(0); + expect(xs.b).to.equal(0); + }); + + it('separates blocks with the appropriate separation', function () { + var root = { a: 'a', b: 'a', c: 'c' }; + var align = { a: 'b', b: 'a', c: 'c' }; + g.graph().nodesep = 75; + g.setNode('a', { rank: 0, order: 0, width: 100 }); + g.setNode('b', { rank: 1, order: 1, width: 200 }); + g.setNode('c', { rank: 1, order: 0, width: 50 }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(50 / 2 + 75 + 200 / 2); + expect(xs.b).to.equal(50 / 2 + 75 + 200 / 2); + expect(xs.c).to.equal(0); + }); + + it('separates classes with the appropriate separation', function () { + var root = { a: 'a', b: 'b', c: 'c', d: 'b' }; + var align = { a: 'a', b: 'd', c: 'c', d: 'b' }; + g.graph().nodesep = 75; + g.setNode('a', { rank: 0, order: 0, width: 100 }); + g.setNode('b', { rank: 0, order: 1, width: 200 }); + g.setNode('c', { rank: 1, order: 0, width: 50 }); + g.setNode('d', { rank: 1, order: 1, width: 80 }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(0); + expect(xs.b).to.equal(100 / 2 + 75 + 200 / 2); + expect(xs.c).to.equal(100 / 2 + 75 + 200 / 2 - 80 / 2 - 75 - 50 / 2); + expect(xs.d).to.equal(100 / 2 + 75 + 200 / 2); + }); + + it('shifts classes by max sep from the adjacent block #1', function () { + var root = { a: 'a', b: 'b', c: 'a', d: 'b' }; + var align = { a: 'c', b: 'd', c: 'a', d: 'b' }; + g.graph().nodesep = 75; + g.setNode('a', { rank: 0, order: 0, width: 50 }); + g.setNode('b', { rank: 0, order: 1, width: 150 }); + g.setNode('c', { rank: 1, order: 0, width: 60 }); + g.setNode('d', { rank: 1, order: 1, width: 70 }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(0); + expect(xs.b).to.equal(50 / 2 + 75 + 150 / 2); + expect(xs.c).to.equal(0); + expect(xs.d).to.equal(50 / 2 + 75 + 150 / 2); + }); + + it('shifts classes by max sep from the adjacent block #2', function () { + var root = { a: 'a', b: 'b', c: 'a', d: 'b' }; + var align = { a: 'c', b: 'd', c: 'a', d: 'b' }; + g.graph().nodesep = 75; + g.setNode('a', { rank: 0, order: 0, width: 50 }); + g.setNode('b', { rank: 0, order: 1, width: 70 }); + g.setNode('c', { rank: 1, order: 0, width: 60 }); + g.setNode('d', { rank: 1, order: 1, width: 150 }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(0); + expect(xs.b).to.equal(60 / 2 + 75 + 150 / 2); + expect(xs.c).to.equal(0); + expect(xs.d).to.equal(60 / 2 + 75 + 150 / 2); + }); + + it('cascades class shift', function () { + var root = { a: 'a', b: 'b', c: 'c', d: 'd', e: 'b', f: 'f', g: 'd' }; + var align = { a: 'a', b: 'e', c: 'c', d: 'g', e: 'b', f: 'f', g: 'd' }; + g.graph().nodesep = 75; + g.setNode('a', { rank: 0, order: 0, width: 50 }); + g.setNode('b', { rank: 0, order: 1, width: 50 }); + g.setNode('c', { rank: 1, order: 0, width: 50 }); + g.setNode('d', { rank: 1, order: 1, width: 50 }); + g.setNode('e', { rank: 1, order: 2, width: 50 }); + g.setNode('f', { rank: 2, order: 0, width: 50 }); + g.setNode('g', { rank: 2, order: 1, width: 50 }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + + // Use f as 0, everything is relative to it + expect(xs.a).to.equal(xs.b - 50 / 2 - 75 - 50 / 2); + expect(xs.b).to.equal(xs.e); + expect(xs.c).to.equal(xs.f); + expect(xs.d).to.equal(xs.c + 50 / 2 + 75 + 50 / 2); + expect(xs.e).to.equal(xs.d + 50 / 2 + 75 + 50 / 2); + expect(xs.g).to.equal(xs.f + 50 / 2 + 75 + 50 / 2); + }); + + it('handles labelpos = l', function () { + var root = { a: 'a', b: 'b', c: 'c' }; + var align = { a: 'a', b: 'b', c: 'c' }; + g.graph().edgesep = 50; + g.setNode('a', { rank: 0, order: 0, width: 100, dummy: 'edge' }); + g.setNode('b', { + rank: 0, + order: 1, + width: 200, + dummy: 'edge-label', + labelpos: 'l', + }); + g.setNode('c', { rank: 0, order: 2, width: 300, dummy: 'edge' }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(0); + expect(xs.b).to.equal(xs.a + 100 / 2 + 50 + 200); + expect(xs.c).to.equal(xs.b + 0 + 50 + 300 / 2); + }); + + it('handles labelpos = c', function () { + var root = { a: 'a', b: 'b', c: 'c' }; + var align = { a: 'a', b: 'b', c: 'c' }; + g.graph().edgesep = 50; + g.setNode('a', { rank: 0, order: 0, width: 100, dummy: 'edge' }); + g.setNode('b', { + rank: 0, + order: 1, + width: 200, + dummy: 'edge-label', + labelpos: 'c', + }); + g.setNode('c', { rank: 0, order: 2, width: 300, dummy: 'edge' }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(0); + expect(xs.b).to.equal(xs.a + 100 / 2 + 50 + 200 / 2); + expect(xs.c).to.equal(xs.b + 200 / 2 + 50 + 300 / 2); + }); + + it('handles labelpos = r', function () { + var root = { a: 'a', b: 'b', c: 'c' }; + var align = { a: 'a', b: 'b', c: 'c' }; + g.graph().edgesep = 50; + g.setNode('a', { rank: 0, order: 0, width: 100, dummy: 'edge' }); + g.setNode('b', { + rank: 0, + order: 1, + width: 200, + dummy: 'edge-label', + labelpos: 'r', + }); + g.setNode('c', { rank: 0, order: 2, width: 300, dummy: 'edge' }); + + var xs = horizontalCompaction(g, buildLayerMatrix(g), root, align); + expect(xs.a).to.equal(0); + expect(xs.b).to.equal(xs.a + 100 / 2 + 50 + 0); + expect(xs.c).to.equal(xs.b + 200 + 50 + 300 / 2); + }); + }); + + describe('alignCoordinates', function () { + it('aligns a single node', function () { + var xss = { + ul: { a: 50 }, + ur: { a: 100 }, + dl: { a: 50 }, + dr: { a: 200 }, + }; + + alignCoordinates(xss, xss.ul); + + expect(xss.ul).to.eql({ a: 50 }); + expect(xss.ur).to.eql({ a: 50 }); + expect(xss.dl).to.eql({ a: 50 }); + expect(xss.dr).to.eql({ a: 50 }); + }); + + it('aligns multiple nodes', function () { + var xss = { + ul: { a: 50, b: 1000 }, + ur: { a: 100, b: 900 }, + dl: { a: 150, b: 800 }, + dr: { a: 200, b: 700 }, + }; + + alignCoordinates(xss, xss.ul); + + expect(xss.ul).to.eql({ a: 50, b: 1000 }); + expect(xss.ur).to.eql({ a: 200, b: 1000 }); + expect(xss.dl).to.eql({ a: 50, b: 700 }); + expect(xss.dr).to.eql({ a: 500, b: 1000 }); + }); + }); + + describe('findSmallestWidthAlignment', function () { + it('finds the alignment with the smallest width', function () { + g.setNode('a', { width: 50 }); + g.setNode('b', { width: 50 }); + + var xss = { + ul: { a: 0, b: 1000 }, + ur: { a: -5, b: 1000 }, + dl: { a: 5, b: 2000 }, + dr: { a: 0, b: 200 }, + }; + + expect(findSmallestWidthAlignment(g, xss)).to.eql(xss.dr); + }); + + it('takes node width into account', function () { + g.setNode('a', { width: 50 }); + g.setNode('b', { width: 50 }); + g.setNode('c', { width: 200 }); + + var xss = { + ul: { a: 0, b: 100, c: 75 }, + ur: { a: 0, b: 100, c: 80 }, + dl: { a: 0, b: 100, c: 85 }, + dr: { a: 0, b: 100, c: 90 }, + }; + + expect(findSmallestWidthAlignment(g, xss)).to.eql(xss.ul); + }); + }); + + describe('balance', function () { + it('aligns a single node to the shared median value', function () { + var xss = { + ul: { a: 0 }, + ur: { a: 100 }, + dl: { a: 100 }, + dr: { a: 200 }, + }; + + expect(balance(xss)).to.eql({ a: 100 }); + }); + + it('aligns a single node to the average of different median values', function () { + var xss = { + ul: { a: 0 }, + ur: { a: 75 }, + dl: { a: 125 }, + dr: { a: 200 }, + }; + + expect(balance(xss)).to.eql({ a: 100 }); + }); + + it('balances multiple nodes', function () { + var xss = { + ul: { a: 0, b: 50 }, + ur: { a: 75, b: 0 }, + dl: { a: 125, b: 60 }, + dr: { a: 200, b: 75 }, + }; + + expect(balance(xss)).to.eql({ a: 100, b: 55 }); + }); + }); + + describe('positionX', function () { + it('positions a single node at origin', function () { + g.setNode('a', { rank: 0, order: 0, width: 100 }); + expect(positionX(g)).to.eql({ a: 0 }); + }); + + it('positions a single node block at origin', function () { + g.setNode('a', { rank: 0, order: 0, width: 100 }); + g.setNode('b', { rank: 1, order: 0, width: 100 }); + g.setEdge('a', 'b'); + expect(positionX(g)).to.eql({ a: 0, b: 0 }); + }); + + it('positions a single node block at origin even when their sizes differ', function () { + g.setNode('a', { rank: 0, order: 0, width: 40 }); + g.setNode('b', { rank: 1, order: 0, width: 500 }); + g.setNode('c', { rank: 2, order: 0, width: 20 }); + g.setPath(['a', 'b', 'c']); + expect(positionX(g)).to.eql({ a: 0, b: 0, c: 0 }); + }); + + it('centers a node if it is a predecessor of two same sized nodes', function () { + g.graph().nodesep = 10; + g.setNode('a', { rank: 0, order: 0, width: 20 }); + g.setNode('b', { rank: 1, order: 0, width: 50 }); + g.setNode('c', { rank: 1, order: 1, width: 50 }); + g.setEdge('a', 'b'); + g.setEdge('a', 'c'); + + var pos = positionX(g); + var a = pos.a; + expect(pos).to.eql({ a: a, b: a - (25 + 5), c: a + (25 + 5) }); + }); + + it('shifts blocks on both sides of aligned block', function () { + g.graph().nodesep = 10; + g.setNode('a', { rank: 0, order: 0, width: 50 }); + g.setNode('b', { rank: 0, order: 1, width: 60 }); + g.setNode('c', { rank: 1, order: 0, width: 70 }); + g.setNode('d', { rank: 1, order: 1, width: 80 }); + g.setEdge('b', 'c'); + + var pos = positionX(g); + var b = pos.b; + var c = b; + expect(pos).to.eql({ + a: b - 60 / 2 - 10 - 50 / 2, + b: b, + c: c, + d: c + 70 / 2 + 10 + 80 / 2, + }); + }); + + it('aligns inner segments', function () { + g.graph().nodesep = 10; + g.setNode('a', { rank: 0, order: 0, width: 50, dummy: true }); + g.setNode('b', { rank: 0, order: 1, width: 60 }); + g.setNode('c', { rank: 1, order: 0, width: 70 }); + g.setNode('d', { rank: 1, order: 1, width: 80, dummy: true }); + g.setEdge('b', 'c'); + g.setEdge('a', 'd'); + + var pos = positionX(g); + var a = pos.a; + var d = a; + expect(pos).to.eql({ + a: a, + b: a + 50 / 2 + 10 + 60 / 2, + c: d - 70 / 2 - 10 - 80 / 2, + d: d, + }); + }); + }); +}); diff --git a/src/dagre/position/index.js b/src/dagre/position/index.js index 62008d8..68ee820 100644 --- a/src/dagre/position/index.js +++ b/src/dagre/position/index.js @@ -21,7 +21,7 @@ function positionY(g) { var maxHeight = _.max( _.map(layer, function (v) { return g.node(v).height; - }) + }), ); _.forEach(layer, function (v) { g.node(v).y = prevY + maxHeight / 2; diff --git a/src/dagre/position/index.test.js b/src/dagre/position/index.test.js new file mode 100644 index 0000000..0b84079 --- /dev/null +++ b/src/dagre/position/index.test.js @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest'; + +import { position } from './index.js'; +import { Graph } from '../../graphlib/index.js'; + +describe('position', function () { + var g; + + beforeEach(function () { + g = new Graph({ compound: true }).setGraph({ + ranksep: 50, + nodesep: 50, + edgesep: 10, + }); + }); + + it('respects ranksep', function () { + g.graph().ranksep = 1000; + g.setNode('a', { width: 50, height: 100, rank: 0, order: 0 }); + g.setNode('b', { width: 50, height: 80, rank: 1, order: 0 }); + g.setEdge('a', 'b'); + position(g); + expect(g.node('b').y).to.equal(100 + 1000 + 80 / 2); + }); + + it('use the largest height in each rank with ranksep', function () { + g.graph().ranksep = 1000; + g.setNode('a', { width: 50, height: 100, rank: 0, order: 0 }); + g.setNode('b', { width: 50, height: 80, rank: 0, order: 1 }); + g.setNode('c', { width: 50, height: 90, rank: 1, order: 0 }); + g.setEdge('a', 'c'); + position(g); + expect(g.node('a').y).to.equal(100 / 2); + expect(g.node('b').y).to.equal(100 / 2); // Note we used 100 and not 80 here + expect(g.node('c').y).to.equal(100 + 1000 + 90 / 2); + }); + + it('respects nodesep', function () { + g.graph().nodesep = 1000; + g.setNode('a', { width: 50, height: 100, rank: 0, order: 0 }); + g.setNode('b', { width: 70, height: 80, rank: 0, order: 1 }); + position(g); + expect(g.node('b').x).to.equal(g.node('a').x + 50 / 2 + 1000 + 70 / 2); + }); + + it('should not try to position the subgraph node itself', function () { + g.setNode('a', { width: 50, height: 50, rank: 0, order: 0 }); + g.setNode('sg1', {}); + g.setParent('a', 'sg1'); + position(g); + expect(g.node('sg1')).to.not.have.property('x'); + expect(g.node('sg1')).to.not.have.property('y'); + }); +}); diff --git a/src/dagre/rank/feasible-tree.test.js b/src/dagre/rank/feasible-tree.test.js new file mode 100644 index 0000000..f9894ce --- /dev/null +++ b/src/dagre/rank/feasible-tree.test.js @@ -0,0 +1,53 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../../graphlib/graph.js'; +import { feasibleTree } from './feasible-tree.js'; + +describe('feasibleTree', function () { + it('creates a tree for a trivial input graph', function () { + var g = new Graph() + .setNode('a', { rank: 0 }) + .setNode('b', { rank: 1 }) + .setEdge('a', 'b', { minlen: 1 }); + + var tree = feasibleTree(g); + expect(g.node('b').rank).to.equal(g.node('a').rank + 1); + expect(tree.neighbors('a')).to.eql(['b']); + }); + + it('correctly shortens slack by pulling a node up', function () { + var g = new Graph() + .setNode('a', { rank: 0 }) + .setNode('b', { rank: 1 }) + .setNode('c', { rank: 2 }) + .setNode('d', { rank: 2 }) + .setPath(['a', 'b', 'c'], { minlen: 1 }) + .setEdge('a', 'd', { minlen: 1 }); + + var tree = feasibleTree(g); + expect(g.node('b').rank).to.eql(g.node('a').rank + 1); + expect(g.node('c').rank).to.eql(g.node('b').rank + 1); + expect(g.node('d').rank).to.eql(g.node('a').rank + 1); + expect(_.sortBy(tree.neighbors('a'))).to.eql(['b', 'd']); + expect(_.sortBy(tree.neighbors('b'))).to.eql(['a', 'c']); + expect(tree.neighbors('c')).to.eql(['b']); + expect(tree.neighbors('d')).to.eql(['a']); + }); + + it('correctly shortens slack by pulling a node down', function () { + var g = new Graph() + .setNode('a', { rank: 2 }) + .setNode('b', { rank: 0 }) + .setNode('c', { rank: 2 }) + .setEdge('b', 'a', { minlen: 1 }) + .setEdge('b', 'c', { minlen: 1 }); + + var tree = feasibleTree(g); + expect(g.node('a').rank).to.eql(g.node('b').rank + 1); + expect(g.node('c').rank).to.eql(g.node('b').rank + 1); + expect(_.sortBy(tree.neighbors('a'))).to.eql(['b']); + expect(_.sortBy(tree.neighbors('b'))).to.eql(['a', 'c']); + expect(_.sortBy(tree.neighbors('c'))).to.eql(['b']); + }); +}); diff --git a/src/dagre/rank/index.test.js b/src/dagre/rank/index.test.js new file mode 100644 index 0000000..886433c --- /dev/null +++ b/src/dagre/rank/index.test.js @@ -0,0 +1,45 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; + +import { rank } from './index.js'; +import { Graph } from '../../graphlib/graph.js'; + +describe('rank', function () { + var RANKERS = ['longest-path', 'tight-tree', 'network-simplex', 'unknown-should-still-work']; + var g; + + beforeEach(function () { + g = new Graph() + .setGraph({}) + .setDefaultNodeLabel(function () { + return {}; + }) + .setDefaultEdgeLabel(function () { + return { minlen: 1, weight: 1 }; + }) + .setPath(['a', 'b', 'c', 'd', 'h']) + .setPath(['a', 'e', 'g', 'h']) + .setPath(['a', 'f', 'g']); + }); + + _.forEach(RANKERS, function (ranker) { + describe(ranker, function () { + it('respects the minlen attribute', function () { + g.graph().ranker = ranker; + rank(g); + _.forEach(g.edges(), function (e) { + var vRank = g.node(e.v).rank; + var wRank = g.node(e.w).rank; + expect(wRank - vRank).to.be.gte(g.edge(e).minlen); + }); + }); + + it('can rank a single node graph', function () { + var g = new Graph().setGraph({}).setNode('a', {}); + g.graph().ranker = ranker; + rank(g); + expect(g.node('a').rank).to.equal(0); + }); + }); + }); +}); diff --git a/src/dagre/rank/network-simplex.test.js b/src/dagre/rank/network-simplex.test.js new file mode 100644 index 0000000..6424c91 --- /dev/null +++ b/src/dagre/rank/network-simplex.test.js @@ -0,0 +1,466 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; +import { Graph } from '../../graphlib/graph.js'; +import { networkSimplex } from './network-simplex.js'; +import { longestPath } from './util.js'; +var initLowLimValues = networkSimplex.initLowLimValues; +var initCutValues = networkSimplex.initCutValues; +var calcCutValue = networkSimplex.calcCutValue; +var leaveEdge = networkSimplex.leaveEdge; +var enterEdge = networkSimplex.enterEdge; +var exchangeEdges = networkSimplex.exchangeEdges; +import { normalizeRanks } from '../util.js'; + +describe('network simplex', function () { + var g, t, gansnerGraph, gansnerTree; + + beforeEach(function () { + g = new Graph({ multigraph: true }) + .setDefaultNodeLabel(function () { + return {}; + }) + .setDefaultEdgeLabel(function () { + return { minlen: 1, weight: 1 }; + }); + + t = new Graph({ directed: false }) + .setDefaultNodeLabel(function () { + return {}; + }) + .setDefaultEdgeLabel(function () { + return {}; + }); + + gansnerGraph = new Graph() + .setDefaultNodeLabel(function () { + return {}; + }) + .setDefaultEdgeLabel(function () { + return { minlen: 1, weight: 1 }; + }) + .setPath(['a', 'b', 'c', 'd', 'h']) + .setPath(['a', 'e', 'g', 'h']) + .setPath(['a', 'f', 'g']); + + gansnerTree = new Graph({ directed: false }) + .setDefaultNodeLabel(function () { + return {}; + }) + .setDefaultEdgeLabel(function () { + return {}; + }) + .setPath(['a', 'b', 'c', 'd', 'h', 'g', 'e']) + .setEdge('g', 'f'); + }); + + it('can assign a rank to a single node', function () { + g.setNode('a'); + ns(g); + expect(g.node('a').rank).to.equal(0); + }); + + it('can assign a rank to a 2-node connected graph', function () { + g.setEdge('a', 'b'); + ns(g); + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(1); + }); + + it('can assign ranks for a diamond', function () { + g.setPath(['a', 'b', 'd']); + g.setPath(['a', 'c', 'd']); + ns(g); + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(1); + expect(g.node('c').rank).to.equal(1); + expect(g.node('d').rank).to.equal(2); + }); + + it('uses the minlen attribute on the edge', function () { + g.setPath(['a', 'b', 'd']); + g.setEdge('a', 'c'); + g.setEdge('c', 'd', { minlen: 2 }); + ns(g); + expect(g.node('a').rank).to.equal(0); + // longest path biases towards the lowest rank it can assign. Since the + // graph has no optimization opportunities we can assume that the longest + // path ranking is used. + expect(g.node('b').rank).to.equal(2); + expect(g.node('c').rank).to.equal(1); + expect(g.node('d').rank).to.equal(3); + }); + + it('can rank the gansner graph', function () { + g = gansnerGraph; + ns(g); + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(1); + expect(g.node('c').rank).to.equal(2); + expect(g.node('d').rank).to.equal(3); + expect(g.node('h').rank).to.equal(4); + expect(g.node('e').rank).to.equal(1); + expect(g.node('f').rank).to.equal(1); + expect(g.node('g').rank).to.equal(2); + }); + + it('can handle multi-edges', function () { + g.setPath(['a', 'b', 'c', 'd']); + g.setEdge('a', 'e', { weight: 2, minlen: 1 }); + g.setEdge('e', 'd'); + g.setEdge('b', 'c', { weight: 1, minlen: 2 }, 'multi'); + ns(g); + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(1); + // b -> c has minlen = 1 and minlen = 2, so it should be 2 ranks apart. + expect(g.node('c').rank).to.equal(3); + expect(g.node('d').rank).to.equal(4); + expect(g.node('e').rank).to.equal(1); + }); + + describe('leaveEdge', function () { + it('returns undefined if there is no edge with a negative cutvalue', function () { + var tree = new Graph({ directed: false }); + tree.setEdge('a', 'b', { cutvalue: 1 }); + tree.setEdge('b', 'c', { cutvalue: 1 }); + expect(leaveEdge(tree)).to.be.undefined; + }); + + it('returns an edge if one is found with a negative cutvalue', function () { + var tree = new Graph({ directed: false }); + tree.setEdge('a', 'b', { cutvalue: 1 }); + tree.setEdge('b', 'c', { cutvalue: -1 }); + expect(leaveEdge(tree)).to.eql({ v: 'b', w: 'c' }); + }); + }); + + describe('enterEdge', function () { + it('finds an edge from the head to tail component', function () { + g.setNode('a', { rank: 0 }) + .setNode('b', { rank: 2 }) + .setNode('c', { rank: 3 }) + .setPath(['a', 'b', 'c']) + .setEdge('a', 'c'); + t.setPath(['b', 'c', 'a']); + initLowLimValues(t, 'c'); + + var f = enterEdge(t, g, { v: 'b', w: 'c' }); + expect(undirectedEdge(f)).to.eql(undirectedEdge({ v: 'a', w: 'b' })); + }); + + it('works when the root of the tree is in the tail component', function () { + g.setNode('a', { rank: 0 }) + .setNode('b', { rank: 2 }) + .setNode('c', { rank: 3 }) + .setPath(['a', 'b', 'c']) + .setEdge('a', 'c'); + t.setPath(['b', 'c', 'a']); + initLowLimValues(t, 'b'); + + var f = enterEdge(t, g, { v: 'b', w: 'c' }); + expect(undirectedEdge(f)).to.eql(undirectedEdge({ v: 'a', w: 'b' })); + }); + + it('finds the edge with the least slack', function () { + g.setNode('a', { rank: 0 }) + .setNode('b', { rank: 1 }) + .setNode('c', { rank: 3 }) + .setNode('d', { rank: 4 }) + .setEdge('a', 'd') + .setPath(['a', 'c', 'd']) + .setEdge('b', 'c'); + t.setPath(['c', 'd', 'a', 'b']); + initLowLimValues(t, 'a'); + + var f = enterEdge(t, g, { v: 'c', w: 'd' }); + expect(undirectedEdge(f)).to.eql(undirectedEdge({ v: 'b', w: 'c' })); + }); + + it('finds an appropriate edge for gansner graph #1', function () { + g = gansnerGraph; + t = gansnerTree; + longestPath(g); + initLowLimValues(t, 'a'); + + var f = enterEdge(t, g, { v: 'g', w: 'h' }); + expect(undirectedEdge(f).v).to.equal('a'); + expect(['e', 'f']).to.include(undirectedEdge(f).w); + }); + + it('finds an appropriate edge for gansner graph #2', function () { + g = gansnerGraph; + t = gansnerTree; + longestPath(g); + initLowLimValues(t, 'e'); + + var f = enterEdge(t, g, { v: 'g', w: 'h' }); + expect(undirectedEdge(f).v).to.equal('a'); + expect(['e', 'f']).to.include(undirectedEdge(f).w); + }); + + it('finds an appropriate edge for gansner graph #3', function () { + g = gansnerGraph; + t = gansnerTree; + longestPath(g); + initLowLimValues(t, 'a'); + + var f = enterEdge(t, g, { v: 'h', w: 'g' }); + expect(undirectedEdge(f).v).to.equal('a'); + expect(['e', 'f']).to.include(undirectedEdge(f).w); + }); + + it('finds an appropriate edge for gansner graph #4', function () { + g = gansnerGraph; + t = gansnerTree; + longestPath(g); + initLowLimValues(t, 'e'); + + var f = enterEdge(t, g, { v: 'h', w: 'g' }); + expect(undirectedEdge(f).v).to.equal('a'); + expect(['e', 'f']).to.include(undirectedEdge(f).w); + }); + }); + + describe('initLowLimValues', function () { + it('assigns low, lim, and parent for each node in a tree', function () { + var g = new Graph() + .setDefaultNodeLabel(function () { + return {}; + }) + .setNodes(['a', 'b', 'c', 'd', 'e']) + .setPath(['a', 'b', 'a', 'c', 'd', 'c', 'e']); + + initLowLimValues(g, 'a'); + + var a = g.node('a'); + var b = g.node('b'); + var c = g.node('c'); + var d = g.node('d'); + var e = g.node('e'); + + expect( + _.sortBy( + _.map(g.nodes(), function (v) { + return g.node(v).lim; + }), + ), + ).to.eql(_.range(1, 6)); + + expect(a).to.eql({ low: 1, lim: 5 }); + + expect(b.parent).to.equal('a'); + expect(b.lim).to.be.lt(a.lim); + + expect(c.parent).to.equal('a'); + expect(c.lim).to.be.lt(a.lim); + expect(c.lim).to.not.equal(b.lim); + + expect(d.parent).to.equal('c'); + expect(d.lim).to.be.lt(c.lim); + + expect(e.parent).to.equal('c'); + expect(e.lim).to.be.lt(c.lim); + expect(e.lim).to.not.equal(d.lim); + }); + }); + + describe('exchangeEdges', function () { + it('exchanges edges and updates cut values and low/lim numbers', function () { + g = gansnerGraph; + t = gansnerTree; + longestPath(g); + initLowLimValues(t); + + exchangeEdges(t, g, { v: 'g', w: 'h' }, { v: 'a', w: 'e' }); + + // check new cut values + expect(t.edge('a', 'b').cutvalue).to.equal(2); + expect(t.edge('b', 'c').cutvalue).to.equal(2); + expect(t.edge('c', 'd').cutvalue).to.equal(2); + expect(t.edge('d', 'h').cutvalue).to.equal(2); + expect(t.edge('a', 'e').cutvalue).to.equal(1); + expect(t.edge('e', 'g').cutvalue).to.equal(1); + expect(t.edge('g', 'f').cutvalue).to.equal(0); + + // ensure lim numbers look right + var lims = _.sortBy( + _.map(t.nodes(), function (v) { + return t.node(v).lim; + }), + ); + expect(lims).to.eql(_.range(1, 9)); + }); + + it('updates ranks', function () { + g = gansnerGraph; + t = gansnerTree; + longestPath(g); + initLowLimValues(t); + + exchangeEdges(t, g, { v: 'g', w: 'h' }, { v: 'a', w: 'e' }); + normalizeRanks(g); + + // check new ranks + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(1); + expect(g.node('c').rank).to.equal(2); + expect(g.node('d').rank).to.equal(3); + expect(g.node('e').rank).to.equal(1); + expect(g.node('f').rank).to.equal(1); + expect(g.node('g').rank).to.equal(2); + expect(g.node('h').rank).to.equal(4); + }); + }); + + // Note: we use p for parent, c for child, gc_x for grandchild nodes, and o for + // other nodes in the tree for these tests. + describe('calcCutValue', function () { + it('works for a 2-node tree with c -> p', function () { + g.setPath(['c', 'p']); + t.setPath(['p', 'c']); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(1); + }); + + it('works for a 2-node tree with c <- p', function () { + g.setPath(['p', 'c']); + t.setPath(['p', 'c']); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(1); + }); + + it('works for 3-node tree with gc -> c -> p', function () { + g.setPath(['gc', 'c', 'p']); + t.setEdge('gc', 'c', { cutvalue: 3 }).setEdge('p', 'c'); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(3); + }); + + it('works for 3-node tree with gc -> c <- p', function () { + g.setEdge('p', 'c').setEdge('gc', 'c'); + t.setEdge('gc', 'c', { cutvalue: 3 }).setEdge('p', 'c'); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(-1); + }); + + it('works for 3-node tree with gc <- c -> p', function () { + g.setEdge('c', 'p').setEdge('c', 'gc'); + t.setEdge('gc', 'c', { cutvalue: 3 }).setEdge('p', 'c'); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(-1); + }); + + it('works for 3-node tree with gc <- c <- p', function () { + g.setPath(['p', 'c', 'gc']); + t.setEdge('gc', 'c', { cutvalue: 3 }).setEdge('p', 'c'); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(3); + }); + + it('works for 4-node tree with gc -> c -> p -> o, with o -> c', function () { + g.setEdge('o', 'c', { weight: 7 }).setPath(['gc', 'c', 'p', 'o']); + t.setEdge('gc', 'c', { cutvalue: 3 }).setPath(['c', 'p', 'o']); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(-4); + }); + + it('works for 4-node tree with gc -> c -> p -> o, with o <- c', function () { + g.setEdge('c', 'o', { weight: 7 }).setPath(['gc', 'c', 'p', 'o']); + t.setEdge('gc', 'c', { cutvalue: 3 }).setPath(['c', 'p', 'o']); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(10); + }); + + it('works for 4-node tree with o -> gc -> c -> p, with o -> c', function () { + g.setEdge('o', 'c', { weight: 7 }).setPath(['o', 'gc', 'c', 'p']); + t.setEdge('o', 'gc').setEdge('gc', 'c', { cutvalue: 3 }).setEdge('c', 'p'); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(-4); + }); + + it('works for 4-node tree with o -> gc -> c -> p, with o <- c', function () { + g.setEdge('c', 'o', { weight: 7 }).setPath(['o', 'gc', 'c', 'p']); + t.setEdge('o', 'gc').setEdge('gc', 'c', { cutvalue: 3 }).setEdge('c', 'p'); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(10); + }); + + it('works for 4-node tree with gc -> c <- p -> o, with o -> c', function () { + g.setEdge('gc', 'c').setEdge('p', 'c').setEdge('p', 'o').setEdge('o', 'c', { weight: 7 }); + t.setEdge('o', 'gc').setEdge('gc', 'c', { cutvalue: 3 }).setEdge('c', 'p'); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(6); + }); + + it('works for 4-node tree with gc -> c <- p -> o, with o <- c', function () { + g.setEdge('gc', 'c').setEdge('p', 'c').setEdge('p', 'o').setEdge('c', 'o', { weight: 7 }); + t.setEdge('o', 'gc').setEdge('gc', 'c', { cutvalue: 3 }).setEdge('c', 'p'); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(-8); + }); + + it('works for 4-node tree with o -> gc -> c <- p, with o -> c', function () { + g.setEdge('o', 'c', { weight: 7 }).setPath(['o', 'gc', 'c']).setEdge('p', 'c'); + t.setEdge('o', 'gc').setEdge('gc', 'c', { cutvalue: 3 }).setEdge('c', 'p'); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(6); + }); + + it('works for 4-node tree with o -> gc -> c <- p, with o <- c', function () { + g.setEdge('c', 'o', { weight: 7 }).setPath(['o', 'gc', 'c']).setEdge('p', 'c'); + t.setEdge('o', 'gc').setEdge('gc', 'c', { cutvalue: 3 }).setEdge('c', 'p'); + initLowLimValues(t, 'p'); + + expect(calcCutValue(t, g, 'c')).to.equal(-8); + }); + }); + + describe('initCutValues', function () { + it('works for gansnerGraph', function () { + initLowLimValues(gansnerTree); + initCutValues(gansnerTree, gansnerGraph); + expect(gansnerTree.edge('a', 'b').cutvalue).to.equal(3); + expect(gansnerTree.edge('b', 'c').cutvalue).to.equal(3); + expect(gansnerTree.edge('c', 'd').cutvalue).to.equal(3); + expect(gansnerTree.edge('d', 'h').cutvalue).to.equal(3); + expect(gansnerTree.edge('g', 'h').cutvalue).to.equal(-1); + expect(gansnerTree.edge('e', 'g').cutvalue).to.equal(0); + expect(gansnerTree.edge('f', 'g').cutvalue).to.equal(0); + }); + + it('works for updated gansnerGraph', function () { + gansnerTree.removeEdge('g', 'h'); + gansnerTree.setEdge('a', 'e'); + initLowLimValues(gansnerTree); + initCutValues(gansnerTree, gansnerGraph); + expect(gansnerTree.edge('a', 'b').cutvalue).to.equal(2); + expect(gansnerTree.edge('b', 'c').cutvalue).to.equal(2); + expect(gansnerTree.edge('c', 'd').cutvalue).to.equal(2); + expect(gansnerTree.edge('d', 'h').cutvalue).to.equal(2); + expect(gansnerTree.edge('a', 'e').cutvalue).to.equal(1); + expect(gansnerTree.edge('e', 'g').cutvalue).to.equal(1); + expect(gansnerTree.edge('f', 'g').cutvalue).to.equal(0); + }); + }); +}); + +function ns(g) { + networkSimplex(g); + normalizeRanks(g); +} + +function undirectedEdge(e) { + return e.v < e.w ? { v: e.v, w: e.w } : { v: e.w, w: e.v }; +} diff --git a/src/dagre/rank/util.js b/src/dagre/rank/util.js index 9f49a8f..c8586f9 100644 --- a/src/dagre/rank/util.js +++ b/src/dagre/rank/util.js @@ -36,7 +36,7 @@ function longestPath(g) { var rank = _.min( _.map(g.outEdges(v), function (e) { return dfs(e.w) - g.edge(e).minlen; - }) + }), ); if ( diff --git a/src/dagre/rank/util.test.js b/src/dagre/rank/util.test.js new file mode 100644 index 0000000..8018b04 --- /dev/null +++ b/src/dagre/rank/util.test.js @@ -0,0 +1,69 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../../graphlib/graph.js'; +import { normalizeRanks } from '../util.js'; +import { longestPath } from './util.js'; + +describe('rank/util', function () { + describe('longestPath', function () { + var g; + + beforeEach(function () { + g = new Graph() + .setDefaultNodeLabel(function () { + return {}; + }) + .setDefaultEdgeLabel(function () { + return { minlen: 1 }; + }); + }); + + it('can assign a rank to a single node graph', function () { + g.setNode('a'); + longestPath(g); + normalizeRanks(g); + expect(g.node('a').rank).to.equal(0); + }); + + it('can assign ranks to unconnected nodes', function () { + g.setNode('a'); + g.setNode('b'); + longestPath(g); + normalizeRanks(g); + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(0); + }); + + it('can assign ranks to connected nodes', function () { + g.setEdge('a', 'b'); + longestPath(g); + normalizeRanks(g); + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(1); + }); + + it('can assign ranks for a diamond', function () { + g.setPath(['a', 'b', 'd']); + g.setPath(['a', 'c', 'd']); + longestPath(g); + normalizeRanks(g); + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(1); + expect(g.node('c').rank).to.equal(1); + expect(g.node('d').rank).to.equal(2); + }); + + it('uses the minlen attribute on the edge', function () { + g.setPath(['a', 'b', 'd']); + g.setEdge('a', 'c'); + g.setEdge('c', 'd', { minlen: 2 }); + longestPath(g); + normalizeRanks(g); + expect(g.node('a').rank).to.equal(0); + // longest path biases towards the lowest rank it can assign + expect(g.node('b').rank).to.equal(2); + expect(g.node('c').rank).to.equal(1); + expect(g.node('d').rank).to.equal(3); + }); + }); +}); diff --git a/src/dagre/util.js b/src/dagre/util.js index eefd1d9..2e8c973 100644 --- a/src/dagre/util.js +++ b/src/dagre/util.js @@ -16,6 +16,11 @@ export { partition, time, notime, + uniqueId, + range, + pick, + mapValues, + zipObject, }; /* @@ -152,7 +157,7 @@ function normalizeRanks(g) { var min = _.min( _.map(g.nodes(), function (v) { return g.node(v).rank; - }) + }), ); _.forEach(g.nodes(), function (v) { var node = g.node(v); @@ -167,7 +172,7 @@ function removeEmptyRanks(g) { var offset = _.min( _.map(g.nodes(), function (v) { return g.node(v).rank; - }) + }), ); var layers = []; @@ -196,7 +201,7 @@ function addBorderNode(g, prefix, rank, order) { var node = { width: 0, height: 0, - }; + }; // as { width: number; height: number; rank?: number; order?: number }; if (arguments.length >= 4) { node.rank = rank; node.order = order; @@ -211,7 +216,7 @@ function maxRank(g) { if (!_.isUndefined(rank)) { return rank; } - }) + }), ); } @@ -248,3 +253,66 @@ function time(name, fn) { function notime(name, fn) { return fn(); } + +let idCounter = 0; +function uniqueId(prefix) { + var id = ++idCounter; + return prefix + id; +} + +/** + * + * @param {number} start - The start of the range. + * @param {number} [limit=null] - The end of the range. If not provided, `start` is used as the limit and the range starts from 0. + * @param {number} [step=1] - The step between each number in the range. Can be negative. + * @returns {number[]} An array of numbers within the specified range. + */ +function range(start, limit = null, step = 1) { + // : number[] + if (limit == null) { + limit = start; + start = 0; + } + + let endCon = (i) => i < limit; + if (step < 0) { + endCon = (i) => limit < i; + } + + const range = []; + for (let i = start; endCon(i); i += step) { + range.push(i); + } + + return range; +} + +function pick(source, keys) { + const dest = {}; + for (const key of keys) { + if (source[key] !== undefined) { + dest[key] = source[key]; + } + } + + return dest; +} + +function mapValues(obj, funcOrProp) { + let func = funcOrProp; + if (typeof funcOrProp === 'string') { + func = (val) => val[funcOrProp]; + } + + return Object.entries(obj).reduce((acc, [k, v]) => { + acc[k] = func(v, k); + return acc; + }, {}); +} + +function zipObject(props, values) { + return props.reduce((acc, key, i) => { + acc[key] = values[i]; + return acc; + }, {}); +} diff --git a/src/dagre/util.test.js b/src/dagre/util.test.js new file mode 100644 index 0000000..118ecc9 --- /dev/null +++ b/src/dagre/util.test.js @@ -0,0 +1,287 @@ +import * as _ from 'lodash-es'; +import { describe, expect, it } from 'vitest'; +import { Graph } from '../graphlib/index.js'; +import * as util from './util.js'; + +describe('util', function () { + describe('simplify', function () { + var g; + + beforeEach(function () { + g = new Graph({ multigraph: true }); + }); + + it('copies without change a graph with no multi-edges', function () { + g.setEdge('a', 'b', { weight: 1, minlen: 1 }); + var g2 = util.simplify(g); + expect(g2.edge('a', 'b')).eql({ weight: 1, minlen: 1 }); + expect(g2.edgeCount()).equals(1); + }); + + it('collapses multi-edges', function () { + g.setEdge('a', 'b', { weight: 1, minlen: 1 }); + g.setEdge('a', 'b', { weight: 2, minlen: 2 }, 'multi'); + var g2 = util.simplify(g); + expect(g2.isMultigraph()).to.be.false; + expect(g2.edge('a', 'b')).eql({ weight: 3, minlen: 2 }); + expect(g2.edgeCount()).equals(1); + }); + + it('copies the graph object', function () { + g.setGraph({ foo: 'bar' }); + var g2 = util.simplify(g); + expect(g2.graph()).eqls({ foo: 'bar' }); + }); + }); + + describe('asNonCompoundGraph', function () { + var g; + + beforeEach(function () { + g = new Graph({ compound: true, multigraph: true }); + }); + + it('copies all nodes', function () { + g.setNode('a', { foo: 'bar' }); + g.setNode('b'); + var g2 = util.asNonCompoundGraph(g); + expect(g2.node('a')).to.eql({ foo: 'bar' }); + expect(g2.hasNode('b')).to.be.true; + }); + + it('copies all edges', function () { + g.setEdge('a', 'b', { foo: 'bar' }); + g.setEdge('a', 'b', { foo: 'baz' }, 'multi'); + var g2 = util.asNonCompoundGraph(g); + expect(g2.edge('a', 'b')).eqls({ foo: 'bar' }); + expect(g2.edge('a', 'b', 'multi')).eqls({ foo: 'baz' }); + }); + + it('does not copy compound nodes', function () { + g.setParent('a', 'sg1'); + var g2 = util.asNonCompoundGraph(g); + expect(g2.parent(g)).to.be.undefined; + expect(g2.isCompound()).to.be.false; + }); + + it('copies the graph object', function () { + g.setGraph({ foo: 'bar' }); + var g2 = util.asNonCompoundGraph(g); + expect(g2.graph()).eqls({ foo: 'bar' }); + }); + }); + + describe('successorWeights', function () { + it('maps a node to its successors with associated weights', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b', { weight: 2 }); + g.setEdge('b', 'c', { weight: 1 }); + g.setEdge('b', 'c', { weight: 2 }, 'multi'); + g.setEdge('b', 'd', { weight: 1 }, 'multi'); + expect(util.successorWeights(g).a).to.eql({ b: 2 }); + expect(util.successorWeights(g).b).to.eql({ c: 3, d: 1 }); + expect(util.successorWeights(g).c).to.eql({}); + expect(util.successorWeights(g).d).to.eql({}); + }); + }); + + describe('predecessorWeights', function () { + it('maps a node to its predecessors with associated weights', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b', { weight: 2 }); + g.setEdge('b', 'c', { weight: 1 }); + g.setEdge('b', 'c', { weight: 2 }, 'multi'); + g.setEdge('b', 'd', { weight: 1 }, 'multi'); + expect(util.predecessorWeights(g).a).to.eql({}); + expect(util.predecessorWeights(g).b).to.eql({ a: 2 }); + expect(util.predecessorWeights(g).c).to.eql({ b: 3 }); + expect(util.predecessorWeights(g).d).to.eql({ b: 1 }); + }); + }); + + describe('intersectRect', function () { + function expectIntersects(rect, point) { + var cross = util.intersectRect(rect, point); + if (cross.x !== point.x) { + var m = (cross.y - point.y) / (cross.x - point.x); + expect(cross.y - rect.y).equals(m * (cross.x - rect.x)); + } + } + + function expectTouchesBorder(rect, point) { + var cross = util.intersectRect(rect, point); + if (Math.abs(rect.x - cross.x) !== rect.width / 2) { + expect(Math.abs(rect.y - cross.y)).equals(rect.height / 2); + } + } + + it("creates a slope that will intersect the rectangle's center", function () { + var rect = { x: 0, y: 0, width: 1, height: 1 }; + expectIntersects(rect, { x: 2, y: 6 }); + expectIntersects(rect, { x: 2, y: -6 }); + expectIntersects(rect, { x: 6, y: 2 }); + expectIntersects(rect, { x: -6, y: 2 }); + expectIntersects(rect, { x: 5, y: 0 }); + expectIntersects(rect, { x: 0, y: 5 }); + }); + + it('touches the border of the rectangle', function () { + var rect = { x: 0, y: 0, width: 1, height: 1 }; + expectTouchesBorder(rect, { x: 2, y: 6 }); + expectTouchesBorder(rect, { x: 2, y: -6 }); + expectTouchesBorder(rect, { x: 6, y: 2 }); + expectTouchesBorder(rect, { x: -6, y: 2 }); + expectTouchesBorder(rect, { x: 5, y: 0 }); + expectTouchesBorder(rect, { x: 0, y: 5 }); + }); + + it('throws an error if the point is at the center of the rectangle', function () { + var rect = { x: 0, y: 0, width: 1, height: 1 }; + expect(function () { + util.intersectRect(rect, { x: 0, y: 0 }); + }).to.throw(); + }); + }); + + describe('buildLayerMatrix', function () { + it('creates a matrix based on rank and order of nodes in the graph', function () { + var g = new Graph(); + g.setNode('a', { rank: 0, order: 0 }); + g.setNode('b', { rank: 0, order: 1 }); + g.setNode('c', { rank: 1, order: 0 }); + g.setNode('d', { rank: 1, order: 1 }); + g.setNode('e', { rank: 2, order: 0 }); + + expect(util.buildLayerMatrix(g)).to.eql([['a', 'b'], ['c', 'd'], ['e']]); + }); + }); + + describe('time', function () { + var consoleLog; + + beforeEach(function () { + consoleLog = console.log; + }); + + afterEach(function () { + console.log = consoleLog; + }); + + it('logs timing information', function () { + var capture = []; + console.log = function () { + capture.push(_.toArray(arguments)[0]); + }; + util.time('foo', function () {}); + expect(capture.length).to.equal(1); + expect(capture[0]).to.match(/^foo time: .*ms/); + }); + + it('returns the value from the evaluated function', function () { + console.log = function () {}; + expect(util.time('foo', _.constant('bar'))).to.equal('bar'); + }); + }); + + describe('normalizeRanks', function () { + it('adjust ranks such that all are >= 0, and at least one is 0', function () { + var g = new Graph() + .setNode('a', { rank: 3 }) + .setNode('b', { rank: 2 }) + .setNode('c', { rank: 4 }); + + util.normalizeRanks(g); + + expect(g.node('a').rank).to.equal(1); + expect(g.node('b').rank).to.equal(0); + expect(g.node('c').rank).to.equal(2); + }); + + it('works for negative ranks', function () { + var g = new Graph().setNode('a', { rank: -3 }).setNode('b', { rank: -2 }); + + util.normalizeRanks(g); + + expect(g.node('a').rank).to.equal(0); + expect(g.node('b').rank).to.equal(1); + }); + + it('does not assign a rank to subgraphs', function () { + var g = new Graph({ compound: true }) + .setNode('a', { rank: 0 }) + .setNode('sg', {}) + .setParent('a', 'sg'); + + util.normalizeRanks(g); + + expect(g.node('sg')).to.not.have.property('rank'); + expect(g.node('a').rank).to.equal(0); + }); + }); + + describe('removeEmptyRanks', function () { + it('Removes border ranks without any nodes', function () { + var g = new Graph() + .setGraph({ nodeRankFactor: 4 }) + .setNode('a', { rank: 0 }) + .setNode('b', { rank: 4 }); + util.removeEmptyRanks(g); + expect(g.node('a').rank).equals(0); + expect(g.node('b').rank).equals(1); + }); + + it('Does not remove non-border ranks', function () { + var g = new Graph() + .setGraph({ nodeRankFactor: 4 }) + .setNode('a', { rank: 0 }) + .setNode('b', { rank: 8 }); + util.removeEmptyRanks(g); + expect(g.node('a').rank).equals(0); + expect(g.node('b').rank).equals(2); + }); + }); + + describe('range', () => { + it('Builds an array to the limit', () => { + const range = util.range(4); + expect(range.length).equals(4); + expect(range.reduce((acc, v) => acc + v)).equals(6); + }); + + it('Builds an array with a start', () => { + const range = util.range(2, 4); + expect(range.length).equals(2); + expect(range.reduce((acc, v) => acc + v)).equals(5); + }); + + it('Builds an array with a negative step', () => { + const range = util.range(5, -1, -1); + expect(range[0]).equals(5); + expect(range[5]).equals(0); + }); + }); + + describe('mapValues', () => { + it('Creates an object with the same keys', () => { + const users = { + fred: { user: 'fred', age: 40 }, + pebbles: { user: 'pebbles', age: 1 }, + }; + + const ages = util.mapValues(users, (user) => user.age); // as { fred: number, pebbles: number }; + expect(ages.fred).equals(40); + expect(ages.pebbles).equals(1); + }); + + it('Can take a property name', () => { + const users = { + fred: { user: 'fred', age: 40 }, + pebbles: { user: 'pebbles', age: 1 }, + }; + + const ages = util.mapValues(users, 'age'); // as { fred: number, pebbles: number }; + expect(ages.fred).equals(40); + expect(ages.pebbles).equals(1); + }); + }); +}); diff --git a/src/graphlib/alg/components.test.js b/src/graphlib/alg/components.test.js new file mode 100644 index 0000000..227458e --- /dev/null +++ b/src/graphlib/alg/components.test.js @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { components } from './components.js'; + +describe('alg.components', function () { + it('returns an empty list for an empty graph', function () { + expect(components(new Graph({ directed: false }))).to.be.empty; + }); + + it('returns singleton lists for unconnected nodes', function () { + var g = new Graph({ directed: false }); + g.setNode('a'); + g.setNode('b'); + + var result = components(g).sort((a, b) => a[0].localeCompare(b[0])); + expect(result).to.eql([['a'], ['b']]); + }); + + it('returns a list of nodes in a component', function () { + var g = new Graph({ directed: false }); + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + + var result = components(g).map((xs) => xs.sort()); + expect(result).to.eql([['a', 'b', 'c']]); + }); + + it('returns nodes connected by a neighbor relationship in a digraph', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'c', 'a']); + g.setEdge('d', 'c'); + g.setEdge('e', 'f'); + + var result = components(g) + .map((xs) => xs.sort()) + .sort((a, b) => a[0].localeCompare(b[0])); + expect(result).to.eql([ + ['a', 'b', 'c', 'd'], + ['e', 'f'], + ]); + }); +}); diff --git a/src/graphlib/alg/dfs.js b/src/graphlib/alg/dfs.js index ce5add1..4278570 100644 --- a/src/graphlib/alg/dfs.js +++ b/src/graphlib/alg/dfs.js @@ -30,7 +30,7 @@ function dfs(g, vs, order) { } function doDfs(g, v, postorder, visited, navigation, acc) { - if (!_.has(visited, v)) { + if (!Object.prototype.hasOwnProperty.call(visited, v)) { visited[v] = true; if (!postorder) { diff --git a/src/graphlib/alg/dijkstra-all.js b/src/graphlib/alg/dijkstra-all.js index bedf44a..7363874 100644 --- a/src/graphlib/alg/dijkstra-all.js +++ b/src/graphlib/alg/dijkstra-all.js @@ -9,6 +9,6 @@ function dijkstraAll(g, weightFunc, edgeFunc) { function (acc, v) { acc[v] = dijkstra(g, v, weightFunc, edgeFunc); }, - {} + {}, ); } diff --git a/src/graphlib/alg/dijkstra-all.test.js b/src/graphlib/alg/dijkstra-all.test.js new file mode 100644 index 0000000..064eb60 --- /dev/null +++ b/src/graphlib/alg/dijkstra-all.test.js @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { dijkstraAll } from './dijkstra-all.js'; +import { allShortestPathsTests } from '../../../test/graphlib/alg/all-shortest-paths.js'; + +describe('alg.dijkstraAll', function () { + allShortestPathsTests(dijkstraAll); + + it('throws an Error if it encounters a negative edge weight', function () { + var g = new Graph(); + g.setEdge('a', 'b', 1); + g.setEdge('a', 'c', -2); + g.setEdge('b', 'd', 3); + g.setEdge('c', 'd', 3); + + expect(function () { + dijkstraAll(g, weight(g)); + }).to.throw(); + }); +}); + +function weight(g) { + return function (e) { + return g.edge(e); + }; +} diff --git a/src/graphlib/alg/dijkstra.js b/src/graphlib/alg/dijkstra.js index 3cb0e9e..55a70a3 100644 --- a/src/graphlib/alg/dijkstra.js +++ b/src/graphlib/alg/dijkstra.js @@ -13,7 +13,7 @@ function dijkstra(g, source, weightFn, edgeFn) { edgeFn || function (v) { return g.outEdges(v); - } + }, ); } @@ -34,7 +34,7 @@ function runDijkstra(g, source, weightFn, edgeFn) { 'Bad edge: ' + edge + ' Weight: ' + - weight + weight, ); } diff --git a/src/graphlib/alg/dijkstra.test.js b/src/graphlib/alg/dijkstra.test.js new file mode 100644 index 0000000..5c01844 --- /dev/null +++ b/src/graphlib/alg/dijkstra.test.js @@ -0,0 +1,96 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { dijkstra } from './dijkstra.js'; + +describe('alg.dijkstra', function () { + it('assigns distance 0 for the source node', function () { + var g = new Graph(); + g.setNode('source'); + expect(dijkstra(g, 'source')).to.eql({ source: { distance: 0 } }); + }); + + it('returns Number.POSITIVE_INFINITY for unconnected nodes', function () { + var g = new Graph(); + g.setNode('a'); + g.setNode('b'); + expect(dijkstra(g, 'a')).to.eql({ + a: { distance: 0 }, + b: { distance: Number.POSITIVE_INFINITY }, + }); + }); + + it('returns the distance and path from the source node to other nodes', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'c']); + g.setEdge('b', 'd'); + expect(dijkstra(g, 'a')).to.eql({ + a: { distance: 0 }, + b: { distance: 1, predecessor: 'a' }, + c: { distance: 2, predecessor: 'b' }, + d: { distance: 2, predecessor: 'b' }, + }); + }); + + it('works for undirected graphs', function () { + var g = new Graph({ directed: false }); + g.setPath(['a', 'b', 'c']); + g.setEdge('b', 'd'); + expect(dijkstra(g, 'a')).to.eql({ + a: { distance: 0 }, + b: { distance: 1, predecessor: 'a' }, + c: { distance: 2, predecessor: 'b' }, + d: { distance: 2, predecessor: 'b' }, + }); + }); + + it('uses an optionally supplied weight function', function () { + var g = new Graph(); + g.setEdge('a', 'b', 1); + g.setEdge('a', 'c', 2); + g.setEdge('b', 'd', 3); + g.setEdge('c', 'd', 3); + + expect(dijkstra(g, 'a', weightFn(g))).to.eql({ + a: { distance: 0 }, + b: { distance: 1, predecessor: 'a' }, + c: { distance: 2, predecessor: 'a' }, + d: { distance: 4, predecessor: 'b' }, + }); + }); + + it('uses an optionally supplied edge function', function () { + var g = new Graph(); + g.setPath(['a', 'c', 'd']); + g.setEdge('b', 'c'); + + expect( + dijkstra(g, 'd', undefined, function (e) { + return g.inEdges(e); + }), + ).to.eql({ + a: { distance: 2, predecessor: 'c' }, + b: { distance: 2, predecessor: 'c' }, + c: { distance: 1, predecessor: 'd' }, + d: { distance: 0 }, + }); + }); + + it('throws an Error if it encounters a negative edge weight', function () { + var g = new Graph(); + g.setEdge('a', 'b', 1); + g.setEdge('a', 'c', -2); + g.setEdge('b', 'd', 3); + g.setEdge('c', 'd', 3); + + expect(function () { + dijkstra(g, 'a', weightFn(g)); + }).to.throw(); + }); +}); + +function weightFn(g) { + return function (e) { + return g.edge(e); + }; +} diff --git a/src/graphlib/alg/find-cycles.test.js b/src/graphlib/alg/find-cycles.test.js new file mode 100644 index 0000000..dc61015 --- /dev/null +++ b/src/graphlib/alg/find-cycles.test.js @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { findCycles } from './find-cycles.js'; + +describe('alg.findCycles', function () { + it('returns an empty array for an empty graph', function () { + expect(findCycles(new Graph())).to.eql([]); + }); + + it('returns an empty array if the graph has no cycles', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'c']); + expect(findCycles(g)).to.eql([]); + }); + + it('returns a single entry for a cycle of 1 node', function () { + var g = new Graph(); + g.setPath(['a', 'a']); + expect(sort(findCycles(g))).to.eql([['a']]); + }); + + it('returns a single entry for a cycle of 2 nodes', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'a']); + expect(sort(findCycles(g))).to.eql([['a', 'b']]); + }); + + it('returns a single entry for a triangle', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'c', 'a']); + expect(sort(findCycles(g))).to.eql([['a', 'b', 'c']]); + }); + + it('returns multiple entries for multiple cycles', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'a']); + g.setPath(['c', 'd', 'e', 'c']); + g.setPath(['f', 'g', 'g']); + g.setNode('h'); + expect(sort(findCycles(g))).to.eql([['a', 'b'], ['c', 'd', 'e'], ['g']]); + }); +}); + +// A helper that sorts components and their contents +function sort(cmpts) { + return cmpts.map((cmpt) => cmpt.sort()).sort((a, b) => a[0].localeCompare(b[0])); +} diff --git a/src/graphlib/alg/floyd-warshall.js b/src/graphlib/alg/floyd-warshall.js index cbf3d27..495331e 100644 --- a/src/graphlib/alg/floyd-warshall.js +++ b/src/graphlib/alg/floyd-warshall.js @@ -11,7 +11,7 @@ function floydWarshall(g, weightFn, edgeFn) { edgeFn || function (v) { return g.outEdges(v); - } + }, ); } diff --git a/src/graphlib/alg/floyd-warshall.test.js b/src/graphlib/alg/floyd-warshall.test.js new file mode 100644 index 0000000..8a9fa84 --- /dev/null +++ b/src/graphlib/alg/floyd-warshall.test.js @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { floydWarshall } from './floyd-warshall.js'; +import { allShortestPathsTests } from '../../../test/graphlib/alg/all-shortest-paths.js'; + +describe('alg.floydWarshall', function () { + allShortestPathsTests(floydWarshall); + + it('handles negative weights', function () { + var g = new Graph(); + g.setEdge('a', 'b', 1); + g.setEdge('a', 'c', -2); + g.setEdge('b', 'd', 3); + g.setEdge('c', 'd', 3); + + expect(floydWarshall(g, weightFn(g))).to.eql({ + a: { + a: { distance: 0 }, + b: { distance: 1, predecessor: 'a' }, + c: { distance: -2, predecessor: 'a' }, + d: { distance: 1, predecessor: 'c' }, + }, + b: { + a: { distance: Number.POSITIVE_INFINITY }, + b: { distance: 0 }, + c: { distance: Number.POSITIVE_INFINITY }, + d: { distance: 3, predecessor: 'b' }, + }, + c: { + a: { distance: Number.POSITIVE_INFINITY }, + b: { distance: Number.POSITIVE_INFINITY }, + c: { distance: 0 }, + d: { distance: 3, predecessor: 'c' }, + }, + d: { + a: { distance: Number.POSITIVE_INFINITY }, + b: { distance: Number.POSITIVE_INFINITY }, + c: { distance: Number.POSITIVE_INFINITY }, + d: { distance: 0 }, + }, + }); + }); + + it('does include negative weight self edges', function () { + var g = new Graph(); + g.setEdge('a', 'a', -1); + + // In the case of a negative cycle the distance is not well-defined beyond + // having a negative value along the diagonal. + expect(floydWarshall(g, weightFn(g))).to.eql({ + a: { + a: { distance: -2, predecessor: 'a' }, + }, + }); + }); +}); + +function weightFn(g) { + return function (edge) { + return g.edge(edge); + }; +} diff --git a/src/graphlib/alg/is-acyclic.test.js b/src/graphlib/alg/is-acyclic.test.js new file mode 100644 index 0000000..e81af51 --- /dev/null +++ b/src/graphlib/alg/is-acyclic.test.js @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { isAcyclic } from './is-acyclic.js'; + +describe('alg.isAcyclic', function () { + it('returns true if the graph has no cycles', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'c']); + expect(isAcyclic(g)).to.be.true; + }); + + it('returns false if the graph has at least one cycle', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'c', 'a']); + expect(isAcyclic(g)).to.be.false; + }); + + it('returns false if the graph has a cycle of 1 node', function () { + var g = new Graph(); + g.setPath(['a', 'a']); + expect(isAcyclic(g)).to.be.false; + }); + + it('rethrows non-CycleException errors', function () { + expect(function () { + isAcyclic(undefined); + }).to.throw(); + }); +}); diff --git a/src/graphlib/alg/postorder.test.js b/src/graphlib/alg/postorder.test.js new file mode 100644 index 0000000..5caac3b --- /dev/null +++ b/src/graphlib/alg/postorder.test.js @@ -0,0 +1,69 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { postorder } from './postorder.js'; + +describe('alg.postorder', function () { + it('returns the root for a singleton graph', function () { + var g = new Graph(); + g.setNode('a'); + expect(postorder(g, 'a')).to.eql(['a']); + }); + + it('visits each node in the graph once', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'd', 'e']); + g.setPath(['a', 'c', 'd', 'e']); + + var nodes = postorder(g, 'a'); + expect(nodes.sort()).to.eql(['a', 'b', 'c', 'd', 'e']); + }); + + it('works for a tree', function () { + var g = new Graph(); + g.setEdge('a', 'b'); + g.setPath(['a', 'c', 'd']); + g.setEdge('c', 'e'); + + var nodes = postorder(g, 'a'); + expect(nodes.indexOf('b')).to.be.lt(nodes.indexOf('a')); + expect(nodes.indexOf('c')).to.be.lt(nodes.indexOf('a')); + expect(nodes.indexOf('d')).to.be.lt(nodes.indexOf('c')); + expect(nodes.indexOf('e')).to.be.lt(nodes.indexOf('c')); + expect(nodes.sort()).to.eql(['a', 'b', 'c', 'd', 'e']); + }); + + it('works for an array of roots', function () { + var g = new Graph(); + g.setEdge('a', 'b'); + g.setEdge('c', 'd'); + g.setNode('e'); + g.setNode('f'); + + var nodes = postorder(g, ['a', 'b', 'c', 'e']); + expect(nodes.indexOf('b')).to.be.lt(nodes.indexOf('a')); + expect(nodes.indexOf('d')).to.be.lt(nodes.indexOf('c')); + expect(nodes.sort()).to.eql(['a', 'b', 'c', 'd', 'e']); + }); + + it('works for multiple connected roots', function () { + var g = new Graph(); + g.setEdge('a', 'b'); + g.setEdge('a', 'c'); + g.setEdge('d', 'c'); + + var nodes = postorder(g, ['a', 'd']); + expect(nodes.indexOf('b')).to.be.lt(nodes.indexOf('a')); + expect(nodes.indexOf('c')).to.be.lt(nodes.indexOf('a')); + expect(nodes.indexOf('c')).to.be.lt(nodes.indexOf('d')); + expect(nodes.sort()).to.eql(['a', 'b', 'c', 'd']); + }); + + it('fails if root is not in the graph', function () { + var g = new Graph(); + g.setNode('a'); + expect(function () { + postorder(g, 'b'); + }).to.throw(); + }); +}); diff --git a/src/graphlib/alg/preorder.test.js b/src/graphlib/alg/preorder.test.js new file mode 100644 index 0000000..9019976 --- /dev/null +++ b/src/graphlib/alg/preorder.test.js @@ -0,0 +1,56 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { preorder } from './preorder.js'; + +describe('alg.preorder', function () { + it('returns the root for a singleton graph', function () { + var g = new Graph(); + g.setNode('a'); + expect(preorder(g, 'a')).to.eql(['a']); + }); + + it('visits each node in the graph once', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'd', 'e']); + g.setPath(['a', 'c', 'd', 'e']); + + var nodes = preorder(g, 'a'); + expect(nodes.sort()).to.eql(['a', 'b', 'c', 'd', 'e']); + }); + + it('works for a tree', function () { + var g = new Graph(); + g.setEdge('a', 'b'); + g.setPath(['a', 'c', 'd']); + g.setEdge('c', 'e'); + + var nodes = preorder(g, 'a'); + expect(nodes.sort()).to.eql(['a', 'b', 'c', 'd', 'e']); + expect(nodes.indexOf('b')).to.be.gt(nodes.indexOf('a')); + expect(nodes.indexOf('c')).to.be.gt(nodes.indexOf('a')); + expect(nodes.indexOf('d')).to.be.gt(nodes.indexOf('c')); + expect(nodes.indexOf('e')).to.be.gt(nodes.indexOf('c')); + }); + + it('works for an array of roots', function () { + var g = new Graph(); + g.setEdge('a', 'b'); + g.setEdge('c', 'd'); + g.setNode('e'); + g.setNode('f'); + + var nodes = preorder(g, ['a', 'c', 'e']); + expect(nodes.sort()).to.eql(['a', 'b', 'c', 'd', 'e']); + expect(nodes.indexOf('b')).to.be.gt(nodes.indexOf('a')); + expect(nodes.indexOf('d')).to.be.gt(nodes.indexOf('c')); + }); + + it('fails if root is not in the graph', function () { + var g = new Graph(); + g.setNode('a'); + expect(function () { + preorder(g, 'b'); + }).to.throw(); + }); +}); diff --git a/src/graphlib/alg/prim.test.js b/src/graphlib/alg/prim.test.js new file mode 100644 index 0000000..dc8e826 --- /dev/null +++ b/src/graphlib/alg/prim.test.js @@ -0,0 +1,58 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { prim } from './prim.js'; + +describe('alg.prim', function () { + it('returns an empty graph for an empty input', function () { + var source = new Graph(); + + var g = prim(source, weightFn(source)); + expect(g.nodeCount()).to.equal(0); + expect(g.edgeCount()).to.equal(0); + }); + + it('returns a single node graph for a graph with a single node', function () { + var source = new Graph(); + source.setNode('a'); + + var g = prim(source, weightFn(source)); + expect(g.nodes()).to.eql(['a']); + expect(g.edgeCount()).to.equal(0); + }); + + it('returns a deterministic result given an optimal solution', function () { + var source = new Graph(); + source.setEdge('a', 'b', 1); + source.setEdge('b', 'c', 2); + source.setEdge('b', 'd', 3); + // This edge should not be in the min spanning tree + source.setEdge('c', 'd', 20); + // This edge should not be in the min spanning tree + source.setEdge('c', 'e', 60); + source.setEdge('d', 'e', 1); + + var g = prim(source, weightFn(source)); + expect(g.neighbors('a').sort()).to.eql(['b']); + expect(g.neighbors('b').sort()).to.eql(['a', 'c', 'd']); + expect(g.neighbors('c').sort()).to.eql(['b']); + expect(g.neighbors('d').sort()).to.eql(['b', 'e']); + expect(g.neighbors('e').sort()).to.eql(['d']); + }); + + it('throws an Error for unconnected graphs', function () { + var source = new Graph(); + source.setNode('a'); + source.setNode('b'); + + expect(function () { + prim(source, weightFn(source)); + }).to.throw(); + }); +}); + +function weightFn(g) { + return function (edge) { + return g.edge(edge); + }; +} diff --git a/src/graphlib/alg/tarjan.js b/src/graphlib/alg/tarjan.js index 1133833..9a050b5 100644 --- a/src/graphlib/alg/tarjan.js +++ b/src/graphlib/alg/tarjan.js @@ -17,7 +17,7 @@ function tarjan(g) { stack.push(v); g.successors(v).forEach(function (w) { - if (!_.has(visited, w)) { + if (!visited && !Object.prototype.hasOwnProperty.call(visited, w)) { dfs(w); entry.lowlink = Math.min(entry.lowlink, visited[w].lowlink); } else if (visited[w].onStack) { diff --git a/src/graphlib/alg/tarjan.test.js b/src/graphlib/alg/tarjan.test.js new file mode 100644 index 0000000..772d03a --- /dev/null +++ b/src/graphlib/alg/tarjan.test.js @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { tarjan } from './tarjan.js'; + +describe('alg.tarjan', function () { + it('returns an empty array for an empty graph', function () { + expect(tarjan(new Graph())).to.eql([]); + }); + + it('returns singletons for nodes not in a strongly connected component', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'c']); + g.setEdge('d', 'c'); + expect(sort(tarjan(g))).to.eql([['a'], ['b'], ['c'], ['d']]); + }); + + it('returns a single component for a cycle of 1 edge', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'a']); + expect(sort(tarjan(g))).to.eql([['a', 'b']]); + }); + + it('returns a single component for a triangle', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'c', 'a']); + expect(sort(tarjan(g))).to.eql([['a', 'b', 'c']]); + }); + + it('can find multiple components', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'a']); + g.setPath(['c', 'd', 'e', 'c']); + g.setNode('f'); + expect(sort(tarjan(g))).to.eql([['a', 'b'], ['c', 'd', 'e'], ['f']]); + }); +}); + +// A helper that sorts components and their contents +function sort(cmpts) { + return cmpts.map((cmpt) => cmpt.sort()).sort((a, b) => a[0].localeCompare(b[0])); +} diff --git a/src/graphlib/alg/topsort.test.js b/src/graphlib/alg/topsort.test.js new file mode 100644 index 0000000..04a3eb7 --- /dev/null +++ b/src/graphlib/alg/topsort.test.js @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from '../graph.js'; +import { topsort } from './topsort.js'; + +describe('alg.topsort', function () { + it('returns an empty array for an empty graph', function () { + expect(topsort(new Graph())).to.be.empty; + }); + + it('sorts nodes such that earlier nodes have directed edges to later nodes', function () { + var g = new Graph(); + g.setPath(['b', 'c', 'a']); + expect(topsort(g)).to.eql(['b', 'c', 'a']); + }); + + it('works for a diamond', function () { + var g = new Graph(); + g.setPath(['a', 'b', 'd']); + g.setPath(['a', 'c', 'd']); + + var result = topsort(g); + expect(result.indexOf('a')).to.equal(0); + expect(result.indexOf('b')).to.be.lt(result.indexOf('d')); + expect(result.indexOf('c')).to.be.lt(result.indexOf('d')); + expect(result.indexOf('d')).to.equal(3); + }); + + it('throws CycleException if there is a cycle #1', function () { + var g = new Graph(); + g.setPath(['b', 'c', 'a', 'b']); + expect(function () { + topsort(g); + }).to.throw(topsort.CycleException); + }); + + it('throws CycleException if there is a cycle #2', function () { + var g = new Graph(); + g.setPath(['b', 'c', 'a', 'b']); + g.setEdge('b', 'd'); + expect(function () { + topsort(g); + }).to.throw(topsort.CycleException); + }); + + it('throws CycleException if there is a cycle #3', function () { + var g = new Graph(); + g.setPath(['b', 'c', 'a', 'b']); + g.setNode('d'); + expect(function () { + topsort(g); + }).to.throw(topsort.CycleException); + }); +}); diff --git a/src/graphlib/data/priority-queue.js b/src/graphlib/data/priority-queue.js index 9731925..080f4aa 100644 --- a/src/graphlib/data/priority-queue.js +++ b/src/graphlib/data/priority-queue.js @@ -104,7 +104,7 @@ class PriorityQueue { ' Old: ' + this._arr[index].priority + ' New: ' + - priority + priority, ); } this._arr[index].priority = priority; diff --git a/src/graphlib/data/priority-queue.test.js b/src/graphlib/data/priority-queue.test.js new file mode 100644 index 0000000..e6a82f1 --- /dev/null +++ b/src/graphlib/data/priority-queue.test.js @@ -0,0 +1,138 @@ +import { describe, expect, it } from 'vitest'; + +import { PriorityQueue } from './priority-queue.js'; + +describe('data.PriorityQueue', function () { + var pq; + + beforeEach(function () { + pq = new PriorityQueue(); + }); + + describe('size', function () { + it('returns 0 for an empty queue', function () { + expect(pq.size()).to.equal(0); + }); + + it('returns the number of elements in the queue', function () { + pq.add('a', 1); + expect(pq.size()).to.equal(1); + pq.add('b', 2); + expect(pq.size()).to.equal(2); + }); + }); + + describe('keys', function () { + it('returns all of the keys in the queue', function () { + pq.add('a', 1); + pq.add(1, 2); + pq.add(false, 3); + pq.add(undefined, 4); + pq.add(null, 5); + expect(pq.keys().sort()).to.eql(['a', '1', 'false', 'undefined', 'null'].sort()); + }); + }); + + describe('has', function () { + it('returns true if the key is in the queue', function () { + pq.add('a', 1); + expect(pq.has('a')).to.be.true; + }); + + it('returns false if the key is not in the queue', function () { + expect(pq.has('a')).to.be.false; + }); + }); + + describe('priority', function () { + it('returns the current priority for the key', function () { + pq.add('a', 1); + pq.add('b', 2); + expect(pq.priority('a')).to.equal(1); + expect(pq.priority('b')).to.equal(2); + }); + + it('returns undefined if the key is not in the queue', function () { + expect(pq.priority('foo')).to.be.undefined; + }); + }); + + describe('min', function () { + it('throws an error if there is no element in the queue', function () { + expect(function () { + pq.min(); + }).to.throw(); + }); + + it('returns the smallest element', function () { + pq.add('b', 2); + pq.add('a', 1); + expect(pq.min()).to.equal('a'); + }); + + it('does not remove the minimum element from the queue', function () { + pq.add('b', 2); + pq.add('a', 1); + pq.min(); + expect(pq.size()).to.equal(2); + }); + }); + + describe('add', function () { + it('adds the key to the queue', function () { + pq.add('a', 1); + expect(pq.keys()).to.eql(['a']); + }); + + it('returns true if the key was added', function () { + expect(pq.add('a', 1)).to.be.true; + }); + + it('returns false if the key already exists in the queue', function () { + pq.add('a', 1); + expect(pq.add('a', 1)).to.be.false; + }); + }); + + describe('removeMin', function () { + it('removes the minimum element from the queue', function () { + pq.add('b', 2); + pq.add('a', 1); + pq.add('c', 3); + pq.add('e', 5); + pq.add('d', 4); + expect(pq.removeMin()).to.equal('a'); + expect(pq.removeMin()).to.equal('b'); + expect(pq.removeMin()).to.equal('c'); + expect(pq.removeMin()).to.equal('d'); + expect(pq.removeMin()).to.equal('e'); + }); + + it('throws an error if there is no element in the queue', function () { + expect(function () { + pq.removeMin(); + }).to.throw(); + }); + }); + + describe('decrease', function () { + it('decreases the priority of a key', function () { + pq.add('a', 1); + pq.decrease('a', -1); + expect(pq.priority('a')).to.equal(-1); + }); + + it('raises an error if the key is not in the queue', function () { + expect(function () { + pq.decrease('a', -1); + }).to.throw(); + }); + + it('raises an error if the new priority is greater than current', function () { + pq.add('a', 1); + expect(function () { + pq.decrease('a', 2); + }).to.throw(); + }); + }); +}); diff --git a/src/graphlib/graph.js b/src/graphlib/graph.js index 7d3754e..b694ae8 100644 --- a/src/graphlib/graph.js +++ b/src/graphlib/graph.js @@ -124,7 +124,7 @@ export class Graph { return this; } setNode(v, value) { - if (_.has(this._nodes, v)) { + if (this._nodes && Object.prototype.hasOwnProperty.call(this._nodes, v)) { if (arguments.length > 1) { this._nodes[v] = value; } @@ -361,7 +361,7 @@ export class Graph { } var e = edgeArgsToId(this._isDirected, v, w, name); - if (_.has(this._edgeLabels, e)) { + if (this._edgeLabels && Object.prototype.hasOwnProperty.call(this._edgeLabels, e)) { if (valueSpecified) { this._edgeLabels[e] = value; } diff --git a/src/graphlib/graph.test.js b/src/graphlib/graph.test.js new file mode 100644 index 0000000..ff83d34 --- /dev/null +++ b/src/graphlib/graph.test.js @@ -0,0 +1,1055 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from './graph.js'; + +describe('Graph', function () { + var g; + + beforeEach(function () { + g = new Graph(); + }); + + describe('initial state', function () { + it('has no nodes', function () { + expect(g.nodeCount()).to.equal(0); + }); + + it('has no edges', function () { + expect(g.edgeCount()).to.equal(0); + }); + + it('has no attributes', function () { + expect(g.graph()).to.be.undefined; + }); + + it('defaults to a simple directed graph', function () { + expect(g.isDirected()).to.be.true; + expect(g.isCompound()).to.be.false; + expect(g.isMultigraph()).to.be.false; + }); + + it('can be set to undirected', function () { + var g = new Graph({ directed: false }); + expect(g.isDirected()).to.be.false; + expect(g.isCompound()).to.be.false; + expect(g.isMultigraph()).to.be.false; + }); + + it('can be set to a compound graph', function () { + var g = new Graph({ compound: true }); + expect(g.isDirected()).to.be.true; + expect(g.isCompound()).to.be.true; + expect(g.isMultigraph()).to.be.false; + }); + + it('can be set to a mulitgraph', function () { + var g = new Graph({ multigraph: true }); + expect(g.isDirected()).to.be.true; + expect(g.isCompound()).to.be.false; + expect(g.isMultigraph()).to.be.true; + }); + }); + + describe('setGraph', function () { + it('can be used to get and set properties for the graph', function () { + g.setGraph('foo'); + expect(g.graph()).to.equal('foo'); + }); + + it('is chainable', function () { + expect(g.setGraph('foo')).to.equal(g); + }); + }); + + describe('nodes', function () { + it('is empty if there are no nodes in the graph', function () { + expect(g.nodes()).to.eql([]); + }); + + it('returns the ids of nodes in the graph', function () { + g.setNode('a'); + g.setNode('b'); + expect(g.nodes().sort()).to.eql(['a', 'b']); + }); + }); + + describe('sources', function () { + it('returns nodes in the graph that have no in-edges', function () { + g.setPath(['a', 'b', 'c']); + g.setNode('d'); + expect(g.sources().sort()).to.eql(['a', 'd']); + }); + }); + + describe('sinks', function () { + it('returns nodes in the graph that have no out-edges', function () { + g.setPath(['a', 'b', 'c']); + g.setNode('d'); + expect(g.sinks().sort()).to.eql(['c', 'd']); + }); + }); + + describe('filterNodes', function () { + it('returns an identical graph when the filter selects everything', function () { + g.setGraph('graph label'); + g.setNode('a', 123); + g.setPath(['a', 'b', 'c']); + g.setEdge('a', 'c', 456); + var g2 = g.filterNodes(function () { + return true; + }); + expect(g2.nodes().sort()).eqls(['a', 'b', 'c']); + expect(g2.successors('a').sort()).eqls(['b', 'c']); + expect(g2.successors('b').sort()).eqls(['c']); + expect(g2.node('a')).eqls(123); + expect(g2.edge('a', 'c')).eqls(456); + expect(g2.graph()).eqls('graph label'); + }); + + it('returns an empty graph when the filter selects nothing', function () { + g.setPath(['a', 'b', 'c']); + var g2 = g.filterNodes(function () { + return false; + }); + expect(g2.nodes()).eqls([]); + expect(g2.edges()).eqls([]); + }); + + it('only includes nodes for which the filter returns true', function () { + g.setNodes(['a', 'b']); + var g2 = g.filterNodes(function (v) { + return v === 'a'; + }); + expect(g2.nodes()).eqls(['a']); + }); + + it('removes edges that are connected to removed nodes', function () { + g.setEdge('a', 'b'); + var g2 = g.filterNodes(function (v) { + return v === 'a'; + }); + expect(g2.nodes().sort()).eqls(['a']); + expect(g2.edges()).eqls([]); + }); + + it('preserves the directed option', function () { + g = new Graph({ directed: true }); + expect( + g + .filterNodes(function () { + return true; + }) + .isDirected(), + ).to.be.true; + + g = new Graph({ directed: false }); + expect( + g + .filterNodes(function () { + return true; + }) + .isDirected(), + ).to.be.false; + }); + + it('preserves the multigraph option', function () { + g = new Graph({ multigraph: true }); + expect( + g + .filterNodes(function () { + return true; + }) + .isMultigraph(), + ).to.be.true; + + g = new Graph({ multigraph: false }); + expect( + g + .filterNodes(function () { + return true; + }) + .isMultigraph(), + ).to.be.false; + }); + + it('preserves the compound option', function () { + g = new Graph({ compound: true }); + expect( + g + .filterNodes(function () { + return true; + }) + .isCompound(), + ).to.be.true; + + g = new Graph({ compound: false }); + expect( + g + .filterNodes(function () { + return true; + }) + .isCompound(), + ).to.be.false; + }); + + it('includes subgraphs', function () { + g = new Graph({ compound: true }); + g.setParent('a', 'parent'); + + var g2 = g.filterNodes(function () { + return true; + }); + expect(g2.parent('a')).eqls('parent'); + }); + + it('includes multi-level subgraphs', function () { + g = new Graph({ compound: true }); + g.setParent('a', 'parent'); + g.setParent('parent', 'root'); + + var g2 = g.filterNodes(function () { + return true; + }); + expect(g2.parent('a')).eqls('parent'); + expect(g2.parent('parent')).eqls('root'); + }); + + it('promotes a node to a higher subgraph if its parent is not included', function () { + g = new Graph({ compound: true }); + g.setParent('a', 'parent'); + g.setParent('parent', 'root'); + + var g2 = g.filterNodes(function (v) { + return v !== 'parent'; + }); + expect(g2.parent('a')).eqls('root'); + }); + }); + + describe('setNodes', function () { + it('creates multiple nodes', function () { + g.setNodes(['a', 'b', 'c']); + expect(g.hasNode('a')).to.be.true; + expect(g.hasNode('b')).to.be.true; + expect(g.hasNode('c')).to.be.true; + }); + + it('can set a value for all of the nodes', function () { + g.setNodes(['a', 'b', 'c'], 'foo'); + expect(g.node('a')).to.equal('foo'); + expect(g.node('b')).to.equal('foo'); + expect(g.node('c')).to.equal('foo'); + }); + + it('is chainable', function () { + expect(g.setNodes(['a', 'b', 'c'])).to.equal(g); + }); + }); + + describe('setNode', function () { + it("creates the node if it isn't part of the graph", function () { + g.setNode('a'); + expect(g.hasNode('a')).to.be.true; + expect(g.node('a')).to.be.undefined; + expect(g.nodeCount()).to.equal(1); + }); + + it('can set a value for the node', function () { + g.setNode('a', 'foo'); + expect(g.node('a')).to.equal('foo'); + }); + + it("does not change the node's value with a 1-arg invocation", function () { + g.setNode('a', 'foo'); + g.setNode('a'); + expect(g.node('a')).to.equal('foo'); + }); + + it("can remove the node's value by passing undefined", function () { + g.setNode('a', undefined); + expect(g.node('a')).to.be.undefined; + }); + + it('is idempotent', function () { + g.setNode('a', 'foo'); + g.setNode('a', 'foo'); + expect(g.node('a')).to.equal('foo'); + expect(g.nodeCount()).to.equal(1); + }); + + it('uses the stringified form of the id', function () { + g.setNode(1); + expect(g.hasNode(1)).to.be.true; + expect(g.hasNode('1')).to.be.true; + expect(g.nodes()).eqls(['1']); + }); + + it('is chainable', function () { + expect(g.setNode('a')).to.equal(g); + }); + }); + + describe('setNodeDefaults', function () { + it('sets a default label for new nodes', function () { + g.setDefaultNodeLabel('foo'); + g.setNode('a'); + expect(g.node('a')).to.equal('foo'); + }); + + it('does not change existing nodes', function () { + g.setNode('a'); + g.setDefaultNodeLabel('foo'); + expect(g.node('a')).to.be.undefined; + }); + + it('is not used if an explicit value is set', function () { + g.setDefaultNodeLabel('foo'); + g.setNode('a', 'bar'); + expect(g.node('a')).to.equal('bar'); + }); + + it('can take a function', function () { + g.setDefaultNodeLabel(function () { + return 'foo'; + }); + g.setNode('a'); + expect(g.node('a')).to.equal('foo'); + }); + + it("can take a function that takes the node's name", function () { + g.setDefaultNodeLabel(function (v) { + return v + '-foo'; + }); + g.setNode('a'); + expect(g.node('a')).to.equal('a-foo'); + }); + + it('is chainable', function () { + expect(g.setDefaultNodeLabel('foo')).to.equal(g); + }); + }); + + describe('node', function () { + it("returns undefined if the node isn't part of the graph", function () { + expect(g.node('a')).to.be.undefined; + }); + + it('returns the value of the node if it is part of the graph', function () { + g.setNode('a', 'foo'); + expect(g.node('a')).to.equal('foo'); + }); + }); + + describe('removeNode', function () { + it('does nothing if the node is not in the graph', function () { + expect(g.nodeCount()).to.equal(0); + g.removeNode('a'); + expect(g.hasNode('a')).to.be.false; + expect(g.nodeCount()).to.equal(0); + }); + + it('removes the node if it is in the graph', function () { + g.setNode('a'); + g.removeNode('a'); + expect(g.hasNode('a')).to.be.false; + expect(g.nodeCount()).to.equal(0); + }); + + it('is idempotent', function () { + g.setNode('a'); + g.removeNode('a'); + g.removeNode('a'); + expect(g.hasNode('a')).to.be.false; + expect(g.nodeCount()).to.equal(0); + }); + + it('removes edges incident on the node', function () { + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + g.removeNode('b'); + expect(g.edgeCount()).to.equal(0); + }); + + it('removes parent / child relationships for the node', function () { + var g = new Graph({ compound: true }); + g.setParent('c', 'b'); + g.setParent('b', 'a'); + g.removeNode('b'); + expect(g.parent('b')).to.be.undefined; + expect(g.children('b')).to.be.undefined; + expect(g.children('a')).to.not.include('b'); + expect(g.parent('c')).to.be.undefined; + }); + + it('is chainable', function () { + expect(g.removeNode('a')).to.equal(g); + }); + }); + + describe('setParent', function () { + beforeEach(function () { + g = new Graph({ compound: true }); + }); + + it('throws if the graph is not compound', function () { + expect(function () { + new Graph().setParent('a', 'parent'); + }).to.throw(); + }); + + it('creates the parent if it does not exist', function () { + g.setNode('a'); + g.setParent('a', 'parent'); + expect(g.hasNode('parent')).to.be.true; + expect(g.parent('a')).to.equal('parent'); + }); + + it('creates the child if it does not exist', function () { + g.setNode('parent'); + g.setParent('a', 'parent'); + expect(g.hasNode('a')).to.be.true; + expect(g.parent('a')).to.equal('parent'); + }); + + it('has the parent as undefined if it has never been invoked', function () { + g.setNode('a'); + expect(g.parent('a')).to.be.undefined; + }); + + it('moves the node from the previous parent', function () { + g.setParent('a', 'parent'); + g.setParent('a', 'parent2'); + expect(g.parent('a')).to.equal('parent2'); + expect(g.children('parent')).to.eql([]); + expect(g.children('parent2')).to.eql(['a']); + }); + + it('removes the parent if the parent is undefined', function () { + g.setParent('a', 'parent'); + g.setParent('a', undefined); + expect(g.parent('a')).to.be.undefined; + expect(g.children().sort()).to.eql(['a', 'parent']); + }); + + it('removes the parent if no parent was specified', function () { + g.setParent('a', 'parent'); + g.setParent('a'); + expect(g.parent('a')).to.be.undefined; + expect(g.children().sort()).to.eql(['a', 'parent']); + }); + + it('is idempotent to remove a parent', function () { + g.setParent('a', 'parent'); + g.setParent('a'); + g.setParent('a'); + expect(g.parent('a')).to.be.undefined; + expect(g.children().sort()).to.eql(['a', 'parent']); + }); + + it('uses the stringified form of the id', function () { + g.setParent(2, 1); + g.setParent(3, 2); + expect(g.parent(2)).equals('1'); + expect(g.parent('2')).equals('1'); + expect(g.parent(3)).equals('2'); + }); + + it('preserves the tree invariant', function () { + g.setParent('c', 'b'); + g.setParent('b', 'a'); + expect(function () { + g.setParent('a', 'c'); + }).to.throw(); + }); + + it('is chainable', function () { + expect(g.setParent('a', 'parent')).to.equal(g); + }); + }); + + describe('parent', function () { + beforeEach(function () { + g = new Graph({ compound: true }); + }); + + it('returns undefined if the graph is not compound', function () { + expect(new Graph({ compound: false }).parent('a')).to.be.undefined; + }); + + it('returns undefined if the node is not in the graph', function () { + expect(g.parent('a')).to.be.undefined; + }); + + it('defaults to undefined for new nodes', function () { + g.setNode('a'); + expect(g.parent('a')).to.be.undefined; + }); + + it('returns the current parent assignment', function () { + g.setNode('a'); + g.setNode('parent'); + g.setParent('a', 'parent'); + expect(g.parent('a')).to.equal('parent'); + }); + }); + + describe('children', function () { + beforeEach(function () { + g = new Graph({ compound: true }); + }); + + it('returns undefined if the node is not in the graph', function () { + expect(g.children('a')).to.be.undefined; + }); + + it('defaults to en empty list for new nodes', function () { + g.setNode('a'); + expect(g.children('a')).to.eql([]); + }); + + it('returns undefined for a non-compound graph without the node', function () { + var g = new Graph(); + expect(g.children('a')).to.be.undefined; + }); + + it('returns an empty list for a non-compound graph with the node', function () { + var g = new Graph(); + g.setNode('a'); + expect(g.children('a')).eqls([]); + }); + + it('returns all nodes for the root of a non-compound graph', function () { + var g = new Graph(); + g.setNode('a'); + g.setNode('b'); + expect(g.children().sort()).eqls(['a', 'b']); + }); + + it('returns children for the node', function () { + g.setParent('a', 'parent'); + g.setParent('b', 'parent'); + expect(g.children('parent').sort()).to.eql(['a', 'b']); + }); + + it('returns all nodes without a parent when the parent is not set', function () { + g.setNode('a'); + g.setNode('b'); + g.setNode('c'); + g.setNode('parent'); + g.setParent('a', 'parent'); + expect(g.children().sort()).to.eql(['b', 'c', 'parent']); + expect(g.children(undefined).sort()).to.eql(['b', 'c', 'parent']); + }); + }); + + describe('predecessors', function () { + it('returns undefined for a node that is not in the graph', function () { + expect(g.predecessors('a')).to.be.undefined; + }); + + it('returns the predecessors of a node', function () { + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + g.setEdge('a', 'a'); + expect(g.predecessors('a').sort()).to.eql(['a']); + expect(g.predecessors('b').sort()).to.eql(['a']); + expect(g.predecessors('c').sort()).to.eql(['b']); + }); + }); + + describe('successors', function () { + it('returns undefined for a node that is not in the graph', function () { + expect(g.successors('a')).to.be.undefined; + }); + + it('returns the successors of a node', function () { + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + g.setEdge('a', 'a'); + expect(g.successors('a').sort()).to.eql(['a', 'b']); + expect(g.successors('b').sort()).to.eql(['c']); + expect(g.successors('c').sort()).to.eql([]); + }); + }); + + describe('neighbors', function () { + it('returns undefined for a node that is not in the graph', function () { + expect(g.neighbors('a')).to.be.undefined; + }); + + it('returns the neighbors of a node', function () { + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + g.setEdge('a', 'a'); + expect(g.neighbors('a').sort()).to.eql(['a', 'b']); + expect(g.neighbors('b').sort()).to.eql(['a', 'c']); + expect(g.neighbors('c').sort()).to.eql(['b']); + }); + }); + + describe('isLeaf', function () { + it('returns false for connected node in undirected graph', function () { + g = new Graph({ directed: false }); + g.setNode('a'); + g.setNode('b'); + g.setEdge('a', 'b'); + expect(g.isLeaf('b')).to.be.false; + }); + it('returns true for an unconnected node in undirected graph', function () { + g = new Graph({ directed: false }); + g.setNode('a'); + expect(g.isLeaf('a')).to.be.true; + }); + it('returns true for unconnected node in directed graph', function () { + g.setNode('a'); + expect(g.isLeaf('a')).to.be.true; + }); + it('returns false for predecessor node in directed graph', function () { + g.setNode('a'); + g.setNode('b'); + g.setEdge('a', 'b'); + expect(g.isLeaf('a')).to.be.false; + }); + it('returns true for successor node in directed graph', function () { + g.setNode('a'); + g.setNode('b'); + g.setEdge('a', 'b'); + expect(g.isLeaf('b')).to.be.true; + }); + }); + + describe('edges', function () { + it('is empty if there are no edges in the graph', function () { + expect(g.edges()).to.eql([]); + }); + + it('returns the keys for edges in the graph', function () { + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + expect(g.edges().sort(sortEdges)).to.eql([ + { v: 'a', w: 'b' }, + { v: 'b', w: 'c' }, + ]); + }); + }); + + describe('setPath', function () { + it('creates a path of mutiple edges', function () { + g.setPath(['a', 'b', 'c']); + expect(g.hasEdge('a', 'b')).to.be.true; + expect(g.hasEdge('b', 'c')).to.be.true; + }); + + it('can set a value for all of the edges', function () { + g.setPath(['a', 'b', 'c'], 'foo'); + expect(g.edge('a', 'b')).to.equal('foo'); + expect(g.edge('b', 'c')).to.equal('foo'); + }); + + it('is chainable', function () { + expect(g.setPath(['a', 'b', 'c'])).to.equal(g); + }); + }); + + describe('setEdge', function () { + it("creates the edge if it isn't part of the graph", function () { + g.setNode('a'); + g.setNode('b'); + g.setEdge('a', 'b'); + expect(g.edge('a', 'b')).to.be.undefined; + expect(g.hasEdge('a', 'b')).to.be.true; + expect(g.hasEdge({ v: 'a', w: 'b' })).to.be.true; + expect(g.edgeCount()).to.equal(1); + }); + + it('creates the nodes for the edge if they are not part of the graph', function () { + g.setEdge('a', 'b'); + expect(g.hasNode('a')).to.be.true; + expect(g.hasNode('b')).to.be.true; + expect(g.nodeCount()).to.equal(2); + }); + + it("creates a multi-edge if if it isn't part of the graph", function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b', undefined, 'name'); + expect(g.hasEdge('a', 'b')).to.be.false; + expect(g.hasEdge('a', 'b', 'name')).to.be.true; + }); + + it('throws if a multi-edge is used with a non-multigraph', function () { + expect(function () { + g.setEdge('a', 'b', undefined, 'name'); + }).to.throw(); + }); + + it('changes the value for an edge if it is already in the graph', function () { + g.setEdge('a', 'b', 'foo'); + g.setEdge('a', 'b', 'bar'); + expect(g.edge('a', 'b')).to.equal('bar'); + }); + + it('deletes the value for the edge if the value arg is undefined', function () { + g.setEdge('a', 'b', 'foo'); + g.setEdge('a', 'b', undefined); + expect(g.edge('a', 'b')).to.be.undefined; + expect(g.hasEdge('a', 'b')).to.be.true; + }); + + it('changes the value for a multi-edge if it is already in the graph', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b', 'value', 'name'); + g.setEdge('a', 'b', undefined, 'name'); + expect(g.edge('a', 'b', 'name')).to.be.undefined; + expect(g.hasEdge('a', 'b', 'name')).to.be.true; + }); + + it('can take an edge object as the first parameter', function () { + g.setEdge({ v: 'a', w: 'b' }, 'value'); + expect(g.edge('a', 'b')).to.equal('value'); + }); + + it('can take an multi-edge object as the first parameter', function () { + var g = new Graph({ multigraph: true }); + g.setEdge({ v: 'a', w: 'b', name: 'name' }, 'value'); + expect(g.edge('a', 'b', 'name')).to.equal('value'); + }); + + it('uses the stringified form of the id #1', function () { + g.setEdge(1, 2, 'foo'); + expect(g.edges()).eqls([{ v: '1', w: '2' }]); + expect(g.edge('1', '2')).to.equal('foo'); + expect(g.edge(1, 2)).to.equal('foo'); + }); + + it('uses the stringified form of the id #2', function () { + g = new Graph({ multigraph: true }); + g.setEdge(1, 2, 'foo', undefined); + expect(g.edges()).eqls([{ v: '1', w: '2' }]); + expect(g.edge('1', '2')).to.equal('foo'); + expect(g.edge(1, 2)).to.equal('foo'); + }); + + it('uses the stringified form of the id with a name', function () { + g = new Graph({ multigraph: true }); + g.setEdge(1, 2, 'foo', 3); + expect(g.edge('1', '2', '3')).to.equal('foo'); + expect(g.edge(1, 2, 3)).to.equal('foo'); + expect(g.edges()).eqls([{ v: '1', w: '2', name: '3' }]); + }); + + it('treats edges in opposite directions as distinct in a digraph', function () { + g.setEdge('a', 'b'); + expect(g.hasEdge('a', 'b')).to.be.true; + expect(g.hasEdge('b', 'a')).to.be.false; + }); + + it('handles undirected graph edges', function () { + var g = new Graph({ directed: false }); + g.setEdge('a', 'b', 'foo'); + expect(g.edge('a', 'b')).to.equal('foo'); + expect(g.edge('b', 'a')).to.equal('foo'); + }); + + it('handles undirected edges where id has different order than Stringified id', function () { + var g = new Graph({ directed: false }); + g.setEdge(9, 10, 'foo'); + expect(g.hasEdge('9', '10')).to.be.true; + expect(g.hasEdge(9, 10)).to.be.true; + expect(g.hasEdge('10', '9')).to.be.true; + expect(g.hasEdge(10, 9)).to.be.true; + expect(g.edge('9', '10')).eqls('foo'); + expect(g.edge(9, 10)).eqls('foo'); + }); + + it('is chainable', function () { + expect(g.setEdge('a', 'b')).to.equal(g); + }); + }); + + describe('setDefaultEdgeLabel', function () { + it('sets a default label for new edges', function () { + g.setDefaultEdgeLabel('foo'); + g.setEdge('a', 'b'); + expect(g.edge('a', 'b')).to.equal('foo'); + }); + + it('does not change existing edges', function () { + g.setEdge('a', 'b'); + g.setDefaultEdgeLabel('foo'); + expect(g.edge('a', 'b')).to.be.undefined; + }); + + it('is not used if an explicit value is set', function () { + g.setDefaultEdgeLabel('foo'); + g.setEdge('a', 'b', 'bar'); + expect(g.edge('a', 'b')).to.equal('bar'); + }); + + it('can take a function', function () { + g.setDefaultEdgeLabel(function () { + return 'foo'; + }); + g.setEdge('a', 'b'); + expect(g.edge('a', 'b')).to.equal('foo'); + }); + + it("can take a function that takes the edge's endpoints and name", function () { + var g = new Graph({ multigraph: true }); + g.setDefaultEdgeLabel(function (v, w, name) { + return v + '-' + w + '-' + name + '-foo'; + }); + g.setEdge({ v: 'a', w: 'b', name: 'name' }); + expect(g.edge('a', 'b', 'name')).to.equal('a-b-name-foo'); + }); + + it('does not set a default value for a multi-edge that already exists', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b', 'old', 'name'); + g.setDefaultEdgeLabel(function () { + return 'should not set this'; + }); + g.setEdge({ v: 'a', w: 'b', name: 'name' }); + expect(g.edge('a', 'b', 'name')).to.equal('old'); + }); + + it('is chainable', function () { + expect(g.setDefaultEdgeLabel('foo')).to.equal(g); + }); + }); + + describe('edge', function () { + it("returns undefined if the edge isn't part of the graph", function () { + expect(g.edge('a', 'b')).to.be.undefined; + expect(g.edge({ v: 'a', w: 'b' })).to.be.undefined; + expect(g.edge('a', 'b', 'foo')).to.be.undefined; + }); + + it('returns the value of the edge if it is part of the graph', function () { + g.setEdge('a', 'b', { foo: 'bar' }); + expect(g.edge('a', 'b')).to.eql({ foo: 'bar' }); + expect(g.edge({ v: 'a', w: 'b' })).to.eql({ foo: 'bar' }); + expect(g.edge('b', 'a')).to.be.undefined; + }); + + it('returns the value of a multi-edge if it is part of the graph', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b', { bar: 'baz' }, 'foo'); + expect(g.edge('a', 'b', 'foo')).to.eql({ bar: 'baz' }); + expect(g.edge('a', 'b')).to.be.undefined; + }); + + it('returns an edge in either direction in an undirected graph', function () { + var g = new Graph({ directed: false }); + g.setEdge('a', 'b', { foo: 'bar' }); + expect(g.edge('a', 'b')).to.eql({ foo: 'bar' }); + expect(g.edge('b', 'a')).to.eql({ foo: 'bar' }); + }); + }); + + describe('removeEdge', function () { + it('has no effect if the edge is not in the graph', function () { + g.removeEdge('a', 'b'); + expect(g.hasEdge('a', 'b')).to.be.false; + expect(g.edgeCount()).to.equal(0); + }); + + it('can remove an edge by edgeObj', function () { + var g = new Graph({ multigraph: true }); + g.setEdge({ v: 'a', w: 'b', name: 'foo' }); + g.removeEdge({ v: 'a', w: 'b', name: 'foo' }); + expect(g.hasEdge('a', 'b', 'foo')).to.be.false; + expect(g.edgeCount()).to.equal(0); + }); + + it('can remove an edge by separate ids', function () { + var g = new Graph({ multigraph: true }); + g.setEdge({ v: 'a', w: 'b', name: 'foo' }); + g.removeEdge('a', 'b', 'foo'); + expect(g.hasEdge('a', 'b', 'foo')).to.be.false; + expect(g.edgeCount()).to.equal(0); + }); + + it('correctly removes neighbors', function () { + g.setEdge('a', 'b'); + g.removeEdge('a', 'b'); + expect(g.successors('a')).to.eql([]); + expect(g.neighbors('a')).to.eql([]); + expect(g.predecessors('b')).to.eql([]); + expect(g.neighbors('b')).to.eql([]); + }); + + it('correctly decrements neighbor counts', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b'); + g.setEdge({ v: 'a', w: 'b', name: 'foo' }); + g.removeEdge('a', 'b'); + expect(g.hasEdge('a', 'b', 'foo')); + expect(g.successors('a')).to.eql(['b']); + expect(g.neighbors('a')).to.eql(['b']); + expect(g.predecessors('b')).to.eql(['a']); + expect(g.neighbors('b')).to.eql(['a']); + }); + + it('works with undirected graphs', function () { + var g = new Graph({ directed: false }); + g.setEdge('h', 'g'); + g.removeEdge('g', 'h'); + expect(g.neighbors('g')).to.eql([]); + expect(g.neighbors('h')).to.eql([]); + }); + + it('is chainable', function () { + g.setEdge('a', 'b'); + expect(g.removeEdge('a', 'b')).to.equal(g); + }); + }); + + describe('inEdges', function () { + it('returns undefined for a node that is not in the graph', function () { + expect(g.inEdges('a')).to.be.undefined; + }); + + it('returns the edges that point at the specified node', function () { + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + expect(g.inEdges('a')).to.eql([]); + expect(g.inEdges('b')).to.eql([{ v: 'a', w: 'b' }]); + expect(g.inEdges('c')).to.eql([{ v: 'b', w: 'c' }]); + }); + + it('works for multigraphs', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b'); + g.setEdge('a', 'b', undefined, 'bar'); + g.setEdge('a', 'b', undefined, 'foo'); + expect(g.inEdges('a')).to.eql([]); + expect(g.inEdges('b').sort(sortEdges)).to.eql([ + { v: 'a', w: 'b', name: 'bar' }, + { v: 'a', w: 'b', name: 'foo' }, + { v: 'a', w: 'b' }, + ]); + }); + + it('can return only edges from a specified node', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b'); + g.setEdge('a', 'b', undefined, 'foo'); + g.setEdge('a', 'c'); + g.setEdge('b', 'c'); + g.setEdge('z', 'a'); + g.setEdge('z', 'b'); + expect(g.inEdges('a', 'b')).to.eql([]); + expect(g.inEdges('b', 'a').sort(sortEdges)).to.eql([ + { v: 'a', w: 'b', name: 'foo' }, + { v: 'a', w: 'b' }, + ]); + }); + }); + + describe('outEdges', function () { + it('returns undefined for a node that is not in the graph', function () { + expect(g.outEdges('a')).to.be.undefined; + }); + + it('returns all edges that this node points at', function () { + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + expect(g.outEdges('a')).to.eql([{ v: 'a', w: 'b' }]); + expect(g.outEdges('b')).to.eql([{ v: 'b', w: 'c' }]); + expect(g.outEdges('c')).to.eql([]); + }); + + it('works for multigraphs', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b'); + g.setEdge('a', 'b', undefined, 'bar'); + g.setEdge('a', 'b', undefined, 'foo'); + expect(g.outEdges('a').sort(sortEdges)).to.eql([ + { v: 'a', w: 'b', name: 'bar' }, + { v: 'a', w: 'b', name: 'foo' }, + { v: 'a', w: 'b' }, + ]); + expect(g.outEdges('b')).to.eql([]); + }); + + it('can return only edges to a specified node', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b'); + g.setEdge('a', 'b', undefined, 'foo'); + g.setEdge('a', 'c'); + g.setEdge('b', 'c'); + g.setEdge('z', 'a'); + g.setEdge('z', 'b'); + expect(g.outEdges('a', 'b').sort(sortEdges)).to.eql([ + { v: 'a', w: 'b', name: 'foo' }, + { v: 'a', w: 'b' }, + ]); + expect(g.outEdges('b', 'a')).to.eql([]); + }); + }); + + describe('nodeEdges', function () { + it('returns undefined for a node that is not in the graph', function () { + expect(g.nodeEdges('a')).to.be.undefined; + }); + + it('returns all edges that this node points at', function () { + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + expect(g.nodeEdges('a')).to.eql([{ v: 'a', w: 'b' }]); + expect(g.nodeEdges('b').sort(sortEdges)).to.eql([ + { v: 'a', w: 'b' }, + { v: 'b', w: 'c' }, + ]); + expect(g.nodeEdges('c')).to.eql([{ v: 'b', w: 'c' }]); + }); + + it('works for multigraphs', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b'); + g.setEdge({ v: 'a', w: 'b', name: 'bar' }); + g.setEdge({ v: 'a', w: 'b', name: 'foo' }); + expect(g.nodeEdges('a').sort(sortEdges)).to.eql([ + { v: 'a', w: 'b', name: 'bar' }, + { v: 'a', w: 'b', name: 'foo' }, + { v: 'a', w: 'b' }, + ]); + expect(g.nodeEdges('b').sort(sortEdges)).to.eql([ + { v: 'a', w: 'b', name: 'bar' }, + { v: 'a', w: 'b', name: 'foo' }, + { v: 'a', w: 'b' }, + ]); + }); + + it('can return only edges between specific nodes', function () { + var g = new Graph({ multigraph: true }); + g.setEdge('a', 'b'); + g.setEdge({ v: 'a', w: 'b', name: 'foo' }); + g.setEdge('a', 'c'); + g.setEdge('b', 'c'); + g.setEdge('z', 'a'); + g.setEdge('z', 'b'); + expect(g.nodeEdges('a', 'b').sort(sortEdges)).to.eql([ + { v: 'a', w: 'b', name: 'foo' }, + { v: 'a', w: 'b' }, + ]); + expect(g.nodeEdges('b', 'a').sort(sortEdges)).to.eql([ + { v: 'a', w: 'b', name: 'foo' }, + { v: 'a', w: 'b' }, + ]); + }); + }); +}); + +function sortEdges(a, b) { + if (a.name) { + return a.name.localeCompare(b.name); + } + + const order = a.v.localeCompare(b.v); + if (order != 0) { + return order; + } + + return a.w.localeCompare(b.w); +} diff --git a/src/graphlib/json.test.js b/src/graphlib/json.test.js new file mode 100644 index 0000000..c222189 --- /dev/null +++ b/src/graphlib/json.test.js @@ -0,0 +1,62 @@ +import { describe, expect, it } from 'vitest'; + +import { Graph } from './graph.js'; +import { read, write } from './json.js'; + +describe('json', function () { + it('preserves the graph options', function () { + expect(rw(new Graph({ directed: true })).isDirected()).to.be.true; + expect(rw(new Graph({ directed: false })).isDirected()).to.be.false; + expect(rw(new Graph({ multigraph: true })).isMultigraph()).to.be.true; + expect(rw(new Graph({ multigraph: false })).isMultigraph()).to.be.false; + expect(rw(new Graph({ compound: true })).isCompound()).to.be.true; + expect(rw(new Graph({ compound: false })).isCompound()).to.be.false; + }); + + it('preserves the graph value, if any', function () { + expect(rw(new Graph().setGraph(1)).graph()).equals(1); + expect(rw(new Graph().setGraph({ foo: 'bar' })).graph()).eqls({ foo: 'bar' }); + expect(rw(new Graph()).graph()).to.be.undefined; + }); + + it('preserves nodes', function () { + expect(rw(new Graph().setNode('a')).hasNode('a')).to.be.true; + expect(rw(new Graph().setNode('a')).node('a')).to.be.undefined; + expect(rw(new Graph().setNode('a', 1)).node('a')).equals(1); + expect(rw(new Graph().setNode('a', { foo: 'bar' })).node('a')).eqls({ foo: 'bar' }); + }); + + it('preserves simple edges', function () { + expect(rw(new Graph().setEdge('a', 'b')).hasEdge('a', 'b')).to.be.true; + expect(rw(new Graph().setEdge('a', 'b')).edge('a', 'b')).to.be.undefined; + expect(rw(new Graph().setEdge('a', 'b', 1)).edge('a', 'b')).equals(1); + expect(rw(new Graph().setEdge('a', 'b', { foo: 'bar' })).edge('a', 'b')).eqls({ foo: 'bar' }); + }); + + it('preserves multi-edges', function () { + var g = new Graph({ multigraph: true }); + + g.setEdge({ v: 'a', w: 'b', name: 'foo' }); + expect(rw(g).hasEdge('a', 'b', 'foo')).to.be.true; + + g.setEdge({ v: 'a', w: 'b', name: 'foo' }); + expect(rw(g).edge('a', 'b', 'foo')).to.be.undefined; + + g.setEdge({ v: 'a', w: 'b', name: 'foo' }, 1); + expect(rw(g).edge('a', 'b', 'foo')).equals(1); + + g.setEdge({ v: 'a', w: 'b', name: 'foo' }, { foo: 'bar' }); + expect(rw(g).edge('a', 'b', 'foo')).eqls({ foo: 'bar' }); + }); + + it('preserves parent / child relationships', function () { + expect(rw(new Graph({ compound: true }).setNode('a')).parent('a')).to.be.undefined; + expect(rw(new Graph({ compound: true }).setParent('a', 'parent')).parent('a')).to.equal( + 'parent', + ); + }); +}); + +function rw(g) { + return read(write(g)); +} diff --git a/test/graphlib/alg/all-shortest-paths.js b/test/graphlib/alg/all-shortest-paths.js new file mode 100644 index 0000000..9f80b89 --- /dev/null +++ b/test/graphlib/alg/all-shortest-paths.js @@ -0,0 +1,128 @@ +import { describe, expect, it } from 'vitest'; +import { Graph } from '../../../src/graphlib/graph.js'; + +export function allShortestPathsTests(sp) { + describe('allShortestPaths', function () { + it('returns 0 for the node itself', function () { + var g = new Graph(); + g.setNode('a'); + expect(sp(g)).to.eql({ a: { a: { distance: 0 } } }); + }); + + it('returns the distance and path from all nodes to other nodes', function () { + var g = new Graph(); + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + expect(sp(g)).to.eql({ + a: { + a: { distance: 0 }, + b: { distance: 1, predecessor: 'a' }, + c: { distance: 2, predecessor: 'b' }, + }, + b: { + a: { distance: Number.POSITIVE_INFINITY }, + b: { distance: 0 }, + c: { distance: 1, predecessor: 'b' }, + }, + c: { + a: { distance: Number.POSITIVE_INFINITY }, + b: { distance: Number.POSITIVE_INFINITY }, + c: { distance: 0 }, + }, + }); + }); + + it('uses an optionally supplied weight function', function () { + var g = new Graph(); + g.setEdge('a', 'b', 2); + g.setEdge('b', 'c', 3); + + expect(sp(g, weightFn(g))).to.eql({ + a: { + a: { distance: 0 }, + b: { distance: 2, predecessor: 'a' }, + c: { distance: 5, predecessor: 'b' }, + }, + b: { + a: { distance: Number.POSITIVE_INFINITY }, + b: { distance: 0 }, + c: { distance: 3, predecessor: 'b' }, + }, + c: { + a: { distance: Number.POSITIVE_INFINITY }, + b: { distance: Number.POSITIVE_INFINITY }, + c: { distance: 0 }, + }, + }); + }); + + it('uses an optionally supplied incident function', function () { + var g = new Graph(); + g.setEdge('a', 'b'); + g.setEdge('b', 'c'); + + expect( + sp(g, undefined, function (v) { + return g.inEdges(v); + }), + ).to.eql({ + a: { + a: { distance: 0 }, + b: { distance: Number.POSITIVE_INFINITY }, + c: { distance: Number.POSITIVE_INFINITY }, + }, + b: { + a: { distance: 1, predecessor: 'b' }, + b: { distance: 0 }, + c: { distance: Number.POSITIVE_INFINITY }, + }, + c: { + a: { distance: 2, predecessor: 'b' }, + b: { distance: 1, predecessor: 'c' }, + c: { distance: 0 }, + }, + }); + }); + + it('works with undirected graphs', function () { + var g = new Graph({ directed: false }); + g.setEdge('a', 'b', 1); + g.setEdge('b', 'c', 2); + g.setEdge('c', 'a', 4); + g.setEdge('b', 'd', 6); + + expect(sp(g, weightFn(g), g.nodeEdges.bind(g))).to.eql({ + a: { + a: { distance: 0 }, + b: { distance: 1, predecessor: 'a' }, + c: { distance: 3, predecessor: 'b' }, + d: { distance: 7, predecessor: 'b' }, + }, + b: { + a: { distance: 1, predecessor: 'b' }, + b: { distance: 0 }, + c: { distance: 2, predecessor: 'b' }, + d: { distance: 6, predecessor: 'b' }, + }, + c: { + a: { distance: 3, predecessor: 'b' }, + b: { distance: 2, predecessor: 'c' }, + c: { distance: 0 }, + d: { distance: 8, predecessor: 'b' }, + }, + d: { + a: { distance: 7, predecessor: 'b' }, + b: { distance: 6, predecessor: 'd' }, + c: { distance: 8, predecessor: 'b' }, + d: { distance: 0 }, + }, + }); + }); + }); +} + +function weightFn(g) { + return function (e) { + return g.edge(e); + }; +} diff --git a/tsconfig.json b/tsconfig.json index f798a16..6347e8a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,13 @@ "declaration": true, "emitDeclarationOnly": true, "lib": ["dom", "dom.iterable", "es2020"], + "types": [ + // vitest types don't work with module "node16", see + // https://github.com/vitejs/vite/issues/11552 + "@types/jest" + ], "module": "node16" - } + }, + "include": ["src/**/*.js"], + "exclude": ["src/**/*.test.js"] } diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..cdc26a5 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,6 @@ +export default { + test: { + // we can't do `import {it} from "vitest"` due to https://github.com/vitejs/vite/issues/11552 + globals: true, + }, +};