javascript - D3 Canvas Network 除非手动刷新,否则不会出现

标签 javascript d3.js

我手动向 Canvas 添加一些节点,然后添加一些链接。我期待网络立即出现,但事实并非如此。经过几个小时的调查,我发现当刚刚添加节点时,添加链接会将节点的 x,y 属性选择为 Nan 和使节点消失。

所以我添加了一个刷新按钮,以便我可以在节点稳定后手动刷新网络。

有没有更好的方法来解决这个问题?我有数百个实时发送的节点,所以最初我不会有任何东西,后来外部服务将调用 addNodeCanvasaddLinkCanvas

运行以下代码时,最初不会出现任何内容,几秒钟后按刷新按钮将显示网络。

<小时/>

编辑

根据下面 Shashank 的回答,我已经摆脱了 onload 功能。

请检查此jsfiddle ,摆脱了onload;我在链接节点后调用刷新方法,但除非节点稳定下来,否则什么也不会显示。

请将此与此进行比较 jsfiddle ,我删除了所有链接,现在节点立即出现。

我发现,当刚刚添加节点时,添加链接会将节点的 x,y 属性选择为 Nan并使节点消失。

<小时/>

原始代码:

<!DOCTYPE html>
<html>
<body onload="connect1();">
  <canvas width="300" height="100"></canvas>
  <button id="ref" onclick="refresh()">refresh </button>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script>
    var canvas = document.querySelector("canvas"),
      context = canvas.getContext("2d"),
      width = canvas.width,
      height = canvas.height;

    var links = [],
      nodes = [];
    var graph = {
        nodes,
        links
      },
      wsConn;
    refresh();

    function connect1() {
      addNodeCanvas("A");
      addNodeCanvas("B");
      addNodeCanvas("C");
      addNodeCanvas("D");
      addNodeCanvas("E");
      addLinkCanvas("A", "B");
      addLinkCanvas("A", "C");
      addLinkCanvas("D", "C");
      addLinkCanvas("E", "D");
      addLinkCanvas("E", "B");
    }

    function addNodeCanvas(nodeName, g) {
      var node = {
        x: 100,
        y: 100,
        id: nodeName,
        grp: g
      };
      var n = nodes.push(node);
      //console.log(node);
      refresh();
    }
    
    function addLinkCanvas(idSrc, idTarget) {
      if (idSrc != idTarget) {
        var s = {},
          t = {};
        nodes.forEach(function(curNode) {
          if (typeof curNode.id != "undefined") {
            if (curNode.id == idSrc) {
              s = curNode;
            }
            if (curNode.id == idTarget) {
              t = curNode;
            }
          }
        });
        //console.log( { s,t});
        links.push({
          source: s,
          target: t
        });
      };
    }

    function refresh() {
      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));

      simulation
        .nodes(nodes)
        .on("tick", ticked)
        .force("link")
        .links(links);
      d3.select(canvas)
        .call(d3.drag()
          .container(canvas)
          .subject(dragsubject)
          .on("start", dragstarted)
          .on("drag", dragged)
          .on("end", dragended));

      function ticked() {
        var margin = 20;
        nodes.forEach(function(d) {
          d.x = Math.max(margin, Math.min(width - margin, d.x))
          d.y = Math.max(margin, Math.min(height - margin, d.y))
        });

        function dblclick() {
          nodes.forEach(function(d) {
            d.fx = d.fy = null;
          })
        };
        context.clearRect(0, 0, width, height);
        context.beginPath();
        links.forEach(drawLink);
        context.strokeStyle = "#aaa";
        context.stroke();
        context.beginPath();
        nodes.forEach(drawNode);
      }

      function dragsubject() {
        return simulation.find(d3.event.x, d3.event.y);
      }
      var clickDate = new Date();
      var difference_ms;

      function dragstarted() {
        if (!d3.event.active) simulation.alphaTarget(0.3).restart();
        d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.subject.x));
        d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.subject.y));
      }

      function dragged() {
        d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.x));
        d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.y));
      }


      function dragended() {}

      function drawLink(d) {
        context.moveTo(d.source.x, d.source.y);
        context.lineTo(d.target.x, d.target.y);
      }

      var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20),
        labelColors = d3.scaleOrdinal().range(['red', 'orange', 'blue', 'green', 'purple']);

      function drawNode(d) {
        context.beginPath();
        context.moveTo(d.x + 10, d.y);
        context.arc(d.x, d.y, 10, 0, 2 * Math.PI);
        context.strokeStyle = "#fff";
        context.stroke();
        context.fillStyle = nodeColors(d.grp);
        context.closePath();
        context.fill();
        context.beginPath();
        context.font = (d.labelSize ? d.labelSize : 10) + 'px Arial';
        context.fillStyle = labelColors(d.grp);
        context.fillText(d.id ? d.id : d.grp, d.x, d.y);
        context.closePath();
      }
    }
  </script>
</body>
</html>

最佳答案

