Donut/Pie Chart (Custom Logic)
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 Supports On Roof display.
Example
Description
You should replace the following…
Element | Replace With |
---|---|
[PROPERTY] | The name of the property you want to report on |
class ActiveReport extends SVGReport { initialise() { super.initialise(); }; create() { this.liveReport("'[PROPERTY]' ALLPOINTS"); } update() { this.draw([]); } draw(data) { //Calculate resulting size... if (this.group == null) { //Create an interior group to draw into 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 empty totals var totalitems = 0; //Here's where you define the different pie segments var finaldata = [{name: "Off-Roof",value: 0},{name: "On-Roof", value: 0}]; // Create the colour scale var color = d3.scaleOrdinal() .domain(["Off-Roof","On-Roof"]) .range(["yellow","green"]); //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]; //Here's where you place your conditions. In this case, if it's less than or more than 300. if (vl > 300) finaldata[1].value++; else finaldata[0].value++; } data = finaldata; // 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) { 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 return [posA, posB, posC] }), update => update .transition() .duration(1000) .attr('points', function(d) { 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 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) { var pos = outerArc.centroid(d); var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1); 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) { var pos = outerArc.centroid(d); var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1); return 'translate(' + pos + ')'; }) .attr("opacity", function (d) { if (d.value == 0) return 0; return 1; }) ); } }