d3 v4 force-directed graph using translate to move nodes
adapted from Force-Directed Graph
forked from kenpenn's block: force simulation using translate
license: gpl-3.0 |
d3 v4 force-directed graph using translate to move nodes
adapted from Force-Directed Graph
forked from kenpenn's block: force simulation using translate
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>v4 fd test</title> | |
<style> | |
body { | |
font-family: sans-serif; | |
margin: 0; | |
min-height: 500px; | |
} | |
line { | |
stroke: goldenrod; | |
stroke-width: 1.5px; | |
} | |
circle.node { | |
cursor: pointer; | |
fill: #000; | |
stroke: none; | |
} | |
</style> | |
</head> | |
<body> | |
<svg></svg> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script> | |
var width = 960; | |
var height = 500; | |
var svg = d3.select('svg') | |
.attr('width', width) | |
.attr('height', height); | |
var randRange = function (min, max) { | |
return Math.round(min + Math.random() * (max - min)); | |
}; | |
var parts = { | |
bod: { ld : 10 }, | |
head: { ld : 50, r : 24 }, | |
arm: { ld : 40 }, | |
elbow: { ld : 50 }, | |
hand: { ld : 50, r : 12 }, | |
hips: { ld : 70 }, | |
leg: { ld : 10 }, | |
knee: { ld : 60 }, | |
foot: { ld : 100, r : 15 } | |
}; | |
var bod = { | |
nodes: [ | |
{ id: 'bod', part: 'bod', req: { x: 0, y: -125 }}, | |
{ id: 'hips', part: 'hips', req: { x: 0, y: -26 }}, | |
{ id: 'lLeg', part: 'leg', req: { x: -17, y: 3 }}, | |
{ id: 'lKnee', part: 'knee', req: { x: -46, y: 72 }}, | |
{ id: 'lFoot', part: 'foot', req: { x: -37, y: 180 }, outer: true }, | |
{ id: 'rLeg', part: 'leg', req: { x: 20, y: 7 }}, | |
{ id: 'rKnee', part: 'knee', req: { x: 46, y: 72 }}, | |
{ id: 'rFoot', part: 'foot', req: { x: 37, y: 180 }, outer: true }, | |
{ id: 'head', part: 'head', req: { x: 0, y: -180 }, outer: true }, | |
{ id: 'lArm', part: 'arm', req: { x: -45, y: -120 }}, | |
{ id: 'lElbow', part: 'elbow', req: { x: -70, y: -60 }}, | |
{ id: 'lHand', part: 'hand', req: { x: -45, y: 11 }, outer: true }, | |
{ id: 'rArm', part: 'arm', req: { x: 45, y: -120 }}, | |
{ id: 'rElbow', part: 'elbow', req: { x: 70, y: -60 }}, | |
{ id: 'rHand', part: 'hand', req: { x: 45, y: 11 }, outer: true } | |
], | |
links: [ | |
{ source: 'bod', target: 'hips'}, | |
{ source: 'bod', target: 'head'}, | |
{ source: 'bod', target: 'lArm'}, | |
{ source: 'lArm', target: 'lElbow'}, | |
{ source: 'lElbow', target: 'lHand'}, | |
{ source: 'bod', target: 'rArm'}, | |
{ source: 'rArm', target: 'rElbow'}, | |
{ source: 'rElbow', target: 'rHand'}, | |
{ source: 'hips', target: 'lLeg'}, | |
{ source: 'lLeg', target: 'lKnee'}, | |
{ source: 'lKnee', target: 'lFoot'}, | |
{ source: 'hips', target: 'rLeg'}, | |
{ source: 'rLeg', target: 'rKnee'}, | |
{ source: 'rKnee', target: 'rFoot'}, | |
] | |
}; | |
// set random x,y vals so you get that crazy flying together behavior like d3 v3 | |
bod.nodes.forEach(function(node) { | |
node.x = randRange(10, width - 10); | |
node.y = randRange(10, height - 10); | |
bod.links.forEach(function(link) { | |
if ( link.source == node.id ){ | |
link.x1 = node.x; | |
link.y1 = node.y; | |
} else if ( link.target == node.id ) { | |
link.x2 = node.x; | |
link.y2 = node.y; | |
} | |
}); | |
}); | |
var fdGrp = svg.append('g'); | |
var linkGrp = fdGrp.append('g') | |
.attr('class', 'links'); | |
var links = linkGrp | |
.selectAll('line.link') | |
.data(bod.links) | |
.enter() | |
.append('line') | |
.attr('class', function(d) { | |
return 'line src-' + d.source + ' trg-' + d.target; | |
}) | |
.attr('x1', function(d) { | |
return d.x1; | |
}) | |
.attr('y1', function(d) { | |
return d.y1; | |
}) | |
.attr('x2', function(d) { | |
return d.x2; | |
}) | |
.attr('y2', function(d) { | |
return d.y2; | |
}); | |
var nodeGrp = fdGrp.append('g') | |
.attr('class', 'nodes'); | |
var nodes = nodeGrp | |
.selectAll('circle.node') | |
.data(bod.nodes) | |
.enter() | |
.append('circle') | |
.attr('class', function(d) { | |
var outer = d.outer ? ' outer' : ''; | |
return 'node ' + d.id + outer; | |
}) | |
.attr('transform', function(d) { | |
return 'translate(' + d.x + ',' + d.y + ')'; | |
}) | |
.attr('r', function (d) { | |
return parts[d.part].r ? parts[d.part].r : 4; | |
}) | |
.call(d3.drag() | |
.on("start", dragstart) | |
.on("drag", dragging) | |
.on("end", dragend)); | |
var sim = d3.forceSimulation() | |
.force("link", d3.forceLink().id(function(d) { return d.id; })) | |
.force("charge", d3.forceManyBody()) | |
.force("center", d3.forceCenter( width / 2, height / 2 )); | |
sim | |
.nodes(bod.nodes) | |
.on("tick", function() { | |
nodes.attr("transform", function(d) { | |
return "translate(" + d.x + "," + d.y + ")"; | |
}); | |
links | |
.attr("x1", function(d) { return d.source.x; }) | |
.attr("y1", function(d) { return d.source.y; }) | |
.attr("x2", function(d) { return d.target.x; }) | |
.attr("y2", function(d) { return d.target.y; }); | |
}); | |
sim.force("link") | |
.links(bod.links) | |
.distance(function (d) { | |
return parts[d.target.part].ld; | |
}); | |
function dragstart(d) { | |
if (!d3.event.active) { sim.alphaTarget(0.3).restart(); } | |
d.fx = d.x; | |
d.fy = d.y; | |
} | |
function dragging(d) { | |
d.fx = d3.event.x; | |
d.fy = d3.event.y; | |
} | |
function dragend(d) { | |
if (!d3.event.active) sim.alphaTarget(0); | |
if (!d.outer) { | |
d.fx = null; | |
d.fy = null; | |
} | |
} | |
</script> | |
</body> | |
</html> |