-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathawesome-pie-chart.html
375 lines (325 loc) · 14.2 KB
/
awesome-pie-chart.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
<!--
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.
VERSION 1.0.0
-->
<link rel="import" href="../polymer/polymer-element.html">
<script src="../d3/d3.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>