Search code examples
javascriptd3.jssvglabeld3-force-directed

Edge Labels Not Shown


I am trying to implement labeled edges on a force directed graph.

The example I use can be found here.

The relevant bits of code in the example are given here.

My code is the following:

<style>
.node {
    stroke: #fff;
    stroke-width: 0.5px;
}

.node text {
    pointer-events: none;
    font: 15px helvetica;
}

.link {
    fill: none;
    stroke: #bbb;
    stroke-width: 3.0px;
    opacity: 0.5;
}

.highlight {
    stroke: #259359;
}

</style>
<body>
<script src= "//d3js.org/d3.v3.min.js" > </script> 
<script>

    var width = 700,
        height = 550;


    var color = d3.scale.category20();

    var force = d3.layout.force()
        .linkStrength(1)
        .distance(0.01)
        .gravity(0.2)
        .charge(-500)
        .size([width, height]);

    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);
    svg.append("defs").selectAll("marker")
        .data(["end"]) // Different link/path types can be defined here
        .enter().append("marker") // This section adds in the arrows
        .attr("id", String)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 16)
        .attr("refY", 0)
        .attr("markerWidth", 3)
        .attr("markerHeight", 3)
        .attr("orient", "auto")
        .append("path")
        .attr("d", "M0,-5L10,0L0,5")
        .style("stroke", "#bbb");


    d3.json("fg.json", function(error, graph) {
        if (error) throw error;


        var nodes = graph.nodes.slice(),
            links = [],
            bilinks = [];

        graph.links.forEach(function(link) {
            var s = nodes[link.source],
                t = nodes[link.target],
                i = {}; // intermediate node
            nodes.push(i);
            links.push({
                source: s,
                target: i
            }, {
                source: i,
                target: t
            });
            bilinks.push([s, i, t]);
        });

        force
            .nodes(nodes)
            .links(links)
            .size([width, height])
            .start();

        var link = svg.selectAll(".link")
            .data(bilinks)
            .enter().append("path")
            .attr("class", "link")
            .style("marker-end", "url(#end)")
            .on("mouseover", function() {
                d3.select(d3.event.target).classed("highlight", true);
            })
            .on("mouseout", function() {
                d3.select(d3.event.target).classed("highlight", false);
            });

        var node = svg.selectAll(".node")
            .data(graph.nodes)
            .enter().append("g")
            .attr("class", "node")
            .call(force.drag)

        node.append("circle")
            .attr("r", 8)
            .style("fill", function(d) {
                return color(d.group);
            })
        node.append("text")
            .attr("dx", 15)
            .attr("dy", ".40em")
            .text(function(d) {
                return d.name
            })
            .style("stroke", "gray");


        //

        var padding = 30, // separation between circles
            radius = 1;

        function collide(alpha) {
            var quadtree = d3.geom.quadtree(graph.nodes);
            return function(d) {
                var rb = 2 * radius + padding,
                    nx1 = d.x - rb,
                    nx2 = d.x + rb,
                    ny1 = d.y - rb,
                    ny2 = d.y + rb;
                quadtree.visit(function(quad, x1, y1, x2, y2) {
                    if (quad.point && (quad.point !== d)) {
                        var x = d.x - quad.point.x,
                            y = d.y - quad.point.y,
                            l = Math.sqrt(x * x + y * y);
                        if (l < rb) {
                            l = (l - rb) / l * alpha;
                            d.x -= x *= l;
                            d.y -= y *= l;
                            quad.point.x += x;
                            quad.point.y += y;
                        }
                    }
                    return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
                });
            };
        }

    var edgepaths = svg.selectAll(".edgepath")
        .data(graph.links)
        .enter()
        .append('path')
        .attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
               'class':'edgepath',
               'fill-opacity':0,
               'stroke-opacity':0,
               'fill':'blue',
               'stroke':'red',
               'id':function(d,i) {return 'edgepath'+i}})
        .style("pointer-events", "none");

    var edgelabels = svg.selectAll(".edgelabel")
        .data(graph.links)
        .enter()
        .append('text')
        .style("pointer-events", "none")
        .attr({'class':'edgelabel',
               'id':function(d,i){return 'edgelabel'+i},
               'dx':80,
               'dy':0,
               'font-size':10,
               'fill':'#aaa'});

    edgelabels.append('textPath')
        .attr('xlink:href',function(d,i) {return '#edgepath'+i})
        .style("pointer-events", "none")
        .text(function(d,i){return 'label '+i});

        force.on("tick", function() {
            link.attr("d", function(d) {
                return "M" + d[0].x + "," + d[0].y + "S" + d[1].x + "," + d[1].y + " " + d[2].x + "," + d[2].y;
            });
            node.attr("transform", function(d) {
                return "translate(" + d.x + "," + d.y + ")";
            });

            edgepaths.attr('d', function(d) { var path='M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y;
                                               return path});       

            edgelabels.attr('transform',function(d,i){
                if (d.target.x<d.source.x){
                    bbox = this.getBBox();
                    rx = bbox.x+bbox.width/2;
                    ry = bbox.y+bbox.height/2;
                    return 'rotate(180 '+rx+' '+ry+')';
                    }
                else {
                    return 'rotate(0)';
                    }
            });
           node.each(collide(0.5));
        });


    });

