d3js dragging circle should rearrange other circles in main boundary circle
Asked Answered
S

1

10

I have bubble chart based on this tutorial.

I have enabled dragging of bubbles with following code. This makes individual circles draggable, but while dragging a circle other circles don't get auto adjusted. I am using pack circle algorithm, please let me know is that possible with this algorithm.

This is my code for dragging:

// draggable
if(this.dragging){
    var drag = d3.behavior.drag()
    .on("drag", function( d, i) {
        var selection = d3.selectAll( '.selected');

        if( selection[0].indexOf( this)==-1) {
            selection.classed( "selected", false);
            selection = d3.select( this);
            selection.classed( "selected", true);
        } 

        selection.attr("transform", function( d, i) {
            d.x += d3.event.dx;
            d.y += d3.event.dy;
            return "translate(" + [ d.x,d.y ] + ")"
        })
        // reappend dragged element as last 
        // so that its stays on top 
        this.parentNode.appendChild( this);
        d3.event.sourceEvent.stopPropagation();
    });
    node.call( drag);
}
Significative answered 8/8, 2014 at 3:55 Comment(12)
You would need to run the pack layout again on drag with the data for the node that you're dragging removed.Henryson
Lars thanks for the comment but I am new to d3 and dont no how I can rerun the layout with same data. can you suggest some example if possible. ThanksSignificative
This is done in this treemap for example when you switch between size and count.Henryson
@LarsKotthoff I am facing the same problem and I have reproduced it here: jsfiddle.net/rdesai/sqvov74j/2 As you suggested, I am running the pack layout again, but its resisting the drag of the bubble. What am I missing?Swain
Not sure what you're doing there in the drag handler, all you need to do is set the transform accordingly: jsfiddle.net/sqvov74j/3Henryson
@LarsKotthoff Yes, I have achieved dragging previously, but what I am looking for is that if I drag a bubble which is deep inside the cloud, the other bubbles should occupy the empty space created after dragging.Swain
As I've said, you need to rerun the pack layout without the data for that node. So you need to filter the data for the node out, then pass that to the pack layout and finally redraw everything.Henryson
@LarsKotthoff How do I filter the data? In the fiddle that I first shared (jsfiddle.net/rdesai/sqvov74j/2) I redrew everything (line # 446). To filter out the selected node I did removeChild() but it didnt help.Swain
Line 446 doesn't redraw anything, it just runs the pack layout again. To filter the data, you have to go through every element of your original data (the data, not the DOM).Henryson
@LarsKotthoff I have been trying it but no success yet. In my code: jsfiddle.net/rdesai/sqvov74j/7, d.className gives the text of the bubble that is being dragged, but I am not sure how to filter the data since there is no key className in the input data.Swain
You have access to the entire data of the bubble being dragged, so you can filter with that.Henryson
I think you will have to use the force layout to get the other circles to move while dragging. Here's an example based on your code: codepen.io/anon/pen/LqDznOvertone
A
1

Here's some code that might achieve the effect you're looking for. It's largely attributable to Mike Bostock's Bubble Chart and Collision Detection examples.

This uses D3's pack layout to initially position the nodes. Then a force layout is used to "auto adjust" the other circles when dragging a node.

Working example at bl.ocks.org

var width = 900;
var height = 500;

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

var nodes = d3.range(128).map(function () { return {radius: Math.random() * 16 + 8}; });
var nodesCopy = $.extend(true, [], nodes);

function dblclick(d) {
  d3.select(this).classed("fixed", d.fixed = false);
}

function dragstart(d) {
  d3.select(this).classed("fixed", d.fixed = true);
}

function collide(node) {
  var r = node.radius + 16;
  var nx1 = node.x - r;
  var nx2 = node.x + r;
  var ny1 = node.y - r;
  var ny2 = node.y + r;
  return function (quad, x1, y1, x2, y2) {
    if (quad.point && (quad.point !== node)) {
      var x = node.x - quad.point.x;
      var y = node.y - quad.point.y;
      var l = Math.sqrt(x * x + y * y);
      var npr = node.radius + quad.point.radius;
      if (l < npr) {
        l = (l - npr) / l * 0.5;
        x *= l;
        node.x -= x;
        y *= l;
        node.y -= y;
        quad.point.x += x;
        quad.point.y += y;
      }
    }
    return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
  };
}

function packup() {
  var pack = d3.layout.pack()
      .sort(null)
      .size([width, height])
      .padding(0)
      .value(function (d) { return d.radius; });

  svg.selectAll(".node")
      .data(pack.nodes({"children": nodes})
      .filter(function (d) { return !d.children; }))
    .enter().append("circle")
      .attr("r", function (d) { return d.radius; })
      .attr("cx", function (d) { return d.x; })
      .attr("cy", function (d) { return d.y; });
}

function forceup() {
  var force = d3.layout.force()
      .nodes(nodes)
      .gravity(0.05)
      .charge(0)
      .size([width, height])
      .start();

  var drag = force.drag().on("dragstart", dragstart);

  force.on("tick", function () {
    var q = d3.geom.quadtree(nodes);
    var i = 0;
    var n = nodes.length;

    while (++i < n) {
      q.visit(collide(nodes[i]));
    }

    svg.selectAll("circle")
        .attr("cx", function (d) { return d.x; })
        .attr("cy", function (d) { return d.y; });
  });

  d3.selectAll("circle")
    .on("dblclick", dblclick)
    .call(drag);
}

function reset() {
  svg.selectAll("*").remove();
  nodes = $.extend(true, [], nodesCopy);
  packup();
  forceup();
}

packup();
forceup();
Antipyrine answered 16/4, 2015 at 3:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.