问题是 nodeslinks当您调用 refresh() 时,尚未将其添加到相应的数组中方法。

以下是 onload 何时出现的解释函数被调用(在 MSDN web docs 中提到):

The load event fires at the end of the document loading process. At this point, all of the objects in the document are in the DOM, and all the images, scripts, links and sub-frames have finished loading.

因此,在设置所有变量后调用刷新(在 <script> 标记中,connect1 尚未被调用,这意味着没有节点,没有链接,因此没有图形。但是当您点击刷新按钮,connect1已经被执行,并且节点、链接已经添加,从而可以绘制图形。

在众多解决方案中,有一种方法:(在 refresh 函数中调用 connect1)

<!DOCTYPE html>
<html>

<body onload="connect1();">
    <canvas width="300" height="100"></canvas>
    <!--button id="ref" onclick="refresh()">refresh </button-->
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script>
    
  var canvas = document.querySelector("canvas"),
      context = canvas.getContext("2d"),
      width = canvas.width,
      height = canvas.height;
    var links =[] , nodes = [] ;

    var graph={nodes,links}, wsConn;    

function connect1(){

    addNodeCanvas("A");
    addNodeCanvas("B");
    addNodeCanvas("C");
    addNodeCanvas("D");
    addNodeCanvas("E");
    addLinkCanvas("A","B");
    addLinkCanvas("A","C");
    addLinkCanvas("D","C");
    addLinkCanvas("E","D");
    addLinkCanvas("E","B");
    
    refresh();
}



function addNodeCanvas(nodeName,g) {
  var node = {
    x: 100,
    y: 100,
    id: nodeName,
    grp:g
  };
  var n = nodes.push(node);
}


function addLinkCanvas(idSrc, idTarget) {

    if (idSrc != idTarget) {
    var s = {},
      t = {};
    nodes.forEach(function(curNode) {
      if (typeof curNode.id != "undefined") {
        if (curNode.id == idSrc) {
          s = curNode;
        }
        if (curNode.id == idTarget) {
          t = curNode;
        }
      }
    });

    links.push({
      source: s,
      target: t
    });
  };

}




    function refresh() {

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

        simulation
            .nodes(nodes)
            .on("tick", ticked)
            .force("link")
            .links(links);
        d3.select(canvas)
            .call(d3.drag()
                .container(canvas)
                .subject(dragsubject)
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));


        function ticked() {
            var margin = 20;
            nodes.forEach(function(d) {
                d.x = Math.max(margin, Math.min(width - margin, d.x))
                d.y = Math.max(margin, Math.min(height - margin, d.y))
            });

            function dblclick() {
                nodes.forEach(function(d) {
                    d.fx = d.fy = null;
                })
            };
            context.clearRect(0, 0, width, height);
            context.beginPath();
            links.forEach(drawLink);
            context.strokeStyle = "#aaa";
            context.stroke();
            context.beginPath();
            nodes.forEach(drawNode);

        }

        function dragsubject() {
            return simulation.find(d3.event.x, d3.event.y);
        }
        var clickDate = new Date();
        var difference_ms;

        function dragstarted() {
            if (!d3.event.active) simulation.alphaTarget(0.3).restart();
            d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.subject.x));
            d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.subject.y));
        }

        function dragged() {
            d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.x));
            d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.y));
        }


        function dragended() {
           
        }


        function drawLink(d) {
            context.moveTo(d.source.x, d.source.y);
            context.lineTo(d.target.x, d.target.y);
        }

        var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20),
            labelColors = d3.scaleOrdinal().range(['red', 'orange', 'blue', 'green', 'purple']);

        function drawNode(d) {
            context.beginPath();
            context.moveTo(d.x + 10, d.y);
            context.arc(d.x, d.y, 10, 0, 2 * Math.PI);
            context.strokeStyle = "#fff";
            context.stroke();
            context.fillStyle = nodeColors(d.grp);
            context.closePath();
            context.fill();
            context.beginPath();
            context.font = (d.labelSize ? d.labelSize : 10) + 'px Arial';
            context.fillStyle = labelColors(d.grp);
            context.fillText(d.id ? d.id : d.grp, d.x, d.y);
            context.closePath();
        }
    }
    </script>
</body>

</html>

编辑:添加每个节点/链接后调用刷新:

这个bl.ocks snippet帮助了解需要做什么来添加动态力布局,这清楚地表明设置只需完成一次,但模拟的节点/链接附件可以完成任意多次。另外,我正在使用 .restart()那:

Restarts the simulation’s internal timer and returns the simulation. In conjunction with simulation.alphaTarget or simulation.alpha, this method can be used to “reheat” the simulation during interaction, such as when dragging a node, or to resume the simulation after temporarily pausing it with simulation.stop.

这是文档链接:d3 simulation restart

我引用的示例是 SVG 示例,但 Canvas 的逻辑几乎相同。

<!DOCTYPE html>
<html>

