Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active Feb 23, 2022
Embed
What would you like to do?
Wrapping Long Labels
license: gpl-3.0

This example, using satirical data from The Onion, demonstrates how to wrap long axis labels to fit on multiple lines.

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
<!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="//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>
@jbetley
Copy link

jbetley commented Feb 23, 2022

Re: hidden text. In my case, the text was hidden in a 'tab-pane.' My solution, also ugly, was to remove the class immediately before the getComputedTextLength() calculation and then restore the class immediately after. Would love to hear if there is a better way to do this now.

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
          x = text.attr("x"),
          y = text.attr("y"),
          dy = parseFloat(text.attr("dy") || 0),
          tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");

       var tabs = d3.selectAll('div.tab-pane').classed('tab-pane', false); // remove tab-pane class
    
       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", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); // add 'x'
         }
       }
      tabs.classed('tab-pane', true); // add tab-pane class back
    });
  }

Original idea here: https://stackoverflow.com/questions/27886734/how-to-word-wrap-legend-labels-in-d3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment