在 D3 力导向图中的节点上垂直居中由多条线组成的标签

Vertically center a label consisting of multiple lines over a node in D3 force directed graph

我有一个 D3 力导向图,每个节点都显示一个标签。此标签的长度可以从单行文本到多行文本不等。参考这个post,我已经成功地为节点创建了一个多行标签。但是,我想将标签垂直居中放置在节点上。我无法以一种对所有标签类型(例如单行标签、两行标签等)都有效的方式将标签位置重新分配到垂直中心。

基本上,我不确定如何将标签作为一个整体居中,同时保持标签中每行文本之间的分隔。如有任何帮助,我们将不胜感激。

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

Select 标签并将其平移一半高度,使用 lines.lengthseparation:

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

这是一个演示:

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