【问题标题】:Dynamically inserted d3 force graph not spreading out动态插入的 d3 力图没有展开
【发布时间】:2017-12-13 13:27:04
【问题描述】:

附加的 sn-p 包含 official d3 force graph example code 的修改版本。当我立即插入图表时,一切都按预期工作。但是,如果我动态插入图表(您可以在演示中按Clear and Redraw 来完成),则节点不会以相同的方式展开。有时它们甚至会停留在 svg 的左上角。

我发现的一个技巧是在插入图表后添加simulation.alphaTarget(1).restart()。不幸的是,这需要更长的时间才能达到稳定的输出,并可能导致残余震颤(或旋转)。

如何使动态插入的图表在页面加载时立即插入图表的行为而无需我的黑客攻击?

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));

// I wrapped the d3.json invocation in this function
function drawGraph() {
  d3.json("https://gist.githubusercontent.com/mbostock/4062045/raw/5916d145c8c048a6e3086915a6be464467391c62/miserables.json", function(error, graph) {
    if (error) throw 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; });
    }
  });
}

// When I call it hear, all is well (as you would expect).
drawGraph()

// But when I clear and redraw it (by pressing a button), the nodes
// don't spread out.
function clearRedraw() {
  d3.selectAll("svg > *").remove()
  drawGraph()
}

function hackySolution() {
  d3.selectAll("svg > *").remove()
  drawGraph()
  simulation.alphaTarget(1).restart()
}


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;
}
.links line {
  stroke: #999;
  stroke-opacity: 0.6;
}

.nodes circle {
  stroke: #fff;
  stroke-width: 1.5px;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <button onclick="clearRedraw()">Clear And Redraw</button>
  <button onclick="hackySolution()">Hacky Solution</button>
  <svg width="960" height="600"></svg>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
</body>
</html>

【问题讨论】:

    标签: javascript d3.js svg force-layout


    【解决方案1】:

    不是一个骇人听闻的解决方案!这是重新加热模拟的正确惯用方法。

    这里的问题是当你这样做时......

    d3.selectAll("svg > *").remove()
    

    ...您只是删除了 DOM 元素。然而,模拟仍在运行,并且已经冷却下来。

    实际上,如果您等到模拟完全完成(大约 5 秒)再单击“清除并重绘”,您会看到节点总是堆积在原点(顶部左角)。尝试点击它们:它们将移动到中心(因为拖动功能会重新加热模拟,因为它有一个alphaTarget)。

    因此,你必须重新加热它。

    但是,您应该使用alpha,而不是使用alphaTarget

    simulation.alpha(0.8).restart()
    

    这是更改后的代码:

    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));
    
    // I wrapped the d3.json invocation in this function
    function drawGraph() {
      d3.json("https://gist.githubusercontent.com/mbostock/4062045/raw/5916d145c8c048a6e3086915a6be464467391c62/miserables.json", function(error, graph) {
        if (error) throw 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; });
        }
      });
    }
    
    // When I call it hear, all is well (as you would expect).
    drawGraph()
    
    // But when I clear and redraw it (by pressing a button), the nodes
    // don't spread out.
    function clearRedraw() {
      d3.selectAll("svg > *").remove()
      drawGraph()
    }
    
    function hackySolution() {
      d3.selectAll("svg > *").remove()
      drawGraph()
      simulation.alpha(0.8).restart()
    }
    
    
    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;
    }
    .links line {
      stroke: #999;
      stroke-opacity: 0.6;
    }
    
    .nodes circle {
      stroke: #fff;
      stroke-width: 1.5px;
    }
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width">
      <title>JS Bin</title>
    </head>
    <body>
      <button onclick="clearRedraw()">Clear And Redraw</button>
      <button onclick="hackySolution()">Hacky Solution</button>
      <svg width="960" height="600"></svg>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
    </body>
    </html>

    或者,如果您仍然觉得重新加热模拟是一个 hacky 解决方案(事实并非如此),只需将模拟分配移动到 drawGraph 函数内部:

    var svg = d3.select("svg"),
      width = +svg.attr("width"),
      height = +svg.attr("height");
    
    var color = d3.scaleOrdinal(d3.schemeCategory20);
    
    
    
    // I wrapped the d3.json invocation in this function
    function drawGraph() {
      d3.json("https://gist.githubusercontent.com/mbostock/4062045/raw/5916d145c8c048a6e3086915a6be464467391c62/miserables.json", function(error, graph) {
        if (error) throw error;
    
        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));
    
        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;
            });
        }
      });
    }
    
    // When I call it hear, all is well (as you would expect).
    drawGraph()
    
    // But when I clear and redraw it (by pressing a button), the nodes
    // don't spread out.
    function clearRedraw() {
      d3.selectAll("svg > *").remove()
      drawGraph()
    }
    
    
    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;
    }
    .links line {
      stroke: #999;
      stroke-opacity: 0.6;
    }
    
    .nodes circle {
      stroke: #fff;
      stroke-width: 1.5px;
    }
    <button onclick="clearRedraw()">Clear And Redraw</button>
    <svg width="960" height="600"></svg>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>

    【讨论】:

    • 你是如何选择 0.8 的 alpha 值(相对于 1 或 0.6)?
    • 这只是一个例子。该值可以从 0 到 1,选择更适合您的值。默认值为 1。
    猜你喜欢
    • 2012-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-13
    • 2016-03-28
    • 1970-01-01
    • 1970-01-01
    • 2014-02-27
    相关资源
    最近更新 更多