javascript/d3 中具有强制导向布局的蜂群图

发布于 2024-12-12 13:37:14 字数 2349 浏览 1 评论 0原文

这是一篇 关于在 R 中创建蜂群图的文章< /a>.

还有蜂群图的R包。接下来的两张图片说明了该软件包提供的一些可能性:

在此处输入图像描述

在此处输入图像描述

但是,我现在尝试使用 d3.js

我的计划是让自定义重力将点拉向垂直线及其适当的 y 值,并且碰撞检测使点彼此远离。

我有一个半工作原型

在此处输入图像描述

不幸的是,我找不到解决两个问题的方法——我怀疑这实际上是同一个问题:

  1. 我的观点不断重叠,至少有一个一点。

  2. 积分累积后,会进行持续的“洗牌” 布局的中心,作为防撞力和“来” 到中心”的力量战斗。

我希望这些点能够很快就他们应该居住的地方达成一致,并且最终不会重叠。

我正在使用的部队代码(如果你想在这里看到它而不是在 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:

enter image description here

enter image description here

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:

enter image description here

Unfortunately, I can't find a way around two problems -- which I suspect are really the same problem:

  1. My points keep overlapping, at least a bit.

  2. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

北音执念 2024-12-19 13:37:14

你的结果对我来说看起来相当不错。但是,如果您想减少重叠的可能性,可以尝试一些方法。

  1. 在节点之间添加一些填充。特别是,您的圆圈有一个笔划,并且该笔划的一半将延伸到圆的半径之外。因此,如果您想避免笔画重叠,则在通过将两个半径相加来计算 r 时,您将需要至少一个像素的填充。 (这假设每个圆上有一个像素描边,这会为每个半径添加 0.5 像素。)

  2. 计算 nudge_factor 时使用 0.5 而不是 0.4。通过将任何重叠的圆推得足够远,使它们不再重叠,从而使重叠分辨率更强。如果您使用小于 0.4 的值,则解会更稳定一点,但收敛速度会更慢,因为即使在微移之后,圆仍然会重叠一点。

  3. 每个周期多次运行碰撞解决。您当前正在运行碰撞分辨率,然后应用自定义重力(朝向 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.

  1. 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.)

  2. 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.

  3. 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.

不喜欢何必死缠烂打 2024-12-19 13:37:14

简单实现

这是另一个实现: http://bl.ocks.org/4732279

在此处输入图像描述

我最初尝试使用力布局来实现此目的,但力布局模拟自然会尝试通过推动数据点来达到平衡两个轴,这可能会破坏数据的顺序(如果这对您来说很重要,就像对我一样)。

通过用智能策略替换正态分布的随机抖动,可以改进该实现。但对于我的目的来说,这已经足够了。

  1. 碰撞访问者的迭代总数直接影响最终状态下发生碰撞的概率。
  2. 提高正态分布的标准差也可以帮助可视化更快地收敛到无碰撞解决方案,但当然最终可能需要更多的垂直空间。

功能更齐全,但更复杂......

这里有点更清晰(带有轴,缩放等): http://bl.ocks.org/4734864

在此处输入图像描述

GIF动画:

在此处输入图像描述

Simple implementation

Here is another implementation: http://bl.ocks.org/4732279

enter image description here

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.

  1. The total number of iterations over the collision visitor directly affects the probability of collisions in the end state.
  2. Bumping the standard deviation of the normal distribution up can help the visualization converge on a no-collision solution more quickly as well, but of course may require more vertical space in the end.

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

enter image description here

GIF animation:

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文