Add text/label onto links in D3 force directed graph

核能气质少年 提交于 2019-11-26 08:12:32

问题


I\'ve been working on modified force directed graph and having some problems with adding text/label onto links where the links are not properly aligned to nodes. How to fix it?

And how I can add an event listener to an SVG text element? Adding .on(\"dblclick\",function(d) {....} just doesn\'t work.

Here\'s the code snippet:

<style type=\"text/css\">
    .link { stroke: #ccc; }
    .routertext { pointer-events: none; font: 10px sans-serif; fill: #000000; }
    .routertext2 { pointer-events: none; font: 9px sans-serif; fill: #000000; }
    .linktext { pointer-events: none; font: 9px sans-serif; fill: #000000; }
</style>

<div id=\"canvas\">
</div>

<script type=\"text/javascript\" src=\"d3/d3.js\"></script>
<script type=\"text/javascript\" src=\"d3/d3.layout.js\"></script>
<script type=\"text/javascript\" src=\"d3/d3.geo\"></script>
<script type=\"text/javascript\" src=\"d3/d3.geom.js\"></script>


<script type=\"text/javascript\">

var w = 960,
    h = 600,
    size = [w, h]; // width height    
var vis = d3.select(\"#canvas\").append(\"svg:svg\")
  .attr(\"width\", w)
  .attr(\"height\", h)
  .attr(\"transform\", \"translate(0,0) scale(1)\")
  .call(d3.behavior.zoom().on(\"zoom\", redraw))
  .attr(\"idx\", -1)
  .attr(\"idsel\", -1)
  ;

var routers = {
    nodes: [
        {id:0, name:\"ROUTER-1\", group:1, ip: \"123.123.123.111\",
            x:394.027, y:450.978,outif:\"ge-0/1/0.0\",inif:\"\"},
        {id:1, name:\"ROUTER-2\", group:1, ip: \"123.123.123.222\",
            x:385.584, y:351.513,outif:\"xe-4/2/0.0\",inif:\"ge-5/0/3.0\"},
        {id:2, name:\"ROUTER-3\", group:1, ip: \"123.123.123.333\",
            x:473.457, y:252.27,outif:\"ae1.0\",inif:\"xe-1/0/1.0\"},
        {id:3, name:\"ROUTER-4\", group:2, ip: \"123.123.123.444\",
            x:723.106, y:266.569,outif:\"as0.0\",inif:\"ae1.0\"},
        {id:4, name:\"ROUTER-5\", group:3, ip: \"123.123.123.555\",
            x:728.14, y:125.287,outif:\"so-4/0/2.0\",inif:\"as1.0\"},
        {id:5, name:\"ROUTER-6\", group:3, ip: \"123.123.123.666\",
            x:738.975, y:-151.772,outif:\"\",inif:\"PO0/2/2/1\" }
    ],
    links: [
        {source:0, target:1, value:3, name:\'link-1\',speed:\"1000mbps\",
            outif:\"ge-0/1/0.0\",nextif:\"ge-5/0/3.0\"},
        {source:1, target:2, value:3, name:\'link-2\',speed:\"10Gbps\",
            outif:\"xe-4/2/0.0\",nextif:\"xe-1/0/1.0\"},
        {source:2, target:3, value:3, name:\'link-3\',speed:\"20Gbps\",
            outif:\"ae1.0\",nextif:\"xe-1/2/1.0\"},
        {source:3, target:4, value:3, name:\'link-4\',speed:\"1Gbps\",
            outif:\"as0.0\",nextif:\"as1.0\"},
        {source:4, target:5, value:3, name:\'link-5\',speed:\"OC3\",
            outif:\"so-4/0/2.0\",nextif:\"PO0/2/2/1\"}
    ]
};  

var force = d3.layout.force()
      .nodes(routers.nodes)
      .links(routers.links)
      .gravity(0)
      .distance(100)
      .charge(0)
      .size([w, h])
      .start();

var link = vis.selectAll(\"g.link\")
      .data(routers.links)
      .enter().append(\"svg:g\");

  link.append(\"svg:line\")   
      .attr(\"class\", \"link\")
      .attr(\"title\", function(d) { return \"From: \"+d.outif+\", To: \"+d.nextif })
      .attr(\"style\", \"stroke:#00d1d6;stroke-width:4px\")
      .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; });

  link.append(\"svg:text\")
      .attr(\"class\", \"linktext\")
      .attr(\"dx\", function(d) { return d.source.x; })
      .attr(\"dy\", function(d) { return d.source.y; })
      .text(\"some text to add...\");

  var node = vis.selectAll(\"g.node\")
      .data(routers.nodes)
     .enter()
      .append(\"svg:g\")
      .attr(\"id\", function(d) { return d.id;})
      .attr(\"title\", function(d) {return d.ip})
      .attr(\"class\", \"node\")
      .attr(\"x\", function(d) { return d.x; })
      .attr(\"y\", function(d) { return d.y; })
      .on(\"dblclick\",function(d) {
           alert(\'router double-clicked\'); d3.event.stopPropagation();
      })
      .on(\"mousedown\", function(d) {
          if (d3.event.which==3) {
              d3.event.stopPropagation();
              alert(\'Router right-clicked\');
          }
      })
      .call(force.drag);

  node.append(\"svg:image\")
      .attr(\"class\", \"node\")
      .attr(\"xlink:href\", \"router.png\")
      .attr(\"x\", -24)
      .attr(\"y\", -18)
      .attr(\"width\", 48)
      .attr(\"height\", 36);

  node.append(\"svg:text\")
      .attr(\"class\", \"routertext\")
      .attr(\"dx\", -30)
      .attr(\"dy\", 20)
      .text(function(d) { return d.name });

  node.append(\"svg:text\")
      .attr(\"class\", \"routertext2\")
      .attr(\"dx\", 0)
      .attr(\"dy\", -20)
      .attr(\"title\", \"some title to show....\")
      .text(function(d) { return d.outif })
      .on(\"click\", function(d,i) {alert(\"outif text clicked\");})
      .call(force.drag);

    node.append(\"svg:text\")
      .attr(\"class\", \"routertext2\")
      .attr(\"dx\", -40)
      .attr(\"dy\", 30)
      .text(function(d) { return d.inif });

  force.on(\"tick\", function() {
    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(\"transform\", function(d) {
        return \"translate(\" + d.x + \",\" + d.y + \")\"; });
    });

  function redraw() {
    vis.attr(\"transform\",
      \"translate(\" + d3.event.translate + \")\"
      + \"scale(\" + d3.event.scale + \")\");
  };

</script>

回答1:


Use a smaller example outside of D3 to see how the SVG stuff works. Then just rebuild this structure using D3 and your custom data.

<html>
    <body>

<svg width="600px" height="400px">

    <defs>
        <!-- DEFINE AN ARROW THAT WE CAN PLACE AT THE END OF EDGES. -->
        <!-- USE REFX TO MOVE THE ARROW'S TIP TO THE END OF THE PATH. -->
        <marker
            orient="auto"
            markerHeight="12"
            markerWidth="12"
            refY="0"
            refX="9"
            viewBox="0 -5 10 10"
            id="ARROW_ID"
            style="fill: red; fill-opacity: 0.5;">

            <path d="M0, -5L10, 0L0, 5"></path>

        </marker>
    </defs>

    <!-- DEFINE A PATH. SET ITS END MARKER TO THE ARROW'S ID. -->
    <!-- SET FILL NONE TO DRAW A LINE INSTEAD OF A SHAPE. -->
    <path
        d="M100,100 A300,250 0 0,1 500,300"
        style="fill:none; stroke:grey; stroke-width:2px;"
        id="PATH_ID"
        marker-end="url(#ARROW_ID)" />

    <!-- DEFINE A TEXT ELEMENT AND SET FONT PROPERTIES. -->
    <!-- USE DY TO MOVE TEXT ABOVE THE PATH. -->
    <text
        style="text-anchor:middle; font: 16px sans-serif;"
        dy="-12">

        <!-- DEFINE A TEXT PATH FOLLOWING THE PATH DEFINED ABOVE. -->
        <!-- USE STARTOFFSET TO CENTER TEXT. -->
        <textPath
            xlink:href="#PATH_ID"
            startOffset="50%">Centered edge label</textPath>
    </text>

</svg>

    </body>
</html>



回答2:


Have you experimented with creating text elements separately in a standalone (simpler) example? It should give you a better feeling for how the different attributes control positioning.

For vertical alignment, use the "dy" attribute:

  • by default, the baseline of the text is at the origin (bottom-aligned)
  • a dy of .35em centers the text vertically
  • a dy of .72em places the topline of the text at the origin (top-aligned)

Using em units is nice because it will scale automatically based on the font size. If you don't specify units (such as -20 in your code), it defaults to pixels.

For horizontal alignment, use the "text-anchor" attribute:

  • the default is "start" (left-aligned for left-to-right languages)
  • "middle"
  • "end"

There's also the "dx" attribute, which is tempting to use for padding. However, I wouldn't recommend it because there is a bug in Firefox and Opera that cause it to not work as expected in conjunction with text-anchor middle or end.




回答3:


Created JS fiddle example for showing labels over links in D3 Forced layout chart

See working demo in JS Fiddle: http://jsfiddle.net/bc4um7pc/

Give Id's to your path like below

var path = svg.append("svg:g").selectAll("path")
    .data(force.links())
  .enter().append("svg:path")
    .attr("class", function(d) { return "link " + d.type; })
    .attr("id",function(d,i) { return "linkId_" + i; })
    .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });

Use SVG textPath element for associating labels with above links by specifying its 'xlink:href' attribute to point to its respective link/path.

 var linktext = svg.append("svg:g").selectAll("g.linklabelholder").data(force.links());
     linktext.enter().append("g").attr("class", "linklabelholder")
     .append("text")
     .attr("class", "linklabel")
     .style("font-size", "13px")
     .attr("x", "50")
     .attr("y", "-20")
     .attr("text-anchor", "start")
     .style("fill","#000")
     .append("textPath")
    .attr("xlink:href",function(d,i) { return "#linkId_" + i;})
     .text(function(d) { 
         return "my text"; //Can be dynamic via d object 
     });



回答4:


I am using an arch as a link between nodes with a label text placed in the middle. Here is a code snippet:

    var vis = d3.select("body")
     .append("svg")
     .attr("width", 600)
     .attr("height", 400)
     .append("g");

    var force = d3.layout.force()
     .gravity(.05)
     .distance(120)
     .charge(-100)
     .size([600, 400]);
    var nodes = force.nodes(), links = force.links();

    // make an arch between nodes and a text label in the middle
    var link = vis.selectAll("path.link").data(links, function(d) {
       return d.source.node_id + "-" + d.target.node_id; });
    link.enter().append("path").attr("class", "link");

    var linktext = vis.selectAll("g.linklabelholder").data(links);
    linktext.enter().append("g").attr("class", "linklabelholder")
     .append("text")
     .attr("class", "linklabel")
     .attr("dx", 1)
     .attr("dy", ".35em")
     .attr("text-anchor", "middle")
     .text(function(d) { return "my label" });

    // add your code for nodes  ....

    force.on("tick", tick); force.start();

    function tick () {   
     // curve
     link.attr("d", function(d) {
      var dx = d.target.x - d.source.x,
      dy = d.target.y - d.source.y,
      dr = Math.sqrt(dx * dx + dy * dy);
     return "M" + d.source.x + "," + d.source.y + "A" + dr + "," 
         + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
     });    
     // link label
     linktext.attr("transform", function(d) {
      return "translate(" + (d.source.x + d.target.x) / 2 + "," 
      + (d.source.y + d.target.y) / 2 + ")"; });
     // nodes 
    node.attr("transform", function(d) {
        return "translate(" + d.x + "," + d.y + ")"; });
    }



回答5:


Just add this line:

.attr("text-anchor", "middle")

to the code after the line:

node.append("svg:text")

it should look like this:

node.append("svg:text")
.attr("text-anchor", "middle")
......


来源:https://stackoverflow.com/questions/8663844/add-text-label-onto-links-in-d3-force-directed-graph

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!