Skip to content

Instantly share code, notes, and snippets.

@sxywu
Last active April 28, 2018 16:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sxywu/7785064979f1e03865625b083741bf69 to your computer and use it in GitHub Desktop.
Save sxywu/7785064979f1e03865625b083741bf69 to your computer and use it in GitHub Desktop.
Updated React+D3, Approach #1
license: mit
function randomData(nodes, width, height) {
var oldNodes = nodes;
// generate some data randomly
nodes = _.chain(_.range(_.random(10, 20)))
.map(() => {
return {
key: _.random(30),
size: _.random(8, 16),
};
}).uniqBy('key').value();
if (oldNodes) {
var end = _.random(oldNodes.length);
var start = _.random(end);
var add = _.slice(oldNodes, start, end + 1);
nodes = _.chain(nodes)
.union(add).uniqBy('key').value();
}
var nodeKeys = _.map(nodes, 'key');
links = _.chain(_.range(_.random(15, 25)))
.map(function() {
var source = nodeKeys[_.random(nodes.length - 1)];
var target = nodeKeys[_.random(nodes.length - 1)];
if (source === target) return;
return {
source,
target,
key: source + ',' + target,
size: _.random(2, 4)
};
}).filter().uniqBy('key').value();
maintainNodePositions(oldNodes, nodes, width, height);
return {nodes, links};
}
function maintainNodePositions(oldNodes, nodes, width, height) {
var kv = {};
_.each(oldNodes, function(d) {
kv[d.key] = d;
});
_.each(nodes, function(d) {
if (kv[d.key]) {
// if the node already exists, maintain current position
d.x = kv[d.key].x;
d.y = kv[d.key].y;
} else {
// else assign it a random position near the center
d.x = width / 2 + _.random(-25, 25);
d.y = height / 2 + _.random(-25, 25);
}
});
}
<meta charset='utf-8'>
<head>
<script src="https://unpkg.com/react@latest/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script src='https://unpkg.com/d3@4.10.0'></script>
<script src='https://unpkg.com/lodash@4.17.4'></script>
<script src='generateData.js'></script>
<style>
svg {
width: 400px;
height: 300px;
}
#root {
width: 400px;
text-align: center;
color: #333;
}
.update {
padding: 5px 10px;
margin: 10px;
cursor: pointer;
border: 1px solid #333;
display: inline-block;
}
.node {
fill: #51aae8;
stroke: #fff;
cursor: pointer;
}
.link {
stroke: #51aae8;
}
</style>
</head>
<body>
<div id='root' />
<script type="text/babel">
var width = 400;
var height = 300;
var simulation = d3.forceSimulation()
.force('collide', d3.forceCollide(d => 2 * d.size))
.force('charge', d3.forceManyBody(-100))
.force('center', d3.forceCenter(width / 2, height / 2))
.stop();
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {selected: null};
}
componentWillMount() {
this.calculateData(this.props);
}
componentWillReceiveProps(nextProps) {
this.calculateData(nextProps);
}
calculateData(props) {
var {nodes, links} = props;
// set up force simulation to calculate node+link positions
simulation.nodes(nodes)
.force('link', d3.forceLink(links).id(d => d.key).distance(100));
// let force simulation run 2000 times
_.times(2000, () => simulation.tick());
}
selectNode(selected) {
if (selected === this.state.selected) {
this.setState({selected: null});
} else {
this.setState({selected: selected});
}
}
render() {
// if a node has been selected, calculate the link+nodes it's connected to
var highlightedNodes = {};
var highlightedLinks = {};
if (this.state.selected) {
highlightedNodes[this.state.selected.key] = 1;
_.each(this.props.links, link => {
if (link.source.key === this.state.selected.key) {
highlightedNodes[link.target.key] = 1;
highlightedLinks[link.key] = 1;
}
if (link.target.key === this.state.selected.key) {
highlightedNodes[link.source.key] = 1;
highlightedLinks[link.key] = 1;
}
});
}
var links = _.map(this.props.links, link => {
var opacity = !this.state.selected || highlightedLinks[link.key] ? 0.5 : 0.1;
return (
<line className='link' key={link.key} opacity={opacity} strokeWidth={link.size}
x1={link.source.x} x2={link.target.x} y1={link.source.y} y2={link.target.y} />
);
});
var nodes = _.map(this.props.nodes, node => {
var opacity = !this.state.selected || highlightedNodes[node.key] ? 1 : 0.2;
return (<circle key={node.key} className='node' opacity={opacity}
cx={node.x} cy={node.y} r={node.size} onClick={() => this.selectNode(node)} />);
});
return (
<svg>
{links}
{nodes}
</svg>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.updateData = this.updateData.bind(this);
this.state = {nodes: [], links: []};
}
componentWillMount() {
this.updateData();
}
updateData() {
var newData = randomData(this.state.nodes, width, height);
this.setState(newData);
}
render() {
return (
<div>
<Graph {...this.state} />
<div className="update" onClick={this.updateData}>update</div>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>
</body>
@worldsayshi
Copy link

worldsayshi commented Apr 28, 2018

Seems your react and react-dom imports got broken. Looks like the compiled source files has moved. This worked for me:

  <script src="https://unpkg.com/react@16.2.0/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@16.0.0/umd/react-dom.production.min.js"></script>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment