-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
layout-d3.js
117 lines (109 loc) · 3.19 KB
/
layout-d3.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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
};