|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<head> |
|
<script src="https://fb.me/react-0.14.3.js"></script> |
|
<script src="https://fb.me/react-dom-0.14.3.js"></script> |
|
<script src="https://npmcdn.com/[email protected]/browser.min.js"></script> |
|
<script src="http://d3js.org/d3.v3.min.js"></script> |
|
<script src="http://underscorejs.org/underscore-min.js"></script> |
|
<script src="generate_data.js"></script> |
|
|
|
<link rel="stylesheet" href="example.css" type="text/css" /> |
|
</head> |
|
|
|
<body> |
|
|
|
<div id="main" /> |
|
|
|
<script type="text/babel"> |
|
|
|
var width = 960; |
|
var height = 500; |
|
var force = d3.layout.force() |
|
.charge(-300) |
|
.linkDistance(50) |
|
.size([width, height]); |
|
|
|
// ***************************************************** |
|
// ** d3 functions to manipulate attributes |
|
// ***************************************************** |
|
|
|
var enterNode = (selection) => { |
|
selection.classed('node', true); |
|
|
|
selection.append('circle') |
|
.attr("r", (d) => d.size) |
|
.call(force.drag); |
|
|
|
selection.append('text') |
|
.attr("x", (d) => d.size + 5) |
|
.attr("dy", ".35em") |
|
.text((d) => d.key); |
|
}; |
|
|
|
var updateNode = (selection) => { |
|
selection.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")"); |
|
}; |
|
|
|
var enterLink = (selection) => { |
|
selection.classed('link', true) |
|
.attr("stroke-width", (d) => d.size); |
|
}; |
|
|
|
var updateLink = (selection) => { |
|
selection.attr("x1", (d) => d.source.x) |
|
.attr("y1", (d) => d.source.y) |
|
.attr("x2", (d) => d.target.x) |
|
.attr("y2", (d) => d.target.y); |
|
}; |
|
|
|
var updateGraph = (selection) => { |
|
selection.selectAll('.node') |
|
.call(updateNode); |
|
selection.selectAll('.link') |
|
.call(updateLink); |
|
}; |
|
|
|
// ***************************************************** |
|
// ** Graph and App components |
|
// ***************************************************** |
|
|
|
var Graph = React.createClass({ |
|
componentDidMount() { |
|
this.d3Graph = d3.select(ReactDOM.findDOMNode(this.refs.graph)); |
|
force.on('tick', () => { |
|
// after force calculation starts, call updateGraph |
|
// which uses d3 to manipulate the attributes, |
|
// and React doesn't have to go through lifecycle on each tick |
|
this.d3Graph.call(updateGraph); |
|
}); |
|
}, |
|
|
|
shouldComponentUpdate(nextProps) { |
|
this.d3Graph = d3.select(ReactDOM.findDOMNode(this.refs.graph)); |
|
|
|
var d3Nodes = this.d3Graph.selectAll('.node') |
|
.data(nextProps.nodes, (node) => node.key); |
|
d3Nodes.enter().append('g').call(enterNode); |
|
d3Nodes.exit().remove(); |
|
d3Nodes.call(updateNode); |
|
|
|
var d3Links = this.d3Graph.selectAll('.link') |
|
.data(nextProps.links, (link) => link.key); |
|
d3Links.enter().insert('line', '.node').call(enterLink); |
|
d3Links.exit().remove(); |
|
d3Links.call(updateLink); |
|
|
|
// we should actually clone the nodes and links |
|
// since we're not supposed to directly mutate |
|
// props passed in from parent, and d3's force function |
|
// mutates the nodes and links array directly |
|
// we're bypassing that here for sake of brevity in example |
|
force.nodes(nextProps.nodes).links(nextProps.links); |
|
force.start(); |
|
|
|
return false; |
|
}, |
|
|
|
render() { |
|
return ( |
|
<svg width={width} height={height}> |
|
<g ref='graph' /> |
|
</svg> |
|
); |
|
} |
|
}); |
|
|
|
var App = React.createClass({ |
|
getInitialState() { |
|
return { |
|
nodes: [], |
|
links: [], |
|
}; |
|
}, |
|
|
|
componentDidMount() { |
|
this.updateData(); |
|
}, |
|
|
|
updateData() { |
|
// randomData is loaded in from external file generate_data.js |
|
// and returns an object with nodes and links |
|
var newState = randomData(this.state.nodes, width, height); |
|
this.setState(newState); |
|
}, |
|
|
|
render() { |
|
return ( |
|
<div> |
|
<div className="update" onClick={this.updateData}>update</div> |
|
<Graph nodes={this.state.nodes} links={this.state.links} /> |
|
</div> |
|
); |
|
}, |
|
}); |
|
|
|
ReactDOM.render( |
|
<App />, |
|
document.getElementById('main') |
|
); |
|
|
|
</script> |
|
|
|
</body> |
This is a really nice example. I have a few question:
How would this be in version4?
Also, componentDidUpdate is triggered every time when other props (in addition to nodes and links) are changed. How should one avoid d3 re-rendering? [EDIT: solved by checking if props are different at componentDidUpdate]
are you cloning the array with slice()?