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"