【问题标题】:Can one specify a custom force function for a force-directed layout?可以为力导向布局指定自定义力函数吗?
【发布时间】:2015-05-09 18:21:59
【问题描述】:

我想试验一种用于力导向图布局的替代系列力函数。

对于每个节点n_i,我可以定义一个“强制函数”f_i 这样

  • f_i ( n_i ) 相同为零;和
  • f_i ( n_j ),其中n_i != n_j,是节点n_i 上的力,它是由于某个其他节点n_j 而产生的。

节点n_i 上的净力应该是力f_i ( n_j ) 的向量和,其中n_j 的范围是所有其他节点1

有没有办法告诉 d3.js 在布局算法中使用这些自定义强制函数

[d3.js 的力导向布局的documentation 描述了可以调整其内置力功能的各种方式,但我还没有找到一种方法来完全指定完全不同的力功能,即通过调整内置力函数的参数无法实现的力函数。]


1IOW,除了从其力函数 f_i 计算的那些之外,没有其他/额外的力应该作用在节点 n_i 上。

【问题讨论】:

  • 你需要创建一个新的布局来使用你的自定义力。
  • @LarsKotthoff:感谢您的指点。我刚刚扫描了 d3.js 文档,但找不到任何有关创建自定义布局的文档。我想这涉及到d3.layout.myLayout = function () { ... } 之类的东西,但我完全不清楚这个函数应该返回什么。 IOW,我在“布局界面”上找不到任何文档。当然,我可以尝试对 d3.js 源进行逆向工程,但我之前的尝试非常痛苦,所以如果可能的话,我真的不想这样做。
  • 恐怕没有关于这方面的文档——你必须去源头。从强制布局开始应该非常简单,但听上去,自定义布局的结构与此非常相似。
  • @LarsKotthoff:再次感谢;如果您这样发布它们,我会接受您的 cmets 作为答案。

标签: d3.js graph-layout


【解决方案1】:

为此,您需要创建自己的自定义布局。我知道没有这方面的教程,但现有力布局的源代码应该是一个很好的起点,因为听起来,您的自定义布局的结构将与此非常相似。

【讨论】:

    【解决方案2】:

    是的,你可以。归功于Shan Carter 和他的bl.ocks example

    let margin = {
      top: 100,
      right: 100,
      bottom: 100,
      left: 100
    };
    
    let width = 960,
      height = 500,
      padding = 1.5, // separation between same-color circles
      clusterPadding = 6, // separation between different-color circles
      maxRadius = 12;
    
    let n = 200, // total number of nodes
      m = 10, // number of distinct clusters
      z = d3.scaleOrdinal(d3.schemeCategory20),
      clusters = new Array(m);
    
    let svg = d3.select('body')
      .append('svg')
      .attr('height', height)
      .attr('width', width)
      .append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
    
    let nodes = d3.range(200).map(() => {
      let i = Math.floor(Math.random() * m),
        radius = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
        d = {
          cluster: i,
          r: radius
        };
      if (!clusters[i] || (radius > clusters[i].r)) clusters[i] = d;
      return d;
    });
    
    let circles = svg.append('g')
      .datum(nodes)
      .selectAll('.circle')
      .data(d => d)
      .enter().append('circle')
      .attr('r', (d) => d.r)
      .attr('fill', (d) => z(d.cluster))
      .attr('stroke', 'black')
      .attr('stroke-width', 1);
    
    let simulation = d3.forceSimulation(nodes)
      .velocityDecay(0.2)
      .force("x", d3.forceX().strength(.0005))
      .force("y", d3.forceY().strength(.0005))
      .force("collide", collide) // <<-------- CUSTOM FORCE
      .force("cluster", clustering)//<<------- CUSTOM FORCE 
      .on("tick", ticked);
    
    function ticked() {
      circles
        .attr('cx', (d) => d.x)
        .attr('cy', (d) => d.y);
    }
    
    // Custom 'clustering' force implementation.
    function clustering(alpha) {
      nodes.forEach(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.r + cluster.r;
        if (l !== r) {
          l = (l - r) / l * alpha;
          d.x -= x *= l;
          d.y -= y *= l;
          cluster.x += x;
          cluster.y += y;
        }
      });
    }
    // Custom 'collide' force implementation.
    function collide(alpha) {
      var quadtree = d3.quadtree()
        .x((d) => d.x)
        .y((d) => d.y)
        .addAll(nodes);
    
      nodes.forEach(function(d) {
        var r = d.r + maxRadius + Math.max(padding, clusterPadding),
          nx1 = d.x - r,
          nx2 = d.x + r,
          ny1 = d.y - r,
          ny2 = d.y + r;
        quadtree.visit(function(quad, x1, y1, x2, y2) {
    
          if (quad.data && (quad.data !== d)) {
            var x = d.x - quad.data.x,
              y = d.y - quad.data.y,
              l = Math.sqrt(x * x + y * y),
              r = d.r + quad.data.r + (d.cluster === quad.data.cluster ? padding : clusterPadding);
            if (l < r) {
              l = (l - r) / l * alpha;
              d.x -= x *= l;
              d.y -= y *= l;
              quad.data.x += x;
              quad.data.y += y;
            }
          }
          return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
        });
      });
    }
    <!doctype html>
    <meta charset="utf-8">
    
    <body>
      <script src="//d3js.org/d3.v4.min.js"></script>

    这里还有更多in-depth 看主题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-08
      相关资源
      最近更新 更多