Skip to content

Instantly share code, notes, and snippets.

@bsurnida
Last active June 9, 2017 12:49
Show Gist options
  • Save bsurnida/581bbe7e91c1b53bc8f0128c9a4de305 to your computer and use it in GitHub Desktop.
Save bsurnida/581bbe7e91c1b53bc8f0128c9a4de305 to your computer and use it in GitHub Desktop.
Interactive Donut Charts - http://bl.ocks.org/erichoco/6694616
license: mit

By using the center part of the chart, this example demonstrates one way to show information in the scenarios of pie or donut charts. We could also perform interactions between these charts by hovering or clicking.

forked from erichoco's block: Interactive Donut Charts

<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
body {
font-size: 100%;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 960px;
}
#refresh-btn {
float: right;
font-size: 12px;
border-radius: 0;
}
</style>
<body>
<h4>
<a href="http://bl.ocks.org/erichoco/6694616">forked from erichoco/6694616</a>
</h4>
<button type="button" id="refresh-btn">Refresh data</button>
<div id="donut-charts"></div>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
$(function() {
var donutData = genData();
var donuts = new DonutCharts();
donuts.create(donutData);
$('#refresh-btn').on('click', function refresh() {
donuts.update(genData);
});
});
function DonutCharts() {
var charts = d3.select('#donut-charts');
var chart_m,
chart_r,
color = d3.scale.category20();
var getCatNames = function(dataset) {
var catNames = new Array();
for (var i = 0; i < dataset[0].data.length; i++) {
catNames.push(dataset[0].data[i].cat);
}
return catNames;
}
var createLegend = function(catNames) {
var legends = charts.select('.legend')
.selectAll('g')
.data(catNames)
.enter().append('g')
.attr('transform', function(d, i) {
return 'translate(' + (i * 150 + 50) + ', 10)';
});
legends.append('circle')
.attr('class', 'legend-icon')
.attr('r', 6)
.style('fill', function(d, i) {
return color(i);
});
legends.append('text')
.attr('dx', '1em')
.attr('dy', '.3em')
.text(function(d) {
return d;
});
}
var createCenter = function(pie) {
var eventObj = {
'mouseover': function(d, i) {
d3.select(this)
.transition()
.attr("r", chart_r * 0.65);
},
'mouseout': function(d, i) {
d3.select(this)
.transition()
.duration(500)
.ease('bounce')
.attr("r", chart_r * 0.6);
},
'click': function(d, i) {
var paths = charts.selectAll('.clicked');
pathAnim(paths, 0);
paths.classed('clicked', false);
resetAllCenterText();
}
}
var donuts = d3.selectAll('.donut');
// The circle displaying total data.
donuts.append("svg:circle")
.attr("r", chart_r * 0.6)
.style("fill", "#E7E7E7")
.on(eventObj);
donuts.append('text')
.attr('class', 'center-txt type')
.attr('y', chart_r * -0.16)
.attr('text-anchor', 'middle')
.style('font-weight', 'bold')
.text(function(d, i) {
return d.type;
});
donuts.append('text')
.attr('class', 'center-txt value')
.attr('text-anchor', 'middle');
donuts.append('text')
.attr('class', 'center-txt percentage')
.attr('y', chart_r * 0.16)
.attr('text-anchor', 'middle')
.style('fill', '#A2A2A2');
}
var setCenterText = function(thisDonut) {
var sum = d3.sum(thisDonut.selectAll('.clicked').data(), function(d) {
return d.data.val;
});
thisDonut.select('.value')
.text(function(d) {
return (sum)? sum.toFixed(1) + d.unit
: d.total.toFixed(1) + d.unit;
});
thisDonut.select('.percentage')
.text(function(d) {
return (sum)? (sum/d.total*100).toFixed(2) + '%'
: '';
});
}
var resetAllCenterText = function() {
charts.selectAll('.value')
.text(function(d) {
return d.total.toFixed(1) + d.unit;
});
charts.selectAll('.percentage')
.text('');
}
var pathAnim = function(path, dir) {
switch(dir) {
case 0:
path.transition()
.duration(500)
.ease('bounce')
.attr('d', d3.svg.arc()
.innerRadius(chart_r * 0.7)
.outerRadius(chart_r)
);
break;
case 1:
path.transition()
.attr('d', d3.svg.arc()
.innerRadius(chart_r * 0.7)
.outerRadius(chart_r * 1.08)
);
break;
}
}
var updateDonut = function() {
var eventObj = {
'mouseover': function(d, i, j) {
pathAnim(d3.select(this), 1);
var thisDonut = charts.select('.type' + j);
thisDonut.select('.value').text(function(donut_d) {
return d.data.val.toFixed(1) + donut_d.unit;
});
thisDonut.select('.percentage').text(function(donut_d) {
return (d.data.val/donut_d.total*100).toFixed(2) + '%';
});
},
'mouseout': function(d, i, j) {
var thisPath = d3.select(this);
if (!thisPath.classed('clicked')) {
pathAnim(thisPath, 0);
}
var thisDonut = charts.select('.type' + j);
setCenterText(thisDonut);
},
'click': function(d, i, j) {
var thisDonut = charts.select('.type' + j);
if (0 === thisDonut.selectAll('.clicked')[0].length) {
thisDonut.select('circle').on('click')();
}
var thisPath = d3.select(this);
var clicked = thisPath.classed('clicked');
pathAnim(thisPath, ~~(!clicked));
thisPath.classed('clicked', !clicked);
setCenterText(thisDonut);
}
};
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.val;
});
var arc = d3.svg.arc()
.innerRadius(chart_r * 0.7)
.outerRadius(function() {
return (d3.select(this).classed('clicked'))? chart_r * 1.08
: chart_r;
});
// Start joining data with paths
var paths = charts.selectAll('.donut')
.selectAll('path')
.data(function(d, i) {
return pie(d.data);
});
paths
.transition()
.duration(1000)
.attr('d', arc);
paths.enter()
.append('svg:path')
.attr('d', arc)
.style('fill', function(d, i) {
return color(i);
})
.style('stroke', '#FFFFFF')
.on(eventObj)
paths.exit().remove();
resetAllCenterText();
}
this.create = function(dataset) {
var $charts = $('#donut-charts');
chart_m = $charts.innerWidth() / dataset.length / 2 * 0.14;
chart_r = $charts.innerWidth() / dataset.length / 2 * 0.85;
charts.append('svg')
.attr('class', 'legend')
.attr('width', '100%')
.attr('height', 50)
.attr('transform', 'translate(0, -100)');
var donut = charts.selectAll('.donut')
.data(dataset)
.enter().append('svg:svg')
.attr('width', (chart_r + chart_m) * 2)
.attr('height', (chart_r + chart_m) * 2)
.append('svg:g')
.attr('class', function(d, i) {
return 'donut type' + i;
})
.attr('transform', 'translate(' + (chart_r+chart_m) + ',' + (chart_r+chart_m) + ')');
createLegend(getCatNames(dataset));
createCenter();
updateDonut();
}
this.update = function(dataset) {
// Assume no new categ of data enter
var donut = charts.selectAll(".donut")
.data(dataset);
updateDonut();
}
}
/*
* Returns a json-like object.
*/
function genData() {
var type = ['Users', 'Avg Upload', 'Avg Files Shared'];
var unit = ['M', 'GB', ''];
var cat = ['Google Drive', 'Dropbox', 'iCloud', 'OneDrive', 'Box'];
var dataset = new Array();
for (var i = 0; i < type.length; i++) {
var data = new Array();
var total = 0;
for (var j = 0; j < cat.length; j++) {
var value = Math.random()*10*(3-i);
total += value;
data.push({
"cat": cat[j],
"val": value
});
}
dataset.push({
"type": type[i],
"unit": unit[i],
"data": data,
"total": total
});
}
console.log(JSON.stringify(dataset, null, 2));
return dataset;
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment