Search code examples
javascriptd3.jsforce-layoutd3-force-directed

d3js Force Directed Graph - Click on node to popup infobox which read from JSON


I have been working with Force-Directed Graph but I am still new to D3js and Javascript.

I want to able to click on the node and the infobox popup on the page and print some information about that node (from JSON).

Example of my json file:

{"directed": false, "graph": {}, 
"nodes": [{"id": 0, "name":"Ant" , "info":"Small Animal"}, 
{"id": 1 , "name":"Apple" , "info":"Fruit"}, 
{"id": 2 , "name":"Bat" , "info":"Fly Animal"}, 
{"id": 3 , "name":"Boat" , "info":"Vehicle"}, 
{"id": 4 , "name":"Banana" , "info":"Long cute Fruit"}, 
{"id": 5 , "name":"Cat" , "info":"Best Animal"}], 

"links": [{"source": 0, "target": 0 , "weight":1}, {"source": 0, "target": 2, "weight": 0.3}, 
{"source": 0, "target": 5, "weight":0.8}, {"source": 1, "target": 1, "weight":1}, 
{"source": 1, "target": 4, "weight":0.5}, {"source": 2, "target": 2, "weight":1}, 
{"source": 3, "target": 3, "weight":1}, {"source": 4, "target": 4, "weight":1}, 
{"source": 5, "target": 5, "weight":1}], 
"multigraph": false}

So when I click on a node. It should popup something like:

Name: Ant
Info: Small Animal
Connected to: Bat with 0.3 weight , Cat with 0.8 weight

My graph code were pretty much like the example of force-directed that I link above.


Solution

  • Here's a quick implementation, it builds your "infobox" using SVG:

      var tip;
      node.on("click", function(d){
        if (tip) tip.remove();
    
        tip  = svg.append("g")
          .attr("transform", "translate(" + d.x  + "," + d.y + ")");
    
        var rect = tip.append("rect")
          .style("fill", "white")
          .style("stroke", "steelblue");
    
        tip.append("text")
          .text("Name: " + d.name)
          .attr("dy", "1em")
          .attr("x", 5);
    
        tip.append("text")
          .text("Info: " + d.info)
          .attr("dy", "2em")
          .attr("x", 5);
    
        var con = graph.links
          .filter(function(d1){
            return d1.source.id === d.id;
          })
          .map(function(d1){
            return d1.target.name + " with weight " + d1.weight;
          })
    
        tip.append("text")
          .text("Connected to: " + con.join(","))
          .attr("dy", "3em")
          .attr("x", 5);
    
        var bbox = tip.node().getBBox();
        rect.attr("width", bbox.width + 5)
            .attr("height", bbox.height + 5)
      });
    

    Running code:

    <!DOCTYPE html>
    <meta charset="utf-8">
    <style>
    
    .links line {
      stroke: #999;
      stroke-opacity: 0.6;
    }
    
    .nodes circle {
      stroke: #fff;
      stroke-width: 1.5px;
    }
    
    text {
      font-family: sans-serif;
    }
    
    </style>
    <svg width="960" height="600"></svg>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script>
    
    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));
        
    d3.json("https://jsonblob.com/api/15daa79f-7573-11e8-b9d7-1b0997147957", function(error, graph) {
      if (error) throw error;
      
      link = svg.append("g")
          .attr("class", "links")
        .selectAll("line")
        .data(graph.links)
        .enter().append("line")
          .attr("stroke-width", function(d) { return d.weight * 3; });
    
      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));
    
      var tip;
      svg.on("click", function(){
        if (tip) tip.remove();
      });
      node.on("click", function(d){
        d3.event.stopPropagation(); 
      
        if (tip) tip.remove();
        
        tip  = svg.append("g")
          .attr("transform", "translate(" + d.x  + "," + d.y + ")");
          
        var rect = tip.append("rect")
          .style("fill", "white")
          .style("stroke", "steelblue");
        
        tip.append("text")
          .text("Name: " + d.name)
          .attr("dy", "1em")
          .attr("x", 5);
          
        tip.append("text")
          .text("Info: " + d.info)
          .attr("dy", "2em")
          .attr("x", 5);
    
        var con = graph.links
          .filter(function(d1){
            return d1.source.id === d.id;
          })
          .map(function(d1){
            return d1.target.name + " with weight " + d1.weight;
          })
          
        tip.append("text")
          .text("Connected to: " + con.join(","))
          .attr("dy", "3em")
          .attr("x", 5);
        
        var bbox = tip.node().getBBox();
        rect.attr("width", bbox.width + 5)
            .attr("height", bbox.height + 5)
      });
    
      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; });
      }
    });
    
    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;
    }
    
    </script>