fix #8056 - more refinements to checking data
[g.raphael] / g.pie.sector.js
1 /*
2  * g.Raphael 0.5 - Charting library, based on RaphaĆ«l
3  *
4  * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
5  * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
6  */
7 Raphael = typeof(Raphael) != 'undefined' ? Raphael :  (imports ? imports.seed.Raphael.Raphael : {});
8 Roo = typeof(Roo) != 'undefined' ? Roo:  (imports ? imports.seed.Roo.Roo: {});
9 //chartinst = typeof(chartinst) != 'undefined' ? chartinst:  (imports ? imports.chartinst.chartinst : {});
10  
11
12
13 (function () {
14
15     /**
16      * @param {Raphael} paper to draw on
17      * @param {int} cx - centre X
18      * @param {int} cy - centre Y
19      * @param {int} r - radius
20      * @param {Array} values
21      * @param {Object} opts options
22      *   background (string) : background color
23      *   start_angle (number) : the angle of the starting position of the first pie
24      *   barwidth (number) : width of a pie
25      *   colors (array) : colors of the pies
26      *   cut (number) : after showing this number of elements using this number of pies, merge and show the rest of the elements using one pie (maximum 'cut' + 1 pies in total)
27      *   others (string) : legend label labelling the pie for the merged elements (*required if there are merged elements)
28      *   no_sort (boolean) : sort the values in descending order if it is not set
29      *   labels (array) : labels on the pie
30      *   labelfont (string) : font family of the labels
31      *   labelsize (number) : font size of the labels
32      *   labelweight (string)(number) : font weight of the labels
33      *   labelcolor (string) : font color of the labels
34      *   showlabel (number) : only show label if the value >= 'showlabel'% of total
35      *   legend (array) : legend
36      *   legendpos (string) : position of the legend ('right' / 'bottom')
37      *   legendkeyshape (string) : shape of the legend keys ('circle' / 'rect')
38      *   legendkeysize (number) : size of the legend keys (diameter for 'circle' and width for 'rect')
39      *   legendfont (string) : font family of the legend labels
40      *   legendfontsize (number) : font size of the legend labels
41      *   legendfontcolor (string) : font color of the legend colors
42      *   lineheight (number) : distance between two legend labels
43      *   legendcolumn (number) : number of columns used to show legend (1 / 2)
44      */
45
46     function Piesectorchart(paper, width, height, cx, cy, r, values, opts) {
47          
48         opts = opts || {};
49
50         var chartinst = this,
51             chart = paper.set(),
52             len = values.length,
53             total = 0,
54             angle = opts.start_angle || 90, 
55             cut = opts.cut || 9,
56             defcut = true,
57             labels = opts.labels || false, // default no labels
58             labelFont = opts.labelfont || "'Fontin Sans', Fontin-Sans, sans-serif",
59             labelSize = opts.labelsize || 18,
60             labelWeight = opts.labelweight || 'normal',
61             labelColor = opts.labelcolor || '#FFFFFF',
62             showLabel = typeof(opts.showlabel) != 'undefined' ? opts.showlabel : 5; // default show label if value >= 5% of total
63         
64         opts.barwidth = opts.barwidth || 80;
65         
66         paper.rect(0, 0, width, height).attr({ stroke: "none", fill: (opts.background || "#F0F4F7") });
67         
68         paper.customAttributes.sector = function (cx, cy, startAngle, endAngle, color, R) {
69             
70             var rad = Math.PI / 180,
71                 x1 = cx + r * Math.cos(-startAngle * rad),
72                 x2 = cx + r * Math.cos(-endAngle * rad),
73                 y1 = cy + r * Math.sin(-startAngle * rad),
74                 y2 = cy + r * Math.sin(-endAngle * rad),
75                 path;
76         
77             path = [["M", x1, y1], ["A", R, R, 0, +(Math.abs(endAngle - startAngle) > 180), 1, x2, y2]];
78
79             return {path: path, stroke: color};
80         };
81
82         for (var i = 0; i < len; i++) {
83             total += values[i] * 1;
84             values[i] = {
85                 value: values[i],
86                 origin: i,
87                 valueOf: function () { return this.value; }
88             };
89         }
90         
91         if (len == 1) {
92             paper.circle(cx, cy, r + opts.barwidth / 2).attr({ fill: opts.colors && opts.colors[0] || chartinst.colors[0] || "#3E66BC", "stroke": "#fff" });
93             paper.circle(cx, cy, r - opts.barwidth / 2).attr({ fill: opts.background || "#F0F4F7", "stroke": "#fff" });
94             
95         } else {
96             
97             if (!opts.no_sort) {
98                 values.sort(function (a, b) {
99                     return b.value - a.value;
100                 });
101             }
102             
103             for (i = 0; i < len; i++) {
104                 if (i > cut) {
105                     defcut = false;
106                     values[cut].value += values[i];
107                     values[cut].others = true;
108                 }
109
110                 // minimum degree of a pie shown
111                 if (defcut && values[i] * 360 / total <= 1.5) {
112                     cut = i;
113                     defcut = false;
114                 }
115             }
116             
117             len = Math.min(cut + 1, values.length);
118
119             var a = angle;
120             
121             for (i = 0; i < len; i++) {
122                 paper.path().attr({
123                     "stroke": "#fff", 
124                     "stroke-width": opts.barwidth
125                 }).attr({sector: [cx, cy, a, a -= 360 * values[i] / total, opts.colors && opts.colors[i] || chartinst.colors[i], r]});
126             }
127             
128         }
129
130         // labels
131         var rad = Math.PI / 180;
132
133         var a = angle;
134
135         for (i = 0; i < len; i++) {
136
137             a -= 360 * values[i] / total;
138
139             // show the label only if the values >= 5% of total
140             if(labels && values[i] / total >= (showLabel / 100)) {
141                 var text = labels[values[i].origin];
142
143                 if(text.indexOf('#qty#') !== -1) {
144                     text = text.replace('#qty#', Roo.util.Format.number(Math.round(values[i]), 0));
145                 }
146
147                 if(text.indexOf('#%#') !== -1) {
148                     text = text.replace('#%#', Math.round(values[i] / total * 100) + '%');
149                 }
150
151                 var tx = cx + r * Math.cos(-(a + 180 * values[i] / total) * rad),
152                     ty = cy + r * Math.sin(-(a + 180 * values[i] / total) * rad);
153
154                 paper.text(tx, ty, text).attr({ 
155                     "font-size": labelSize,
156                     "font-family": labelFont,
157                     "font-weight" : labelWeight,
158                     "text-anchor": "middle",
159                     fill : labelColor
160                 });
161             }
162         }
163
164         
165         legend(paper, cx, cy, r, values, opts, total, len);
166
167         chart.cx = cx;
168         chart.cy = cy;
169         chart.r = r;
170         return chart;
171     }
172     
173     // draw legend
174     function legend(paper, cx, cy, r, values, opts, total, len) 
175     {
176         var chartinst = this,
177             legendPos = opts.legendpos || 'right',
178             legendKeyShape = opts.legendkeyshape || 'circle',
179             legendKeySize = opts.legendkeysize || 12,
180             legendFont = opts.legendfont || "'Fontin Sans', Fontin-Sans, sans-serif",
181             legendFontSize = opts.legendfontsize || 18,
182             legendFontColor = opts.legendfontcolor || '#0C014F',
183             lineHeight = opts.lineheight || 30,
184             legendColumn = opts.legendcolumn || 1;
185
186         // default 'legendPos' is 'right'
187         // ix, iy: center position of legend key
188         var ix = cx + r + opts.barwidth / 2 + 30, // 30 pixels away from right of the chart
189             iy = cy - r - 30;
190
191         if(legendPos == 'bottom') {
192             ix = cx - r - opts.barwidth / 2 - 30; // 30 pixels away from left of the chart
193             iy = cy + r + opts.barwidth / 2 + 30; // 30 pixels away from bottom of the chart
194
195             // default 'legendColumn' is 1
196             if(legendColumn == 2) {
197                 ix = cx - r - opts.barwidth / 2 - 90; // 90 pixels away from left of the chart
198             }
199         }
200
201         for (var i = 0; i < len; i++) {
202             
203             if(legendKeyShape == 'rect') {
204                 // pass top left position for 'rect'
205                 paper.rect(ix - (legendKeySize / 2), iy - (legendKeySize / 2), legendKeySize, legendKeySize, 0).attr({ fill: opts.colors && opts.colors[i] || chartinst.colors[i] || "#3E66BC", "stroke": "#fff" });
206             }
207             else {
208                 // pass center position for 'circle'
209                 paper.circle(ix, iy, legendKeySize / 2).attr({ fill: opts.colors && opts.colors[i] || chartinst.colors[i] || "#3E66BC", "stroke": "#fff" });
210             }
211
212             var text = (values[i].others) ? opts.others : opts.legend[values[i].origin] || values[i];
213             
214             if(text.indexOf('#qty#') !== -1) {
215                 text = text.replace('#qty#', Roo.util.Format.number(Math.round(values[i]), 0));
216             }
217
218             if(text.indexOf('#%#') !== -1) {
219                 text = text.replace('#%#', Math.round(values[i] / total * 100) + '%');
220             }
221
222             var ty = iy - legendFontSize / 10;
223
224             if(text.indexOf("\n")> -1) {
225                 var ty = iy - legendFontSize / 10 + legendFontSize / 2;
226             }
227             
228             // 12 pixels away from the right of legend key
229             // align legend key and text horizontally
230             paper.text(ix + legendKeySize / 2 + 12, ty, text).attr({ 
231                 "font-size": legendFontSize,
232                 "font-family": legendFont,
233                 "text-anchor": "start",
234                 fill : legendFontColor
235             });
236
237             if(legendColumn == 2) {
238                 if(i % 2 == 0) {
239                     ix += r + opts.barwidth / 2 + 120;
240                     iy -= lineHeight;
241                 }
242                 else {
243                     ix -= r + opts.barwidth / 2 + 120;
244                 }
245             }
246
247             iy += lineHeight;
248         }
249     }
250
251     //inheritance
252     var F = function() {};
253     F.prototype = Raphael.g;
254     Piesectorchart.prototype = new F;
255     
256     //public
257     Raphael.fn.piesectorchart = function(width, height, cx, cy, r, values, opts) {
258         return new Piesectorchart(this, width, height, cx, cy, r, values, opts);
259     }
260     
261 })();