Convert d3.js bubbles into forced/gravity based layout
Asked Answered
R

1

9

I have a set of data that I am visualizing using d3.js. I am representing data points in the form of bubbles, where the configuration for bubbles is as follows:

var dot = svg.selectAll("g")
            .data(data)
            .enter()
            .append("g");

dot.append("circle")
  .attr("class", "dot")
  .attr("cx", function(d) { return xp(x(d)); })
  .attr("cy", function(d) { return yp(y(d)); })
  .style("fill", function(d) { return colorp(color(d)); })
  .attr("r", function(d) { return radiusp(radius(d)*2000000); });

dot.append("text")
   .attr("x", function(d) { return xp(x(d)); })
   .attr("y", function(d) { return yp(y(d)); })
   .text(function(d) { return d.name; })

Where xp, yp, colorp and radiusp are defined as follows:

var xp = d3.scale.log().domain([300, 1e5]).range([0, width]),
   yp = d3.scale.linear().domain([10, 85]).range([height, 0]),
   radiusp = d3.scale.sqrt().domain([0, 5e8]).range([0, 40]),
   colorp = d3.scale.category10();

At this point, the bubbles are being displayed as static on their positions (where position is defined by xp and yp), while the size of the bubble is basically coming from radiusp and color is defined by colorp.

Right now I am showing them exactly as this example: http://bl.ocks.org/mbostock/4063269

enter image description here

What I need is to display them in this form: http://jsfiddle.net/andycooper/PcjUR/1/

enter image description here

That is: They should be packed using gravity function, have some charge, can be dragged and repel each other to some extent. I can see that there is a way through d3.layout.force() but not really able to integrate that into this.. I will be really thankful if you can suggest me the right path or some working example or even a hint. Thank you.

Ranaerancagua answered 19/7, 2013 at 19:23 Comment(4)
What is the problem you are facing while integrating to d3.layout.force()Mideast
The problem is that d3.layout.force() defines cx and cy coordinates automatically and I am not able to exactly figure out how to approach and implement it. Even if I remove xp and yp variables that force the positioning of bubbles, I am not able to achieve it. On the first hand, I tried implementing the forced layout for these bubbles but was unsuccessful in that. :-/Ranaerancagua
There are many many examples for force layouts with D3. You are correct in removing the explicit setting of the coordinates, but you need to elaborate on how you were unsuccessful on achieving what you want before we can really help you.Hawkbill
@Ranaerancagua It would be especially helpful if you could provide a jsfiddle or similar example of your code. As Lars mentioned, it's hard to help you without knowing more specifics of your situation.Prim
S
2

I think you were almost there but the specification of your dot variable is not the best one. I would transform it like this:

    var dot = svg.selectAll(".dot")
        .data(data)
        .enter()

Afterwards, once the circles have been plotted, what you do is that you create a force layout, instantiate it with the nodes you just created, add a on("tick") method, and then start the layout. An example is the following:

var force = d3.layout.force().nodes(data).size([width, height])
            .gravity(0)
            .charge(0)
            .on("tick", function(e){
                dot
                .each(gravity(.2 * e.alpha))
                    .each(collide(.5))
                    .attr("cx", function (d) {return d.x;})
                    .attr("cy", function (d) {return d.y;});
            })
            .start();

To have a complete answer, I will add also the gravity and collide methods from your fiddle (with adjusted variable names)

    function gravity(alpha) {
        return function (d) {
            d.y += (d.cy - d.y) * alpha;
            d.x += (d.cx - d.x) * alpha;
        };
    }   

    function collide(alpha) {
        var padding = 6
        var quadtree = d3.geom.quadtree(dot);
        return function (d) {
            var r = d.r + radiusp.domain()[1] + padding,
                nx1 = d.x - r,
                nx2 = d.x + r,
                ny1 = d.y - r,
                ny2 = d.y + r;
            quadtree.visit(function (quad, x1, y1, x2, y2) {
                if (quad.point && (quad.point !== d)) {
                    var x = d.x - quad.point.x,
                        y = d.y - quad.point.y,
                        l = Math.sqrt(x * x + y * y),
                        r = d.r + quad.point.r + (d.color !== quad.point.color) * padding;
                    if (l < r) {
                        l = (l - r) / l * alpha;
                        d.x -= x *= l;
                        d.y -= y *= l;
                        quad.point.x += x;
                        quad.point.y += y;
                    }
                }
                return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
            });
        };
    }

I think the problem you had was that perhaps you were applying the force layout to the g element of each of the circles, which unfortunately was not working. I hope this will give you an idea how to proceed. Your last line of the dot declaration was adding a g element for each circle, which was a little difficult to handle.

Thanks.

PS I assume that the x, y, and r attributes of your data contain the x,y, and radius.

Succotash answered 2/1, 2015 at 13:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.