|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
.node { |
|
stroke: #fff; |
|
stroke-width: 1.5px; |
|
fill: #315de0 |
|
} |
|
|
|
.link { |
|
fill: none; |
|
stroke: #bbb; |
|
stroke-width: 5px; |
|
} |
|
.walking.circle { |
|
fill: #c12105 |
|
} |
|
</style> |
|
<svg width="960" height="600"></svg> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
var svg = d3.select("svg"), |
|
width = +svg.attr("width"), |
|
height = +svg.attr("height"); |
|
|
|
var color = d3.scaleOrdinal(d3.schemeCategory20); |
|
|
|
var simulation = d3.forceSimulation() |
|
.force("link", d3.forceLink().distance(200).strength(0.5)) |
|
.force("charge", d3.forceManyBody()) |
|
.force("center", d3.forceCenter(width / 2, height / 2)); |
|
|
|
|
|
d3.json("miserables.json", function(error, graph) { |
|
if (error) throw error; |
|
|
|
var nodes = graph.nodes, |
|
nodeById = d3.map(nodes, function(d) { |
|
return d.id; |
|
}), |
|
links = graph.links, |
|
bilinks = []; |
|
|
|
links.forEach(function(link) { |
|
var s = link.source = nodeById.get(link.source), |
|
t = link.target = nodeById.get(link.target), |
|
i = {}; // intermediate node |
|
nodes.push(i); |
|
links.push({ |
|
source: s, |
|
target: i |
|
}, { |
|
source: i, |
|
target: t |
|
}); |
|
bilinks.push([s, i, t]); |
|
}); |
|
|
|
|
|
var link = svg.selectAll(".link") |
|
.data(bilinks) |
|
.enter().append("path") |
|
.attr("class", "link"); |
|
|
|
svg.append("circle") |
|
.attr("class", "walking circle") |
|
.attr("r", 15); |
|
|
|
var node = svg.selectAll(".node") |
|
.data(nodes.filter(function(d) { |
|
return d.id; |
|
})) |
|
.enter().append("circle") |
|
.attr("class", "node") |
|
.attr("r", 30) |
|
.attr("fill", function(d) { |
|
return color(d.group); |
|
}); |
|
|
|
simulation |
|
.nodes(nodes) |
|
.on("tick", ticked); |
|
|
|
simulation.force("link") |
|
.links(links); |
|
|
|
transition(); |
|
|
|
function ticked() { |
|
link.attr("d", positionLink); |
|
node.attr("transform", positionNode); |
|
} |
|
}); |
|
|
|
|
|
///////////////////////////////////////////////////////////////////////// |
|
|
|
function positionLink(d) { |
|
return "M" + d[0].x + "," + d[0].y + |
|
"S" + d[1].x + "," + d[1].y + |
|
" " + d[2].x + "," + d[2].y; |
|
} |
|
|
|
function positionNode(d) { |
|
return "translate(" + d.x + "," + d.y + ")"; |
|
} |
|
|
|
function dragstarted(d) { |
|
if (!d3.event.active) simulation.alphaTarget(0.1).restart(); |
|
d.fx = d.x, d.fy = d.y; |
|
} |
|
|
|
function dragged(d) { |
|
d.fx = d3.event.x, d.fy = d3.event.y; |
|
} |
|
|
|
function dragended(d) { |
|
if (!d3.event.active) simulation.alphaTarget(0); |
|
d.fx = null, d.fy = null; |
|
} |
|
|
|
|
|
function transition(node_index = 0) { |
|
var node_count = svg.selectAll('path').nodes().length |
|
if (node_index == node_count) { |
|
node_index = 0; |
|
} |
|
svg.selectAll('.walking.circle').transition() |
|
.duration(1000) |
|
.attr("opacity", 1) |
|
.attrTween("transform", translateAlong(svg.selectAll('path').nodes()[node_index])) |
|
.on("end", function() { |
|
transition(node_index + 1); |
|
}); |
|
} |
|
|
|
// Returns an attrTween for translating along the specified path element. |
|
function translateAlong(path) { |
|
var l = path.getTotalLength(); |
|
return function(d, i, a) { |
|
return function(t) { |
|
var p = path.getPointAtLength(t * l); |
|
return "translate(" + p.x + "," + p.y + ")"; |
|
}; |
|
}; |
|
} |
|
</script> |