Donut/Pie Chart (Discrete Values)
A Pie or Donut chart is used to show the percentage of a given population that have a particular characteristic, or the percentage of time that things were in given states.
Note that generally pie charts are not the best visualisation. Because the human eye has more difficulty comparing angles than lines, it's usually better to show this sort of information through a bar-chart.
An example can be found in the Roof Support Mode Donut Chart.
Example
Description
You should replace the following…
Element | Replace With |
---|---|
[PROPERTY] | The name of the property you want to report on |
[PROPERTYID] | The ARDI ID number for the property you want to report on |
class ActiveReport extends SVGReport { initialise() { super.initialise(); }; create() { this.liveReport("'[PROPERTY]' PROPERTY ALLPOINTS"); } update() { this.draw([]); } draw(data) { //Calculate resulting size... if (this.group == null) { // append the svg object to the div called 'my_dataviz' this.group = d3.select("#reportsvg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); } var width = this.width; var height = this.height; var margin = this.margin; // The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin. var radius = Math.min(width, height) / 2 - (margin.left + margin.right); var svg = this.group; svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); // Create variables to keep information var finaldata = []; var domain_names = []; var domain_colours = []; var totalitems = 0; //For every possible value (in ValueSet[PROPERTYID])... for(var q=0;q<ValueSet[PROPERTYID].length;q++) { //Get the name for that value var nm = MapValue[PROPERTYID](ValueSet[PROPERTYID][q]); //Place a segment in the pie-chart to represent that value finaldata.push({v: ValueSet[PROPERTYID][q],name: nm,value: 0}); //If needed, add a colour lookup for that name if (!domain_names.includes(nm)) { domain_names.push(nm); domain_colours.push(MapColour[PROPERTYID](ValueSet[PROPERTYID][q])); } } //Count up the number of items that fit in each category for(var q=0;q<this.livedata.length;q++) { totalitems++; var vl = this.livedata[q]; for(var n=0;n<finaldata.length;n++) { if (finaldata[n].v == vl) { finaldata[n].value += 1; break; } } } data = finaldata; // Create the colour scale var color = d3.scaleOrdinal() .domain(domain_names) .range(domain_colours); // Compute the position of each group on the pie: var pie = d3.pie() .sort(null) // Do not sort group by size .value(function(d) { return d.value; }) //Convert the data into pie-chart information var data_ready = pie(data); // The arc generator var arc = d3.arc() .outerRadius(radius * 0.8) .innerRadius(radius * 0.5); // This is the size of the donut hole // Another, larger arc. This isn't drawn but is used to position the labels. var outerArc = d3.arc() .outerRadius(radius * 0.9) .innerRadius(radius * 0.9); // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function. svg.selectAll('.slices') .data(data_ready) .join( enter => enter.append('path') .attr('d', arc) .attr('fill', function(d){ return(color(d.data.name)) }) .attr("stroke", "white") .style("stroke-width", "2px") .style("opacity", 0.7) .attr("name",function(d) { return d.data.name; }) .attr("value",function(d) { return d.data.value; }) .attr("units"," supports") .call(this.tip) .attr("class","slices"), update => update .attr('d', arc) .attr('fill', function(d){ return(color(d.data.name)) }) .attr("value",function(d) { return d.data.value; }) ); //NOTE: Strange distortions will occur if you try to transition the arc. // Add the polylines between chart and labels: svg.selectAll('.pointerlines') .data(data_ready) .join( enter => enter.append("polyline") .attr("stroke", "white") .style("fill", "none") .attr("class","pointerlines") .attr("stroke-width", 1) .attr("opacity", function (d) { if (d.value == 0) return 0; return 1; }) .attr('points', function(d,i) { var posA = arc.centroid(d) // line insertion in the slice var posB = outerArc.centroid(d) // line break: we use the other arc generator that has been built only for that var posC = outerArc.centroid(d); // Label position = almost the same as posB var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left posC[0] = radius * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left posC[1] -= i*8; posB[1] -= i*8; return [posA, posB, posC] }), update => update .transition() .duration(1000) .attr('points', function(d,i) { var posA = arc.centroid(d) // line insertion in the slice var posB = outerArc.centroid(d) // line break: we use the other arc generator that has been built only for that var posC = outerArc.centroid(d); // Label position = almost the same as posB var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left posC[0] = radius * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left posC[1] -= i*8; posB[1] -= i*8; return [posA, posB, posC] }) .attr("opacity", function (d) { if (d.value == 0) return 0; return 1; }) ); // Add the polylines between chart and labels: svg .selectAll('.labels') .data(data_ready) .join( enter => enter .append('text') .attr("class","labels") .attr("opacity", function (d) { if (d.value == 0) return 0; return 1; }) .text( function(d) { return d.data.name + " ( " + d.data.value + "/" + totalitems + " )" } ) .attr('transform', function(d,i) { var pos = outerArc.centroid(d); var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1); pos[1] -= i*8; return 'translate(' + pos + ')'; }) .attr("fill","white") .style('text-anchor', function(d) { var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 return (midangle < Math.PI ? 'start' : 'end') }), update => update .text( function(d) { return d.data.name + " ( " + d.data.value + "/" + totalitems + " )" } ) .style('text-anchor', function(d) { var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 return (midangle < Math.PI ? 'start' : 'end') }) .transition() .duration(1000) .attr('transform', function(d,i) { var pos = outerArc.centroid(d); var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1); pos[1] -= i*8; return 'translate(' + pos + ')'; }) .attr("opacity", function (d) { if (d.value == 0) return 0; return 1; }) ); } }