Convert d3.js bubbles into forced/gravity based layout

冷暖自知 提交于 2019-12-03 13:44:06

问题


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

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

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.


回答1:


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.



来源:https://stackoverflow.com/questions/17753964/convert-d3-js-bubbles-into-forced-gravity-based-layout

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!