# Pie Chart
The code is under file `awesome-pie-chart-demo.html`. The chart is draw for a set of dummy spends
<link rel="import" href="https://polygit.org/polymer+polymer+v2.0.0-rc.3/webcomponentsjs+webcomponents+v1.0.0-rc.6/components/polymer/polymer-element.html">
<link rel="import" href="awesome-charts/awesome-pie-chart.html">
<dom-module id="awesome-pie-chart-demo">
<template>
<style>
:host {
display: block;
}
.container{
margin:10px;
background-color:#fafafa;
padding:10px;
border:1px solid #e5e5e5;
}
pre{
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;
margin-bottom: 10px;
overflow: auto;
width: auto;
padding: 5px;
background-color: #eee;
width: 650px!ie7;
padding-bottom: 20px!ie7;
max-height: 600px;
}
.chart1{
--label-text-size : 15px;
--label-stroke-width : 0.3;
--polyline-stroke-color:#666666;
}
.chart2{
width:350px;
margin:auto;
}
</style>
<div class="container">
<h2>Basic Pie chart</h2>
<blockquote>
<pre>
<code>
#1 : Charts binded with data and basic minimal configuration
#2 : mixins used for styling
</code>
</pre>
</blockquote>
<div class="chart1">
<awesome-pie-chart data={{data1}} chart-config={{config1}}></awesome-pie-chart>
</div>
</div>
<div class="container">
<h2>Configurable Pie chart with events</h2>
<blockquote>
<pre>
<code>
#1 : Charts binded with data and few configuration factors
#2 : The hover tooltip here is not default its resolved via a hook 'hover-labels'
#3 : Click events are binded, click any slice to see in action
</code>
</pre>
</blockquote>
<div class="chart2">
<awesome-pie-chart data={{data2}} chart-config={{config2}} pie-click="{{pieClick2}}" hover-labels={{hoverLables2}}></awesome-pie-chart>
</div>
</div>
<div class="container">
<h2>Closed Pie chart with events</h2>
<blockquote>
<pre>
<code>
#1 : Charts binded with data and few configuration factors including closed pie due to inner radius factor being set to 0
#2 : The labels here is not default its resolved via a hook 'span-labels'
#3 : Click events are binded, click any slice to see in action
</code>
</pre>
</blockquote>
<div class="chart3">
<awesome-pie-chart data={{data3}} chart-config={{config3}} pie-click="{{pieClick3}}" span-labels={{spanLabels3}}></awesome-pie-chart>
</div>
</div>
</template>
<script>
// Define the class for a new element called custom-element
class AwesomePieChartDemo extends Polymer.Element {
static get is() { return "awesome-pie-chart-demo"; }
static get properties(){
return{
data1:{
type:Array,
value:[],
notify:true
},
data2:{
type:Array,
value:[],
notify:true
},
data3:{
type:Array,
value:[],
notify:true
},
config1:{
type:Object,
value:{
prop:"value",
label:"id"
}
},
config2:{
type:Object,
value:{
prop:"value",
label:"id",
color:["#ff6961","#ffb861","#61f7ff","#ff61f7","#61a8ff"],
hoverRadius:0.78,
padding:0.030,
hoverAnimationDuration:500,
labelsInside:true,
outerRadius:0.8,
innerRadius:0.6,
radiusSize:0.85,
}
},
config3:{
type:Object,
value:{
prop:"value",
label:"id",
innerRadius:0,
padding:0.030,
disableHoverAnimation:true
}
}
}
}
ready(){
super.ready();
this.data1=[{id:'grocery',value:10000},{id:'rent',value:5000},{id:'shopping',value:35000},{id:'bills',value:20000},{id:'other expense',value:1500}];
this.data2=[{id:'grocery',value:10000},{id:'rent',value:5000},{id:'shopping',value:35000},{id:'bills',value:20000},{id:'other expense',value:1500}];
this.data3=[{id:'grocery',value:10000},{id:'rent',value:5000},{id:'shopping',value:35000},{id:'bills',value:20000},{id:'other expense',value:1500}];
}
hoverLables2(data,p,el){
return data.id + ': $ ' + data.value.toLocaleString('en') + ' ( ' + p.toFixed(2) + '% )';
}
pieClick2(data,p,el){
alert(data.id + " clicked");
console.log(data,p,el);
}
spanLabels3(data,p,el){
return data.id + ': $ ' + data.value.toLocaleString('en')
}
pieClick3(data,p,el){
alert(data.id + " clicked");
console.log(data,p,el);
}
}
// Register the new element with the browser
customElements.define(AwesomePieChartDemo.is, AwesomePieChartDemo);
</script>
</dom-module>
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://polygit.org/webcomponentsjs+1.0.0-rc.5/components/webcomponentsjs/webcomponents-loader.js"></script>
<link rel="import" href="awesome-pie-chart-demo.html">
</head>
<body>
<awesome-pie-chart-demo></awesome-pie-chart-demo>
</body>
</html>
<!--
MIT License
Copyright (c) 2017 Vinod Louis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
-->
<link rel="import" href="https://polygit.org/polymer+polymer+v2.0.0-rc.3/webcomponentsjs+webcomponents+v1.0.0-rc.6/components/polymer/polymer-element.html">
<script src="https://d3js.org/d3.v4.min.js"></script>
<dom-module id="awesome-pie-chart">
<template>
<style>
:host {
display: block;
}
polyline{
opacity: var(--polyline-opacity,0.5);
stroke: var(--polyline-stroke-color,#666666);
stroke-width: var(--polyline-stroke-width,2px);
fill: var(--polyline-fill,none);
pointer-events:none;
}
.cursor-pointer{
cursor:pointer;
}
.pie-label-text{
font-size:var(--label-text-size,12px);
stroke:var(--label-stroke-color,#999999);
stroke-width:var(--label-stroke-width,0.5);
}
</style>
<div id="spanningPieChart"></div>
</template>
<script>
/**
* `awesome-pie-chart`
*
*
* @AwesomePieChart
* @polymer
* @demo demo/awesome-pie-chart.html
*
------------
Properties
------------
#1: Data is the array of object with minimal 2 key value pair which can act as label and prop for pie chart
*REQUIRED
data : {
type: Array,
value:[{id:'grocery',value:10000},{id:'rent',value:5000},{id:'shopping',value:35000},{id:'bills',value:20000},{id:'other expense',value:1500}]
}
#2 chart-config an composite object of properties not all are required provides additional customization to chart
chartConfig:{
type: object,
value:{
//provide a size for chart else its takes the available width
chartSize:300,
//The size of the radius of the pie max to be 1, useful in adjusting the chart in case of huge labels
radiusSize:0.8,
//In case of true labels are draw inside the circle of pie/donught good for small chart
labelsInside:false,
//Array of color size to be equal or greater than size of input data array
color:[],
//on pie hover control animation enable/disable
disableHoverAnimation:false,
//duration of animation on pie hover in milliseconds
hoverAnimationDuration:1000,
//The point at which the chart should animateon hover if greater than 'outerRadius' chart animates outside if less animates inside
hoverRadius:0.8,
*REQUIRED
//label or context text name on which pie chart is to be drawn must be from input object array
label:"",
*REQUIRED
//property value to be considered for pie calculation
prop:"",
//outer radius of the pie should be less than 'radiusSize'
outerRadius:0.7,
//inner radius of the pie if 0 behaves as a full pie chart else gives a feel of donught
innerRadius:0.4,
//corner radius of each pie
cornerRadius:3,
//padding between each slice in chart
padding:0.015,
//The position of the polyline to be drawn for labels should be greater than 'outerRadius'
spanningLine:0.8,
//The position of the labels outside chart make sense to be equal to outer radius always but can experiment
textPositionRadius:0.7
}
}
----------
Methods
----------
#1 : pie-click
Function params include : (param1:<data of the pie clicked>,param2:percentage value of pie clicked:param3:svg object clicked)
--------
Events
--------
#1 : span-labels
Hook so that user can define his own text in the text labels
Return type : String
params passed include : (param1:<data of the pie in context>,param2:percentage value of pie in context:param3:svg object)
#2 : hover-labels
Hook so that user can define his own text in the hover tooltip
Return type : String
params passed include : (param1:<data of the pie in context>,param2:percentage value of pie in context:param3:svg object)
------------------------------
mixins supported for styling
------------------------------
--polyline-opacity : To set opacity of the line for labels
--polyline-stroke-color, : To set stroke color of line
--polyline-stroke-width : To set stroke width of line
--polyline-fill : To set fill color for line
--label-text-size : To set label font size
--label-stroke-color : To set label color
--label-stroke-width : To set label stroke width
*/
class AwesomePieChart extends Polymer.Element {
static get is() { return 'awesome-pie-chart'; }
static get properties() {
return {
chartConfig:{
type: Object
},
__defaultConfig:{
type: Object,
value:{
chartSize:300,
radiusSize:0.8,
labelsInside:false,
color:[],
disableHoverAnimation:false,
label:"",
prop:"",
hoverAnimationDuration:1000,
outerRadius:0.7,
innerRadius:0.4,
cornerRadius:3,
padding:0.015,
spanningLine:0.8,
hoverRadius:0.8,
textPositionRadius:0.7
},
observer:'_dataChanged'
},
data:{
type: Array,
value:[],
observer:'_dataChanged'
}
};
}
ready(){
super.ready();
}
_dataChanged(){
if(this.data){
//Check for label property
if(!this.chartConfig.label || typeof this.chartConfig.label !== "string"){
throw new Error("'label' property of type string not defined for chartConfig");
return;
}
//Check for prop property
if(!this.chartConfig.prop || typeof this.chartConfig.prop !== "string"){
throw new Error("'prop' property of type string not defined for chartConfig");
return;
}
if(this.chartConfig.radiusSize && (typeof this.chartConfig.radiusSize !== "number" || this.chartConfig.radiusSize < 0 || this.chartConfig.radiusSize > 1)){
throw new Error("'radiusSize' should be a numerical value between 0 to 1");
return;
}
this.paintDelayedChart();
}
}
paintDelayedChart(){
var self = this;
setTimeout(()=>self.drawPieChartViz(),0)
}
drawPieChartViz(){
this.chartConfig = Object.assign(JSON.parse(JSON.stringify(this.__defaultConfig)),this.chartConfig);
//Remove the old scraps
d3.select(this.$.spanningPieChart).html("");
//Size and margin set
var self = this;
var margin = {top: 20, right: 0, bottom: 0, left: 0};
var width = (this.offsetWidth == 0) ? this.chartConfig.chartSize : this.offsetWidth;
var height = width;
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
//Calculate the size of the radius
var radius = Math.floor((Math.min(width, height) / 2) * this.chartConfig.radiusSize) ;
//some cosmetics
var legendRectSize = (radius * 0.05);
var legendSpacing = radius * 0.04;
//Set the color scale
var color = (this.chartConfig.color && this.chartConfig.color.length > 0) ? this.chartConfig.color : d3.schemeCategory20;
//Begin the chart drawing by property passed
var pie = d3.pie()
.value((d)=>d[this.chartConfig.prop])
.sort(null);
//Get the basic arc in place based on passed params
var arc = d3.arc()
.outerRadius(radius * this.chartConfig.outerRadius)
.innerRadius(radius * this.chartConfig.innerRadius)
.cornerRadius(this.chartConfig.cornerRadius)
.padAngle(this.chartConfig.padding);
//Get the outer arc padded so that spanning polyline can be drawn
var outerArc = d3.arc()
.outerRadius(radius * this.chartConfig.spanningLine)
.innerRadius(radius * this.chartConfig.spanningLine);
//Define Arc params on hover effect
var outerArcAnimate = d3.arc()
.outerRadius(radius * this.chartConfig.hoverRadius)
.cornerRadius(this.chartConfig.cornerRadius)
.padAngle(this.chartConfig.padding)
.innerRadius(radius * this.chartConfig.innerRadius);
//Lets get the ground ready for charts
var svg = d3.select(this.$.spanningPieChart).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 + ')');
//Group elements such as they adhere to a modular approach
var slices = svg.append('g').attr('class', 'slices');
var labels = svg.append('g').attr('class', 'labelName');
var lines = svg.append('g').attr('class', 'lines');
var legends = svg.append('g').attr('class','legend')
//Add the basic shape using `arc` defined earlier
var path = slices.selectAll("path")
.data(pie(this.data))
.enter().append('path')
.attr("class",()=>(this.pieClick && typeof this.pieClick === "function")?"cursor-pointer":"")
.attr('fill',(d,i)=> color[i])
.attr('d', arc)
.on("mouseenter",function(d){
if(self.chartConfig.disableHoverAnimation)
return;
d3.select(this)
.transition()
.duration(self.chartConfig.hoverAnimationDuration)
.attr("d", outerArcAnimate)
})
.on("mouseleave",function(d){
if(self.chartConfig.disableHoverAnimation)
return;
d3.select(this).transition()
.attr("d", arc)
})
.on('click',function(d){
if(self.pieClick && typeof self.pieClick === "function")
self.pieClick(d.data,((d.endAngle - d.startAngle) / (2 * Math.PI) * 100),this);
})
.append("title").text(function(d){
return (self.hoverLabels && typeof self.hoverLabels === "function") ? self.hoverLabels(d.data,((d.endAngle - d.startAngle) / (2 * Math.PI) * 100),this) : ((d.endAngle - d.startAngle) / (2 * Math.PI) * 100).toFixed(1) + " %";
});
if(!this.chartConfig.labelsInside){
//draw the spanning labels
var label = labels.selectAll('text')
.data(pie(this.data))
.enter().append('text')
.attr('dy', '.35em')
.html(function(d){
//add "key: value" for given category.
return (self.spanLabels && typeof self.spanLabels === "function") ? self.spanLabels(d.data,((d.endAngle - d.startAngle) / (2 * Math.PI) * 100),this) : d.data[self.chartConfig.label] + ': <tspan>' + d.data[self.chartConfig.prop] + '</tspan>';
})
.attr('class',"pie-label-text")
.attr('transform',(d)=>{
// effectively computes the centre of the slice.
// see https://github.com/d3/d3-shape/blob/master/README.md#arc_centroid
var pos = outerArc.centroid(d);
// changes the point to be on left or right depending on where label is.
pos[0] = radius * this.chartConfig.textPositionRadius * (midAngle(d) < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor',(d)=>{
// if slice centre is on the left, anchor text to start, otherwise anchor to end
return (midAngle(d)) < Math.PI ? 'start' : 'end';
});
// add lines connecting labels to slice. A polyline creates straight lines connecting several points
var polyline = lines.selectAll('polyline')
.data(pie(this.data))
.enter().append('polyline')
.attr('points',(d)=>{
// see label transform function for explanations of these three lines.
var pos = outerArc.centroid(d);
pos[0] = radius * this.chartConfig.textPositionRadius * (midAngle(d) < Math.PI ? 1 : -1);
return [arc.centroid(d), outerArc.centroid(d), pos]
});
// calculates the angle for the middle of a slice
function midAngle(d) { return d.startAngle + (d.endAngle - d.startAngle) / 2; }
}else{
var legend = legends.selectAll('.legend')
.data(this.data)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform',(d,i)=> {
var height = legendRectSize + legendSpacing;
var offset = height * this.data.length / 2;
var horz = -6 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', (d,i)=> color[i])
.style('stroke', (d,i)=> color[i]);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize)
.text(function(d) {
return d[self.chartConfig.label]
});
}
}
}
window.customElements.define(AwesomePieChart.is, AwesomePieChart);
</script>
</dom-module>