import * as d3 from 'd3'; import { action } from 'mobx'; export interface ForceSimulationNode extends d3.SimulationNodeDatum { id: string; collisionRadius: number; update(): void; } interface ForceSimulationOptions { collideDistance: number; simulationSpeed: number; chargeStrength: number; onSimulationEnd?: () => void; } class ForceSimulation { private forceLink: d3.ForceLink>; private simulation: any; private options: ForceSimulationOptions; private destroyed = false; constructor(options?: Partial) { this.options = { ...{ collideDistance: 0, simulationSpeed: 10, chargeStrength: 0 }, ...options }; // Setup Force Simulation this.simulation = d3.forceSimulation(); this.simulation.force( 'collide', d3.forceCollide().radius((d) => d.collisionRadius + this.options.collideDistance) ); this.simulation.force('charge', d3.forceManyBody().strength(this.options.chargeStrength)); this.simulation.alpha(0); this.forceLink = d3.forceLink>().id((e) => e.id); this.simulation.force('link', this.forceLink); this.simulation.on( 'tick', action(() => { // speed up the simulation for (let i = 0; i < this.options.simulationSpeed; i++) { this.simulation.tick(); } this.simulation.nodes().forEach((d: ForceSimulationNode) => !this.destroyed && d.update()); }) ); if (options.onSimulationEnd) { this.simulation.on('end', this.options.onSimulationEnd); } } public destroy(): void { this.destroyed = true; this.simulation.stop(); } public useForceSimulation( nodes: ForceSimulationNode[], links: d3.SimulationLinkDatum[], distance: ( link: d3.SimulationLinkDatum, i: number, links: d3.SimulationLinkDatum[] ) => number ): void { this.forceLink.distance(distance); // first remove the links so that the layout doesn't error this.forceLink.links([]); this.simulation.nodes([...nodes]); this.forceLink.links([...links]); } public haltForceSimulation(): void { this.simulation.alpha(0); this.simulation.nodes([]); this.forceLink.links([]); } public forceCenter(cx: number, cy: number): void { this.simulation.force('center', d3.forceCenter(cx, cy)); } public stopSimulation(): void { this.simulation.stop(); } public restart() { this.simulation.restart(); } public alpha(value: number) { this.simulation.alpha(value); } public alphaTarget(value: number) { this.simulation.alphaTarget(value); } } export { ForceSimulation };