简体   繁体   中英

D3: Repositioning Pie Chart Labels on Update

Fiddle: https://jsfiddle.net/vpkarep8/

I have three pie charts that are animating when updating with new data and I can't seem to get the labels to properly update. A fiddle is attached above.

To get the text to change, I've had to do another data join on the text (line 486-489), but then I am unable to use arc.centroid(). Narrowed it down to how I'm handing the update, but don't know the best way to handle all this. Seems centroid needs d, but to update the text needs d.values.

Any thoughts?

Tried the answers from Label outside arc (Pie chart) d3.js and How to update both the content and location of text labels on a D3 pie chart .

function drawESGraph() {
  d3.selectAll('.ES__graph__container svg')
    .remove();

  d3.selectAll('.ES__buttons button')
    .remove();

  var $container = $('.ES__graph__container');

  var width = $container.width() / 3;

  var m = 40,
      r = width / 3,
      labelr = r + 20;

  var arc = d3.svg.arc()
              .outerRadius(r)
              .innerRadius(r / 2);

  var pie = d3.layout.pie()
              .value(function(d) {
                return +d.val;
              })
             .sort(null);


  var allBrands = d3.set(data.map(function(d) {
                      return d.brand;
                    })).values();

  var buttons = d3.select('.ES__buttons')
                  .selectAll('button')
                  .data(allBrands)
                  .enter()
                  .append('button')
                  .attr('class', function(d) {
                     return d + ' button';
                  })
                 .text(function(d) {
                   return d;
                  })
                 .on('click', function(d) {
                   updateChart(d);
                 })
                .style('opacity', 0);

  buttons.transition().duration(1000)
         .style('opacity', 1);

  d3.select('.brand1.button')
    .attr('class', 'brand1 button active');

  function updateChart(brand) {
var brandData = data.filter(function(d) {
  return d.brand === brand;
});

var brandDataByYear = d3.nest()
  .key(function(d) {
    return d.year;
  })
  .entries(brandData);

var svg = d3.select('.ES__graph__container')
  .selectAll('svg')
  .data(brandDataByYear)
  .enter()
  .append('svg')
  .style('margin-top', '25px')
  .attr('width', (r + m) * 2)
  .attr('height', (r + m) * 2)
  .attr('id', function(d, i) {
    return 'pie' + i;
  })
  .append('svg:g')
  .attr('transform', 'translate(' + (r + m) + ',' + (r + m) + ')');

var pieLabel = svg.append('svg:text')
  .attr('dy', '.35em')
  .attr('text-anchor', 'middle')
  .text(function(d) {
    return d.key;
  })
  .style('fill', 'black')
  .style('opacity', 0);

pieLabel.transition().duration(1000)
  .style('opacity', 1);

var slice = svg.selectAll('.arc')
  .data(function(d) {
    return pie(d.values);
  })
  .enter()
  .append('g')
  .attr('class', 'arc');

var path = slice.append('svg:path')
  .attr('d', arc)
  .attr('class', function(d) {
    return 'arc ' + d.data.platform;
  })
  .each(function(d) {
    this._current = d;
  });

var text = slice.append('text')
  .text(function(d) {
    if (d.data.val > 0) {
      return d.data.val + '%';
    }
  })
  .attr('transform', function(d) {
    if (d.data.val > 3) {
      return 'translate(' + arc.centroid(d) + ')';
    } else {
      var c = arc.centroid(d),
        x = c[0],
        y = c[1],
        h = Math.sqrt(x * x + y * y);

      return 'translate(' + (x / h * labelr) + ',' + (y / h * labelr) + ')';
    }
  })
  .attr('text-anchor', function(d) {
    if (d.data.val < 3) {
      return (d.endAngle + d.startAngle) / 2 > Math.PI ? 'end' : 'start';
    }
  })
  .attr('dx', function(d) {
    return d.data.val > 3 ? -15 : 18;
  })
  .attr('dy', function(d) {
    return d.data.val > 3 ? 5 : 3;
  })
  .style('fill', function(d) {
    return d.data.val > 3 ? 'white' : 'black';
  })
  .attr('class', 'label');

change();

function change() {
  var newdata = brandDataByYear;

  for (x in newdata) {
    var nslice = d3.select('#pie' + x)
      .data(newdata);

    var npath = nslice.selectAll('path')
      .data(function(d) {
        return pie(d.values);
      })
      .attr('class', function(d) {
        return 'arc ' + d.data.platform;
      });

    npath.transition().duration(1000)
      .attrTween('d', arcTween);

    npath.exit()
      .remove();

    var ntext = nslice.selectAll('.label')
      .data(function(d) {
         return d.values;
       })
      .style('opacity', 0);

    ntext.transition().duration(1000)
      .style('opacity', 1)
      .text(function(d) {
        if (d.val > 0) {
          return d.val + '%';
        }
      })
      // .attr("transform", function(d) {
      //   return "translate(" + 
      //     ( (radius - 12) * Math.sin( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) +
      //     ", " +
      //     ( -1 * (radius - 12) * Math.cos( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) +
      //   ")";
      //  })
      // .style("text-anchor", function(d) {
      //    var rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle;
      //    if ( (rads > 7 * Math.PI / 4 && rads < Math.PI / 4) || (rads > 3 * Math.PI / 4 && rads < 5 * Math.PI / 4) ) {
      //      return "middle";
      //    } else if (rads >= Math.PI / 4 && rads <= 3 * Math.PI / 4) {
      //      return "start";
      //    } else if (rads >= 5 * Math.PI / 4 && rads <= 7 * Math.PI / 4) {
      //      return "end";
      //    } else {
      //      return "middle";
      //    }
      //  })

    // ntext.exit()
    //      .remove();
  }
}

function arcTween(a) {
  var i = d3.interpolate(this._current, a);

  this._current = i(0);

  return function(t) {
    return arc(i(t));
  }
}
  }

  updateChart('brand1');
}

