From a5df87961916e3aa97b2c31b19959ffaf2b5e963 Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 8 Apr 2024 16:49:05 +0800 Subject: [PATCH 1/2] refactor: perfect type definition --- package.json | 8 +- rollup.config.js | 44 ++++--- src/index.d.ts | 294 +++++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 104 +++++++++++++++++ 4 files changed, 435 insertions(+), 15 deletions(-) create mode 100644 src/index.d.ts diff --git a/package.json b/package.json index d2fd16c..e85c638 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "d3-force-3d", - "version": "3.0.5", + "version": "3.0.6", "description": "Force-directed graph layout in 1D, 2D or 3D using velocity Verlet integration.", "homepage": "https://github.com/vasturiano/d3-force-3d", "repository": { @@ -32,7 +32,9 @@ "main": "src/index.js", "jsdelivr": "dist/d3-force-3d.min.js", "unpkg": "dist/d3-force-3d.min.js", + "types": "dist/d3-force-3d.d.ts", "exports": { + "types": "./dist/d3-force-3d.d.ts", "umd": "./dist/d3-force-3d.min.js", "default": "./src/index.js" }, @@ -49,7 +51,9 @@ "@rollup/plugin-terser": "^0.4.0", "eslint": "^8.33.0", "mocha": "^10.2.0", - "rollup": "^3.14.0" + "rollup": "^3.14.0", + "rollup-plugin-dts": "^6.1.0", + "typescript": "^5.4.4" }, "scripts": { "test": "mocha 'test/**/*-test.js' && eslint src test", diff --git a/rollup.config.js b/rollup.config.js index 0f4063c..5bd7ac2 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,20 +1,28 @@ import nodeResolve from '@rollup/plugin-node-resolve'; -import terser from "@rollup/plugin-terser"; +import terser from '@rollup/plugin-terser'; +import dts from 'rollup-plugin-dts'; import meta from './package.json' assert { type: 'json' }; const config = { - input: "src/index.js", - external: Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)), + input: 'src/index.js', + external: Object.keys(meta.dependencies || {}).filter((key) => + /^d3-/.test(key) + ), output: { file: `dist/${meta.name}.js`, - name: "d3", - format: "umd", + name: 'd3', + format: 'umd', indent: false, extend: true, banner: `// ${meta.homepage} v${meta.version}`, - globals: Object.assign({}, ...Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)).map(key => ({[key]: "d3"}))) + globals: Object.assign( + {}, + ...Object.keys(meta.dependencies || {}) + .filter((key) => /^d3-/.test(key)) + .map((key) => ({ [key]: 'd3' })) + ), }, - plugins: [] + plugins: [], }; export default [ @@ -23,16 +31,26 @@ export default [ ...config, output: { ...config.output, - file: `dist/${meta.name}.min.js` + file: `dist/${meta.name}.min.js`, }, plugins: [ ...config.plugins, nodeResolve({ jsnext: true }), terser({ output: { - preamble: config.output.banner - } - }) - ] - } + preamble: config.output.banner, + }, + }), + ], + }, + { + input: 'src/index.d.ts', + output: [ + { + file: `dist/${meta.name}.d.ts`, + format: 'es', + }, + ], + plugins: [dts()], + }, ]; diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..dde9d14 --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,294 @@ +declare module 'd3-force-3d' { + export function forceCenter(x?: number, y?: number, z?: number): ForceCenter; + + export function forceCollide( + radius?: + | number + | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): ForceCollide; + + export function forceLink(links?: LinkData[]): ForceLink; + + export function forceManyBody(): ForceManyBody; + + export function forceRadial( + radius?: + | number + | ((node: NodeData, index: number, nodes: NodeData[]) => number), + x?: number, + y?: number, + z?: number + ): ForceRadial; + + export function forceSimulation( + nodes?: NodeData[], + numDimensions?: Dimensions + ): ForceSimulation; + + export function forceX(x?: number): ForceX; + + export function forceY(y?: number): ForceY; + + export function forceZ(z?: number): ForceZ; + + interface ForceSimulation { + tick(iterations?: number): this; + + restart(): this; + + stop(): this; + + numDimensions(): Dimensions; + numDimensions(value: Dimensions): this; + + nodes(): NodeData[]; + nodes(nodes: NodeData[]): this; + + alpha(): number; + alpha(alpha: number): this; + + alphaMin(): number; + alphaMin(min: number): this; + + alphaDecay(): number; + alphaDecay(decay: number): this; + + alphaTarget(): number; + alphaTarget(target: number): this; + + velocityDecay(): number; + velocityDecay(decay: number): this; + + randomSource(): () => number; + randomSource(source: () => number): this; + + force(name: string): T; + force(name: string, force: Force | null): this; + + find(x?: number, y?: number, z?: number, radius?: number): NodeData; + + on(name: string): (...args: any[]) => void; + on(name: string, listener: (...args: any[]) => void): this; + } + + type Force = + | ForceCenter + | ForceCollide + | ForceLink + | ForceManyBody + | ForceRadial + | ForceX + | ForceY + | ForceZ; + + interface ForceCenter { + (): void; + + initialize(nodes: NodeData[]): void; + + x(): number; + x(x: number): this; + + y(): number; + y(y: number): this; + + z(): number; + z(z: number): this; + + strength(): number; + strength(strength: number): this; + } + + interface ForceCollide { + (): void; + + initialize( + nodes: NodeData[], + random?: () => number, + nDim?: Dimensions + ): void; + + iterations(): number; + iterations(iterations: number): this; + + strength(): number; + strength(strength: number): this; + + radius(): number; + radius( + radius: + | number + | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): this; + } + + interface ForceLink { + (alpha: number): void; + + initialize(nodes: NodeData[], random: () => number, dim: Dimensions): void; + + links(): LinkData[]; + links(links: LinkData[]): this; + + id(): (node: NodeData, index: number, nodes: NodeData[]) => any; + id(id: (node: NodeData, index: number, nodes: NodeData[]) => any): this; + + iterations(): number; + iterations(iterations: number): this; + + strength(): (link: LinkData, index: number, links: LinkData[]) => number; + strength( + strength: + | number + | ((link: LinkData, index: number, links: LinkData[]) => number) + ): this; + + distance(): (link: LinkData, index: number, links: LinkData[]) => number; + distance( + distance: + | number + | ((link: LinkData, index: number, links: LinkData[]) => number) + ): this; + } + + interface ForceManyBody { + (alpha: number): void; + + initialize(nodes: NodeData[], random: () => number, dim: Dimensions): void; + + strength(): (node: NodeData, index: number, nodes: NodeData[]) => number; + strength( + strength: + | number + | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): this; + + distanceMin(): number; + distanceMin(min: number): this; + + distanceMax(): number; + distanceMax(max: number): this; + + theta(): number; + theta(theta: number): this; + } + + interface ForceRadial { + (alpha: number): void; + + initialize(nodes: NodeData[], dim: Dimensions): void; + + strength(): (node: NodeData, index: number, nodes: NodeData[]) => number; + strength( + strength: + | number + | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): this; + + radius(): (node: NodeData, index: number, nodes: NodeData[]) => number; + radius( + radius: + | number + | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): this; + + x(): number; + x(x: number): this; + + y(): number; + y(y: number): this; + + z(): number; + z(z: number): this; + } + + interface ForceX { + (alpha: number): void; + + initialize(nodes: NodeData[]): void; + + strength(): number; + strength( + strength: + | number + | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): this; + + x(): number; + x( + x: number | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): this; + } + + interface ForceY { + (alpha: number): void; + + initialize(nodes: NodeData[]): void; + + strength(): number; + strength( + strength: + | number + | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): this; + + y(): number; + y( + y: number | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): this; + } + + interface ForceZ { + (alpha: number): void; + + initialize(nodes: NodeData[]): void; + + strength(): number; + strength( + strength: + | number + | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): this; + + z(): number; + z( + z: number | ((node: NodeData, index: number, nodes: NodeData[]) => number) + ): this; + } + + interface NodeData { + /** the node’s zero-based index into nodes */ + index?: number; + /** the node’s current x-position */ + x?: number; + /** the node’s current y-position (if using 2 or more dimensions) */ + y?: number; + /** the node’s current z-position (if using 3 dimensions) */ + z?: number; + /** the node’s current x-velocity */ + vx?: number; + /** the node’s current y-velocity (if using 2 or more dimensions) */ + vy?: number; + /** the node’s current z-velocity (if using 3 dimensions) */ + vz?: number; + /** the node’s fixed x-position */ + fx?: number; + /** the node’s fixed y-position */ + fy?: number; + /** the node’s fixed z-position */ + fz?: number; + [key: string]: any; + } + + interface LinkData { + /** the zero-based index into links, assigned by this method */ + index?: number; + /** the link’s source node */ + source: NodeData | any; + /** the link’s target node */ + target: NodeData | any; + [key: string]: any; + } + + type Dimensions = 1 | 2 | 3; +} diff --git a/yarn.lock b/yarn.lock index c623a71..a90f75a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,29 @@ # yarn lockfile v1 +"@babel/code-frame@^7.22.13": + version "7.24.2" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@eslint/eslintrc@^1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e" @@ -68,6 +91,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + "@jridgewell/trace-mapping@^0.3.9": version "0.3.17" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" @@ -167,6 +195,13 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -244,6 +279,15 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -276,6 +320,13 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -283,6 +334,11 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" @@ -381,6 +437,11 @@ escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + eslint-scope@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" @@ -621,6 +682,11 @@ grapheme-splitter@^1.0.4: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -742,6 +808,11 @@ js-sdsl@^4.1.4: resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711" integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -787,6 +858,13 @@ log-symbols@4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" +magic-string@^0.30.4: + version "0.30.9" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz#8927ae21bfdd856310e07a1bc8dd5e73cb6c251d" + integrity sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + minimatch@5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" @@ -913,6 +991,11 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -983,6 +1066,15 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +rollup-plugin-dts@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.1.0.tgz#56e9c5548dac717213c6a4aa9df523faf04f75ae" + integrity sha512-ijSCPICkRMDKDLBK9torss07+8dl9UpY9z1N/zTeA1cIqdzMlpkV3MOOC7zukyvQfDyxa1s3Dl2+DeiP/G6DOw== + dependencies: + magic-string "^0.30.4" + optionalDependencies: + "@babel/code-frame" "^7.22.13" + rollup@^3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.14.0.tgz#f5925255f3b6e8de1dba3916d7619c7da5708d95" @@ -1074,6 +1166,13 @@ supports-color@8.1.1: dependencies: has-flag "^4.0.0" +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -1120,6 +1219,11 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +typescript@^5.4.4: + version "5.4.4" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz#eb2471e7b0a5f1377523700a21669dce30c2d952" + integrity sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" From 78f2e44b15e34322caaac564f794a13a4e20a0b7 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 9 Apr 2024 10:56:33 +0800 Subject: [PATCH 2/2] refactor: refactor type definitions based on d3-force --- package.json | 8 +- src/index.d.ts | 542 +++++++++++++++++++++++-------------------------- yarn.lock | 5 + 3 files changed, 262 insertions(+), 293 deletions(-) diff --git a/package.json b/package.json index e85c638..2c65af7 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ }, "type": "module", "files": [ - "src/**/*.js", - "dist/**/*.js" + "src/**/*", + "dist/**/*" ], "module": "src/index.js", "main": "src/index.js", @@ -49,12 +49,16 @@ "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-terser": "^0.4.0", + "@types/d3-force": "^3.0.9", "eslint": "^8.33.0", "mocha": "^10.2.0", "rollup": "^3.14.0", "rollup-plugin-dts": "^6.1.0", "typescript": "^5.4.4" }, + "peerDependencies": { + "@types/d3-force": "^3.0.9" + }, "scripts": { "test": "mocha 'test/**/*-test.js' && eslint src test", "prepare": "rm -rf dist && yarn test && rollup -c" diff --git a/src/index.d.ts b/src/index.d.ts index dde9d14..22065e2 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,294 +1,254 @@ -declare module 'd3-force-3d' { - export function forceCenter(x?: number, y?: number, z?: number): ForceCenter; - - export function forceCollide( - radius?: - | number - | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): ForceCollide; - - export function forceLink(links?: LinkData[]): ForceLink; - - export function forceManyBody(): ForceManyBody; - - export function forceRadial( - radius?: - | number - | ((node: NodeData, index: number, nodes: NodeData[]) => number), - x?: number, - y?: number, - z?: number - ): ForceRadial; - - export function forceSimulation( - nodes?: NodeData[], - numDimensions?: Dimensions - ): ForceSimulation; - - export function forceX(x?: number): ForceX; - - export function forceY(y?: number): ForceY; - - export function forceZ(z?: number): ForceZ; - - interface ForceSimulation { - tick(iterations?: number): this; - - restart(): this; - - stop(): this; - - numDimensions(): Dimensions; - numDimensions(value: Dimensions): this; - - nodes(): NodeData[]; - nodes(nodes: NodeData[]): this; - - alpha(): number; - alpha(alpha: number): this; - - alphaMin(): number; - alphaMin(min: number): this; - - alphaDecay(): number; - alphaDecay(decay: number): this; - - alphaTarget(): number; - alphaTarget(target: number): this; - - velocityDecay(): number; - velocityDecay(decay: number): this; - - randomSource(): () => number; - randomSource(source: () => number): this; - - force(name: string): T; - force(name: string, force: Force | null): this; - - find(x?: number, y?: number, z?: number, radius?: number): NodeData; - - on(name: string): (...args: any[]) => void; - on(name: string, listener: (...args: any[]) => void): this; - } - - type Force = - | ForceCenter - | ForceCollide - | ForceLink - | ForceManyBody - | ForceRadial - | ForceX - | ForceY - | ForceZ; - - interface ForceCenter { - (): void; - - initialize(nodes: NodeData[]): void; - - x(): number; - x(x: number): this; - - y(): number; - y(y: number): this; - - z(): number; - z(z: number): this; - - strength(): number; - strength(strength: number): this; - } - - interface ForceCollide { - (): void; - - initialize( - nodes: NodeData[], - random?: () => number, - nDim?: Dimensions - ): void; - - iterations(): number; - iterations(iterations: number): this; - - strength(): number; - strength(strength: number): this; - - radius(): number; - radius( - radius: - | number - | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): this; - } - - interface ForceLink { - (alpha: number): void; - - initialize(nodes: NodeData[], random: () => number, dim: Dimensions): void; - - links(): LinkData[]; - links(links: LinkData[]): this; - - id(): (node: NodeData, index: number, nodes: NodeData[]) => any; - id(id: (node: NodeData, index: number, nodes: NodeData[]) => any): this; - - iterations(): number; - iterations(iterations: number): this; - - strength(): (link: LinkData, index: number, links: LinkData[]) => number; - strength( - strength: - | number - | ((link: LinkData, index: number, links: LinkData[]) => number) - ): this; - - distance(): (link: LinkData, index: number, links: LinkData[]) => number; - distance( - distance: - | number - | ((link: LinkData, index: number, links: LinkData[]) => number) - ): this; - } - - interface ForceManyBody { - (alpha: number): void; - - initialize(nodes: NodeData[], random: () => number, dim: Dimensions): void; - - strength(): (node: NodeData, index: number, nodes: NodeData[]) => number; - strength( - strength: - | number - | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): this; - - distanceMin(): number; - distanceMin(min: number): this; - - distanceMax(): number; - distanceMax(max: number): this; - - theta(): number; - theta(theta: number): this; - } - - interface ForceRadial { - (alpha: number): void; - - initialize(nodes: NodeData[], dim: Dimensions): void; - - strength(): (node: NodeData, index: number, nodes: NodeData[]) => number; - strength( - strength: - | number - | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): this; - - radius(): (node: NodeData, index: number, nodes: NodeData[]) => number; - radius( - radius: - | number - | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): this; - - x(): number; - x(x: number): this; - - y(): number; - y(y: number): this; - - z(): number; - z(z: number): this; - } - - interface ForceX { - (alpha: number): void; - - initialize(nodes: NodeData[]): void; - - strength(): number; - strength( - strength: - | number - | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): this; - - x(): number; - x( - x: number | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): this; - } - - interface ForceY { - (alpha: number): void; - - initialize(nodes: NodeData[]): void; - - strength(): number; - strength( - strength: - | number - | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): this; - - y(): number; - y( - y: number | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): this; - } - - interface ForceZ { - (alpha: number): void; - - initialize(nodes: NodeData[]): void; - - strength(): number; - strength( - strength: - | number - | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): this; - - z(): number; - z( - z: number | ((node: NodeData, index: number, nodes: NodeData[]) => number) - ): this; - } - - interface NodeData { - /** the node’s zero-based index into nodes */ - index?: number; - /** the node’s current x-position */ - x?: number; - /** the node’s current y-position (if using 2 or more dimensions) */ - y?: number; - /** the node’s current z-position (if using 3 dimensions) */ - z?: number; - /** the node’s current x-velocity */ - vx?: number; - /** the node’s current y-velocity (if using 2 or more dimensions) */ - vy?: number; - /** the node’s current z-velocity (if using 3 dimensions) */ - vz?: number; - /** the node’s fixed x-position */ - fx?: number; - /** the node’s fixed y-position */ - fy?: number; - /** the node’s fixed z-position */ - fz?: number; - [key: string]: any; - } +import type { + SimulationNodeDatum as _SimulationNodeDatum, + SimulationLinkDatum, + Simulation as _Simulation, + Force, + ForceCenter, + forceCenter, + ForceCollide, + forceCollide, + ForceLink, + forceLink, + ForceManyBody, + forceManyBody, + ForceRadial, + forceRadial, + ForceX, + forceX, + ForceY, + forceY, +} from 'd3-force'; + +export { + SimulationLinkDatum, + Force, + ForceCenter, + forceCenter, + ForceCollide, + forceCollide, + ForceLink, + forceLink, + ForceManyBody, + forceManyBody, + ForceRadial, + forceRadial, + ForceX, + forceX, + ForceY, + forceY, +}; + +type Dimensions = 1 | 2 | 3; + +export interface SimulationNodeDatum extends _SimulationNodeDatum { + /** + * Node’s current z-position + */ + z?: number | undefined; + /** + * Node’s current z-velocity + */ + vz?: number | undefined; + /** + * Node’s fixed z-position (if position was fixed) + */ + fz?: number | null | undefined; +} - interface LinkData { - /** the zero-based index into links, assigned by this method */ - index?: number; - /** the link’s source node */ - source: NodeData | any; - /** the link’s target node */ - target: NodeData | any; - [key: string]: any; - } +/** + * Create a new simulation with the specified array of nodes and no forces. + * If nodes is not specified, it defaults to the empty array. + * The simulator starts automatically; use simulation.on to listen for tick events as the simulation runs. + * If you wish to run the simulation manually instead, call simulation.stop, and then call simulation.tick as desired. + * + * Use this signature, when creating a simulation WITHOUT link force(s). + * + * The generic refers to the type of the data for a node. + * + * @param nodesData Optional array of nodes data, defaults to empty array. + */ +export function forceSimulation( + nodesData?: NodeDatum[], + numDimensions?: Dimensions +): Simulation; +/** + * Create a new simulation with the specified array of nodes and no forces. + * If nodes is not specified, it defaults to the empty array. + * The simulator starts automatically; use simulation.on to listen for tick events as the simulation runs. + * If you wish to run the simulation manually instead, call simulation.stop, and then call simulation.tick as desired. + * + * Use this signature, when creating a simulation WITH link force(s). + * + * The first generic refers to the type of data for a node. + * The second generic refers to the type of data for a link. + * + * @param nodesData Optional array of nodes data, defaults to empty array. + */ +export function forceSimulation< + NodeDatum extends SimulationNodeDatum, + LinkDatum extends SimulationLinkDatum +>( + nodesData?: NodeDatum[], + numDimensions?: Dimensions +): Simulation; + +/** + * A Force Simulation + * + * The first generic refers to the type of the datum associated with a node in the simulation. + * The second generic refers to the type of the datum associated with a link in the simulation, if applicable. + */ +export interface Simulation< + NodeDatum extends SimulationNodeDatum, + LinkDatum extends SimulationLinkDatum | undefined +> extends _Simulation { + /** + * Return the current dimensions of the simulation, which defaults to 2. + */ + numDimensions(): Dimensions; + /** + * Sets the simulation’s number of dimensions to use (1, 2 or 3) and return this simulation. + * The default is 2. + * + * A one-dimensional simulation will only consider and manipulate the x and vx coordinate attributes, + * while a two-dimensional will extend the domain to y and vy, and a three-dimensional to z and vz. + * + * @param nDim Current dimensions of simulation. + */ + numDimensions(nDim: Dimensions): this; +} - type Dimensions = 1 | 2 | 3; +/** + * The z-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength. + * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position. + * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes. + * + * The generic refers to the type of data for a node. + */ +export interface ForceZ + extends Force { + /** + * Supplies the array of nodes and random source to this force. This method is called when a force is bound to a simulation via simulation.force + * and when the simulation’s nodes change via simulation.nodes. + * + * A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force. + */ + initialize(nodes: NodeDatum[], random: () => number): void; + + /** + * Returns the current strength accessor, which defaults to a constant strength for all nodes of 0.1. + */ + strength(): (d: NodeDatum, i: number, data: NodeDatum[]) => number; + /** + * Set the strength accessor to the specified constant strength for all nodes, re-evaluates the strength accessor for each node, and returns this force. + * + * The strength determines how much to increment the node’s z-velocity: (z - node.z) × strength. + * + * For example, a value of 0.1 indicates that the node should move a tenth of the way from its current z-position to the target z-position with each application. + * Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints. + * + * A value outside the range [0,1] is not recommended. + * + * The constant is internally wrapped into a strength accessor function. + * + * The strength accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes. + * The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or + * when this method is called with a new strength, and not on every application of the force. + * + * @param strength Constant value of strength to be used for all nodes. + */ + strength(strength: number): this; + /** + * Set the strength accessor to the specified function, re-evaluates the strength accessor for each node, and returns this force. + * + * The strength determines how much to increment the node’s z-velocity: (z - node.z) × strength. + * + * For example, a value of 0.1 indicates that the node should move a tenth of the way from its current z-position to the target z-position with each application. + * Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints. + * + * A value outside the range [0,1] is not recommended. + * + * The strength accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes. + * The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or + * when this method is called with a new strength, and not on every application of the force. + * + * @param strength A strength accessor function which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes. + * The function returns the strength. + */ + strength( + strength: (d: NodeDatum, i: number, data: NodeDatum[]) => number + ): this; + + /** + * Return the current z-accessor, which defaults to a function returning 0 for all nodes. + */ + z(): (d: NodeDatum, i: number, data: NodeDatum[]) => number; + /** + * Set the z-coordinate accessor to the specified number, re-evaluates the z-accessor for each node, + * and returns this force. + * + * The constant is internally wrapped into an z-coordinate accessor function. + * + * The z-accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes. + * The resulting number is then stored internally, such that the target z-coordinate of each node is only recomputed when the force is initialized or + * when this method is called with a new z, and not on every application of the force. + * + * @param z Constant z-coordinate to be used for all nodes. + */ + z(z: number): this; + /** + * Set the z-coordinate accessor to the specified function, re-evaluates the z-accessor for each node, + * and returns this force. + * + * The z-accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes. + * The resulting number is then stored internally, such that the target z-coordinate of each node is only recomputed when the force is initialized or + * when this method is called with a new z, and not on every application of the force. + * + * @param z A z-coordinate accessor function which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes. + * The function returns the z-coordinate. + */ + z(z: (d: NodeDatum, i: number, data: NodeDatum[]) => number): this; } + +/** + * Create a new positioning force along the z-axis towards the given position z which is defaulted to a constant 0 for all nodes. + * + * The z-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength. + * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position. + * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes. + * + * The generic refers to the type of data for a node. + */ +export function forceZ< + NodeDatum extends SimulationNodeDatum +>(): ForceZ; +/** + * Create a new positioning force along the z-axis towards the given position z which is constant for all nodes. + * + * The z-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength. + * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position. + * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes. + * + * The generic refers to the type of data for a node. + * + * @param z Constant z-coordinate to be used for all nodes. + */ +export function forceZ( + z: number +): ForceZ; +/** + * Create a new positioning force along the z-axis towards the position z given by evaluating the specified z-coordinate accessor + * for each node. + * + * The z-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength. + * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position. + * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes. + * + * The generic refers to the type of data for a node. + * + * @param z A z-coordinate accessor function which is invoked for each node in the simulation, being passed the node and its zero-based index. + * The function returns the z-coordinate. + */ +export function forceZ( + z: (d: NodeDatum, i: number, data: NodeDatum[]) => number +): ForceZ; diff --git a/yarn.lock b/yarn.lock index a90f75a..e27f4a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -155,6 +155,11 @@ estree-walker "^2.0.2" picomatch "^2.3.1" +"@types/d3-force@^3.0.9": + version "3.0.9" + resolved "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz#dd96ccefba4386fe4ff36b8e4ee4e120c21fcf29" + integrity sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA== + "@types/estree@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2"