function donutChart() { var width, height, margin = {top: 10, right: 10, bottom: 10, left: 10}, colour = d3.scaleOrdinal(d3.schemeCategory20), // 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%'), intFormat = d3.format(",.0f"); 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); // the total var tots = d3.sum(data, function(d) { return floatFormat(d[variable]); }); // constructs an 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.7) .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 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) { d3.selectAll('.toolCircle').remove(); // add category text to the circle. svg.append('text') .attr('class', 'toolCircle') .attr('dy', -15) // hard-coded. can adjust this to adjust text vertical alignment in tooltip .html(function(d) { // add "key: value" for given category. Number inside tspan is bolded in stylesheet. return data.data[category] + '' + data.data[variable] + '' + ' / ' + tots }) //.style('font-size', '2.0em') //.style('font-weight', '700') .style('text-anchor', 'middle'); // centres text in tooltip svg.append('circle') .attr('class', 'toolCircle') .attr('r', radius * 0.69) // 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 (data) { d3.selectAll('.toolCircle').remove(); // add category text to the circle. svg.append('text') .attr('class', 'toolCircle') .attr('dy', -15) .html(toolTipHTML(data)) .style('text-anchor', 'middle'); svg.append('circle') .attr('class', 'toolCircle') .attr('r', radius * 0.69) // radius of tooltip circle .style('fill', 'white') // colour based on category mouse is over .style('fill-opacity', 0); }); } // 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 an integer var value = (!isNaN(parseFloat(data.data[key]))) ? intFormat(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 (value === "Completed On Time") { tip += 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; }