简体   繁体   中英

D3JS: Animate a 2 arc donut

I'm kind of new to D3JS and I've made a 2 arc Donut (my data has only two values). I'm trying to figure out how to animate them so that the largest path get "self drawn" first and then the smallest one gets self drawn as well.

I know this can be achieved setting the start angle to 0 and then use the D3js attrTween() and play with the desired final angle. but since I can not declare the final angle by myself and it depends on the data, and due to my very little knowledge about djse I haven´t been able to achieve it.

This is my JS code:

var width = 100,
  height = 100,
  radius = (Math.min(width, height) / 2);

function drawDonut(dataa, divchart) {
  var sym = "%"

  var color = ["#00338D", "#BC204B"];

  var pie = d3.pie()
    .value(function(d) {
      return d
    })(dataa);

  var arc = d3.arc()
    .outerRadius(radius - 10)
    .innerRadius(radius - (radius / 1.9));

  var labelArc = d3.arc()
    .outerRadius(radius - 31)
    .innerRadius(radius - 31);

  var svg = d3.select(divchart)
    .append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(" + 50 + "," + 50 + ")");

  var g = svg.selectAll("arc")
    .data(pie)
    .enter().append("g")
    .attr("class", "arc");

  g.append("path")
    .attr("d", arc)
    .data(color)
    .style("fill", function(d) {
      return d
    });

  g.append("g")
    .attr("transform", "translate(-17,-17) scale(0.7)")
    .html(myGroup);    
}

