score:1

Accepted answer

The issue is that the nodes and the links haven't been added to the respective arrays when you call the refresh() method.

Here's the explanation to when the onload function gets called (mentioned in the 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.

So by the time refresh is called after setting all the variables (in the <script> tag, the connect1 hasn't been called yet which means there are no nodes and no links and hence no graph. But when you click the the refresh button, the connect1 has already been executed and the nodes, links have been added which leads to drawing the graph.

Amongst many solutions to this, here's one approach: (calling refresh in the connect1 function)

<!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>

EDIT: Call refresh after every node/link addition:

This bl.ocks snippet helped in knowing what has to be done to add a dynamic force layout which clearly indicates that the setup has to be done just once but the nodes/links attachment to the simulation can be done any number of times. Also, I'm using .restart() that:

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.

Here's the doc link: d3 simulation restart

The example I refer to is a SVG one but the logic is pretty much the same for a 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>

And here's the jsfiddle. Hope this helps.


Related Query