d3js Force Directed Graph - 单击节点以弹出从 JSON 读取的信息框

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

我一直在使用 Force-Directed Graph,但我对 D3js 和 Javascript 还是个新手。

我希望能够单击页面上的节点和信息框弹出窗口并打印有关该节点的一些信息(来自 JSON)。

我的 json 文件示例:

{"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}

所以当我点击一个节点时。它应该弹出如下内容:

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

我的图形代码与上面 link 的力导向示例非常相似。

您可以使用 .on('click', function(d){ }); 在节点上添加点击事件 例如:

node.on("click", function(d){
      console.log(d);
       // here you can access data of node using d.key 
      alert("You clicked on node " + d.name);
    });

这是一个快速实现,它使用 SVG 构建您的 "infobox":

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

运行代码:

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