d3 v4 force-directed graph using translate to move nodes
adapted from Force-Directed Graph
| license: gpl-3.0 |
d3 v4 force-directed graph using translate to move nodes
adapted from Force-Directed Graph
| <!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> |