I try to show multiple donut charts on the same page. But it's not showing. I am using a loop for creating this chart and added multiple html id to show this.
var details = [{category: "Doctor", number: 25}, {category: "Shopping", number: 20}, { category: "Restaurants", number: 10}, { category: "FastFood", number: 5}, { category: "Internet", number: 8},{ category: "Other", number: 32}];
for (i = 1; i <= 4; i++)
chartScript(details, '#custom-chart-' + i);
function chartScript(details, cls) {
var width = 300,
height = 500;
var colors = d3.scaleOrdinal(d3.schemeDark2);
var svg = d3.select(cls).append("svg")
.attr("width", width).attr("height", height);
details.sort(function (a, b) {
return b['number'] - a['number'];
});
var data = d3.pie().sort(null).value(function (d) {
return d.number;
})(details);
var segments = d3.arc()
.innerRadius(80)
.outerRadius(150)
.padAngle(.05)
.padRadius(30);
var sections = svg.append("g").attr("transform", "translate(150,250)").selectAll("path").data(data);
sections.enter().append("path").attr("d", segments).attr("fill", function (d) {
return colors(d.data.number);
});
var content = d3.select("g").selectAll("text").data(data);
content.enter().append("text").classed("inside", true).each(function (d) {
var center = segments.centroid(d);
d3.select(this).attr("x", center[0]).attr("y", center[1]).text(d.data.number + '%')
})
}
I got output like this(1st one is showing % but others are not):
You are using d3 like this
var sections = svg.append("g").attr("transform", "translate(150,250)")
and in this line
var content = d3.select("g").selectAll("text").data(data);
instead of d3 You should use
var content = sections.select("g").selectAll("text").data(data);
That way it will select all the <g> tags in the sections
As a rule of thumb: in a D3 code, never use a loop (for, while, forEach etc...) for appending elements.
So, just change that awkward loop for a proper D3 selection...
var cls = d3.select("body").selectAll(null)
.data(d3.range(4))
.enter()
.append("div");
... and not only you will fix the issue you mentioned but also avoid future problems.
Here is a demo using (most of) your code:
var details = [{
category: "Doctor",
number: 25
}, {
category: "Shopping",
number: 20
}, {
category: "Restaurants",
number: 10
}, {
category: "FastFood",
number: 5
}, {
category: "Internet",
number: 8
}, {
category: "Other",
number: 32
}];
var cls = d3.select("body").selectAll(null)
.data(d3.range(4))
.enter()
.append("div");
var width = 180,
height = 180;
var colors = d3.scaleOrdinal(d3.schemeDark2);
var svg = cls.append("svg")
.attr("width", width).attr("height", height);
details.sort(function(a, b) {
return b['number'] - a['number'];
});
var data = d3.pie().sort(null).value(function(d) {
return d.number;
})(details);
var segments = d3.arc()
.innerRadius(45)
.outerRadius(85)
.padAngle(.05)
.padRadius(30);
var g = svg.append("g").attr("transform", "translate(90,90)");
var sections = g.selectAll("path").data(data);
sections.enter().append("path").attr("d", segments).attr("fill", function(d) {
return colors(d.data.number);
});
var content = g.selectAll("text").data(data);
content.enter().append("text").classed("inside", true).each(function(d) {
var center = segments.centroid(d);
d3.select(this).attr("x", center[0]).attr("y", center[1]).text(d.data.number + '%')
})
div {
display: inline-block;
}
text {
text-anchor: middle;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Related
I have a bit of JS code that I use to draw svg circles on the page. The circles are a based on school data and the size of the circle is dependant on the SchoolCapacity field. The problem is that my array schooldata contains 100s of elements and the circles are very crowded and overlap over each other. My question is how can I rewrite the process schools data code to display Local Authorities (LA (Name)) and their school capacities as draw those as circles instead.
Array of school data:
const schooldata = [{
"URN": 135691,
"LA (code)": 938,
"LA (name)": "West Sussex",
"EstablishmentNumber": 6228,
"EstablishmentName": "Seadown School",
"SchoolCapacity": 30,
"Postcode": "BN11 2BE"
}, {
"URN": 112080,
"LA (code)": 908,
"LA (name)": "Cornwall",
"EstablishmentNumber": 6084,
"EstablishmentName": "St Ia School",
"SchoolCapacity": 45,
"Postcode": "TR26 2SF"
},
{
"URN": 130842,
"LA (code)": 938,
"LA (name)": "West Sussex",
"EstablishmentNumber": 8003,
"EstablishmentName": "Greater Brighton Metropolitan College",
"SchoolCapacity": "",
"Postcode": "BN12 6NU"
},
JS Business logic
function getcoordinates(postcode) {
let url = "https://api.getthedata.com/postcode/" + postcode.replace(" ", "+");
try {
return fetch(url).then(function(response) {
return response.json(); // Process it inside the `then`
});
} catch (error) {
console.log(error);
}
}
//svg setup
var svgns = "http://www.w3.org/2000/svg",
container = document.getElementById('cont');
function drawcircle(easting, northing, size, label, identifier) {
var xScale = (container.width.baseVal.value - 20) / (700000);
var x = 10 + (xScale * easting);
var yScale = (container.height.baseVal.value - 20) / (700000);
var y = 10 + (yScale * (700000 - northing));
var sizeScale = 100;
var radius = size / sizeScale;
//draw or update the svg circle
var circle = document.getElementById("Circle_" + identifier);
if (circle == null) {
circle = document.createElementNS(svgns, 'circle');
}
circle.setAttributeNS(null, 'id', "Circle_" + identifier);
circle.setAttributeNS(null, 'cx', x);
circle.setAttributeNS(null, 'cy', y);
circle.setAttributeNS(null, 'r', radius);
circle.setAttributeNS(null, 'style', 'fill: #c6605e; stroke: none; stroke-width: 1px;');
container.appendChild(circle);
//draw or update the circle label
var newText = document.getElementById("Circle_Label_" + identifier);
if (newText == null) {
newText = document.createElementNS(svgns, "text");
var textNode = document.createTextNode(label);
newText.appendChild(textNode);
}
newText.setAttributeNS(null, 'id', "Circle_Label_" + identifier);
newText.setAttributeNS(null, "x", x);
newText.setAttributeNS(null, "y", y);
newText.setAttributeNS(null, "font-size", "10");
var textNode = document.createTextNode(label);
newText.replaceChild(textNode, newText.childNodes[0]);
container.appendChild(newText);
}
**//process schools data** -- TODO: change to LA instead of schools
let schools = schooldata.reduce(function(allSchools, school) {
allSchools[school.EstablishmentName] = {
SchoolCapacity: school.SchoolCapacity || 0,
coordinates: []
};
getcoordinates(school.Postcode).then(function(data) {
allSchools[school.EstablishmentName].coordinates.push([data.data.easting, data.data.northing]);
drawcircle(data.data.easting, data.data.northing, school.SchoolCapacity, school.EstablishmentName, school.URN);
});
return allSchools
}, {});
HTML
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Create an element where the map will take place -->
<svg id="cont" width="900" height="900"></svg>
I assume I need to do some Array reducing?
First, reduce your schools to Local Authorities using Array.reduce():
const locAuthorities = schooldata.reduce(function (prev, curr) {
const laCode = curr["LA (code)"]
const laName = curr["LA (name)"]
if (prev[laCode]) {
prev[laCode].SchoolCapacity += curr.SchoolCapacity || 0
} else {
prev[laCode] = {
"LA (code)": laCode,
"LA (name)": laName,
"SchoolCapacity": curr.SchoolCapacity,
"Postcode": curr.Postcode,
"URN": curr.URN
}
}
return prev
}, {})
const locAuthoritiesArray = Object.values(locAuthorities)
Now use localAuthoritiesArray to draw the circles:
locAuthoritiesArray.forEach(function (la) {
getcoordinates(la.Postcode).then(function (data) {
drawcircle(
data.data.easting,
data.data.northing,
la.SchoolCapacity,
la["LA (name)"],
la.URN
);
});
})
Learning Javascript and D3.
Trying to create a graph where it draws each line in a series, one at a time or on a delayed interval.
What I've got so far (relevant code at the end of this post)
https://jsfiddle.net/jimdholland/5n6xrLk0/180/
My data is structures with each series as one row, with 12 columns. When you read it in, the object approximately looks like
mydata = [
{NumDays01: "0", NumDays02: "0", NumDays03: "0", NumDays04: "0", NumDays05: "0",Numdays06: 30}
1: {NumDays01: "0", NumDays02: "0", NumDays03: "0", NumDays04: "0",...
]
I can get it to create line, but it draws all the paths at once. Likewise, I've tried a loop, but that still draws them all at once. Also tried d3.timer and similar results. But I'm having a hard time understanding the timer callback stuff and creating those functions correctly.
I haven't found another example to really study other than a chart from FlowingData which has a lot of bells and whistles out of my league.
https://flowingdata.com/2019/02/01/how-many-kids-we-have-and-when-we-have-them/
Any help or tips would be appreciated.
var svg = d3.select("#chart").select("svg"),
margin = { top: 30, right: 20, bottom: 10, left: 40 },
width = parseInt(d3.select("#chart").style('width'), 10) - margin.left - margin.right;
d3.tsv("https://s3.us-east-2.amazonaws.com/jamesdhollandwebfiles/data/improvementTest.tsv", function(error, data) {
if (error) throw error;
console.log(data);
myData = data;
var t = d3.timer(pathMaker);
}); // #end d3.tsv()
function pathMaker() {
var peeps = myData[rowToRun];
var coords = [];
var lineToRemove = [];
for (var nameIter in dayList) {
coords.push({ x: x(nameIter), y: y(peeps[dayList[nameIter]])})
}
var improvementPath = g.append("path")
.datum(coords)
.attr("id", "path" + peeps.caseid)
.attr("d", lineMaker)
.attr("stroke", "#90c6e4")
.attr("fill", "none")
.attr("stroke-width", 2);
var total_length = improvementPath.node().getTotalLength();
var startPoint = pathStartPoint(improvementPath);
improvementPath = improvementPath
.attr("stroke-dasharray", total_length + " " + total_length)
.attr("stroke-dashoffset", total_length)
.transition() // Call Transition Method
.duration(4000) // Set Duration timing (ms)
.ease(d3.easeLinear) // Set Easing option
.attr("stroke-dashoffset", 0); // Set final value of dash-offset for transition
rowToRun += 1;
if (rowToRun == 5) {rowToRun = 0;}
}
//Get path start point for placing marker
function pathStartPoint(Mypath) {
var d = Mypath.attr("d"),
dsplitted = d.split(/M|L/)[1];
return dsplitted;
}
there are a few problems. I tried to configure your code as little as I could to make it work. If you need further explanations, please let me know
d3.tsv("https://s3.us-east-2.amazonaws.com/jamesdhollandwebfiles/data/improvementTest.tsv", function(error, data) {
if (error) throw error;
console.log(data);
myData = data;
pathMaker()
}); // #end d3.tsv()
function pathMaker() {
var peeps = myData[rowToRun];
var coords_data = [];
var lineToRemove = [];
for (let i = 0; i < myData.length; i++) {
var coords = [];
for (var nameIter in dayList) {
coords.push({ x: x(nameIter), y: y(myData[i][dayList[nameIter]])})
}
coords_data.push(coords)
}
console.log(coords_data)
var improvementPath = g.selectAll("path")
.data(coords_data)
.enter()
.append("path")
.attr("d", lineMaker)
.attr("stroke", "#90c6e4")
.attr("fill", "none")
.attr("stroke-width", 2);
improvementPath = improvementPath.each(function (d,i) {
var total_length = this.getTotalLength();
var startPoint = pathStartPoint(improvementPath);
const path = d3.select(this)
.attr("stroke-dasharray", total_length + " " + total_length)
.attr("stroke-dashoffset", total_length)
.transition() // Call Transition Method
.duration(4000) // Set Duration timing (ms)
.delay(i*4000)
.ease(d3.easeLinear) // Set Easing option
.attr("stroke-dashoffset", 0); // Set final value of dash-offset for transition
})
rowToRun += 1;
if (rowToRun == 5) {rowToRun = 0;}
}
I am playing with d3.js to draw a curve like the below. The below was drawn in Excel. As I am trying to achive the below result, I am facing an issue where my Y axis data lives in nested array and I am having a trouble to expose that.
Using the code below, I am able to get the x axis but whenever I try to bring my y axis, the data would come up as undefined.
var w = 900;
var h = 180;
var margin = {
'top': 5,
'right': 20,
'bottom': 20,
'left': 50
};
//creating axis
function buildLine(data) {
var xScale = d3.scale.linear()
.domain([100, d3.max(data, function(d) {
return d.spend;
})])
.range([margin.left, w - margin.right], 100);
/*var yScale=d3.scale.linear()
.domain([0,d3.max(data.arr,function(d){return d.y})])
.range([h-padding,10])
.nice();*/ //getting error as it is nested
var xAxisGen = d3.svg.axis().scale(xScale).orient("bottom").ticks(15);
/* var yAxisGen=d3.svg.axis().scale(yScale).orient("left");*/
var svg = d3.select("body").append("svg").attr({
width: w,
height: h
});
/*var yAxis= svg.append("g")
.call(yAxisGen)
.attr("class","axis")
.attr("transorm","translate("+padding+",0)");*/
var xAxis = svg.append("g")
.call(xAxisGen)
.attr("class", "axis")
.attr("transorm", "translate(" + padding + ",0)");
}
d3.json("sample-data.json", function(error, data) {
//check the file loaded properly
if (error) { //is there an error?
console.log(error); //if so, log it to the console
} else { //If not we're golden!
//Now show me the money!
ds = data; //put the data in the global var
}
buildLine(data);
data.forEach(function(jsonData) {
var lineData = d3.range(0, jsonData.spend, 100)
.map(x => [x, jsonData.alpha * (1 - 2.71828 * (-jsonData.beta * x))]);
/* console.log("this is the data:",lineData);*/
//i think line date returns an array with each item in it also an array of 2 items
var arr = [];
for (var i = 0; i < lineData.length; i++) {
arr.push({
x: lineData[i][0],
y: lineData[i][1]
});
}
jsonData.arr = arr;
console.log(jsonData);
});
});
*********The data structure as below ******************
So there is one object and there needs to be a line for each object and therefore each object has nested array which holds x, y and I need to get the Y to build y.axis. My brain feels like a marshmallow right now.
{x: 10000, y: 286.89585120000004}
{x: 10100, y: 288.364809712}
{x: 10200, y: 289.833768224}
{x: 10300, y: 291.30272673600007}
{x: 10400, y: 292.771685248}
I'm trying to create a pie chart using typescript and d3 with static data.
But I'm just getting a blank screen.Can someone help? I'm new to both d3 and typescript.
There's no tsconfig.json file. I simply compile the .ts file using tsc command and link the corresponding .js file to the HTML.
Here's the typescript code:
interface Data {
quantity: number;
category: string;
}
let testData: Data[] = [
{
quantity: 10,
category: 'a'
},
{
quantity: 20,
category: 'b'
},
{
quantity: 10,
category: 'c'
},
{
quantity: 100,
category: 'd'
},
{
quantity: 500,
category: 'e'
}
];
drawChart(testData);
function drawChart(data: Data[]) {
let width = 400,
height = 400,
radius = Math.min(width, height) / 2,
colourValues = d3.scale.category20c();
let arc = d3.svg.arc<d3.layout.pie.Arc<Data>>()
.innerRadius(radius - 70)
.outerRadius(radius - 0);
let pie = d3.layout.pie<Data>().value((d: Data):number => d.quantity);
let fill = (d: d3.layout.pie.Arc<Data>): string => colourValues(d.data.category);
let tf = (d: d3.layout.pie.Arc<Data>): string => `translate(${arc.centroid(d)})`;
let text = (d: d3.layout.pie.Arc<Data>): string => d.data.category;
let svg = d3.select('.pie-chart').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
let g = svg.selectAll('.arc')
.data(pie(data))
.enter().append('g').attr('class', 'arc');
g.append('path').attr('d', arc).attr('fill', fill);
g.append('text').attr('transform', tf).text(text);
}
Here's my HTML :
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="piechart1.js"></script>
</head>
<body>
<div class="pie-chart"></div>
</body>
You are calling the function before the element exists... if you move the scripts the order of execution should be better:
<head>
</head>
<body>
<div class="pie-chart"></div>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="piechart1.js"></script>
</body>
I made a simple animated pie/donut chart with d3 and I was wondering if it would be possible to animate the radius and the grow at the same time.
As you can see from the example or the snippet below, only the grow is animated.
const dataset = {
apples: [{
label: 'Category A',
value: 53245,
isSelected: true
}, {
label: 'Category B',
value: 28479,
isSelected: false
}, {
label: 'Category C',
value: 24037,
isSelected: false
}, {
label: 'Category D',
value: 40245,
isSelected: false
}, {
label: 'Category E',
value: 30245,
isSelected: false
}],
oranges: [{
label: 'Category A',
value: 200,
isSelected: false
}, {
label: 'Category B',
value: 200,
isSelected: true
}, {
label: 'Category C',
value: 200,
isSelected: false
}, {
label: 'Category D',
value: 200,
isSelected: false
}]
};
/**
* Pie chart class
*/
function PieChart(options) {
// Observable stream source
this.selectionSource = new Rx.Subject();
// Observable stream
this.selection = this.selectionSource.asObservable();
// Chart options/settings
this.width = options.width;
this.height = options.height;
this.radius = Math.min(this.width, this.height) / 2;
this.multiple = options.multiple;
this.legend = options.legend;
this.colorRange = d3.scale.category20();
this.color = d3.scale.ordinal()
.range(this.colorRange.range());
// Animation directions
this.clockwise = {
startAngle: 0,
endAngle: 0
};
this.counterclock = {
startAngle: Math.PI * 2,
endAngle: Math.PI * 2
};
// Create the SVG on which the plot is painted.
this.svg = d3.select(options.target)
.append('svg:svg')
.attr('width', this.width)
.attr('height', this.height)
.append('g')
.attr('transform', `translate(${this.width / 2}, ${this.height / 2})`);
// Initial path creation.
this.path = this.svg.selectAll('path');
// Create the pie layout.
this.pie = d3.layout.pie()
.value(function(d) {
return d.value;
})
.sort(null);
// Create arc functions.
this.arc = d3.svg.arc()
.innerRadius(this.radius - 100)
.outerRadius(this.radius - 20);
// Arc when a slice is selected/toggled on.
this.arcSelected = d3.svg.arc()
.innerRadius(this.radius - 90)
.outerRadius(this.radius - 10);
this.arcTween = arcTween;
this.arcTweenOut = arcTweenOut;
this.updateSelection = updateSelection;
// Used by some of the functions that get a different context when called by d3.
const thisRef = this;
// Store the displayed angles in `current`.
// Then, interpolate from `current` to the new angles.
// During the transition, `current` is updated in-place by d3.interpolate.
function arcTween(a) {
const i = d3.interpolate(this.current, a);
this.current = i(0);
const slice = d3.select(this);
return arcFn(slice, i);
}
function arcTweenOut() {
const i = d3.interpolate(this.current, {
startAngle: Math.PI * 2,
endAngle: Math.PI * 2,
value: 0
});
this.current = i(0);
const slice = d3.select(this);
return arcFn(slice, i);
}
function arcFn(slice, i) {
return function(t) {
if (slice.classed('selected')) {
return thisRef.arcSelected(i(t));
}
return thisRef.arc(i(t));
};
}
// NOTE: `this` will not be the class context,
// but the contaxt set
function updateSelection(d) {
const node = this;
const slice = d3.select(node);
const isToggled = slice.classed('selected');
const event = {
data: d.data
};
if (thisRef.multiple) {
// Allow multiple slice toggling.
toggle();
} else {
// Find previously selected slice.
const selected = thisRef.svg.selectAll('path')
.filter(function() {
return !this.isEqualNode(node) && d3.select(this).classed('selected');
});
// Deselect previous selection.
if (!selected.empty()) {
selected.classed('selected', false)
.transition()
.attr('d', thisRef.arc);
}
// Toggle current slice.
toggle();
}
function toggle() {
if (isToggled) {
event.selected = false;
slice.classed('selected', false)
.transition()
.attr('d', thisRef.arc)
.each('end', emit);
} else {
event.selected = true;
slice.classed('selected', true)
.transition()
.attr('d', thisRef.arcSelected)
.each('end', emit);
}
}
function emit() {
thisRef.selectionSource.onNext(event);
}
}
}
PieChart.prototype.direction = function direction() {
// Set the start and end angles to Math.PI * 2 so we can transition counterclockwise to the actual values later.
let direction = this.counterclock;
// Set the start and end angles to 0 so we can transition clockwise to the actual values later.
if (!this.painted) {
direction = this.clockwise;
}
return direction;
}
PieChart.prototype.update = function update(data) {
const direction = this.direction();
const thisRef = this;
this.path = this.path
.data(this.pie(data), function(d) {
return d.data.label;
})
.classed('selected', selected.bind(this));
function selected(datum) {
return datum.data.isSelected;
}
// Append slices when data is added.
this.path.enter()
.append('svg:path')
.attr('class', 'slice')
.style('stroke', '#f3f5f6')
.attr('stroke-width', 2)
.attr('fill', function(d, i) {
return thisRef.color(d.data.label);
})
.attr('d', this.arc(direction))
// Store the initial values.
.each(function(d) {
this.current = {
data: d.data,
value: d.value,
startAngle: direction.startAngle,
endAngle: direction.endAngle
};
})
.on('click', this.updateSelection);
// Remove slices when data is removed.
this.path.exit()
.transition()
.duration(450)
.attrTween('d', this.arcTweenOut)
// Now remove the exiting arcs.
.remove();
// Redraw the arcs.
this.path.transition()
.duration(450)
.attrTween('d', this.arcTween);
// Add legend
this.addLegend();
// Everything is painted now,
// we only do updates from this point on.
if (!this.painted) {
this.painted = true;
}
}
PieChart.prototype.addLegend = function addLegend() {
// The legend does not need to be repainted when we update the slices.
if (this.painted || !this.legend) {
return;
}
const thisRef = this;
const rect = this.radius * 0.04;
const spacing = this.radius * 0.02;
const legend = this.svg.selectAll('.legend')
.data(this.color.domain());
legend.enter()
.append('g')
.attr('class', 'legend')
.attr('fill-opacity', 0)
.attr('transform', function(d, i) {
const height = rect + spacing * 2;
const offset = height * thisRef.color.domain().length / 2;
const horizontal = -4 * rect;
const vertical = i * height - offset;
return `translate(${horizontal}, ${vertical})`;
});
legend.append('rect')
.attr('width', rect)
.attr('height', rect)
.style('fill', this.color);
legend.append('text')
.attr('x', rect + spacing)
.attr('y', rect)
.text(function(d) {
return d;
});
legend.transition()
.duration(450)
.attr('fill-opacity', 1);
};
// DEMO/USAGE
const pieChart = new PieChart({
target: '#chart',
multiple: true,
legend: true,
width: 400,
height: 400
});
console.log(pieChart);
pieChart.selection.subscribe(function(selection) {
console.log(selection);
});
// Paint the plot.
pieChart.update(dataset.apples);
// This is only here for demo purposes
d3.selectAll("input")
.on("change", update);
var timeout = setTimeout(function() {
d3.select("input[value=\"oranges\"]").property("checked", true).each(update);
}, 2000);
function update() {
clearTimeout(timeout); // This is only here for demo purposes
// Update the data.
pieChart.update(dataset[this.value]);
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.container {
position: relative;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
// Graph
.slice {
cursor: pointer;
}
.legend {
font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div class="container">
<form>
<label>
<input type="radio" name="dataset" value="apples" checked> Apples</label>
<label>
<input type="radio" name="dataset" value="oranges"> Oranges</label>
</form>
<div id="chart"></div>
</div>
Here is a jsfiddle example showing how you can achieve that: https://jsfiddle.net/kmandov/9jrb1qLr/
I've used Mike Bostock's pie chart example as a base, but you can adapt the code to your PieChart implementation.
The basic idea is that as soon as you switch category(oranges/apples), the pie arcs are recalculated to match the new data. The animation is done via a transition in the change function:
function change() {
// ... calculate arcs
path.transition().duration(750).attrTween("d", arcTween(selected));
}
then the real magic is happening in the arcTween function. In the original example only the start and end angles are updated. You can store the target outerRadius and then update the arc generator on each step of the transition:
function arcTween(selected) {
return function(target, i) {
target.outerRadius = radius - (i === selected ? 0 : 20);
var arcInterpolator = d3.interpolate(this._current, target);
this._current = arcInterpolator(0);
return function(t) {
var interpolatedArc = arcInterpolator(t);
arc.outerRadius(interpolatedArc.outerRadius);
return arc(interpolatedArc);
};
}
}