关于javascript:如何在力向图中禁用动画?

How to disable animation in a force-directed graph?

有什么方法可以禁用D3力导向图中的动画吗?

我正在使用此示例:https://bl.ocks.org/mbostock/4062045

我想在没有初始动画的情况下渲染图形,即显示所有节点和链接的最终位置。


尽管这个问题已经得到了公认的答案,但是提出的解决方案并不是禁用D3力图中的动画的正确方法。浏览器仍在移动每个跳动的节点和链接!您只是看不到它们在移动,而是浏览器在移动它们,进行了大量的计算,并且浪费了大量的时间/资源。另外,您不需要服务器端。

我的答案提出了一个不同的解决方案,实际上不绘制动画。例如,您可以在此代码中由Mike Bostock(D3创建者)看到。

当您了解什么是tick函数时,可以轻松采用该解决方案:它只是一个计算模拟中所有位置并前进一个步骤的函数。尽管绝大多数D3力导向图在每个刻度上都绘制了节点和链接,但您无需这样做。

您可以执行以下操作:

  • 定义后,立即使用stop()停止仿真:

    1
    2
    3
    4
    5
    var simulation = d3.forceSimulation(graph.nodes)
        .force("link", d3.forceLink().id(function(d) { return d.id; }))
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(width / 2, height / 2))
        .stop();//stop the simulation here
  • 使模拟运行而无需绘制任何内容。那是最重要的步骤:您不必在每个刻度上移动元素。在这里,我正在运行300 ticks,这大约是默认数字:

    1
    for (var i = 0; i < 300; ++i) simulation.tick();
  • 然后,只需使用模拟创建的属性(xysourcetarget)就可以绘制一次圆和直线:

    1
    2
    node.attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; })
  • 这是仅具有那些更改的链接块:http://bl.ocks.org/anonymous/8a4e4e2fed281ea5e2a5c804a9a03783/85ced3ea82a4bed20a2010530562b655d8f6e464

    比较此解决方案的时间与"隐藏节点"解决方案的时间(可接受的答案)。这是一个更快的方法。在测试中,我得到了以下结果:

    • "隐藏节点"解决方案:约5000ms
    • 解决方案:约200ms

    也就是说,快25倍。

    PS:为简单起见,我删除了分叉块中的ticked函数。如果要拖动节点,只需将其重新添加即可。

    编辑D3 v5.8

    现在,D3 v5.8允许将交互次数传递给simulation.tick(),您甚至不再需要for循环。因此,代替:

    1
    for (var i = 0; i < 300; ++i) simulation.tick();

    您可以这样做:

    1
    simulation.tick(300);


    编辑

    这种方法只是隐藏了仿真的动画部分。请参阅Gerardo Furtado的答案,该答案无需绘制中间结果即可执行仿真,这意味着用户不必等待解决方案缓慢发展。

    ========

    "动画"实际上是模拟运行。可以在运行模拟的时间上玩,但这可能意味着节点卡在局部最小值上-有关更多详细信息,请参见此处的文档。

    您可以选择向end事件添加侦听器,该事件在模拟完成时触发。我创建了一个片段,其中Graph最初是隐藏的,然后在完成模拟后出现。

    另一种方法是在服务器端渲染图表(如果可以的话),然后提供可用d3进一步处理的现成的SVG。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    var svg = d3.select("svg"),
      width = +svg.attr("width"),
      height = +svg.attr("height");

    var color = d3.scaleOrdinal(d3.schemeCategory20);

    var simulation = d3.forceSimulation()
      .force("link", d3.forceLink().id(function(d) {
        return d.id;
      }))
      .force("charge", d3.forceManyBody())
      .force("center", d3.forceCenter(width / 2, height / 2))
      .on('end', function() {
        svg.classed('hidden', false)
        d3.select('#loading').remove()
      });

    // I wasn't able to get the snippet to load the original data from https://bl.ocks.org/mbostock/raw/4062045/miserables.json so this is a copy hosted on glitch
    d3.json("https://cdn.glitch.com/8e57a936-9a34-4e95-a03d-598e5738f44d%2Fmiserables.json", function(error, graph) {
      if (error) {
        console.log(error)
      };

      var link = svg.append("g")
        .attr("class","links")
        .selectAll("line")
        .data(graph.links)
        .enter().append("line")
        .attr("stroke-width", function(d) {
          return Math.sqrt(d.value);
        });

      var node = svg.append("g")
        .attr("class","nodes")
        .selectAll("circle")
        .data(graph.nodes)
        .enter().append("circle")
        .attr("r", 5)
        .attr("fill", function(d) {
          return color(d.group);
        })
        .call(d3.drag()
          .on("start", dragstarted)
          .on("drag", dragged)
          .on("end", dragended));

      node.append("title")
        .text(function(d) {
          return d.id;
        });

      simulation
        .nodes(graph.nodes)
        .on("tick", ticked);

      simulation.force("link")
        .links(graph.links);

      function ticked() {
        link
          .attr("x1", function(d) {
            return d.source.x;
          })
          .attr("y1", function(d) {
            return d.source.y;
          })
          .attr("x2", function(d) {
            return d.target.x;
          })
          .attr("y2", function(d) {
            return d.target.y;
          });

        node
          .attr("cx", function(d) {
            return d.x;
          })
          .attr("cy", function(d) {
            return d.y;
          });
      }
    });

    function dragstarted(d) {
      if (!d3.event.active) simulation.alphaTarget(0.3).restart();
      d.fx = d.x;
      d.fy = d.y;
    }

    function dragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }

    function dragended(d) {
      if (!d3.event.active) simulation.alphaTarget(0);
      d.fx = null;
      d.fy = null;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    .links line {
      stroke: #999;
      stroke-opacity: 0.6;
    }

    .nodes circle {
      stroke: #fff;
      stroke-width: 1.5px;
    }

    .hidden {
      visibility: hidden
    }

    img {
        display: block;
        margin-left: auto;
        margin-right: auto;
       }
    1
    2
    3
    <script src="https://d3js.org/d3.v4.min.js">
    <img id ="loading" src="http://thinkfuture.com/wp-content/uploads/2013/10/loading_spinner.gif" />
    <svg width="960" height="600" class="hidden"></svg>