I'm working on a tag visualization where tags transition between different force-directed layouts.
I had few issues figuring out how to transition from a bubble chart to a node chart, but I'm a bit stuck as to how to get the charts to transition into a word cloud. My difficulties largely stem from my inexperience at writing custom clustering/collision detection functions.
I declare the forces as globals and then stop and start them when the user clicks a button:
var force1 = d3.layout.force()
.size([width, height])
.charge(0)
.gravity(0.02)
.on("tick", ticka);
//layout for node chart
var force2 = d3.layout.force()
.size([width, height])
.charge(-50)
.gravity(0.005)
.linkDistance(120)
.on("tick", tickb);
//layout for bubble chart
var force3 = d3.layout.force()
.size([width, height])
.charge(0)
.gravity(0.02)
.on("tick", tickc);
Relevant node/link functions are added to the force when the function that draws the nodes is called (as data changes according to a slider value).
The code for creating node data is as follows:
nodes = splicedCounts.map(function(d, e) {
var choice;
var i = 0,
r = d[1],
d = { count: d[1],
sentiment: d[2]/d[1],
cluster: i,
radius: radScale(r),
name: d[0],
index: e,
x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
};
if (!clusters[i] || (r > clusters[i].radius))
clusters[i] = d;
return d;
});
In order to keep this question relatively brief, the code I use for drawing the bubble chart is derivative of this example: http://bl.ocks.org/mbostock/7881887 and the code for drawing the node chart are similarly generic (I am happy to provide this code if it would help to solve my issue).
This is where my issue comes in:
I found this nice example for collision detection between rectangles and incorporated it into my code. However, since I'm using SVG text and the font size changes on transition, I opted to estimate the text size/bounding box size based on text-length and radius.
The entire "tick" functions for the word chart are below.
function tickc(e) {
node = nodeGroup.selectAll(".node");
var nodeText = nodeGroup.selectAll(".node text");
node.each(cluster(5 * e.alpha * e.alpha));
var k = e.alpha;
nodeText.each(function(a, i) {
var compWidth = d3.select(this).attr("bWidth");
var compHeight = d3.select(this).attr("bHeight");
nodes.slice(i + 1).forEach(function(b) {
// console.log(a);
var lineWidthA = a["name"].length * a["radius"]/2.5;
var lineHeightA = a["radius"]/0.9;
var lineWidthB = b["name"].length * b["radius"]/2.5;
var lineHeightB = b["radius"]/0.9;
dx = (a.x - b.x)
dy = (a.y - b.y)
adx = Math.abs(dx)
ady = Math.abs(dy)
mdx = (1 + 0.07) * (lineWidthA + lineWidthB)/2
mdy = (1 + 0.07) * (lineHeightA + lineHeightB)/2
if (adx < mdx && ady < mdy) {
l = Math.sqrt(dx * dx + dy * dy)
lx = (adx - mdx) / l * k
ly = (ady - mdy) / l * k
// choose the direction with less overlap
if (lx > ly && ly > 0)
lx = 0;
else if (ly > lx && lx > 0)
ly = 0;
dx *= lx
dy *= ly
a.x -= dx
a.y -= dy
b.x += dx
b.y += dy
}
});
});
node.select("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
node.select("text")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
// Move d to be adjacent to the cluster node.
function cluster2(alpha) {
return function(d) {
var cluster = clusters[d.cluster];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = (d["name"].length * d["radius"]) + (cluster["name"].length * cluster["radius"]);
};
}
I was unsure of how to conclude the clustering function so as to move the nodes appropriately. I tried to adapt the standard cluster function, i.e.
// Move d to be adjacent to the cluster node.
function cluster(alpha) {
return function(d) {
var cluster = clusters[d.cluster];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + cluster.radius;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
cluster.x += x;
cluster.y += y;
}
};
}
to be more similar to the aforementioned rectangular cluster force layout but without luck (I'm afraid I no longer have copies of my exact attempts).
I'm afraid I can't attach images due to my lack of reputation but I can try to find a way to provide them if it would help. The overlap problem with the word cloud is minor (most words resolve into adjacent but not touching positions) but, if possible, I'd like it to resolve as perfectly as the bubble chart. I'm pretty sure that these issues arose from a.) the unfinished cluster function and b.) my hack at using text length and radius to estimate text size rather than proper bounding box coords, but I'm not sure exactly how to fix these things.
I'd recommend using the d3-cloud
package which should do a lot of what you need. If not, then at least it's a good starting point https://github.com/jasondavies/d3-cloud
The way it seems to work is by calculating a bounds for each word and then resolving collisions between those bounds. You can see that here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With