Search code examples
javascriptd3.jssvgtransition

Error when transitioning an arc: <path> attribute d: Expected arc flag ('0' or '1')


I have looked everywhere for a solution, I've tried everything but nothing works!

When I click to update the data and the pie chart, the transition does not work properly and prints error (Error: attribute d: Expected arc flag ('0' or '1')) more than 100 times. Can someone please help me?

This is the beggining of the code:

// set the dimensions and margins of the graph
var width = 320
    height = 450
    margin = 40

// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = Math.min(width, height) / 2 - margin

// append the svg object to the div called 'my_dataviz'
var svg = d3.select("#recipe-graph")
  .append("svg")
    .attr("width", width)
    .attr("height", height)
  .append("g")
    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

// create 2 data_set
//Aromatic Profile
//a: woody, b: floral, c: resinous, d: herbaceus, e: citrus, f: minty, 
var data1 = {a:56.2, b: 25, c:18.8} //woody, floral, resinous
var data2 = {b: 31.2, d: 50, e:18.8} //floral, herbaceus, citus
var data3 = {d: 33.3, e:11.1, f: 55.6} //herbaceous, citrus, minty
var data4 = {b: 56.2, c: 43.8} //floral, resinous
var data5 = {b: 82.6, e: 17.4} //floral, citrus

// set the color scale
var color = d3.scaleOrdinal()
  .domain(["a", "b", "c", "d", "e", "f"])
  .range(d3.schemeDark2);

I believe the problem is somewhere below. (I'm using the latest d3.js library)

 // A function that create / update the plot for a given variable:
function update(data) {

  // Compute the position of each group on the pie:
  var pie = d3.pie()
    .value(function(d) {return d.value; })
    .sort(function(a, b) { console.log(a) ; return d3.ascending(a.key, b.key);} ) // This make sure that group order remains the same in the pie chart
  var data_ready = pie(d3.entries(data))

  // map to data
  var u = svg.selectAll("path")
    .data(data_ready)

  // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
  u
    .enter()
    .append('path')
    .merge(u)
    .transition()
    .duration(1000)
    .attr('d', d3.arc()
      .innerRadius(0)
      .outerRadius(radius)
    )
    .attr('fill', function(d){ return(color(d.data.key)) })
    .attr("stroke", "white")
    .style("stroke-width", "2px")
    .style("opacity", 1)

  // remove the group that is not present anymore
  u
    .exit()
    .remove()

}

// Initialize the plot with the first dataset
update(data1)


//Change Recipe according to button selected
function selectCategory(category, results) {
    var postResults = document.getElementById(results);
    postResults.innerHTML = category;
  }

Solution

  • This is not very well documented in D3 API, but you'll find several online examples of how to transition an arc. The biggest problem you're facing (and the reason of the errors you're getting) is that the SVG d attribute is a long and complex string, and the default interpolator provided by transition.attr, which is d3.interpolateString, doesn't know how to interpolate it.

    The solution is using attrTween with a custom interpolator. For that to work, you have to save the previous datum of each element. For that purpose, I like using local variables:

    var local = d3.local();
    
    selection.each(function(d) {
        local.set(this, d)
    })
    

    Then, in the attrTween:

    transition.attrTween('d', function(d) {
      var i = d3.interpolate(local.get(this), d);
      local.set(this, i(0));
      return function(t) {
        return arc(i(t));
      };
    })
    

    Here is your code, transitioning from data1 to data2:

    // set the dimensions and margins of the graph
    var width = 320
    height = 450
    margin = 40
    
    // The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
    var radius = Math.min(width, height) / 2 - margin
    
    var local = d3.local();
    
    // append the svg object to the div called 'my_dataviz'
    var svg = d3.select("body")
      .append("svg")
      .attr("width", width)
      .attr("height", height)
      .append("g")
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
    
    // create 2 data_set
    //Aromatic Profile
    //a: woody, b: floral, c: resinous, d: herbaceus, e: citrus, f: minty, 
    var data1 = {
      a: 56.2,
      b: 25,
      c: 18.8
    } //woody, floral, resinous
    var data2 = {
      b: 31.2,
      d: 50,
      e: 18.8
    } //floral, herbaceus, citus
    var data3 = {
      d: 33.3,
      e: 11.1,
      f: 55.6
    } //herbaceous, citrus, minty
    var data4 = {
      b: 56.2,
      c: 43.8
    } //floral, resinous
    var data5 = {
      b: 82.6,
      e: 17.4
    } //floral, citrus
    
    // set the color scale
    var color = d3.scaleOrdinal()
      .domain(["a", "b", "c", "d", "e", "f"])
      .range(d3.schemeDark2);
    
    var arc = d3.arc()
      .innerRadius(0)
      .outerRadius(radius)
    
    // A function that create / update the plot for a given variable:
    function update(data) {
    
      // Compute the position of each group on the pie:
      var pie = d3.pie()
        .value(function(d) {
          return d.value;
        })
        .sort(function(a, b) {
          return d3.ascending(a.key, b.key);
        }) // This make sure that group order remains the same in the pie chart
      var data_ready = pie(d3.entries(data))
    
      // map to data
      var u = svg.selectAll("path")
        .data(data_ready)
    
      // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
      u
        .enter()
        .append('path')
        .each(function(d) {
          local.set(this, d)
        })
        .merge(u)
        .transition()
        .duration(1000)
        .attrTween('d', function(d) {
          var i = d3.interpolate(local.get(this), d);
          local.set(this, i(0));
          return function(t) {
            return arc(i(t));
          };
        })
        .attr('fill', function(d) {
          return (color(d.data.key))
        })
        .attr("stroke", "white")
        .style("stroke-width", "2px")
        .style("opacity", 1)
    
      // remove the group that is not present anymore
      u
        .exit()
        .remove()
    
    }
    
    // Initialize the plot with the first dataset
    update(data1)
    
    
    d3.timeout(function() {
      update(data2)
    }, 1000);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>