JavaCodeNet
JavaCodeNet

Reputation: 1175

Transition color in donut chart

I have a donut chart which displays rating out of 10. If rating > 5 then it will be displayed as red else as green. I also added animation to the chart so that rating increases gradually. The only change I need is if the rating is more than 5 then rating arc should start with green color and then should change to red once it crosses 5.

Here is the codepen url: https://codepen.io/javacodenet/pen/pKGmRx

var duration = 1500,
  transition = 200,
  percent = 7;
var width = 260;
var height = 260;
var thickness = 30;
var anglesRange = 0.75 * Math.PI
var dataset = {
    lower: calcPercent(0),
    upper: calcPercent(percent)
  },
  radius = Math.min(width, height) / 2;
var colors = ["#fb4f5d", "#efefef"]
var arc = d3.arc()
  .innerRadius(radius - thickness)
  .outerRadius(radius);

var pie = d3.pie()
  .value(function(d) {
    return d;
  })
  .sort(null)
  .startAngle(anglesRange * -1)
  .endAngle(anglesRange);

var svg = d3.select("#chart").append("svg")
  .attr("width", width)
  .attr("height", height)
  .append("g")
  .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

var path = svg.selectAll("path")
  .data(pie(dataset.lower))
  .enter().append("path")
  .attr('fill', '#efefef')
  .attr("d", arc)
  .each(function(d) {
    this._current = d;
  });

var text = svg.append("text")
  .attr("text-anchor", "middle")
  .attr("dy", ".3em");

var progress = 0;
format = d3.format("0");
var timeout = setTimeout(function() {
  clearTimeout(timeout);
  path = path.data(pie(dataset.upper))
    .attr('fill', (d, i) => {
      return i == 0 ? (d.value > 5 ? '#fb4f5d' : '#00FF00') : '#efefef';
    });
  path.transition().duration(duration).attrTween("d", function(a) {
    var i = d3.interpolate(this._current, a);
    var i2 = d3.interpolateNumber(progress, percent)
    console.log(i2);
    this._current = i(0);
    return function(t) {
      text.text(Math.round(i2(t)));
      return arc(i(t));
    };
  });
}, 200);

function calcPercent(percent) {
  return [percent, 10 - percent];
};
body {
  width: 100%;
  height: 100%;
  font-family: Lora, "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: #fff;
  background-color: #000;
}

path.color0 {
  fill: #fff;
}

path.color1 {
  fill: rgba(255, 255, 255, .3);
}

text {
  font-size: 7em;
  font-weight: 400;
  line-height: 16em;
  fill: #fff;
}
<html>
<head>
  <script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
  <div id="chart"></div>
</body>
</html>

Upvotes: 1

Views: 58

Answers (2)

Pierre Capo
Pierre Capo

Reputation: 1053

Basically, what you can do to solve your problem is :

  • Adding a d3 attribute for filling the color of the donut
  • Evaluating where your cursor is, so you return green or red according to this calculus

Let's code this :

    var duration = 1500,
    transition = 200,
    percent = 7;
var width = 260;
var height = 260;
var thickness = 30;
  var anglesRange = 0.75 * Math.PI
var dataset = {
            lower: calcPercent(0),
            upper: calcPercent(percent)
        },
        radius = Math.min(width, height) / 2;
var colors = ["#fb4f5d", "#efefef"]
var arc = d3.arc()
.innerRadius(radius - thickness)
.outerRadius(radius);

var pie = d3.pie()
.value(function(d) { return d; })
.sort(null)
.startAngle(anglesRange * -1)
    	.endAngle( anglesRange);

var svg = d3.select("#chart").append("svg")
        .attr("width", width)
        .attr("height", height)
        .append("g")
        .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

var path = svg.selectAll("path")
                .data(pie(dataset.lower))
                .enter().append("path")
                .attr('fill', '#efefef')
                .attr("d", arc)
                .each(function (d) {
                    this._current = d;
                });

var text = svg.append("text")
        .attr("text-anchor", "middle")
        .attr("dy", ".3em");

var progress = 0;
 format = d3.format("0");
