如何在 D3 图表的标签中包含换行符?

Posted

技术标签:

【中文标题】如何在 D3 图表的标签中包含换行符?【英文标题】:How do I include newlines in labels in D3 charts? 【发布时间】:2012-10-25 20:12:10 【问题描述】:

我正在使用 D3 生成条形图(我改编了来自 this example 的代码)。我在 x 轴上使用的标签每个都有几个词长,由于这会使所有标签重叠,我需要将这些标签跨行打断。 (如果我可以用换行符替换每个标签中的所有空格就可以了。)

我最初尝试用文字换行符 (
) 替换空格并在标签的 <text> 元素上设置 xml:space="preserve"。不幸的是,事实证明 SVG 不尊重这个属性。接下来,我尝试将每个单词包装在 <tspan> 中,以便以后设置样式。我通过这个函数传递了每个标签:

function (text) 
    return '<tspan>' + text.replace(/ /g, '</tspan><tspan>') + '</tspan>';

但这只是将文字 &lt;tspan&gt;s 放入输出中。如何将我的文本标签包裹在 tspans 中(或做其他事情),这样我的标签就不会重叠?

【问题讨论】:

这是一个重复的问题吗? ***.com/questions/4991171/… @PaulArmstrong 不是真的,OP 需要插入 tspan 的非自动换行文本和 foreignObject (这是一种矫枉过正且不受 IE9(和 10?)支持。 【参考方案1】:

我最终使用以下代码将每个 x 轴标签跨行分开:

var insertLinebreaks = function (d) 
    var el = d3.select(this);
    var words = d.split(' ');
    el.text('');

    for (var i = 0; i < words.length; i++) 
        var tspan = el.append('tspan').text(words[i]);
        if (i > 0)
            tspan.attr('x', 0).attr('dy', '15');
    
;

svg.selectAll('g.x.axis g text').each(insertLinebreaks);

请注意,这假定标签已经创建。 (如果您关注the canonical histogram example,那么标签将按照您需要的方式设置。)也不存在任何真正的换行逻辑;该函数将每个空格转换为换行符。这很符合我的目的,但您可能需要编辑 split() 行,以便更智能地将字符串的各个部分划分为行。

【讨论】:

这正是我所需要的,谢谢。对于那些可能处于类似情况的人,我应该指出 d 这里是一个数据点,不是您正在格式化的字符串,所以需要 .split()(至少对我来说)更改为d.description.split("\n"); 效果很好。对于从事此工作的人,请确保您拥有正确的元素。 在@DanielQuinn 建议的换行符之外的另一个改进:使用d3.select(this).text() 来获取实际的元素文本而不是绑定的数据值。这有两个原因:首先,您的轴值可能是数字而不是字符串(在这种情况下此代码会失败),其次,我的改进允许通过轴生成器上的 tickFormat 函数插入自定义文本。跨度> 对于其他使用它的人,发现我需要将变量 words 设置为 d.toString().split(' ')。 这对我使用 v4 有效。我在日期格式中添加了一个管道,因为这是我希望我的换行符出现的地方:var dateFormat = d3.timeFormat("%b %e|%H:%M"); ... var el = d3.select(this); var words = el.text().split('|'); el.text('');【参考方案2】:

SVG 文本元素不支持文本换行,所以有两种选择:

将文本拆分为多个 SVG 文本元素 在 SVG 上使用覆盖 html div

查看 Mike Bostock 对此here 的评论。

【讨论】:

【参考方案3】:

我发现有用的是使用“foreignObject”标签而不是文本或 tspan 元素。这允许 HTML 的简单嵌入,允许单词自然中断。需要注意的是满足特定需求的对象的整体尺寸:

var myLabel = svg.append('foreignObject')
    .attr(
        height: 50,
        width: 100, // dimensions determined based on need
        transform: 'translate(0,0)' // put it where you want it...
     )
     .html('<div class"style-me"><p>My label or other text</p></div>');

您在此对象中放置的任何元素稍后都可以使用 d3.select/selectAll 获取,以动态更新文本值。

【讨论】:

【参考方案4】:

环顾四周后,我发现 Mike Bostock 提供了一种解决方案,让您可以将文本环绕。

http://bl.ocks.org/mbostock/7555321

在我的代码上实现它(我使用的是折叠树图)。我只是复制了“wrap”方法。

然后附加以下内容

    // Standard code for a node    
    nodeEnter.append("text")
        .attr("x", function(d)  return d.children || d._children ? -10 : 10; )
        .attr("dy", ".35em")
        .text(function(d)  return d.text; )
        // New added line to call the function to wrap after a given width
        .call(wrap, 40);

我认为这不适用于强制导向、条形或任何其他模式

修改:

我已将 wrap 函数修改为以下内容,适用于阅读此内容并使用可折叠图表的任何人。 “x”属性中的更改正确设置了对齐方式,递增行号是在单独的行上执行的,因为原始代码中指出了问题,并且“y”已硬设置为零,否则会出现行间距增加的问题每一行。

function wrap(text, width) 
    text.each(function() 
        var text = d3.select(this),
        words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        y = text.attr("y"),
        dy = parseFloat(text.attr("dy")),
        lineHeight = 1.1, // ems
        tspan = text.text(null).append("tspan").attr("x", function(d)  return d.children || d._children ? -10 : 10; ).attr("y", y).attr("dy", dy + "em");     
        while (word = words.pop()) 
            line.push(word);
            tspan.text(line.join(" "));
            var textWidth = tspan.node().getComputedTextLength();
            if (tspan.node().getComputedTextLength() > width) 
                line.pop();
                tspan.text(line.join(" "));
                line = [word];
                ++lineNumber;
                tspan = text.append("tspan").attr("x", function(d)  return d.children || d._children ? -10 : 10; ).attr("y", 0).attr("dy", lineNumber * lineHeight + dy + "em").text(word);
            
        
    );

【讨论】:

【参考方案5】:

还有this关于包装长标签的答案。

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.bar 
  fill: steelblue;


.bar:hover 
  fill: brown;


.title 
  font: bold 14px "Helvetica Neue", Helvetica, Arial, sans-serif;


.axis 
  font: 10px sans-serif;


.axis path,
.axis line 
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;


.x.axis path 
  display: none;


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

var margin = top: 80, right: 180, bottom: 80, left: 180,
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1, .3);

var y = d3.scale.linear()
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .ticks(8, "%");

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

d3.tsv("data.tsv", type, function(error, data) 
  x.domain(data.map(function(d)  return d.name; ));
  y.domain([0, d3.max(data, function(d)  return d.value; )]);

  svg.append("text")
      .attr("class", "title")
      .attr("x", x(data[0].name))
      .attr("y", -26)
      .text("Why Are We Leaving Facebook?");

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis)
    .selectAll(".tick text")
      .call(wrap, x.rangeBand());

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis);

  svg.selectAll(".bar")
      .data(data)
    .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d)  return x(d.name); )
      .attr("width", x.rangeBand())
      .attr("y", function(d)  return y(d.value); )
      .attr("height", function(d)  return height - y(d.value); );
);

function wrap(text, width) 
  text.each(function() 
    var text = d3.select(this),
        words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        lineHeight = 1.1, // ems
        y = text.attr("y"),
        dy = parseFloat(text.attr("dy")),
        tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
    while (word = words.pop()) 
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width) 
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
      
    
  );


function type(d) 
  d.value = +d.value;
  return d;


</script>

和数据文件“data.tsv”:

name    value
Family in feud with Zuckerbergs .17
Committed 671 birthdays to memory   .19
Ex is doing too well    .10
High school friends all dead now    .15
Discovered how to “like” things mentally    .27
Not enough politics .12

【讨论】:

【参考方案6】:

使用&lt;tspan&gt;

在 nv.d3 中

nv.models.axis = function()

...

      .select('text')
            .attr('dy', '0em')
            .attr('y', -axis.tickPadding())
            .attr('text-anchor', 'middle')
            .text(function(d,i) 
              var v = fmt(d);
              return ('' + v).match('NaN') ? '' : v;
            );

将所有出现的 .text( 更改为 .html(

【讨论】:

以上是关于如何在 D3 图表的标签中包含换行符?的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式匹配文本中包含换行符等空白字符如何匹配

将 mysql 表导出到 csv 中,其中数据在列中包含换行符

Node中包含换行符的环境变量?

kendo-ui图表的类别标签中的换行符

有没有办法在没有换行符的情况下使用 div 标签,或者有​​替代方法吗?

<input> 标签的换行