0

I have a D3 force directed graph and each node displays a label. This label can vary in length from a single line of text to multiple lines of text. I have successfully created a multiline label for the nodes by referring to this post. However, I would like to vertically center the label over the node. I have not been able to reassign the label position to vertical center in a way that is effective across all label types (e.g. one line labels, two line labels, etc).

Essentially, I am unsure how to center the label as a whole while maintaining separation between each line of text in the label. Any assistance would be appreciated.

var node = svg.selectAll('.node')
    .data(force.nodes())
    .enter().append('circle')
    .attr('class', 'node')
    .attr('r', width * 0.01)
    .style('fill', function(d) {return color(d.group); })

var maxLength = 20;
var separation = 12;
var textX = 0;

var text = svg.selectAll(".text")
    .data(nodes)
    .enter().append("text")
    .style("text-anchor", "middle")
    .each(function (d) {
          var lines = wordwrap2(d.name, maxLength).split('\n');
          for (var i = 0; i < lines.length; i++) {
              d3.select(this)
                .append("tspan")
                .attr("dy", separation)
                .attr("x", textX)
                .text(lines[i]);
           }});

function wordwrap2( str, width, brk, cut ) {
    brk = brk || '\n';
    width = width || 75;
    cut = cut || false;
    if (!str) { return str; }
    var regex = '.{0,' +width+ '}(\\s|$)' + (cut ? '|.{' +width+ '}|.+$' : '|\\S+?(\\s|$)');
    return str.match( RegExp(regex, 'g') ).join( brk );
};
Community
  • 1
  • 1
Derek Smith
  • 67
  • 1
  • 8

1 Answers1

2

Select the label and translate it up half its height, using lines.length and separation:

d3.select(this).attr("transform", "translate(0," + (separation*lines.length/2*-1) + ")");

Here is a demo:

<script src="https://d3js.org/d3.v2.min.js?2.9.3"></script>
<style>
    .link {
        stroke: #aaa;
    }
    .node text {
        stroke: #333;
        cursor: pointer;
    }
    .node circle {
        stroke: #fff;
        stroke-width: 3px;
        fill: #555;
    }
</style>

<body>
    <script>
        var width = 400,
            height = 300

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

        var force = d3.layout.force()
            .distance(50)
            .charge(-3000)
            .size([width, height]);

        var json = {
            "nodes": [{
                "name": "node1"
            }, {
                "name": "node2"
            }, {
                "name": "node3"
            }, {
                "name": "node4"
            }, {
                "name": "node5 has a very very very very long long name"
            }],
            "links": [{
                "source": 0,
                "target": 1
            }, {
                "source": 0,
                "target": 2
            }, {
                "source": 0,
                "target": 3
            }, {
                "source": 0,
                "target": 4
            }]
        };

        force
            .nodes(json.nodes)
            .links(json.links)
            .start();

        var link = svg.selectAll(".link")
            .data(json.links)
            .enter().append("line")
            .attr("class", "link")
            .style("stroke-width", 2);

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

        node.append("circle")
            .attr("r", 8);
      
      var maxLength = 10;
var separation = 12;
var textX = 0;

        node.append("text")
          .attr("dominant-baseline", "central")
            .attr("dx", 12)
            .attr("dy", ".35em")
            .each(function (d) {
          var lines = wordwrap2(d.name, maxLength).split('\n');
          for (var i = 0; i < lines.length; i++) {
              d3.select(this)
                .append("tspan")
                .attr("dy", separation)
                .attr("x", textX)
                .text(lines[i]);
        
        d3.select(this).attr("transform", "translate(0," + (separation*lines.length/2*-1) + ")");
           }});
      
      
      function wordwrap2( str, width, brk, cut ) {
    brk = brk || '\n';
    width = width || 75;
    cut = cut || false;
    if (!str) { return str; }
    var regex = '.{0,' +width+ '}(\\s|$)' + (cut ? '|.{' +width+ '}|.+$' : '|\\S+?(\\s|$)');
    return str.match( RegExp(regex, 'g') ).join( brk );
};

        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 + ")";
            });
        });
    </script>
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
  • Unfortunately, this does not work for my graph. I believe it has to do with how I assigned the text labels to the nodes. I have edited the original code to include var node. Why would transform not adjust the label position in my diagram since the labels are appended to the nodes? – Derek Smith Dec 02 '16 at 07:13
  • To answer that you'll have to create a working fiddle or Plunker, I'm afraid. – Gerardo Furtado Dec 02 '16 at 07:18
  • I believe this should work: https://jsfiddle.net/dereksmith5822/eyu9tw37/ – Derek Smith Dec 02 '16 at 15:12
  • For that to work, you'll have to append both circles and texts to a group. Check your updated fiddle: https://jsfiddle.net/gerardofurtado/ynpj48f7/ – Gerardo Furtado Dec 02 '16 at 15:41
  • Thanks for providing that updated version. Unfortunately, I've spent quite a bit of time testing but I'm still having difficultly modifying my script to make this functional. I have other functions dependent on the current structure and if I modify the text variable to append to the node like your example the graph fails. I'm still learning, but is there a way that I can append the text to the circle without modifying .enter().append('text') or .enter().append('circle') in my jsFiddle? – Derek Smith Dec 03 '16 at 04:45
  • No, you can't append a text to a circle. – Gerardo Furtado Dec 03 '16 at 04:47