</script>

The data is given below:

    {
      "nodes":[
        {"name":"alkene","group":1},
        {"name":"alkane","group":1},
        {"name":"halogenoalkane","group":2},
        {"name":"dihalogenoalkane","group":2},
        {"name":"amine","group":3},
        {"name":"alcohol","group":4},
        {"name":"ketone","group":5},
        {"name":"aldehyde","group":6},
        {"name":"hydroxynitrile","group":7},
        {"name":"ester","group":8},
        {"name":"carboxylic acid","group":9},
        {"name":"acyl chloride","group":9},
        {"name":"amide","group":10},
        {"name":"nitrile","group":11}
      ],
      "links":[
        {"source":0,"target":2,"value":2},
        {"source":0,"target":1,"value":1},
        {"source":2,"target":0,"value":8},
        {"source":0,"target":3,"value":10},
        {"source":2,"target":4,"value":10},
        {"source":5,"target":2,"value":1},
        {"source":2,"target":5,"value":1},
        {"source":6,"target":5,"value":1},
        {"source":5,"target":6,"value":1},
        {"source":7,"target":5,"value":1},
        {"source":5,"target":7,"value":1},
        {"source":7,"target":8,"value":2},
        {"source":7,"target":10,"value":1},
        {"source":10,"target":7,"value":1},
        {"source":5,"target":9,"value":3},
        {"source":10,"target":9,"value":3},
        {"source":13,"target":10,"value":5},
        {"source":10,"target":11,"value":1},
        {"source":11,"target":10,"value":1},
        {"source":11,"target":12,"value":1}
      ]
    }

Unfortunately, the labels on the graph are not visible.

The final objective is to show the corresponding value "value" on each edge.

Could you please tell me what I am doing wrong?

Thank you for your time.

UPDATE

The labels were sucessfully added to the edges by subbing

"M" + d[0].x + "," + d[0].y + "S" + d[1].x + "," + d[1].y + " " + d[2].x + "," + d[2].y

for

'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y

However, the question remains: how can the "value" datum be added instead of the labels "label i"? Moreover, how can their appearance on mouseover be implemented?

UPDATE II

The "value" datum was made to be shown by defining .data(graph.links) for textPath of edgelabels and then returning a d.value. Could you please tell me how the mouseover can be implemented? It would be nice if the "value" datum of each edge would be seen only on hover. Thank you!


Solution

  • From this example : http://jsfiddle.net/7HZcR/3/

    I have created the same view but with your data here : http://jsfiddle.net/thatOneGuy/7HZcR/515/

    So you need to implement the arrows.

    What you had previously, you couldn't log the data on mouseover as the data you brought through didn't contain it.

    So in this one I have brought the data through like so :

    var links = graph.links;
    

    Set it to your data, but since this only has indexes as source and target you need to attach nodes to the source and target so it targets correctly :

    // Compute the distinct nodes from the links.
    links.forEach(function(link) {
      link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
      link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
    });
    

    Now the data brought back would be along the lines of :

    {
       source : someNode,
       target : someNode,
       value : linkValue
    }
    

    So I have created a text output as to work out where to put the value on the link would be difficult as you have to work out the curve etc :

    var textOutput = svg.append('text').attr('class', 'textOutput')
    .attr("transform","translate(50, 100)"); 
    

    So on mouseover set the textOutput :

    .on('mouseover', function(d){
        console.log(d);
        d3.select(this).style('stroke', 'red').style('stroke-width', '5')
        textOutput.text('LINK VALUE : ' + d.value);
        })
         .on('mouseout', function(d){ 
        d3.select(this).style('stroke', '#666') .style('stroke-width', '1')
        });
    

    Hope that helps :)