fhs41666
fhs41666

Reputation: 21

How to drag nodes with children in d3 circle packing?

I have been trying to drag in d3 packed nodes using this code as a basis https://codepen.io/MrHen/pen/GZQOPW. Unfortunately I can't find a method to use so that when a node with children is dragged around it's (visible) children move with the parent. I use this function to drag circles around:

var drag = d3.behavior.drag()
.on("drag", function(d, i) {
  d.x += d3.event.dx;
  d.y += d3.event.dy;
  draw();
})

I the example above for instance I want to be able to drag the nodes on layer 1 (light blue) and when I do this their children change their position so they stay (visually) in the borders of their parents.

Thank you!

Upvotes: 2

Views: 485

Answers (1)

Mark
Mark

Reputation: 108512

First remove the pointer-events: none on the root and middle nodes. Then set yourself up a little recursion to walk a node's descendants and update their position:

  function recurOnChildren(d) {
    d.x += d3.event.dx;
    d.y += d3.event.dy;
    if (!d.children) return;
    d.children.forEach(c => {
      recurOnChildren(c);
    });
  }

And call from your drag handler:

  var drag = d3.behavior.drag()
    .on("drag", function(d, i) {
      recurOnChildren(d);
      draw();
    })

Running code:

var margin = 20,
  diameter = 960;

var color = d3.scale.linear()
  .domain([-1, 5])
  .range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
  .interpolate(d3.interpolateHcl);

var pack = d3.layout.pack()
  .padding(2)
  .size([diameter - margin, diameter - margin])
  .value(function(d) {
    return d.size;
  })

var svg = d3.select("body").append("svg")
  .attr("width", diameter)
  .attr("height", diameter)
  .append("g")
  .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");

d3.json("https://gist.githubusercontent.com/mbostock/7607535/raw/695f8ed6298c06a946406c707200a1f6b21875d8/flare.json", function(error, root) {
  if (error) throw error;

  var focus = root,
    nodes = pack.nodes(root),
    view;

  function recurOnChildren(d) {
    d.x += d3.event.dx;
    d.y += d3.event.dy;
    if (!d.children) return;
    d.children.forEach(c => {
      recurOnChildren(c);
    });
  }

  var drag = d3.behavior.drag()
    .on("drag", function(d, i) {
      recurOnChildren(d);
      draw();
    })

  var circle = svg.selectAll("circle")
    .data(nodes)
    .enter().append("circle")
    .attr("class", function(d) {
      return d.parent ? d.children ? "node node--middle" : "node node--leaf" : "node node--root";
    })
    .style("fill", function(d) {
      return d.children ? color(d.depth) : null;
    });

  var text = svg.selectAll("text")
    .data(nodes)
    .enter().append("text")
    .attr("class", "label")
    .style("fill-opacity", function(d) {
      return d.parent === root ? 1 : 0;
    })
    .style("display", function(d) {
      return d.parent === root ? "inline" : "none";
    })
    .text(function(d) {
      return d.name;
    });

  var node = svg.selectAll("circle,text");

  svg.selectAll(".node").call(drag);

  d3.select("body")
    .style("background", color(-1))

  draw();

  function draw() {
    var k = diameter / (root.r * 2 + margin);
    node.attr("transform", function(d) {
      return "translate(" + (d.x - root.x) * k + "," + (d.y - root.y) * k + ")";
    });
    circle.attr("r", function(d) {
      return d.r * k;
    });
  }
});
.node {
  cursor: pointer;
}

.node:hover {
  stroke: #000;
  stroke-width: 1.5px;
}

.node--leaf {
  fill: white;
}

.label {
  font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
  text-anchor: middle;
  text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}

.label {
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

Upvotes: 2

Related Questions