drawESGraph();

First, don't reuse the class name arc on two different objects; you have it on the parent g for each slice and the child path .

Second, rebind the data to the parent g so that both the path and the text can use it.

 var slice = svg.selectAll('.slice') //<-- for the g
   .data(function(d) {
     return pie(d.values);
   })
   .enter()
   .append('g')
   .attr('class', 'slice');

Later in update:

var npath = nslice.selectAll('.slice') //<-- rebind to `g`
  .data(function(d) {
    return pie(d.values);
  });

npath
  .select("path")
  .attr('class', function(d) {
    return 'arc ' + d.data.platform;
  })
  .transition().duration(1000)
  .attrTween('d', arcTween); //<-- update the paths

npath.exit()
  .remove(); //<-- remove the whole g

npath.select("text") //<-- update the text
  .transition()
  .duration(1000)
  .style('opacity', 1)
  .text(function(d) {
    if (d.data.val > 0) {
      return d.data.val + '%';
    }
  })
  .attr('transform', function(d) {
    console.log(arc.centroid(d)); //<-- you can now use centroid
  });

EDITS

My bad, I should have caught that. The problem is you are combining .selectAll and .data with an explicit loop. d3 data binding is all about it doing the looping based on the data. So, how do we fix it?

We start with correct data binding from the get go.

var svg = d3.select('.ES__graph__container')
  .selectAll('svg')
  .data(brandDataByYear)
  .enter()
  .append('svg')
  .style('margin-top', '25px')
  .attr('width', (r + m) * 2)
  .attr('height', (r + m) * 2)
  .attr('class', 'pie') //<-- give each svg a class, not id
  ...

Then in your update:

// for (x in newdata) { //<-- NO EXPLICIT LOOPING!
var nslice = d3.selectAll('.pie')
  .data(newdata);

