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

Posted

技术标签:

【中文标题】将由多条线组成的标签垂直居中于 D3 力有向图中的节点上【英文标题】:Vertically center a label consisting of multiple lines over a node in D3 force directed graph 【发布时间】:2017-04-16 17:23:25 【问题描述】:

我有一个 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 );
;

【问题讨论】:

【参考方案1】:

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

【讨论】:

不幸的是,这不适用于我的图表。我相信这与我如何将文本标签分配给节点有关。我已经编辑了原始代码以包含 var 节点。由于标签已附加到节点,为什么 transform 不会调整图表中的标签位置? 恐怕要回答你必须创建一个工作小提琴或 Plunker。 我相信这应该可行:jsfiddle.net/dereksmith5822/eyu9tw37 为此,您必须将圆圈和文本都附加到组中。检查您更新的小提琴:jsfiddle.net/gerardofurtado/ynpj48f7 感谢您提供更新版本。不幸的是,我花了很多时间进行测试,但我仍然很难修改我的脚本以使其正常工作。我还有其他依赖于当前结构的函数,如果我修改文本变量以附加到节点,就像你的例子一样,图表会失败。我还在学习,但是有没有一种方法可以在不修改 .enter().append('text') 或 .enter().append('circle') 的情况下在我的 jsFiddle 中将文本附加到圆圈中?

以上是关于将由多条线组成的标签垂直居中于 D3 力有向图中的节点上的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 d3.js 在弧内沿 textPath 居中(水平和垂直)文本?

d3中力有向图的语义缩放

d3.js - 在力有向图组上添加背景矩形

D3 具有非树数据的可折叠力有向图 - 链接对齐

Snapkit centerY 约束将项目居中于中心 Y 轴上方

使用 HTML div 中的引导程序水平和垂直居中文本