var timeout = setTimeout(function () {
    clearTimeout(timeout);
    path = path.data(pie(dataset.upper)).attr('e', (d,i)=> console.log(i));

    path.transition().duration(duration).attrTween("d", function (a) {
        var i = d3.interpolate(this._current, a);
        var i2 = d3.interpolateNumber(progress, percent)
        this._current = i(0);
        return function (t) {
            text.text( Math.round(i2(t)));
            return arc(i(t));
        };
    }).attrTween('fill', function (d,x) {
        var i = d3.interpolate(this._current, d);
        var i2 = d3.interpolateNumber(progress, percent)
        this._current = i(0);
        return function (t) {
            if(x){ return "#efefef"; }
            else { return i2(t) > 5 ? "#fb4f5d" : "#00FF00"; }
        }});
}, 200);

function calcPercent(percent) {
    return [percent, 10 - percent];
}; 
body {
    width: 100%;
    height: 100%;
    font-family: Lora,"Helvetica Neue",Helvetica,Arial,sans-serif;
    color: #fff;
    background-color: #000;
}
path.color0 {
    fill: #fff;
}
path.color1 {
    fill: rgba(255,255,255,.3);
}
text {
    font-size: 7em;
    font-weight: 400;
    line-height: 16em;
    fill: #fff;
}
<html>
  <head>
    <script src="https://d3js.org/d3.v5.min.js"></script>
  </head>
  <body>
    <div id="chart"></div>
  </body>
</html>

Upvotes: 0

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

Your desired outcome is not exactly clear: do you want a single <path> element turning to red when it goes above 5, or do you want two <path> elements, one painted in green going from 0 to 5 and another one painted in red from 5 to 7?

If the former is correct, just move the "fill" to inside the attrTween method. This is your code with that change:

var duration = 1500,
  transition = 200,
  percent = 7;
var width = 260;
var height = 260;
var thickness = 30;
var anglesRange = 0.75 * Math.PI
var dataset = {
    lower: calcPercent(0),
    upper: calcPercent(percent)
  },
  radius = Math.min(width, height) / 2;
var colors = ["#fb4f5d", "#efefef"]
var arc = d3.arc()
  .innerRadius(radius - thickness)
  .outerRadius(radius);

var pie = d3.pie()
  .value(function(d) {
    return d;
  })
  .sort(null)
  .startAngle(anglesRange * -1)
  .endAngle(anglesRange);

var svg = d3.select("#chart").append("svg")
  .attr("width", width)
  .attr("height", height)
  .append("g")
  .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

var path = svg.selectAll("path")
  .data(pie(dataset.lower))
  .enter().append("path")
  .attr('fill', '#efefef')
  .attr("d", arc)
  .each(function(d) {
    this._current = d;
  });

var text = svg.append("text")
  .attr("text-anchor", "middle")
  .attr("dy", ".3em");

var progress = 0;
format = d3.format("0");
var timeout = setTimeout(function() {
  clearTimeout(timeout);
  path = path.data(pie(dataset.upper));
  path.transition().duration(duration).attrTween("d", function(a, index) {
    var self = this;
    var i = d3.interpolate(this._current, a);
    var i2 = d3.interpolateNumber(progress, percent)
    this._current = i(0);
    return function(t) {
      d3.select(self).attr('fill', index !== 0 ? '#efefef' : i2(t) > 5 ? '#fb4f5d' : '#00FF00');
      text.text(Math.round(i2(t)));
      return arc(i(t));
    };
  });
}, 200);

function calcPercent(percent) {
  return [percent, 10 - percent];
};
body {
  width: 100%;
  height: 100%;
  font-family: Lora, "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: #fff;
  background-color: #000;
}

path.color0 {
  fill: #fff;
}

path.color1 {
  fill: rgba(255, 255, 255, .3);
}

text {
  font-size: 7em;
  font-weight: 400;
  line-height: 16em;
  fill: #fff;
}
<html>

<head>
  <script src="https://d3js.org/d3.v5.min.js"></script>
</head>

<body>
  <div id="chart"></div>
</body>

</html>

Upvotes: 1

Related Questions