简体   繁体   English

如何使用新数据平滑更新D3 v4力图

[英]How to smoothly update a D3 v4 force diagram with new data

I'm looking for a way to introduce new nodes into a force directed directed graph that comes from brand new data (eg from a data stream). 我正在寻找一种方法将新节点引入来自全新数据(例如来自数据流)的力导向有向图。

In mbostock's examples (either this or this ), the nodes are able to smoothly enter and exit because in the initial setup, every node is rendered. 在mbostock的示例中(无论是这个还是这个 ),节点能够平滑地进入和退出,因为在初始设置中,每个节点都被渲染。

However, if a brand new data point is introduced, the graph renders from scratch again. 但是,如果引入了全新的数据点,则图表将再次从头开始呈现。 Is there a way to get a brand new node to transition smoothly into the graph? 有没有办法让一个全新的节点顺利过渡到图表?

See this codepen (it's a direct adaption of the 2nd example) for what I mean; 我的意思是看这个codepen (它是第二个例子的直接改编); entering and exiting existing nodes is fine, but the transition for when a brand new node enters is jumpy. 进入和退出现有节点是好的,但是当一个全新的节点进入时的过渡是跳跃的。

  //smooth update
  nodes = [a, b];
  links = [l_ab];
  restart();

  //not as smooth
  var d = {id: id++};
  nodes = [a, b, c, d];
    links = [l_ab, l_bc, l_ca, {
      source: a,
      target: d
    }];
  restart();

The entrance of the brand new node seems "jumpy" for a simple reason: when the simulation starts, that node first appears at the top-left corner ( 0,0 in the SVG coordinates system). 全新节点的入口看似“跳跃” ,原因很简单:当模拟开始时,该节点首先出现在左上角(SVG坐标系中的0,0 )。

There are different solutions here, depending on the definition of "smoothly" . 根据“平滑”的定义,这里有不同的解决方案。 I reckon that the most obvious way to make it smoothier is setting the initial position of the node to the center of the SVG. 我认为,最明显的方式,使其smoothier是设置节点到SVG中心的初始位置。 That way, the new node will not travel that much to its final position. 这样,新节点将不会那么多地移动到最终位置。

We can do it setting the x and y properties of the new node: 我们可以设置新节点的xy属性:

var d = {id: id++, x: width/2, y: height/2};

Here is your code with that change: 以下是您更改的代码:

 var svg = d3.select("svg"), width = 250 height = 250 color = d3.scaleOrdinal(d3.schemeCategory10); var a = { id: "a" }, b = { id: "b" }, c = { id: "c" }, nodes = [a, b, c], l_ab = { source: a, target: b } l_bc = { source: b, target: c }, l_ca = { source: c, target: a } links = [l_ab, l_bc, l_ca]; var id = 0; var simulation = d3.forceSimulation(nodes) .force('charge', d3.forceManyBody()) .force('link', d3.forceLink()) .force('center', d3.forceCenter(width / 2, height / 2)) .alphaTarget(1) .on("tick", ticked) .stop() var g = svg.append("g") link = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".link"), node = g.append("g").attr("stroke", "#fff").attr("stroke-width", 1.5).selectAll(".node"); restart(); function restart() { // Apply the general update pattern to the nodes. node = node.data(nodes, function(d) { return d.id; }); node.exit().transition() .attr("r", 0) .remove(); node = node.enter().append("circle") .attr("fill", function(d) { return color(d.id); }) .call(function(node) { node.transition().attr("r", 8); }) .merge(node); // Apply the general update pattern to the links. link = link.data(links, function(d) { return d.source.id + "-" + d.target.id; }); // Keep the exiting links connected to the moving remaining nodes. link.exit().transition() .attr("stroke-opacity", 0) .attrTween("x1", function(d) { return function() { return d.source.x; }; }) .attrTween("x2", function(d) { return function() { return d.target.x; }; }) .attrTween("y1", function(d) { return function() { return d.source.y; }; }) .attrTween("y2", function(d) { return function() { return d.target.y; }; }) .remove(); link = link.enter().append("line") .call(function(link) { link.transition().attr("stroke-opacity", 1); }) .merge(link); // Update and restart the simulation. simulation.nodes(nodes); simulation.force("link").links(links); simulation.alpha(1).restart(); } function ticked() { node.attr("cx", function(d) { return dx; }) .attr("cy", function(d) { return dy; }) 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; }); } restart(); document.getElementById('btnRenderExisting3Node').addEventListener('click', function() { nodes = [a, b, c]; links = [l_ab, l_bc, l_ca]; restart(); }); document.getElementById('btnRenderExisting2Node').addEventListener('click', function() { nodes = [a, b]; links = [l_ab]; restart(); }); document.getElementById('btnRenderNewNode').addEventListener('click', function() { var d = { id: id++, x: width / 2, y: height / 2 }; nodes = [a, b, c, d]; links = [l_ab, l_bc, l_ca, { source: a, target: d }]; restart(); }); 
 svg { border: 1px black solid } 
 <script src="https://d3js.org/d3.v4.min.js"></script> <div> <button id='btnRenderExisting2Node'>Render 2 existing nodes</button> <button id='btnRenderExisting3Node'>Render 3 existing nodes</button> <button id='btnRenderNewNode'>Render 3 existing nodes and 1 brand new node</button> </div> <svg width="250" height="250"></svg> </div> 

I published a bl.ocks where I worked on smooth transitions between two successive layouts of the graph as data is added/removed. 我发布了一个bl.ocks ,我在添加/删除数据时,在两个连续的图形布局之间进行平滑过渡。 Instead of using a tick function to update the view at each iteration of the force simulation, the new layout is calculated in a Web Worker and the nodes/links are re-positioned with d3 transition mechanism once the simulation has converged. 在力模拟的每次迭代中,不使用tick函数来更新视图,而是在Web Worker中计算新布局,并且一旦模拟收敛,则使用d3过渡机制重新定位节点/链接。 The result allows you to easily track the position of the nodes. 结果使您可以轻松跟踪节点的位置。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM