function donutChart() {
var width,
height,
margin = {top: 10, right: 10, bottom: 10, left: 10},
colour = d3.scaleOrdinal(d3.schemeCategory20c), // colour scheme
variable, // value in data that will dictate proportions on chart
category, // compare data by
padAngle, // effectively dictates the gap between slices
floatFormat = d3.format('.4r'),
cornerRadius, // sets how rounded the corners are on each slice
percentFormat = d3.format(',.2%');
function chart(selection){
selection.each(function(data) {
// generate chart
// ===========================================================================================
// Set up constructors for making donut. See https://github.com/d3/d3-shape/blob/master/README.md
var radius = Math.min(width, height) / 2;
// creates a new pie generator
var pie = d3.pie()
.value(function(d) { return floatFormat(d[variable]); })
.sort(null);
// contructs and arc generator. This will be used for the donut. The difference between outer and inner
// radius will dictate the thickness of the donut
var arc = d3.arc()
.outerRadius(radius * 0.8)
.innerRadius(radius * 0.6)
.cornerRadius(cornerRadius)
.padAngle(padAngle);
// this arc is used for aligning the text labels
var outerArc = d3.arc()
.outerRadius(radius * 0.9)
.innerRadius(radius * 0.9);
// ===========================================================================================
// ===========================================================================================
// append the svg object to the selection
var svg = selection.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
// ===========================================================================================
// ===========================================================================================
// g elements to keep elements within svg modular
svg.append('g').attr('class', 'slices');
svg.append('g').attr('class', 'labelName');
svg.append('g').attr('class', 'lines');
// ===========================================================================================
// ===========================================================================================
// add and colour the donut slices
var path = svg.select('.slices')
.datum(data).selectAll('path')
.data(pie)
.enter().append('path')
.attr('fill', function(d) { return colour(d.data[category]); })
.attr('d', arc);
// ===========================================================================================
// ===========================================================================================
// ===========================================================================================
// add lines connecting labels to slice. A polyline creates straight lines connecting several points
// var polyline = svg.select('.lines')
// .selectAll('polyline')
// .data(pie)
// .enter().append('polyline')
// .attr('points', function(d) {
// // see label transform function for explanations of these three lines.
// var pos = outerArc.centroid(d);
// pos[0] = radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1);
// return [arc.centroid(d), outerArc.centroid(d), pos]
// });
// ===========================================================================================
// ===========================================================================================
// add tooltip to mouse events on slices and labels
// d3.selectAll('.labelName text, .slices path').call(toolTip);
// ===========================================================================================
// ===========================================================================================
// Functions
// calculates the angle for the middle of a slice
function midAngle(d) { return d.startAngle + (d.endAngle - d.startAngle) / 2; }
// function that creates and adds the tool tip to a selected element
function toolTip(selection) {
// add tooltip (svg circle element) when mouse enters label or slice
selection.on('mouseenter', function (data) {
svg.append('text')
.attr('class', 'toolCircle')
.attr('dy', -15) // hard-coded. can adjust this to adjust text vertical alignment in tooltip
.html(toolTipHTML(data)) // add text to the circle.
.style('font-size', '.9em')
.style('text-anchor', 'middle'); // centres text in tooltip
svg.append('circle')
.attr('class', 'toolCircle')
.attr('r', radius * 0.55) // radius of tooltip circle
.style('fill', colour(data.data[category])) // colour based on category mouse is over
.style('fill-opacity', 0.35);
});
// remove the tooltip when mouse leaves the slice/label
selection.on('mouseout', function () {
d3.selectAll('.toolCircle').remove();
});
}
// function to create the HTML string for the tool tip. Loops through each key in data object
// and returns the html string key: value
function toolTipHTML(data) {
var tip = '',
i = 0;
for (var key in data.data) {
// if value is a number, format it as a percentage
var value = (!isNaN(parseFloat(data.data[key]))) ? percentFormat(data.data[key]) : data.data[key];
// leave off 'dy' attr for first tspan so the 'dy' attr on text element works. The 'dy' attr on
// tspan effectively imitates a line break.
if (i === 0) tip += '' + key + ': ' + value + '';
else tip += '' + key + ': ' + value + '';
i++;
}
return tip;
}
// ===========================================================================================
});
}
// getter and setter functions. See Mike Bostocks post "Towards Reusable Charts" for a tutorial on how this works.
chart.width = function(value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.height = function(value) {
if (!arguments.length) return height;
height = value;
return chart;
};
chart.margin = function(value) {
if (!arguments.length) return margin;
margin = value;
return chart;
};
chart.radius = function(value) {
if (!arguments.length) return radius;
radius = value;
return chart;
};
chart.padAngle = function(value) {
if (!arguments.length) return padAngle;
padAngle = value;
return chart;
};
chart.cornerRadius = function(value) {
if (!arguments.length) return cornerRadius;
cornerRadius = value;
return chart;
};
chart.colour = function(value) {
if (!arguments.length) return colour;
colour = value;
return chart;
};
chart.variable = function(value) {
if (!arguments.length) return variable;
variable = value;
return chart;
};
chart.category = function(value) {
if (!arguments.length) return category;
category = value;
return chart;
};
return chart;
}