How to disable animation in a force-directed graph?
有什么方法可以禁用D3力导向图中的动画吗?
我正在使用此示例:https://bl.ocks.org/mbostock/4062045
我想在没有初始动画的情况下渲染图形,即显示所有节点和链接的最终位置。
尽管这个问题已经得到了公认的答案,但是提出的解决方案并不是禁用D3力图中的动画的正确方法。浏览器仍在移动每个跳动的节点和链接!您只是看不到它们在移动,而是浏览器在移动它们,进行了大量的计算,并且浪费了大量的时间/资源。另外,您不需要服务器端。
我的答案提出了一个不同的解决方案,实际上不绘制动画。例如,您可以在此代码中由Mike Bostock(D3创建者)看到。
当您了解什么是
您可以执行以下操作:
定义后,立即使用
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(); |
然后,只需使用模拟创建的属性(
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:为简单起见,我删除了分叉块中的
编辑D3 v5.8
现在,D3 v5.8允许将交互次数传递给
1 | for (var i = 0; i < 300; ++i) simulation.tick(); |
您可以这样做:
1 | simulation.tick(300); |
编辑
这种方法只是隐藏了仿真的动画部分。请参阅Gerardo Furtado的答案,该答案无需绘制中间结果即可执行仿真,这意味着用户不必等待解决方案缓慢发展。
========
"动画"实际上是模拟运行。可以在运行模拟的时间上玩,但这可能意味着节点卡在局部最小值上-有关更多详细信息,请参见此处的文档。
您可以选择向
另一种方法是在服务器端渲染图表(如果可以的话),然后提供可用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> |