====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 [[https://demo.optrix.com.au/s/long/displaylist/info?report=onroof|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 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; }) ); } }