Using the data from your previous post and attrTween as a reference, here's a snippet with the large-path-first animation applied to

 var myGroup = '<g><path class="st0" d="M15.6,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C13.2,9.7,14.2,10.9,15.6,10.9L15.6,10.9z"/><path class="st0" d="M18.6,11.6h-1.2l-1.8,5.5l-1.8-5.5h-1.2c-1.3,0-2.4,1.2-2.4,2.7v13h2.4l1.2,16.4h3.6l1.2-16.4H21v-13C21,12.8,19.9,11.6,18.6,11.6L18.6,11.6z"/><path class="st0" d="M31.9,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C29.5,9.7,30.6,10.9,31.9,10.9L31.9,10.9z"/><path class="st0" d="M39.8,25.2l-3.6-11.6c0,0-0.6-2-2.4-2h-3.6c-1.8,0-2.4,2-2.4,2l-3.6,11.6l1.2,0.7l4.2-9.5l-3.6,14.3h3.6l1.2,13h2.4l1.2-13H38l-3.6-14.3l4.2,9.5L39.8,25.2L39.8,25.2z"/></g>'; var data1 = [4, 96]; var data2 = [1, 99]; var data3 = [16, 84]; var data4 = [12, 88]; var data5 = [29, 71]; var data6 = [15, 85]; var data7 = [12, 88]; var data8 = [10, 90]; var width = 100, height = 100, radius = (Math.min(width, height) / 2); function drawDonut(data, divchart) { var sym = "%" var color = ["#BC204B","#00338D"]; // sort data data = data.sort(function (a, b) { return ba; }); var pie = d3.pie() .value(function(d) { return d })(data); var arc = d3.arc() .outerRadius(radius - 10) .innerRadius(radius - (radius / 1.9)); var labelArc = d3.arc() .outerRadius(radius - 31) .innerRadius(radius - 31); var svg = d3.select(divchart) .append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + 50 + "," + 50 + ")"); var g = svg.selectAll("arc") .data(pie) .enter().append("g") .attr("class", "arc"); g.append("path") .style("fill", function(d, i) { return color[i]; }) .transition().delay(function (d, i) { return i*800}).duration(800) .attrTween("d", arcTween) function arcTween(d) { var i = d3.interpolate(d.startAngle, d.endAngle); return function (t) { d.endAngle = i(t); return arc(d); } } g.append("g") .attr("transform", "translate(-17,-17) scale(0.7)") .html(myGroup); } drawDonut(data1, "#pie1") drawDonut(data2, "#pie2") drawDonut(data3, "#pie3") drawDonut(data4, "#pie4") drawDonut(data5, "#pie5") drawDonut(data6, "#pie6") drawDonut(data7, "#pie7") drawDonut(data8, "#pie8")
 div { display: inline; }
 <script src="https://d3js.org/d3.v5.min.js"></script> <div id="pie1"></div> <div id="pie2"></div> <div id="pie3"></div> <div id="pie4"></div> <div id="pie5"></div> <div id="pie6"></div> <div id="pie7"></div> <div id="pie1"></div> <div id="pie8"></div>

Relevant changes:

  1. AttrTween function

    function arcTween(d) { var i = d3.interpolate(d.startAngle, d.endAngle); return function (t) { d.endAngle = i(t); return arc(d); } }
  2. Use the above for the paths along with a delay in the transition which btw is used to animate the paths in sequence.

     g.append("path") .style("fill", function(d, i) { return color[i]; }) .transition().delay(function (d, i) { return i*800}).duration(800) .attrTween("d", arcTween);
  3. Sorted data in a descending order to draw the largest arc first.

     // sort data data = data.sort(function (a, b) { return ba; });

Based on the answer by Shashank I like to propose a modification

  • do not sort the data because the order looks important (d[0] == man, d[1] == woman) and the arcs are colored in order

    // sort data //data = data.sort(function (a, b) { return ba; });
  • the pie generator should also not sort the data based on value

    var pie = d3.pie() .sortValues(null) // both null means NO sort .value(function(d) { return d })(data);
  • in Shashanks answer both parts are animated with easeCubic and each part (small or large) takes 800ms. To get a constant angular speed animation we must set the delay and duration based on the startAngle and the endAngle of the part. And the transition must be set to easeLinear . For the demo I have set the complete animation to 5000ms.

     var msecPerRad = 5000 / (2*Math.PI); g.append("path") .style("fill", function(d, i) { return color[i]; }) .transition() .ease(d3.easeLinear) .delay(function (d) { return d.startAngle * msecPerRad;}) .duration(function (d) { return (d.endAngle - d.startAngle) * msecPerRad;}) .attrTween("d", arcTween);

Example with Linear easing

 var myGroup = '<g><path class="st0" d="M15.6,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C13.2,9.7,14.2,10.9,15.6,10.9L15.6,10.9z"/><path class="st0" d="M18.6,11.6h-1.2l-1.8,5.5l-1.8-5.5h-1.2c-1.3,0-2.4,1.2-2.4,2.7v13h2.4l1.2,16.4h3.6l1.2-16.4H21v-13C21,12.8,19.9,11.6,18.6,11.6L18.6,11.6z"/><path class="st0" d="M31.9,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C29.5,9.7,30.6,10.9,31.9,10.9L31.9,10.9z"/><path class="st0" d="M39.8,25.2l-3.6-11.6c0,0-0.6-2-2.4-2h-3.6c-1.8,0-2.4,2-2.4,2l-3.6,11.6l1.2,0.7l4.2-9.5l-3.6,14.3h3.6l1.2,13h2.4l1.2-13H38l-3.6-14.3l4.2,9.5L39.8,25.2L39.8,25.2z"/></g>'; var data1 = [50, 50]; var data2 = [70, 30]; var data3 = [16, 84]; var data4 = [12, 88]; var data5 = [29, 71]; var data6 = [15, 85]; var data7 = [12, 88]; var data8 = [10, 90]; var width = 100, height = 100, radius = (Math.min(width, height) / 2); function drawDonut(data, divchart) { var sym = "%" var color = ["#BC204B","#00338D"]; // sort data //data = data.sort(function (a, b) { return ba; }); var pie = d3.pie() .sortValues(null) // both null means NO sort .value(function(d) { return d })(data); var arc = d3.arc() .outerRadius(radius - 10) .innerRadius(radius - (radius / 1.9)); var labelArc = d3.arc() .outerRadius(radius - 31) .innerRadius(radius - 31); var svg = d3.select(divchart) .append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + 50 + "," + 50 + ")"); var g = svg.selectAll("arc") .data(pie) .enter().append("g") .attr("class", "arc"); var msecPerRad = 5000 / (2*Math.PI); g.append("path") .style("fill", function(d, i) { return color[i]; }) .transition() .ease(d3.easeLinear) .delay(function (d) { return d.startAngle * msecPerRad;}) .duration(function (d) { return (d.endAngle - d.startAngle) * msecPerRad;}) .attrTween("d", arcTween); function arcTween(d) { var i = d3.interpolate(d.startAngle, d.endAngle); return function (t) { d.endAngle = i(t); return arc(d); } } g.append("g") .attr("transform", "translate(-17,-17) scale(0.7)") .html(myGroup); } drawDonut(data1, "#pie1") drawDonut(data2, "#pie2") drawDonut(data3, "#pie3") drawDonut(data4, "#pie4") drawDonut(data5, "#pie5") drawDonut(data6, "#pie6") drawDonut(data7, "#pie7") drawDonut(data8, "#pie8")
 div { display: inline; }
 <script src="https://d3js.org/d3.v5.min.js"></script> <div id="pie1"></div> <div id="pie2"></div> <div id="pie3"></div> <div id="pie4"></div> <div id="pie5"></div> <div id="pie6"></div> <div id="pie7"></div> <div id="pie1"></div> <div id="pie8"></div>

If you want the complete angle animation performed with an easeCubic it takes little more math. The main reason is that both arc segments are independent trasitions. This should work for more then 2 arc segments.

It only works for monotone increasing easing functions, not for things like bounce, elastic and back(in/out).

  function easeInverse(ease) {
    return function(e) {
      var min = 0, max = 1;
      while (max - min > 1e-3) {
        var mid = (max + min) * 0.5;
        emid = ease(mid);
        if (emid > e) { max = mid; }
        else { min = mid; }
      }
      return max;
    }
  }
  var inverseCubic = easeInverse(d3.easeCubic);
  var oneOver2Pi = 1.0 / (2*Math.PI);
  var total_msec = 5000;

  g.append("path")
    .style("fill", function(d, i) { return color[i]; })
    .transition()
    .ease(d3.easeLinear)
    .delay(function (d) { return total_msec * inverseCubic(d.startAngle * oneOver2Pi);})
    .duration(function (d) { return total_msec * (inverseCubic(d.endAngle * oneOver2Pi) - inverseCubic(d.startAngle * oneOver2Pi));})
    .attrTween("d", arcTween);

  function arcTween(d) {
    var i = d3.interpolate(inverseCubic(d.startAngle * oneOver2Pi), inverseCubic(d.endAngle * oneOver2Pi));
    return function (t) {
      d.endAngle = 2*Math.PI*d3.easeCubic(i(t));
      return arc(d);
    }
  }

Example with Cubic easing for the full circle angle.

 var myGroup = '<g><path class="st0" d="M15.6,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C13.2,9.7,14.2,10.9,15.6,10.9L15.6,10.9z"/><path class="st0" d="M18.6,11.6h-1.2l-1.8,5.5l-1.8-5.5h-1.2c-1.3,0-2.4,1.2-2.4,2.7v13h2.4l1.2,16.4h3.6l1.2-16.4H21v-13C21,12.8,19.9,11.6,18.6,11.6L18.6,11.6z"/><path class="st0" d="M31.9,10.9c1.3,0,2.4-1.2,2.4-2.7c0-1.5-1.1-2.7-2.4-2.7c-1.3,0-2.4,1.2-2.4,2.7C29.5,9.7,30.6,10.9,31.9,10.9L31.9,10.9z"/><path class="st0" d="M39.8,25.2l-3.6-11.6c0,0-0.6-2-2.4-2h-3.6c-1.8,0-2.4,2-2.4,2l-3.6,11.6l1.2,0.7l4.2-9.5l-3.6,14.3h3.6l1.2,13h2.4l1.2-13H38l-3.6-14.3l4.2,9.5L39.8,25.2L39.8,25.2z"/></g>'; var data1 = [50, 50]; var data2 = [70, 30]; var data3 = [16, 84]; var data4 = [12, 88]; var data5 = [29, 71]; var data6 = [15, 85]; var data7 = [12, 88]; var data8 = [10, 90]; var width = 100, height = 100, radius = (Math.min(width, height) / 2); function drawDonut(data, divchart) { var sym = "%" var color = ["#BC204B","#00338D"]; // sort data //data = data.sort(function (a, b) { return ba; }); var pie = d3.pie() .sortValues(null) // both null means NO sort .value(function(d) { return d })(data); var arc = d3.arc() .outerRadius(radius - 10) .innerRadius(radius - (radius / 1.9)); var labelArc = d3.arc() .outerRadius(radius - 31) .innerRadius(radius - 31); var svg = d3.select(divchart) .append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + 50 + "," + 50 + ")"); var g = svg.selectAll("arc") .data(pie) .enter().append("g") .attr("class", "arc"); function easeInverse(ease) { return function(e) { var min = 0, max = 1; while (max - min > 1e-3) { var mid = (max + min) * 0.5; emid = ease(mid); if (emid > e) { max = mid; } else { min = mid; } } return max; } } var inverseCubic = easeInverse(d3.easeCubic); var oneOver2Pi = 1.0 / (2*Math.PI); var total_msec = 5000; g.append("path") .style("fill", function(d, i) { return color[i]; }) .transition() .ease(d3.easeLinear) .delay(function (d) { return total_msec * inverseCubic(d.startAngle * oneOver2Pi);}) .duration(function (d) { return total_msec * (inverseCubic(d.endAngle * oneOver2Pi) - inverseCubic(d.startAngle * oneOver2Pi));}) .attrTween("d", arcTween); function arcTween(d) { var i = d3.interpolate(inverseCubic(d.startAngle * oneOver2Pi), inverseCubic(d.endAngle * oneOver2Pi)); return function (t) { d.endAngle = 2*Math.PI*d3.easeCubic(i(t)); return arc(d); } } g.append("g") .attr("transform", "translate(-17,-17) scale(0.7)") .html(myGroup); } drawDonut(data1, "#pie1") drawDonut(data2, "#pie2") drawDonut(data3, "#pie3") drawDonut(data4, "#pie4") drawDonut(data5, "#pie5") drawDonut(data6, "#pie6") drawDonut(data7, "#pie7") drawDonut(data8, "#pie8")
 div { display: inline; }
 <script src="https://d3js.org/d3.v5.min.js"></script> <div id="pie1"></div> <div id="pie2"></div> <div id="pie3"></div> <div id="pie4"></div> <div id="pie5"></div> <div id="pie6"></div> <div id="pie7"></div> <div id="pie1"></div> <div id="pie8"></div>

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