<body >
    <canvas width="900" height="600"></canvas>
    <button id="ref" onclick="refresh()">refresh </button>
    <script src="https://d3js.org/d3.v4.js"></script>
    <script>

    var canvas = document.querySelector("canvas"),
        context = canvas.getContext("2d"),
        width = canvas.width,
        height = canvas.height;

    var links =[] , nodes = [] ;
    var graph={nodes,links}, wsConn;
    
    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));
    refresh();

    connect1();

function connect1(){

    addNodeCanvas("A");
    addNodeCanvas("B");
    addNodeCanvas("C");
    addNodeCanvas("D");
    addNodeCanvas("E");
    addLinkCanvas("A", "B");
    addLinkCanvas("A", "C");
    addLinkCanvas("D", "C");
    addLinkCanvas("E", "D");
    addLinkCanvas("E", "B");
    refresh();
}



function addNodeCanvas(nodeName,g) {
  var node = {
    x: 100,
    y: 100,
    id: nodeName,
    grp:g
  };
  var n = nodes.push(node);
  //console.log(node);
  refresh();
}


function addLinkCanvas(idSrc, idTarget) {

    if (idSrc != idTarget) {
    var s = {},
      t = {};
    nodes.forEach(function(curNode) {
      if (typeof curNode.id != "undefined") {
        if (curNode.id == idSrc) {
          s = curNode;
        }
        if (curNode.id == idTarget) {
          t = curNode;
        }
      }
    });

//console.log( { s,t});
    links.push({
      source: s,
      target: t
    });
  };
 refresh();
}




    function refresh() {

        simulation
            .nodes(nodes)
            .on("tick", ticked);
            
       	simulation
            .force("link")
            .links(links);
            
				simulation.alpha(1).restart();            
        d3.select(canvas)
            .call(d3.drag()
                .container(canvas)
                .subject(dragsubject)
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));


        function ticked() {
            var margin = 20;
            nodes.forEach(function(d) {
                d.x = Math.max(margin, Math.min(width - margin, d.x))
                d.y = Math.max(margin, Math.min(height - margin, d.y))
            });

            function dblclick() {
                nodes.forEach(function(d) {
                    d.fx = d.fy = null;
                })
            };
            context.clearRect(0, 0, width, height);
            context.beginPath();
            links.forEach(drawLink);
            context.strokeStyle = "#aaa";
            context.stroke();
            context.beginPath();
            nodes.forEach(drawNode);

        }

        function dragsubject() {
            return simulation.find(d3.event.x, d3.event.y);
        }
        var clickDate = new Date();
        var difference_ms;

        function dragstarted() {
            if (!d3.event.active) simulation.alphaTarget(0.3).restart();
            d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.subject.x));
            d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.subject.y));
        }

        function dragged() {
            d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.x));
            d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.y));
        }


        function dragended() {
            if (!d3.event.active) simulation.alphaTarget(0);

            // Time between 2 ends of drag:
            difference_ms = (new Date()).getTime() - clickDate.getTime();
            clickDate = new Date();
            // if the time between these 2 ends of drag is short enough, then
            // it's considered a double click:
            if (difference_ms < 200) {
                // And we can release the node:
                simulation.alphaTarget(0.3).restart()
                d3.event.subject.fx = null;
                d3.event.subject.fy = null;
            }
        }


        function drawLink(d) {
            context.moveTo(d.source.x, d.source.y);
            context.lineTo(d.target.x, d.target.y);
        }

        var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20),
            labelColors = d3.scaleOrdinal().range(['red', 'orange', 'blue', 'green', 'purple']);

        function drawNode(d) {
            context.beginPath();
            context.moveTo(d.x + 10, d.y);
            context.arc(d.x, d.y, 10, 0, 2 * Math.PI);
            context.strokeStyle = "#fff";
            context.stroke();
            context.fillStyle = nodeColors(d.grp);
            context.closePath();
            context.fill();
            context.beginPath();
            context.font = (d.labelSize ? d.labelSize : 10) + 'px Arial';
            context.fillStyle = labelColors(d.grp);
            context.fillText(d.id ? d.id : d.grp, d.x, d.y);
            context.closePath();
        }
    }
    </script>
</body>
</html>

这是 jsfiddle 。希望这会有所帮助。

关于javascript - D3 Canvas Network 除非手动刷新,否则不会出现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51418360/

相关文章:

javascript - d3 enter()/merge() 创建副本而不是更新

javascript - 如何在 React Material-UI 中更改事件选项卡 inkBarStyle 颜色和厚度?

javascript - 我的 JavaScript 函数没有使用我需要的变量之一

javascript - 结果未显示在输入文本中

javascript - 将 CSV 文件作为 map 加载(D3 和 JavaScript)

javascript - 如何在 Apple Watch 中显示 D3.js 图表?

javascript - 使用触摸事件在移动浏览器上模拟鼠标悬停的最佳方法是什么?

JavaScript:null 是什么数据类型?

javascript - c3 图表中的多线标签

d3.js - crossfilter - 计算具有属性的所有记录的百分比