Skip to content

Commit c3b5d1d

Browse files
authored
Force directed graph demo (#341)
1 parent 6d00394 commit c3b5d1d

14 files changed

+642
-0
lines changed

docs/docs-app/constants/pages.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ComplexChartExample from '../../../examples/complex-chart/complex-chart-example';
22
import Candlestick from '../../../examples/candlestick/candlestick-example';
33
import StreamgraphExample from '../../../examples/streamgraph/streamgraph-example';
4+
import ForceDirectedGraph from '../../../examples/force-directed-graph/force-directed-example';
45
import ShowcaseApp from '../../../showcase/showcase-app';
56

67
const generatePath = tree => {
@@ -38,6 +39,12 @@ export const examplePages = generatePath([
3839
pageType: 'example',
3940
component: Candlestick
4041
}
42+
}, {
43+
name: 'Force Directed Graph',
44+
content: {
45+
pageType: 'example',
46+
component: ForceDirectedGraph
47+
}
4148
}, {
4249
name: 'Complex Chart',
4350
content: {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
react-vis complex-chart example
2+
=================
3+
4+
## Setup
5+
```bash
6+
git clone https://github.com/uber/react-vis
7+
cd react-vis/examples/
8+
npm install
9+
cd complex-chart
10+
npm start
11+
```

examples/force-directed-graph/app.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) 2016 - 2017 Uber Technologies, Inc.
2+
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
import ReactDOM from 'react-dom';
22+
import React, {Component} from 'react';
23+
import window from 'global/window';
24+
import document from 'global/document';
25+
26+
import ForceDirectedExample from './force-directed-example';
27+
import '../../src/styles/examples.scss';
28+
29+
export default class App extends Component {
30+
componentWillMount() {
31+
window.addEventListener(
32+
'resize',
33+
() => this.setState({width: window.innerWidth})
34+
);
35+
}
36+
37+
render() {
38+
return (
39+
<article>
40+
<h1>Force Directed Example</h1>
41+
<section>
42+
<ForceDirectedExample />
43+
</section>
44+
</article>
45+
);
46+
}
47+
}
48+
49+
ReactDOM.render(<App />, document.querySelector('#index'));
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) 2016 - 2017 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
import React from 'react';
22+
import LesMisData from './les-mis-data.json';
23+
24+
import './force-directed.scss';
25+
import ForceDirectedGraph from './force-directed-graph';
26+
27+
export default class Example extends React.Component {
28+
state = {
29+
strength: Math.random() * 60 - 30
30+
}
31+
32+
render() {
33+
const {strength} = this.state;
34+
return (
35+
<div className="force-directed-example">
36+
<button onClick={() => this.setState({strength: Math.random() * 60 - 30})}> REWEIGHT </button>
37+
<ForceDirectedGraph data={LesMisData} height={500} width={500} animation strength={strength}/>
38+
</div>
39+
);
40+
}
41+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright (c) 2016 - 2017 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
import React, {PropTypes} from 'react';
22+
import {
23+
forceSimulation,
24+
forceLink,
25+
forceManyBody,
26+
forceCenter
27+
} from 'd3-force';
28+
29+
import {
30+
XYPlot,
31+
MarkSeries,
32+
LineSeries
33+
} from 'react-vis';
34+
35+
const colors = [
36+
'#19CDD7',
37+
'#DDB27C',
38+
'#88572C',
39+
'#FF991F',
40+
'#F15C17',
41+
'#223F9A',
42+
'#DA70BF',
43+
'#4DC19C',
44+
'#12939A',
45+
'#B7885E',
46+
'#FFCB99',
47+
'#F89570',
48+
'#E79FD5',
49+
'#89DAC1'
50+
];
51+
52+
/**
53+
* Create the list of nodes to render.
54+
* @returns {Array} Array of nodes.
55+
* @private
56+
*/
57+
function generateSimulation(props) {
58+
const {data, height, width, maxSteps, strength} = props;
59+
if (!data) {
60+
return {nodes: [], links: []};
61+
}
62+
// copy the data
63+
const nodes = data.nodes.map(d => ({...d}));
64+
const links = data.links.map(d => ({...d}));
65+
// build the simuatation
66+
const simulation = forceSimulation(nodes)
67+
.force('link', forceLink().id(d => d.id))
68+
.force('charge', forceManyBody().strength(strength))
69+
.force('center', forceCenter(width / 2, height / 2))
70+
.stop();
71+
72+
simulation.force('link').links(links);
73+
74+
const upperBound = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay()));
75+
for (let i = 0; i < Math.min(maxSteps, upperBound); ++i) {
76+
simulation.tick();
77+
}
78+
79+
return {nodes, links};
80+
}
81+
82+
class ForceDirectedGraph extends React.Component {
83+
84+
static get propTypes() {
85+
return {
86+
// animation: AnimationPropType,
87+
className: PropTypes.string,
88+
data: PropTypes.object.isRequired,
89+
height: PropTypes.number.isRequired,
90+
width: PropTypes.number.isRequired,
91+
steps: PropTypes.number
92+
};
93+
}
94+
95+
static get defaultProps() {
96+
return {
97+
className: '',
98+
data: {nodes: [], links: []},
99+
maxSteps: 50
100+
};
101+
}
102+
103+
constructor(props) {
104+
super(props);
105+
this.state = {
106+
data: generateSimulation(props)
107+
};
108+
}
109+
110+
componentWillReceiveProps(nextProps) {
111+
this.setState({
112+
data: generateSimulation(nextProps)
113+
});
114+
}
115+
116+
render() {
117+
const {className, height, width, animation} = this.props;
118+
const {data} = this.state;
119+
const {nodes, links} = data;
120+
return (
121+
<XYPlot width={width} height={height} className={className}>
122+
{links.map(({source, target}, index) => {
123+
return (
124+
<LineSeries
125+
animation={animation}
126+
color={'#B3AD9E'}
127+
key={`link-${index}`}
128+
opacity={0.3}
129+
data={[{...source, color: null}, {...target, color: null}]}
130+
/>
131+
);
132+
})}
133+
<MarkSeries
134+
data={nodes}
135+
animation={animation}
136+
colorType={'category'}
137+
stroke={'#ddd'}
138+
strokeWidth={2}
139+
colorRange={colors}
140+
/>
141+
</XYPlot>
142+
);
143+
}
144+
145+
}
146+
147+
ForceDirectedGraph.displayName = 'ForceDirectedGraph';
148+
149+
export default ForceDirectedGraph;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.candlestick-example {
2+
width: 100%;
3+
4+
.chart {
5+
width: 100%;
6+
}
7+
8+
.dashed-example-line {
9+
stroke-dasharray: 2, 2;
10+
}
11+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../index.html

0 commit comments

Comments
 (0)