d3 / d3

Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:

Home Page:https://d3js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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?