问题
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