====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;
})
);
}
}