SuperUser01
SuperUser01

Reputation: 319

d3.js: align link-label to link itself

https://i.sstatic.net/yDYD3.png

Hello Community! That in the picture is what I'd like to achieve... This is my working code so far (associated to the left half of the picture):

    var diagramElement = this.getElement(); 
    var links = eval(this.getState().string);
    // string = e.g. "[{source: \"Germany\", target: \"Europe\", property: \"is type\", type: \"connection\"}]"
    var width = 800, height = 300;

    var svg = d3.select(diagramElement).append("svg")
    .attr("width", width)
    .attr("height", height);

    var force = d3.layout.force()
    .size([width, height])
    .linkDistance(100)
    .charge(-400)
    .on("tick", tick);

    var link;
    var circle;
    var text;
    var nodes = {};
    var linktext;

   svg.append("defs")
    .selectAll("marker").data(["connection", "new"]).enter()
    .append("marker")
    .attr("id", function(d) { return d; })
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 15)
    .attr("refY", 0)
    .attr("markerWidth", 8)
    .attr("markerHeight", 8)
    .attr("orient", "auto")
    .append("svg:path")
    .attr("d", "M0,-5L10,0L0,5");

    this.onStateChange = function() {

        svg.selectAll("g").remove();
        nodes = {};
        links = [];

        links = eval(this.getState().string);
        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});
        });

        force
            .nodes(d3.values(nodes))
            .links(links)
            .on("tick", tick)
            .start();

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


        circle = svg.append("g").selectAll("circle")
            .data(force.nodes())
            .enter()
            .append("circle")
            .attr("r", 8)
            .call(force.drag);

        text = svg.append("g").selectAll("text")
            .data(force.nodes())
            .enter()
            .append("text")
            .style("font-size","15px")
            .attr("x", 10)
            .attr("y", ".42em")
            .text(function(d) {return d.name;});

            // this is where the linktext is aligned relatively to the links
            // must change something here 
        linktext = svg.append("g").selectAll("text")
            .data(force.links())
            .enter()
            .append("text")
            .style("font-size","15px")
            .attr("dx", 1)
            .attr("dy", ".35em")
            .attr("text-anchor", "middle")
            .text(function(d) {return d.type;});


    };

    function tick() {

        circle.attr("cx", function(d) { return d.x = Math.max(12, Math.min(798 - 12, d.x)); })
              .attr("cy", function(d) { return d.y = Math.max(12, Math.min(279 - 12, d.y)); });

        text.attr("transform", transform);

        // linktext position update     
        linktext.attr("transform", function(d) {return "translate(" + (d.source.x + d.target.x) / 2 + "," + (d.source.y + d.target.y) / 2 + ")";});   

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

So I've appended the text successfully to the links, but now how would I position in slightly above the link, in link direction, as shown in my attached picture?

I appreciate any help

Upvotes: 0

Views: 1197

Answers (1)

OldDew
OldDew

Reputation: 67

Inside on('tick') after determining the position of your links you can also apply some transformations like so:

linkLabels
    .attr('transform', d => {
        // Adding to the transform. Multiple transforms would just get overwritten.
        let transformation = ``

        // The text position should be at the middle of the line
        const x = (d.source.x + d.target.x) / 2
        const y = (d.source.y + d.target.y) / 2
        transformation += `translate(${x}, ${y}) `
        
        // Make sure the text is always upright
        if(d.source.x > d.target.x) {
            transformation += `rotate(180) `
        }
        // Aligning text to the slope.
        // The formula for the tangent of a slope is (y2 - y1) / (x2 - x1) 
        // where (xi, yi) are the coordinates of the line endings
        const angle = Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x)
        transformation += `rotate(${angle * 180/Math.PI}) `

        return transformation
    })

Upvotes: 0

Related Questions