Making a non-contiguous piechart
skaphan opened this issue · comments
Hi, I am newish to d3, so maybe there is a way to do this that is easier than what I came up with, so if so please let me know. I couldn't find anything helpful.
When you make a pie chart, I think you have to do something like this at the start:
const g = d3.select(svg)
.attr('width', width)
.attr('height', height)
.append("g")
.attr("id",chartID)
.attr("transform", translate(${x}, ${y})
);
const pie = d3.pie()
.sort(null)
.value(d => d.value);
const arc = d3.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius);
const slices = g.selectAll('.slice')
.data(pie(data))
.enter()
.append("g")
.attr("class", 'slice');
That is all well and good if your slices are contiguous, but what if they are not? I did it this way:
Everything the same up to the slices definition except you can leave out the pie definition. Then
const slices = g.selectAll('.slice')
.data(info)
.enter()
.append("g")
.attr("class", 'slice');
where 'info' is in the format produced by the function returned by d3.pie().
Apparently data() is fairly flexible, but I ended up using this format because I was not sure
what it was expecting in order to properly construct the pie segments. Maybe just something with
startAngle and endAngle properties??? I don't like using an undocumented interface there because it
might change out from under me.
Then I wanted to move the slices with a transition. This was also harder than it should have been,
since apparently there was no way to access the "from" startAngle and endAngle from within the interpolator.
So I had to do something like this:
slices
.select("path")
.transition()
.duration(1000)
.attrTween("d", function(d) {
const interpolateStartAngle = d3.interpolate(getStartAngle(d.data.label), d.startAngle);
const interpolateEndAngle = d3.interpolate(getEndAngle(d.data.label), d.endAngle);
this._current = d;
return t => {
updatedArc
.startAngle(x=interpolateStartAngle(t))
.endAngle(interpolateEndAngle(t));
return updatedArc(d);
};
});
with these functions defined:
function getStartAngle(name) {
let d = previousDonutInfoGlobal.find(obj => name.indexOf(obj.data.label)===0);
return d.startAngle;
}
function getEndAngle(name) {
let d = previousDonutInfoGlobal.find(obj => name.indexOf(obj.data.label)===0);
return d.endAngle;
}
First off, is there a better/easier way that would work? And if not, couldn't you make the start info available somehow to the interpolator?