D3强制布局,其中较大的节点聚集在中心 [英] D3 Force Layout where larger nodes cluster in center

查看:274
本文介绍了D3强制布局,其中较大的节点聚集在中心的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在修改将用于标签云的强制布局,每个标签由<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

  • 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 and p.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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