Steve Chacko
Steve Chacko

Reputation: 135

How do I split labels for my donut chart to multiple lines using d3.js?

I am displaying labels with my donut chart like this;

this.graphGroup
      .selectAll('allLabels')
      .data(data_ready)
      .enter()
      .append('text').style('font-size', '24px')
      .text(d => {
        return d.data.name;   //LINE 1
      })
      .attr('transform',`${calculatedValue}`)
      .style('text-anchor', 'start');

I want to use this answer How do I include newlines in labels in D3 charts? but adding the code after this addition of labels like this,

this.graphGroup
    .selectAll('text')
    .each
    ( function (d) {
      var el = this.d3.select(this);
      console.log(el);
      var words = d.name.split(' ');
      console.log(words);
      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');
      }
    });

When I use the each() on the first selection I get an error saying the selection is undefined. I tried adding the code on line 1 but I can only pass text into a d3.text().

How can I break the labels correctly?

EDIT: I also had an error saying my local variable d3 was undefined, but it worked while creating the graphGroup and all the other selections, I then imported select from d3 directly.

Upvotes: 2

Views: 737

Answers (1)

Steve Chacko
Steve Chacko

Reputation: 135

I figured out the answer; each() does not work if the data() function has been called on that selection, so the answer directly from the linked post will not work.

The other option is to use the call() on every text element made, like this;

this.graphGroup
      .selectAll('allLabels')
      .data(data_ready)
      .enter()
      .append('text').style('font-size', '24px')
      .attr('transform',`${calculatedValue}`)
      .style('text-anchor', 'start')
      .text(d => {
        return d.data.name;
      })
      .call(lineBreak, 40);

Where lineBreak is another function I have defined, the first parameter that gets passed into the function will be a reference to the looped element in the case, the different text elements.

the linebreak function:

function lineBreak(text, width) {
      text.each(function () {
        var el = d3.select(this);
        let words = el.text().split(' ');
        let wordsFormatted = [];

        let string = '';
        for (let i = 0; i < words.length; i++) {
          if (words[i].length + string.length <= width) {
            string = string + words[i] + ' ';
          }
          else {
            wordsFormatted.push(string);
            string = words[i] + ' ';
          }
        }
        wordsFormatted.push(string);

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

Will optimize this function and edit later. Thanks!

Upvotes: 1

Related Questions