showcases/graph/graph-layer/layout/layout-d3.js (90 lines of code) (raw):

import {forceCenter, forceLink, forceManyBody, forceSimulation} from 'd3-force'; /** * LayoutD3 calculates a force-directed network graph using D3. * It accepts a list of nodes and a list of links, * and returns {x,y} node locations and alpha (simulation "heat") on update(). * It provides the graph data management and layout logic * using [d3-force](https://github.com/d3/d3-force). */ export default class LayoutD3 { constructor(props) { this.props = Object.assign({}, LayoutD3.defaultProps, props); } /** * @param {data} Data formatted as {nodes: [], links: []} * @param {layoutProps} Any props relevant to this layout update */ update(data, layoutProps) { const {alphaOnDataChange, alphaOnDrag} = this.props; if (this._simulation && data) { // If new data are passed, update the simulation with the new data this._simulation .nodes(data.nodes) .force( 'link', forceLink(data.links) .id(n => n.id) .strength(this.props.linkStrength) .distance(this.props.linkDistance) ) .alpha(alphaOnDataChange); } else if (!this._simulation) { if (data) { // Instantiate the simulation with the passed data const {nBodyStrength, nBodyDistanceMin, nBodyDistanceMax} = this.props; this._simulation = forceSimulation(data.nodes) .force( 'link', forceLink(data.links) .id(n => n.id) .strength(this.props.linkStrength) .distance(this.props.linkDistance) ) .force( 'charge', forceManyBody() .strength(nBodyStrength) .distanceMin(nBodyDistanceMin) .distanceMax(nBodyDistanceMax) ) .force('center', forceCenter()) .stop(); } else { // No data passed and simulation has not yet been instantiated, // so return empty object. return { nodes: [], isUpdating: false }; } } const {fixedNodes, unfixedNodes} = layoutProps; if (fixedNodes) { fixedNodes.forEach(n => { n.node.fx = n.x; n.node.fy = n.y; }); this._reheat(alphaOnDrag); } if (unfixedNodes) { unfixedNodes.forEach(n => { n.node.fx = null; n.node.fy = null; }); } // Process one simulation tick and return results. this._simulation.tick(); return { nodes: this._simulation.nodes(), isUpdating: this.isUpdating() }; } isUpdating() { return this._simulation && this._simulation.alpha() > this._simulation.alphaMin(); } dispose() { if (this._simulation) { this._simulation.stop(); this._simulation.on('tick', null); this._simulation = null; } } /** * "Reheat" the simulation on interaction. */ _reheat(alpha = 0.01, decay = 0.0228) { if (this._simulation.alpha() < alpha) { this._simulation.alpha(alpha).alphaDecay(decay); } } } LayoutD3.defaultProps = { alphaOnDataChange: 0.25, alphaOnDrag: 0.1, linkDistance: 200, linkStrength: 0.5, nBodyStrength: -60, nBodyDistanceMin: 1, nBodyDistanceMax: 100 };