0

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();
Community
  • 1
  • 1
BastionGamma
  • 321
  • 3
  • 17
  • This is exactly the same problem as in the second question you've linked to. You're not updating the data bound to the `text` elements. – Lars Kotthoff Dec 16 '15 at 17:47

1 Answers1

1

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>
Mark
  • 106,305
  • 20
  • 172
  • 230
  • THANK YOU! You're a life-saver. I didn't realize that the .arc I was selecting in change() was a different thing. No wonder the data wasn't being properly inherited. – BastionGamma Dec 16 '15 at 18:38
  • Spoke too soon. All the pie charts are updating with the same data, making them identical. Will dig a little further. – BastionGamma Dec 16 '15 at 18:56