How do I include newlines in labels in D3 charts?

Solution 1:

I ended up using the following code to break each x-axis label across lines:

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

Note that this assumes that the labels have already been created. (If you follow the canonical histogram example then the labels will have been set up in just the way you need.) There also isn't any real line-breaking logic present; the function converts every space into a newline. This fits my purposes fine but you may need to edit the split() line to be smarter about how it partitions the parts of the string into lines.

Solution 2:

SVG text element does not support text-wrapping, so there are two options:

  • split the text into multiple SVG text elements
  • use an overlay HTML div on top of the SVG

See Mike Bostock's comment on this here.

Solution 3:

Something I've found to be useful is using a 'foreignObject' tag instead of text or tspan elements. This allows for the simple embedding of HTML, allowing for words to break naturally. The caveat being the overall dimensions of the object meeting specific needs:

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

Whatever elements you place inside of this object can later be obtained using d3.select/selectAll to update text values dynamically as well.

Solution 4:

Having looked around I found that Mike Bostock has provided a solution enabling you to wrap text round.

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

To implement it on my code (I'm using collapsed tree diagram). I simply copied the "wrap" method.

Then appended the following

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

I don't see any reason this should not work for a force-directed, bar or any other pattern

Amendment :

I've modified the wrap function to the following for anyone who reads this and is using collapisible graph. The change in the "x" attribute sets the allignment correctly, incrementing linenumber was performed on a separate line as issues were noted in the original code and "y" has been hard set to zero otherwise issues would occur in which the line spacing increased with each line.

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