javascript/d3 中具有强制导向布局的蜂群图
还有蜂群图的R包。接下来的两张图片说明了该软件包提供的一些可能性:
但是,我现在尝试使用 d3.js。
我的计划是让自定义重力将点拉向垂直线及其适当的 y 值,并且碰撞检测使点彼此远离。
我有一个半工作原型:
不幸的是,我找不到解决两个问题的方法——我怀疑这实际上是同一个问题:
我的观点不断重叠,至少有一个一点。
积分累积后,会进行持续的“洗牌” 布局的中心,作为防撞力和“来” 到中心”的力量战斗。
我希望这些点能够很快就他们应该居住的地方达成一致,并且最终不会重叠。
我正在使用的部队代码(如果你想在这里看到它而不是在 bl.ocks.org 上)是:
force.on("tick", function(e) {
var q,
node,
i = 0,
n = nodes.length;
var q = d3.geom.quadtree(nodes);
while (++i < n) {
node = nodes[i];
q.visit(collide(node));
xerr = node.x - node.true_x;
yerr = node.y - node.true_y;
node.x -= xerr*0.005;
node.y -= yerr*0.9;
}
svg.selectAll("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
function collide(node) {
var r = node.radius,
nx1,
nx2,
ny1,
ny2,
xerr,
yerr;
nx1 = node.x - r;
nx2 = node.x + r;
ny1 = node.y - r;
ny2 = node.y + r;
return function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== node)) {
var x = node.x - quad.point.x,
y = node.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = node.radius + quad.point.radius;
if (l < r) {
// we're colliding.
var xnudge, ynudge, nudge_factor;
nudge_factor = (l - r) / l * .4;
xnudge = x*nudge_factor;
ynudge = y*nudge_factor;
node.x -= xnudge;
node.y -= ynudge;
quad.point.x += xnudge;
quad.point.y += ynudge;
}
}
return x1 > nx2
|| x2 < nx1
|| y1 > ny2
|| y2 < ny1;
};
}
这是我第一次尝试强制导向布局,所以如果这是新手,我深表歉意......
Here is an article on creating beeswarm plots in R.
There is also R package for beeswarm plot. Next two pictures illustrate some of possibilities that that package offers:
However, I'm now trying to make it using the force-directed layout of d3.js.
My plan is to have custom gravity pull the points towards a vertical line and their proper y value, and collision detection keep the points off each other.
I've got a semi-working prototype:
Unfortunately, I can't find a way around two problems -- which I suspect are really the same problem:
My points keep overlapping, at least a bit.
There's an ongoing "shuffle" after the points have piled up in
the center of the layout, as the anti-collision forces and the "come
to the center" forces fight.
I'd like the points to pretty quickly come to an agreement about where they should live, and wind up not overlapping.
The force code I'm using (in case you want to see it here and not on bl.ocks.org) is:
force.on("tick", function(e) {
var q,
node,
i = 0,
n = nodes.length;
var q = d3.geom.quadtree(nodes);
while (++i < n) {
node = nodes[i];
q.visit(collide(node));
xerr = node.x - node.true_x;
yerr = node.y - node.true_y;
node.x -= xerr*0.005;
node.y -= yerr*0.9;
}
svg.selectAll("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
function collide(node) {
var r = node.radius,
nx1,
nx2,
ny1,
ny2,
xerr,
yerr;
nx1 = node.x - r;
nx2 = node.x + r;
ny1 = node.y - r;
ny2 = node.y + r;
return function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== node)) {
var x = node.x - quad.point.x,
y = node.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = node.radius + quad.point.radius;
if (l < r) {
// we're colliding.
var xnudge, ynudge, nudge_factor;
nudge_factor = (l - r) / l * .4;
xnudge = x*nudge_factor;
ynudge = y*nudge_factor;
node.x -= xnudge;
node.y -= ynudge;
quad.point.x += xnudge;
quad.point.y += ynudge;
}
}
return x1 > nx2
|| x2 < nx1
|| y1 > ny2
|| y2 < ny1;
};
}
This is my first foray into force-directed layouts, so apologies if this is noobish...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
你的结果对我来说看起来相当不错。但是,如果您想减少重叠的可能性,可以尝试一些方法。
在节点之间添加一些填充。特别是,您的圆圈有一个笔划,并且该笔划的一半将延伸到圆的半径之外。因此,如果您想避免笔画重叠,则在通过将两个半径相加来计算
r
时,您将需要至少一个像素的填充。 (这假设每个圆上有一个像素描边,这会为每个半径添加 0.5 像素。)计算 nudge_factor 时使用 0.5 而不是 0.4。通过将任何重叠的圆推得足够远,使它们不再重叠,从而使重叠分辨率更强。如果您使用小于 0.4 的值,则解会更稳定一点,但收敛速度会更慢,因为即使在微移之后,圆仍然会重叠一点。
每个周期多次运行碰撞解决。您当前正在运行碰撞分辨率,然后应用自定义重力(朝向 true_x 和 true_y)。如果每个刻度多次运行碰撞分辨率,它会使碰撞分辨率相对于重力更强。
另外,如果您只想要静态布局,您还可以考虑让强制布局运行固定次数的迭代(或稳定),然后在最后渲染一次,而不是迭代渲染。这使得布局速度更快,但如果运行太多迭代,可能会导致渲染暂时出现问题。
Your results look pretty good to me. But, if you want to reduce the likelihood of overlap, there are a few things to try.
Add some padding between nodes. In particular, your circles have a stroke, and half of this stroke will extend beyond the radius of the circle. So if you want to avoid overlapping strokes, you'll need at least one pixel of padding when you compute
r
by adding the two radii together. (This assumes that you have one pixel stroke on each circle, which adds 0.5 pixels to each radius.)Use .5 rather than .4 when computing the nudge_factor. This makes the overlap resolution stronger, by pushing any overlapping circles enough apart so they no longer overlap. If you use a value less than .4, the solution is a bit more stable, but it converges more slowly as circles still overlap a bit even after the nudge.
Run the collision resolution multiple times per tick. You're currently running the collision resolution and then applying the custom gravity (towards true_x and true_y). If you run the collision resolution multiple times per tick, it makes the collision resolution stronger relative to gravity.
Also, if you just want a static layout, you might also consider letting the force layout run a fixed number of iterations (or stabilize) and then render once at the end, rather than rendering iteratively. This makes the layout much faster, though it can cause a temporary hiccup in rendering if you run too many iterations.
简单实现
这是另一个实现: http://bl.ocks.org/4732279
我最初尝试使用力布局来实现此目的,但力布局模拟自然会尝试通过推动数据点来达到平衡两个轴,这可能会破坏数据的顺序(如果这对您来说很重要,就像对我一样)。
通过用智能策略替换正态分布的随机抖动,可以改进该实现。但对于我的目的来说,这已经足够了。
功能更齐全,但更复杂......
这里有点更清晰(带有轴,缩放等): http://bl.ocks.org/4734864
GIF动画:
Simple implementation
Here is another implementation: http://bl.ocks.org/4732279
I initially tried to implement this with force layout, but the force layout simulation naturally tries to reach its equilibrium by pushing data points along both axes, which can be disruptive to the ordering of the data (if that is important to you like it is for me).
This implementation could be improved by replacing the normally distributed random jittering with an intelligent strategy. But for my purpose, this sufficed.
A little more fully featured, but more complex...
Here it is a little more flushed out (with an axis, zooming, etc.): http://bl.ocks.org/4734864
GIF animation: