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
};