I am having a great deal of trouble getting my pie/donut chart to update data dynamically. I have it configured so that the user slides a range input to select which month of data he/she wants to see, then the data is passed to my d3 visual. For the sake of simplicity I have hard-coded the data in my example. You may view the snippet below:
var width = 450;
var height = 350;
var margins = { left: 0, top: 0, right: 0, bottom: 0 };
var svg = d3.select("body")
.append("svg")
.attr("width", width+margins.right)
.attr("height", height+margins.top);
var period = ['JAN 2016','FEB 2016','MAR 2016', 'APR 2016', 'MAY 2016', 'JUN 2016',
'JUL 2016', 'AUG 2016', 'SEP 2016', 'OCT 2016', 'NOV 2016', 'DEC 2016'];
d3.select('#timeslide').on('input', function() {
update(+this.value);
});
function update(value) {
document.getElementById('range').innerHTML=period[value];
create_pie(period[value]);
}
var pie_data = {
'JAN2016': [16,4,1,30],
'FEB2016': [17,4,0,30],
'MAR2016': [16,5,1,29],
'APR2016': [17,4,1,29],
'MAY2016': [17,4,1,29],
'JUN2016': [17,4,2,28],
'JUL2016': [18,3,2,28],
'AUG2016': [18,3,2,28],
'SEP2016': [18,3,2,28],
'OCT2016': [18,3,3,27],
'NOV2016': [18,3,3,27],
'DEC2016': [18,3,3,27]
}
function create_pie(month) {
var radius = Math.min(width, height) / 4;
var color = d3.scale.ordinal().range(['darkblue','steelblue','blue', 'lightblue']);
var arc = d3.svg.arc()
.innerRadius(radius - 50)
.outerRadius(radius - 20);
var sMonth = String(month).replace(' ','');
var pData = pie_data[sMonth];
var pie = d3.layout.pie()
.padAngle(.05)
.value(function(d) { return d; })
.sort(null);
var pieG = svg.append('g')
.attr('transform', 'translate(' + 100 +',' + 75 + ')');
var Ppath = pieG.datum(pData).selectAll(".pie")
.data(pie);
Ppath
.enter().append("path").attr('class','pie');
Ppath
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
Ppath
.transition()
.duration(650)
.attrTween("d", arcTweenUpdate);
Ppath
.exit().remove();
function arcTweenUpdate(a) {
var i = d3.interpolate({x: this.x0, dx: this.dx0}, a);
return function(t) {
var b = i(t);
this.x0 = b.x;
this.dx0 = b.dx;
return arc(b);
};
}
}
create_pie('JAN 2016');
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="//d3js.org/d3.v3.min.js"></script>
</head>
<body>
<div id='sliderContainer'>
<input id='timeslide' type='range' min='0' max='11' value='0' step='1'/><br>
<span id='range'>JAN 2016</span>
</div>
The good news is the pie chart is getting the new data each time the month is updated, because it looks like the pie chart is indeed moving. The bad news is the pie chart is looking very jerky and it seems my .transition().duration(650)
is not working at all. Actually I am started to think that the pie chart is being drawn again and again on top of itself, because the pie chart looks more blurry with each update of the data. I'm not sure why that would be since I was extra careful to include the Ppath.exit().remove();
at the presumably correct place. Ultimately, I'm left feeling like my understanding of how to dynamically update pie data is fundamentally flawed.
I soon realized I wasn't the first to have some issues with the pie transitions. The trickiest thing seems to be the arcTween
part. I looked at http://stackoverflow.com/questions/22312943/d3-sunburst-transition-given-updated-data-trying-to-animate-not-snap and followed along as closely as I could. I used that implementation of the arcTweenUpdate
, but unfortunately my situation did not improve. You may notice from the snippet that the colored arcs are moving around but the empty spaces that "divide" or "slice" up the pie are static. That's not what I want, it should all be transitioning, nice and smooth. There should not be static parts or awkwardly transitioning parts like it is currently.
Question: How can I keep the dynamic nature of the visual (access pie_data
in its current format via my function create_pie
) but also have a smooth, clean looking transition like M. Bostock's classic donut?
Note: M. Bostock's block uses a change()
function to update the pie chart. I prefer an answer that corrects/augments/adds to my existing code structure (i.e. Ppath.enter()
... Ppath.transition()
... Ppath.exit().remove()
) However, I would be willing to accept a change()
function similar to M. Bostock's orginial if someone can explain explicitly why my method as per this post is impossible / highly impracticle.
Edit
Another unforseen issue when I try to update the data dynamically is concerning radio buttons. As per Karan3112's formatData()
function:
function formatData(data) {
if (document.getElementById("radioButton1").checked) {
return data[Object.keys(data)[0]].slice(0,4).map(function(item, index) {
let obj = {};
Object.keys(data).forEach(function(key) {
obj[key] = data[key][index] //JAN2016 : [index]
})
return obj;
})
}
else if (document.getElementById("radioButton2").checked) {
return data[Object.keys(data)[0]].slice(5,8).map(function(item, index) {
let obj = {};
Object.keys(data).forEach(function(key) {
obj[key] = data[key][index] //JAN2016 : [index]
})
return obj;
})
}
}
Basically, in my real data, each month has an array of length 8 like this:
'JAN2016': [17,4,1,29,7,1,1,42],
So depending on which radio button is checked, I want to have the pie be drawn according to either the first 4 items in the array for radioButton1
or the last 4 items in the array for radioButton2
.
I initially omitted this part of the task for my OP because I figured it would be simple enough to adapt, but after trying for a good while with little progress, I have reconsidered. My slices don't seem to be working. I think it is because the formatData
function is only called once. I tried putting a formatData
call inside the change()
function, but that didnt work either.