除非手动刷新D3画布网络,否则它不会出现

时间:2018-07-19 08:55:18

标签: javascript d3.js

我正在向画布手动添加一些节点,然后添加一些链接。我期望该网络会立即出现,但不会出现。经过数小时的调查,我发现当刚添加节点时,添加链接会将节点的xy属性选择为Nan,并使节点消失。

因此,我添加了一个refresh按钮,以便在节点沉降后可以稍后手动刷新网络。

是否有更好的方法来解决此问题?我有数百个节点是实时发送的,因此最初,我什么都没有,后来外部服务将调用addNodeCanvasaddLinkCanvas

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


编辑

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

请检查此jsfiddle,摆脱onload;我在链接节点后立即调用refresh方法,但是除非节点安定下来,否则什么都不会显示。

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

我发现,刚添加节点时,添加链接会将节点的xy属性选择为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>

1 个答案:

答案 0 :(得分:1)

问题是,在调用nodes方法时,linksrefresh()尚未添加到各自的数组中。

以下是onload函数何时被调用的说明(在MSDN web docs中提到):

  

在文档加载过程结束时触发load事件。至此,文档中的所有对象都在DOM中,并且所有图像,脚本,链接和子框架均已完成加载。

因此,在设置了所有变量之后,刷新被调用的时间(在<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()

  

重新启动模拟的内部计时器并返回模拟。结合simulation.alphaTarget或simulation.alpha,此方法可用于在交互过程中(例如,拖动节点时)“重新加热”仿真,或在使用simulation.stop暂时将其暂停后恢复仿真。

这是文档链接:d3 simulation restart

我引用的示例是一个SVG,但其逻辑与画布几乎相同。

<!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。希望这会有所帮助。