-1

I was trying to break long lines of y-axis labels. My bar chart is like this but with longer labels (usually more than 10 words with spaces). This is my code (it works and I see the chart, the only 'error' is the text, i load the JSON dinamically, this data is only for SO):

HTML:

<svg id='table1'></svg>

<script>
    var window_width = $(window).width();
    var main_width = (window_width / 100 ) * 80;
    var w = main_width - 400;

    var data = {
      labels: [
        'the text to be splitted 1', 'the text to be splitted 2'
      ],
      series: [
        {
          label: '2012',
          values: [4, 8]
        },
        {
          label: '2013',
          values: [12, 43]
        }
      ]
    };

    var chartWidth   = w,
    barHeight        = 25,
    groupHeight      = barHeight * data.series.length,
    gapBetweenGroups = 10,
    spaceForLabels   = 175,
    spaceForLegend   = 0;

    // Zip the series data together (first values, second values, etc.)
    var zippedData = [];
    for (var i=0; i<data.labels.length; i++) {
      for (var j=0; j<data.series.length; j++) {
        zippedData.push(data.series[j].values[i]);
      }
    }

    var chartHeight = barHeight * zippedData.length + gapBetweenGroups * data.labels.length;
    var x = d3.scale.linear()
        .domain([0, d3.max(zippedData)])
        .range([0, chartWidth]);

    var y = d3.scale.linear()
        .range([chartHeight + gapBetweenGroups, 0]);

    var yAxis = d3.svg.axis()
        .scale(y)
        .tickFormat('')
        .tickSize(0)
        .orient("left");

    // Specify the chart area and dimensions
    var chart = d3.select("#table"+ambit)
        .attr("width", spaceForLabels + chartWidth + spaceForLegend)
        .attr("height", chartHeight);

    // Create bars
    var bar = chart.selectAll("g")
        .data(zippedData)
        .enter().append("g")
        .attr("transform", function(d, i) {
          return "translate(" + spaceForLabels + "," + (i * barHeight + gapBetweenGroups * (0.5 + Math.floor(i/data.series.length))) + ")";
        });

    // Create rectangles of the correct width
    bar.append("rect")
        .attr("fill", function(d,i) { return color(i % data.series.length); })
        .attr("class", "bar")
        .attr("width", x)
        .attr("height", barHeight - 1);

    // Add text label in bar
    bar.append("text")
        .attr("class", "label_txt1")    
        .attr("x", function(d) { return x(d) - 3; })
        .attr("y", barHeight / 2)
        .attr("fill", "red")
        .attr("dy", ".35em")
        .text(function(d) { return d; });

    // Draw labels
    bar.append("text")
        .attr("class", "label_txt")
        .attr("x", function(d) { return - 10; })
        .attr("y", groupHeight / 2)
        .attr("dy", ".35em")
        .text(function(d,i) {
          if (i % data.series.length === 0)
        return data.labels[Math.floor(i/data.series.length)];
          else
        return ""});

    chart.append("g")
          .attr("class", "y axis")
          .attr("transform", "translate(" + spaceForLabels + ", " + -gapBetweenGroups/2 + ")")
          .call(yAxis);

</script>

I follow multiple examples to solve this, like: How do I include newlines in labels in D3 charts? and Adding a line break to D3 graph y-axis labels and Wrapping Long Labels.

First attempt:

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

chart.selectAll('g.y.axis text').each(insertLinebreaks); 

and:

bar.selectAll('.label_txt1').each(insertLinebreaks);

Also I tried:

bar.append("text")
    .attr("class", "label_txt1")    
    .attr("x", function(d) { return x(d) - 3; })
    .attr("y", barHeight / 2)
    .attr("fill", "red")
    .attr("dy", ".35em")
    .text(function(d) { return d; });

chart.selectAll(".label_txt1").call(wrap, 40)

function wrap(text, width) {
    alert(JSON.stringify(text));
    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);
            }
        }
    });
}

I changed text() to html() as I read at: How do I include newlines in labels in D3 charts?

In the browser I see the <text> created with class label_txt1. When I try to select the text with selectAll(".label_txt1") I get the values of the line, not the text.

Can someone help me?

Shayan Shafiq
  • 1,447
  • 5
  • 18
  • 25
JP. Aulet
  • 4,375
  • 4
  • 26
  • 39
  • The way I did this for one of my projects was by creating a function `formatLines(text, lineWidth, maxLines)`. It then puts the string into a SVG text element word by word until `elem.getComputedTextLength()` exceeds `maxWidth`. It pushes the contents of the element (minus the last word) into an array to be returned. Repeat until `array.length == maxLines` or the string is empty. I would use `.selectAll("text").data(formatLines(...)).enter().append("text")` and then offset each text element vertically by the text height. `.attr("y", function(d,i){return i*textHeight;});`. – JSBob Mar 17 '16 at 16:34
  • 1
    Possible duplicate of [How to do wordwrap for chart labels using d3.js](http://stackoverflow.com/questions/16039693/how-to-do-wordwrap-for-chart-labels-using-d3-js) – Elfayer Mar 17 '16 at 16:37

1 Answers1

1

This example that you reference is the canonical way to do wrap text when you are using an svg text node. You are simply calling it wrong (on the wrong class, you are wrapping the numbers). Simplify this to:

// Draw labels
bar.append("text")
  .attr("class", "label_txt")
  .attr("x", function(d) {
    return -10;
  })
  .attr("y", groupHeight / 2) //<-- not sure you need this
  .attr("dy", ".35em")
  .text(function(d, i) {
    if (i % data.series.length === 0)
      return data.labels[Math.floor(i / data.series.length)];
    else
      return ""
  })
  .call(wrap, 40);

Full code:

<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>

<body>
  <svg id="table1"></svg>
  <script>
    var window_width = 1000;
    var main_width = 400;
    var w = 400;

    var data = {
      labels: [
        'the text to be splitted 1', 'the text to be splitted 2'
      ],
      series: [{
        label: '2012',
        values: [4, 8]
      }, {
        label: '2013',
        values: [12, 43]
      }]
    };

    var chartWidth = w,
      barHeight = 25,
      groupHeight = barHeight * data.series.length,
      gapBetweenGroups = 10,
      spaceForLabels = 175,
      spaceForLegend = 0;

    var color = d3.scale.category10();

    // Zip the series data together (first values, second values, etc.)
    var zippedData = [];
    for (var i = 0; i < data.labels.length; i++) {
      for (var j = 0; j < data.series.length; j++) {
        zippedData.push(data.series[j].values[i]);
      }
    }

    var chartHeight = barHeight * zippedData.length + gapBetweenGroups * data.labels.length;
    var x = d3.scale.linear()
      .domain([0, d3.max(zippedData)])
      .range([0, chartWidth]);

    var y = d3.scale.linear()
      .range([chartHeight + gapBetweenGroups, 0]);

    var yAxis = d3.svg.axis()
      .scale(y)
      .tickFormat('')
      .tickSize(0)
      .orient("left");

    // Specify the chart area and dimensions
    var chart = d3.select("#table1")
      .attr("width", spaceForLabels + chartWidth + spaceForLegend)
      .attr("height", chartHeight);

    // Create bars
    var bar = chart.selectAll("g")
      .data(zippedData)
      .enter().append("g")
      .attr("transform", function(d, i) {
        return "translate(" + spaceForLabels + "," + (i * barHeight + gapBetweenGroups * (0.5 + Math.floor(i / data.series.length))) + ")";
      });

    // Create rectangles of the correct width
    bar.append("rect")
      .attr("fill", function(d, i) {
        return color(i % data.series.length);
      })
      .attr("class", "bar")
      .attr("width", x)
      .attr("height", barHeight - 1);

    // Add text label in bar
    bar.append("text")
      .attr("class", "label_txt1")
      .attr("x", function(d) {
        return x(d) - 3;
      })
      .attr("y", barHeight / 2)
      .attr("fill", "red")
      .attr("dy", ".35em")
      .text(function(d) {
        return d;
      });

    // Draw labels
    bar.append("text")
      .attr("class", "label_txt")
      .attr("x", function(d) {
        return -10;
      })
      //.attr("y", groupHeight / 2) //<-- you don't need this...
      .attr("dy", ".35em")
      .text(function(d, i) {
        if (i % data.series.length === 0)
          return data.labels[Math.floor(i / data.series.length)];
        else
          return ""
      })
      .call(wrap, 40);

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

    chart.append("g")
      .attr("class", "y axis")
      .attr("transform", "translate(" + spaceForLabels + ", " + -gapBetweenGroups / 2 + ")")
      .call(yAxis);
  </script>
</body>

</html>
Mark
  • 106,305
  • 20
  • 172
  • 230