Updated full code:

 <!DOCTYPE html> <html> <head> <script data-require="jquery@2.1.4" data-semver="2.1.4" src="https://code.jquery.com/jquery-2.1.4.js"></script> <script src="//d3js.org/d3.v3.js" charset="utf-8"></script> <style> .arc.platform1 { fill: #e74341; } .arc.platform2 { fill: #3c5a96; } .arc.platform3 { fill: #3c94d1; } .arc.platform4 { fill: #837369; } </style> </head> <body> <div class="ES__buttons"></div> <div class="ES__graph__container"></div> <script> function drawESGraph() { d3.selectAll('.ES__graph__container svg') .remove(); d3.selectAll('.ES__buttons button') .remove(); var $container = $('.ES__graph__container'); var width = $container.width() / 3; var m = 40, r = width / 3, labelr = r + 20; var arc = d3.svg.arc() .outerRadius(r) .innerRadius(r / 2); var pie = d3.layout.pie() .value(function(d) { return +d.val; }) .sort(null); var data = [{ brand: 'brand1', platform: 'platform1', year: '2012-2013', val: 85.8 }, { brand: 'brand1', platform: 'platform2', year: '2012-2013', val: 14 }, { brand: 'brand1', platform: 'platform3', year: '2012-2013', val: 0.2 }, { brand: 'brand1', platform: 'platform4', year: '2012-2013', val: 0 }, { brand: 'brand1', platform: 'platform1', year: '2013-2014', val: 91 }, { brand: 'brand1', platform: 'platform2', year: '2013-2014', val: 8 }, { brand: 'brand1', platform: 'platform3', year: '2013-2014', val: 1 }, { brand: 'brand1', platform: 'platform4', year: '2013-2014', val: 0 }, { brand: 'brand1', platform: 'platform1', year: '2014-2015', val: 77 }, { brand: 'brand1', platform: 'platform2', year: '2014-2015', val: 8 }, { brand: 'brand1', platform: 'platform3', year: '2014-2015', val: 2 }, { brand: 'brand1', platform: 'platform4', year: '2014-2015', val: 13 }, { brand: 'brand2', platform: 'platform1', year: '2012-2013', val: 76.9 }, { brand: 'brand2', platform: 'platform2', year: '2012-2013', val: 23 }, { brand: 'brand2', platform: 'platform3', year: '2012-2013', val: 0.1 }, { brand: 'brand2', platform: 'platform4', year: '2012-2013', val: 0 }, { brand: 'brand2', platform: 'platform1', year: '2013-2014', val: 87.6 }, { brand: 'brand2', platform: 'platform2', year: '2013-2014', val: 7 }, { brand: 'brand2', platform: 'platform3', year: '2013-2014', val: 0.4 }, { brand: 'brand2', platform: 'platform4', year: '2013-2014', val: 5 }, { brand: 'brand2', platform: 'platform1', year: '2014-2015', val: 55 }, { brand: 'brand2', platform: 'platform2', year: '2014-2015', val: 7 }, { brand: 'brand2', platform: 'platform3', year: '2014-2015', val: 1 }, { brand: 'brand2', platform: 'platform4', year: '2014-2015', val: 37 }, { brand: 'brand3', platform: 'platform1', year: '2012-2013', val: 72.9 }, { brand: 'brand3', platform: 'platform2', year: '2012-2013', val: 24 }, { brand: 'brand3', platform: 'platform3', year: '2012-2013', val: 0.1 }, { brand: 'brand3', platform: 'platform4', year: '2012-2013', val: 3 }, { brand: 'brand3', platform: 'platform1', year: '2013-2014', val: 76 }, { brand: 'brand3', platform: 'platform2', year: '2013-2014', val: 10 }, { brand: 'brand3', platform: 'platform3', year: '2013-2014', val: 1 }, { brand: 'brand3', platform: 'platform4', year: '2013-2014', val: 13 }, { brand: 'brand3', platform: 'platform1', year: '2014-2015', val: 56 }, { brand: 'brand3', platform: 'platform2', year: '2014-2015', val: 12 }, { brand: 'brand3', platform: 'platform3', year: '2014-2015', val: 1 }, { brand: 'brand3', platform: 'platform4', year: '2014-2015', val: 31 }, { brand: 'brand4', platform: 'platform1', year: '2012-2013', val: 1 }, { brand: 'brand4', platform: 'platform2', year: '2012-2013', val: 63 }, { brand: 'brand4', platform: 'platform3', year: '2012-2013', val: 1 }, { brand: 'brand4', platform: 'platform4', year: '2012-2013', val: 35 }, { brand: 'brand4', platform: 'platform1', year: '2013-2014', val: 0 }, { brand: 'brand4', platform: 'platform2', year: '2013-2014', val: 22 }, { brand: 'brand4', platform: 'platform3', year: '2013-2014', val: 1 }, { brand: 'brand4', platform: 'platform4', year: '2013-2014', val: 77 }, { brand: 'brand4', platform: 'platform1', year: '2014-2015', val: 0 }, { brand: 'brand4', platform: 'platform2', year: '2014-2015', val: 14 }, { brand: 'brand4', platform: 'platform3', year: '2014-2015', val: 1 }, { brand: 'brand4', platform: 'platform4', year: '2014-2015', val: 85 }] var allBrands = d3.set(data.map(function(d) { return d.brand; })).values(); var buttons = d3.select('.ES__buttons') .selectAll('button') .data(allBrands) .enter() .append('button') .attr('class', function(d) { return d + ' button'; }) .text(function(d) { return d; }) .on('click', function(d) { updateChart(d); }) .style('opacity', 0); buttons.transition().duration(1000) .style('opacity', 1); d3.select('.brand1.button') .attr('class', 'brand1 button active'); function updateChart(brand) { var brandData = data.filter(function(d) { return d.brand === brand; }); var brandDataByYear = d3.nest() .key(function(d) { return d.year; }) .entries(brandData); var svg = d3.select('.ES__graph__container') .selectAll('svg') .data(brandDataByYear) .enter() .append('svg') .style('margin-top', '25px') .attr('width', (r + m) * 2) .attr('height', (r + m) * 2) .attr('class', 'pie') .append('svg:g') .attr('transform', 'translate(' + (r + m) + ',' + (r + m) + ')'); var pieLabel = svg.append('svg:text') .attr('dy', '.35em') .attr('text-anchor', 'middle') .text(function(d) { return d.key; }) .style('fill', 'black') .style('opacity', 0); pieLabel.transition().duration(1000) .style('opacity', 1); var slice = svg.selectAll('.slice') .data(function(d) { return pie(d.values); }) .enter() .append('g') .attr('class', 'slice'); var path = slice.append('svg:path') .attr('d', arc) .attr('class', function(d) { return 'arc ' + d.data.platform; }) .each(function(d) { this._current = d; }); var text = slice.append('text') .text(function(d) { if (d.data.val > 0) { return d.data.val + '%'; } }) .attr('transform', function(d) { if (d.data.val > 3) { return 'translate(' + arc.centroid(d) + ')'; } else { var c = arc.centroid(d), x = c[0], y = c[1], h = Math.sqrt(x * x + y * y); return 'translate(' + (x / h * labelr) + ',' + (y / h * labelr) + ')'; } }) .attr('text-anchor', function(d) { if (d.data.val < 3) { return (d.endAngle + d.startAngle) / 2 > Math.PI ? 'end' : 'start'; } }) .attr('dx', function(d) { return d.data.val > 3 ? -15 : 18; }) .attr('dy', function(d) { return d.data.val > 3 ? 5 : 3; }) .style('fill', function(d) { return d.data.val > 3 ? 'white' : 'black'; }) .attr('class', 'label'); change(); function change() { var newdata = brandDataByYear; // for (x in newdata) { var nslice = d3.selectAll('.pie') .data(newdata); //return; var npath = nslice.selectAll('.slice') .data(function(d) { console.log(d); return pie(d.values); }); npath .select("path") .attr('class', function(d) { return 'arc ' + d.data.platform; }) .transition().duration(1000) .attrTween('d', arcTween); npath.exit() .remove(); npath.select("text") .transition() .duration(1000) .style('opacity', 1) .text(function(d) { if (d.data.val > 0) { return d.data.val + '%'; } }) .attr('transform', function(d) { if (d.data.val > 3) { return 'translate(' + arc.centroid(d) + ')'; } else { var c = arc.centroid(d), x = c[0], y = c[1], h = Math.sqrt(x * x + y * y); return 'translate(' + (x / h * labelr) + ',' + (y / h * labelr) + ')'; } }); // .attr("transform", function(d) { // return "translate(" + // ( (radius - 12) * Math.sin( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) + // ", " + // ( -1 * (radius - 12) * Math.cos( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) + // ")"; // }) // .style("text-anchor", function(d) { // var rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle; // if ( (rads > 7 * Math.PI / 4 && rads < Math.PI / 4) || (rads > 3 * Math.PI / 4 && rads < 5 * Math.PI / 4) ) { // return "middle"; // } else if (rads >= Math.PI / 4 && rads <= 3 * Math.PI / 4) { // return "start"; // } else if (rads >= 5 * Math.PI / 4 && rads <= 7 * Math.PI / 4) { // return "end"; // } else { // return "middle"; // } // }) // ntext.exit() // .remove(); } // } function arcTween(a) { var i = d3.interpolate(this._current, a); this._current = i(0); return function(t) { return arc(i(t)); } } } updateChart('brand1'); } drawESGraph(); </script> </body> </html> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM