ELing
ELing

Reputation: 259

D3 - smooth radial chart transition animation

I've prepared D3 Radial Chart component to show percentage value of some target. It would be great to add smoothy transition effect when start drawing foreground circle from 0 to chartPercentage (eg. 70%).

The question is - how to prepare transition / delay / duration effect with code which is attached below ?

Second idea which I also want to implement is to count value inside of the circle (radial-content) with animation from 0 to chartValue. How to prepare such solution?

Thank you !

  const chartPercentage = 70;
  const chartValue = 1.1242  
  const radius = 75;
  const border = 7;
  const padding = 0;
  const width = 400;
  const height = 400;
  const twoPi = Math.PI * 2;
  const boxSize = (radius + padding) * 2;
  
  let svg;
  
  function setArc() {
    return d3.arc()
      .startAngle(0)
      .innerRadius(radius)
      .outerRadius(radius - border)
      .cornerRadius(50);
  }
  
	function draw() {
    svg = d3.select(".chart").append("svg")
      .attr('width', width)
      .attr('height', height);

    svg.append("foreignObject")
      .attr("width", boxSize)
      .attr("height", boxSize)
      .append("xhtml:div")
      .attr('class', 'radial-wrapper')
      .html(`<div class="radial-content">${chartValue}</div>`);

    const field = svg.append('g')
      .attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');

    const meter = field.append('g')
      .attr('class', 'progress-meter');

    const background = meter.append("path")
      .datum({endAngle: twoPi})
      .attr('class', 'background')
      .attr('fill', '#2D2E2F')
      .attr('fill-opacity', 0.1)
      .attr("d", setArc());

    const foreground = meter.append("path")
      .datum({endAngle: (chartPercentage/100) * twoPi})
      .attr('class', 'foreground')
      .attr('fill', 'red')
      .attr('fill-opacity', 1)
      .attr('d', setArc());
  }
  
  draw();
  body { margin:30px;position:fixed;top:0;right:0;bottom:0;left:0; }
  .radial-wrapper{ display: flex; align-items: center; justify-content: center;width: 100%; height: 100%;}

  
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="chart"></div>

Upvotes: 1

Views: 1212

Answers (2)

Mikhail Shabrikov
Mikhail Shabrikov

Reputation: 8509

I rewrote your code. When you need to animate some attribute, you should use attrTween not attr method.

const chartPercentage = 70;
const chartValue = 1.1242
const radius = 75;
const border = 7;
const padding = 0;
const width = 400;
const height = 400;
const twoPi = Math.PI * 2;
const boxSize = (radius + padding) * 2;

let svg;

const setArc = d3.arc()
  .startAngle(0)
  .innerRadius(radius)
  .outerRadius(radius - border)
  .cornerRadius(50);

const arcParams = {};

function draw() {
  svg = d3.select(".chart").append("svg")
    .attr('width', width)
    .attr('height', height);

  svg.append("foreignObject")
    .attr("width", boxSize)
    .attr("height", boxSize)
    .append("xhtml:div")
    .attr('class', 'radial-wrapper')
    .html(`<div class="radial-content"></div>`);

  const field = svg.append('g')
    .attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');

  const meter = field.append('g')
    .attr('class', 'progress-meter');

  const background = meter
  	.append("path")
    .attr('class', 'background')
    .attr('fill', '#2D2E2F')
    .attr('fill-opacity', 0.1)
    .attr("d", setArc({ endAngle: twoPi }));

  const foreground = meter
  	.append("path")
    .transition()
    .ease(d3.easeBounce)
    .duration(1500)
    .attr('class', 'foreground')
    .attr('fill', 'red')
    .attr('fill-opacity', 1)
    .attrTween("d", function() {
      return arcTween({ endAngle: 0 }, chartPercentage/100 )
    })
}

function arcTween(d, new_score) {
    var new_startAngle = 0
    var new_endAngle = new_startAngle + new_score * 2 * Math.PI
    var interpolate_start = d3.interpolate(d.startAngle, new_startAngle)
    var interpolate_end = d3.interpolate(d.endAngle, new_endAngle)
    return function(t) {
      d.endAngle = interpolate_end(t)
      d3.select('.radial-content')
      	.text((d.endAngle / new_endAngle * chartValue).toFixed(4));
      return setArc(d)
    }
}

draw();
body {
  margin: 30px;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.radial-wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="chart"></div>

Upvotes: 1

Cyril Cherian
Cyril Cherian

Reputation: 32327

Make a function for attribute tween.

function arcTween(a) {
  var j = {"endAngle":0};//start angle
  var i = d3.interpolateObject(j, a);
  return function(t) {
    d3.select(".radial-content").text(d3.format(".4n")(chartValue*t));
    return arc(i(t));
  };
}

In the above function

d3.select(".radial-content").text(d3.format(".4n")(chartValue*t));

this will change the text(and output it in the format) in the radial content as the transition runs.

now add the tween function to the foreground path.

  const foreground = meter.append("path")
    .datum({
      endAngle: (chartPercentage / 100) * twoPi
    })
    .attr('class', 'foreground')
    .attr('fill', 'red')
    .attr('fill-opacity', 1)
  .transition().duration(750).attrTween("d", arcTween);

working code here

Upvotes: 1

Related Questions