Search code examples
javascriptsvgd3.js

Update from D3 v3, no errors but output SVG path not visible


I am working with code built on D3 version 3 and am attempting to update it. I've done all the more obvious updates -

  • d3.layout.pie() => d3.pie()
  • d3.svg.arc() => d3.arc()
  • d3.scale.ordinal() => d3.scaleOrdinal()

but I end up with an SVG with blank paths <path class="slice" style="fill: rgb(0, 128, 0);"></path>

Here's the JS:

const width = 350, height = 200, radius = Math.min(width, height) / 2;

const svg = d3.select(".donut")
    .append("svg")
    .append("g")

svg.append("g")
    .attr("class", "slices");

const pie = d3.pie()
    .sort(null)
    .value((d) => d.value);

const arc = d3.arc()
    .outerRadius(radius * .9)
    .innerRadius(radius * 0.6);

svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

const key = (d) => d.data.label;

const color = d3.scaleOrdinal()
    .domain(['a','b','c'])
    .range(["#FF0000", "#FFF000", "#008000"]);

const generate = (data) => {
    const dataSum = d3.sum(data, (d) => d.value);

    /* ------- PIE SLICES -------*/
    const slice = svg.select(".slices").selectAll("path.slice")
    .data(pie(data), key);

    slice.enter()
        .insert("path")
        .style("fill", (d) => color(d.data.label))
        .attr("class", "slice");

    slice       
        .transition().duration(1000)
        .attrTween("d", function(d) {
            this._current = this._current || d;
            const interpolate = d3.interpolate(this._current, d);
            this._current = interpolate(0);
            return function(t) {
                return arc(interpolate(t));
            };
        })

    slice.exit()
        .remove();
};

const donutData = [
    {label: 'a', value: 80},
    {label: 'b', value: 10},
    {label: 'c', value: 10},
];

generate(donutData);

The CSS is quite simple and shouldn't be the problem:

.donut { 
  width: 960px;
  height: 500px;
}

svg {
  width: 100%;
  height: 100%;
}
    
path.slice{
  stroke-width:2px;
}

My impression is that there is an issue with the slice.enter() part (missing a .merge()?) or, more likely, the whole interpolate() section before the exit. Unfortunately, nothing in the documentation, or at least my interpretation of it, was helpful. You can view the current (old, version 3) code in action on CodePen.

Additionally, if there is a better way to generate a three-section donut chart from data that doesn't use D3, I'm open to those suggestions too. Thanks!


Solution

  • i have rewritten your section slice.enter() (add merge):

          slice.enter()
              .insert("path")
              .style("fill", (d) => color(d.data.label))
              .attr("class", "slice")
              .merge(slice)
              .transition().duration(1000)
              .attrTween("d", function (d) {
                  const interpolate = d3.interpolate(this._current, d);
                  this._current = interpolate(0);
                  return function (t) {
                      return arc(interpolate(t));
                  };
              });
    

          const width = 350,
              height = 200,
              radius = Math.min(width, height) / 2;
    
          const svg = d3.select(".donut")
              .append("svg")
              .attr("width", width)
              .attr("height", height)
              .append("g")
              .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
    
          svg.append("g")
              .attr("class", "slices");
    
          const pie = d3.pie()
              .sort(null)
              .value((d) => d.value);
    
          const arc = d3.arc()
              .outerRadius(radius * 0.9)
              .innerRadius(radius * 0.6);
    
          const key = (d) => d.data.label;
    
          const color = d3.scaleOrdinal()
              .domain(['a', 'b', 'c'])
              .range(["#FF0000", "#FFF000", "#008000"]);
    
          const generate = (data) => {
              const dataSum = d3.sum(data, (d) => d.value);
    
              const slice = svg.select(".slices").selectAll("path.slice")
                  .data(pie(data), key);
    
              slice.enter()
                  .insert("path")
                  .style("fill", (d) => color(d.data.label))
                  .attr("class", "slice")
                  .merge(slice)
                  .transition().duration(1000)
                  .attrTween("d", function (d) {
                      const interpolate = d3.interpolate(this._current, d);
                      this._current = interpolate(0);
                      return function (t) {
                          return arc(interpolate(t));
                      };
                  });
    
              slice.exit()
                  .remove();
          };
    
          const donutData = [
              { label: 'a', value: 80 },
              { label: 'b', value: 10 },
              { label: 'c', value: 10 },
          ];
    
          generate(donutData);
          .donut {
              width: 960px;
              height: 500px;
          }
    
          svg {
              width: 100%;
              height: 100%;
          }
    
          path.slice {
              stroke-width: 2px;
          }
        <div class="donut"></div>
    
      
      <!-- === Vendor JS Files ================================= -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>