D3强制布局,其中较大的节点聚集在中心 [英] D3 Force Layout where larger nodes cluster in center
问题描述
我一直在修改将用于标签云的强制布局,每个标签由<circle>
表示,其半径与该标签的问题成比例.我的问题是确定如何使更受欢迎的标签趋向群集的中心,而使不那么受欢迎的标签聚集在标签云的边缘周围.到目前为止,我的代码如下:
I've been tinkering with a force layout that will be used for a tag cloud and each tag is represented by a <circle>
whose radius is proportional to the questions with that tag. My question is to determine how to make more popular tags trend toward the center of the cluster and less popular tags congregate around the edges of the tag cloud. So far my code looks like this:
function(err, results) {
var nodes = results.tags;
var width = 1020;
var height = 800;
var extent = d3.extent(nodes, function(tag) { return tag.questions.length; });
var min = extent[0] || 1;
var max = extent[1];
var padding = 2;
var radius = d3.scale.linear()
.clamp(true)
.domain(extent)
.range([15, max * 5 / min]);
// attempted to make gravity proportional?
var gravity = d3.scale.pow().exponent(5)
.domain(extent)
.range([0, 200]);
var minRadius = radius.range()[0];
var maxRadius = radius.range()[1];
var svg = d3.select('#question_force_layout').append('svg')
.attr('width', width)
.attr('height', height);
var node = svg.selectAll('circle')
.data(nodes)
.enter().append('circle')
.attr('class', 'tag')
.attr('r', function(tag) { return (tag.radius = radius(tag.questions.length)); });
var tagForce = d3.layout.force()
.nodes(results.tags)
.size([width, height])
.charge(200) // seemed like an okay effect
.gravity(function(tag) {
return gravity(tag.questions.length);
})
.on('tick', tagTick)
.start();
function tagTick(e) {
node
.each(collide(.5))
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });
}
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + padding;
var nx1 = d.x - r;
var nx2 = d.x + r;
var ny1 = d.y - r;
var 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;
var y = d.y - quad.point.y;
var l = Math.sqrt(x * x + y * y);
var r = d.radius + quad.point.radius + 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;
});
};
}
}
为了让您大致了解要处理的数据量,目前有52个问题和42个标签.同样,输出通常以如下形式结束:
To give you an idea of the amount of data being dealt with, there are 52 questions and 42 tags currently. Also the output usually ends up something like this:
我希望较大的节点位于中心.
I'd like the larger nodes to end up in the center.
推荐答案
另一种可能性是使节点具有质量,并在碰撞函数中将其考虑在内.然后,您可以打开重力,让它们为位置而战.
此示例经过一阵忙之后才到达.
Another possibility is to give the nodes something like mass and take it into account in the collision function. Then you can switch on gravity and let them fight for position.
This example gets there after a bit of a flurry.
这是修改后的碰撞函数...
Here is the modified collision function...
function Collide(nodes, padding) {
// Resolve collisions between nodes.
var maxRadius = d3.max(nodes, function(d) {return d.radius});
return function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + 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) {
var possible = !(x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1);
if (quad.point && (quad.point !== d) && possible) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + padding,
m = Math.pow(quad.point.radius, 3),
mq = Math.pow(d.radius, 3),
mT = m + mq;
if (l < r) {
//move the nodes away from each other along the radial (normal) vector
//taking relative mass into consideration, the sign is already established
//in calculating x and y and the nodes are modelled as spheres for calculating mass
l = (r - l) / l * alpha;
d.x += (x *= l) * m/mT;
d.y += (y *= l) * m/mT;
quad.point.x -= x * mq/mT;
quad.point.y -= y * mq/mT;
}
}
return !possible;
});
};
}
}
具有自排序节点的强制有向图- 位置交换
Force Directed Graph with self sorting nodes - Position swapping
- 加速退火
退火计算每个滴答完成,但直到alpha降到0.05以下,即仅每n个滴答更新一次viz(n当前为4).这样可以显着减少达到平衡所需的时间(大约2倍). - 强制动态
力动力学是α的函数,具有两个阶段.初始阶段具有零电荷,低重力和低阻尼.这是为了最大程度地混合和分类而设计的.第二阶段具有更高的重力,较大的负电荷和更高的阻尼,其目的是清理并稳定节点的外观. - 节点之间的冲突
基于此示例,但经过增强以根据尺寸对节点的径向位置进行排序,其中较大节点更靠近中心.每次碰撞都被用作校正相对位置的机会.如果它们不在适当位置,则将交换碰撞节点的径向坐标(在极坐标中).因此,分选效率取决于碰撞中的良好混合.为了最大化混合效果,所有节点都在图中心的同一点创建.交换节点时,将保留它们的速度.这也可以通过更改先前的点(p.px
和p.py
)来完成.假设节点是球体,则使用r 3 来计算质量,并根据相对质量"计算回弹.
- Accelerated annealing
The annealing calc is done every tick but, until alpha drops below 0.05, the viz is only updated every nth tick (n is currently 4). This delivers significant reductions in the time to reach equilibrium (roughly a factor of 2). - Force dynamics
The force dynamics are a function of alpha, with two phases. The initial phase has zero charge, low gravity and low damping. This is designed to maximise mixing and sorting. The second phase has a higher gravity and a large, negative charge and much higher damping, this is designed to clean up and stabilise the presentation of the nodes. - Collisions between nodes
Based on this example but enhanced to sort the radial position of the nodes based on size, with larger nodes closer to the center. Every collision is used as an opportunity to correct the relative positions. If they are out of position then the radial ordinates of the colliding nodes (in polar coordinates) are swapped. The sorting efficiency is therefore reliant on good mixing in the collisions. In order to maximise the mixing, the nodes are all created at the same point in the center of the graph. When the nodes are swapped, their velocities are preserved. This is done by also changing the previous points (p.px
andp.py
). The mass is calculated assuming the nodes are spheres, using r3, and the rebounds calculated according to relative "mass".
function Collide(nodes, padding) {
// Resolve collisions between nodes.
var maxRadius = d3.max(nodes, function(d) {
return d.q.radius
});
return function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function v(quad, x1, y1, x2, y2) {
var possible = !(x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1);
if(quad.point && (quad.point !== d) && possible) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + padding;
if(l < r) {
for(; Math.abs(l) == 0;) {
x = Math.round(Math.random() * r);
y = Math.round(Math.random() * r);
l = Math.sqrt(x * x + y * y);
}
;
//move the nodes away from each other along the radial (normal) vector
//taking relative size into consideration, the sign is already established
//in calculating x and y
l = (r - l) / l * alpha;
// if the nodes are in the wrong radial order for there size, swap radius ordinate
var rel = d.radius / quad.point.radius, bigger = (rel > 1),
rad = d.r / quad.point.r, farther = rad > 1;
if(bigger && farther || !bigger && !farther) {
var d_r = d.r;
d.r = quad.point.r;
quad.point.r = d_r;
d_r = d.pr;
d.pr = quad.point.pr;
quad.point.pr = d_r;
}
// move nodes apart but preserve their velocity
d.x += (x *= l);
d.y += (y *= l);
d.px += x;
d.py += y;
quad.point.x -= x;
quad.point.y -= y;
quad.point.px -= x;
quad.point.py -= y;
}
}
return !possible;
});
};
}
}
位置交换加动量: 位置交换加动量
这有点快,但看起来也更自然...
Position swapping plus momentum : Position swapping + momentum
This is a little bit faster but also more organic looking...
-
冲突排序事件
交换节点时,较大节点的速度得以保留,而较小节点则被加速.因此,由于较小的节点从碰撞点抛出,因此分类效率得以提高.假设节点是球体,则使用r 3 来计算质量,并根据相对质量"计算回弹.
Collisions sort events
When the nodes are swapped, the velocity of the bigger node is preserved while the smaller node is accelerated. Thus, the sorting efficiency is enhanced because the smaller nodes are flung out from the collision point. The mass is calculated assuming the nodes are spheres, using r3, and the rebounds calculated according to relative "mass".
function Collide(nodes, padding) {
// Resolve collisions between nodes.
var maxRadius = d3.max(nodes, function(d) {
return d.radius
});
return function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes), hit = false;
return function c(d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function v(quad, x1, y1, x2, y2) {
var possible = !(x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1);
if(quad.point && (quad.point !== d) && possible) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = (Math.sqrt(x * x + y * y)),
r = (d.radius + quad.point.radius + padding),
mq = Math.pow(quad.point.radius, 3),
m = Math.pow(d.radius, 3);
if(hit = (l < r)) {
for(; Math.abs(l) == 0;) {
x = Math.round(Math.random() * r);
y = Math.round(Math.random() * r);
l = Math.sqrt(x * x + y * y);
}
//move the nodes away from each other along the radial (normal) vector
//taking relative size into consideration, the sign is already established
//in calculating x and y
l = (r - l) / l * (1 + alpha);
// if the nodes are in the wrong radial order for there size, swap radius ordinate
var rel = m / mq, bigger = rel > 1,
rad = d.r / quad.point.r, farther = rad > 1;
if(bigger && farther || !bigger && !farther) {
var d_r = d.r;
d.r = quad.point.r;
quad.point.r = d_r;
d_r = d.pr;
d.pr = quad.point.pr;
quad.point.pr = d_r;
}
// move nodes apart but preserve the velocity of the biggest one
// and accelerate the smaller one
d.x += (x *= l);
d.y += (y *= l);
d.px += x * bigger || -alpha;
d.py += y * bigger || -alpha;
quad.point.x -= x;
quad.point.y -= y;
quad.point.px -= x * !bigger || -alpha;
quad.point.py -= y * !bigger || -alpha;
}
}
return !possible;
});
};
}
}
这篇关于D3强制布局,其中较大的节点聚集在中心的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!