block by kenpenn 268878b410ddb1201277be3f0305d566

force simulation using translate

Full Screen

d3 v4 force-directed graph using translate to move nodes

adapted from Force-Directed Graph

index.html

<!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>