g.pie.js
[g.raphael] / g.pie.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 (function () {
8
9     function Piechart(paper, cx, cy, r, values, opts) {
10         opts = opts || {};
11
12         var chartinst = this,
13             sectors = [],
14             covers = paper.set(),
15             chart = paper.set(),
16             series = paper.set(),
17             order = [],
18             len = values.length,
19             angle = 0,
20             total = 0,
21             others = 0,
22             cut = opts.cut || 9,
23             defcut = true;
24
25         function sector(cx, cy, r, startAngle, endAngle, fill) {
26             var rad = Math.PI / 180,
27                 x1 = cx + r * Math.cos(-startAngle * rad),
28                 x2 = cx + r * Math.cos(-endAngle * rad),
29                 xm = cx + r / 2 * Math.cos(-(startAngle + (endAngle - startAngle) / 2) * rad),
30                 y1 = cy + r * Math.sin(-startAngle * rad),
31                 y2 = cy + r * Math.sin(-endAngle * rad),
32                 ym = cy + r / 2 * Math.sin(-(startAngle + (endAngle - startAngle) / 2) * rad),
33                 res = [
34                     "M", cx, cy,
35                     "L", x1, y1,
36                     "A", r, r, 0, +(Math.abs(endAngle - startAngle) > 180), 1, x2, y2,
37                     "z"
38                 ];
39
40             res.middle = { x: xm, y: ym };
41             return res;
42         }
43         
44         chart.covers = covers;
45
46         if (len == 1) {
47             series.push(paper.circle(cx, cy, r).attr({ fill: chartinst.colors[0], stroke: opts.stroke || "#fff", "stroke-width": opts.strokewidth == null ? 1 : opts.strokewidth }));
48             covers.push(paper.circle(cx, cy, r).attr(chartinst.shim));
49             total = values[0];
50             values[0] = { value: values[0], order: 0, valueOf: function () { return this.value; } };
51             series[0].middle = {x: cx, y: cy};
52             series[0].mangle = 180;
53         } else {
54             for (var i = 0; i < len; i++) {
55                 total += values[i];
56                 values[i] = { value: values[i], order: i, valueOf: function () { return this.value; } };
57             }
58             if (!opts.no_sort) {
59                 values.sort(function (a, b) {
60                     return b.value - a.value;
61                 });
62             }
63             for (i = 0; i < len; i++) {
64                 if (defcut && values[i] * 360 / total <= 1.5) {
65                     cut = i;
66                     defcut = false;
67                 }
68
69                 if (i > cut) {
70                     defcut = false;
71                     values[cut].value += values[i];
72                     values[cut].others = true;
73                     others = values[cut].value;
74                 }
75             }
76
77             len = Math.min(cut + 1, values.length);
78             others && values.splice(len) && (values[cut].others = true);
79
80             for (i = 0; i < len; i++) {
81                 var mangle = angle - 360 * values[i] / total / 2;
82
83                 if (!i) {
84                     // start..
85                     if (typeof(opts.start_angle) == 'undefined') {
86                         angle = 90 - mangle;
87                     } else {
88                         angle = opts.start_angle;
89                     }
90                     
91                     Roo.log('start angle: ' + angle);
92                     mangle = angle - 360 * values[i] / total / 2;
93                 }
94
95                 if (opts.init) {
96                     var ipath = sector(cx, cy, 1, angle, angle - 360 * values[i] / total).join(",");
97                 }
98
99                 var path = sector(cx, cy, r, angle, angle -= 360 * values[i] / total);
100                 var p = paper.path(opts.init ? ipath : path).attr({ fill: opts.colors && opts.colors[i] || chartinst.colors[i] || "#666", stroke: opts.stroke || "#fff", "stroke-width": (opts.strokewidth == null ? 1 : opts.strokewidth), "stroke-linejoin": "round" });
101
102                 p.value = values[i];
103                 p.middle = path.middle;
104                 p.mangle = mangle;
105                 sectors.push(p);
106                 series.push(p);
107                 opts.init && p.animate({ path: path.join(",") }, (+opts.init - 1) || 1000, ">");
108             }
109
110             for (i = 0; i < len; i++) {
111                 p = paper.path(sectors[i].attr("path")).attr(chartinst.shim);
112                 opts.href && opts.href[i] && p.attr({ href: opts.href[i] });
113                 p.attr = function () {};
114                 covers.push(p);
115                 series.push(p);
116             }
117         }
118
119         chart.hover = function (fin, fout) {
120             fout = fout || function () {};
121
122             var that = this;
123
124             for (var i = 0; i < len; i++) {
125                 (function (sector, cover, j) {
126                     var o = {
127                         sector: sector,
128                         cover: cover,
129                         cx: cx,
130                         cy: cy,
131                         mx: sector.middle.x,
132                         my: sector.middle.y,
133                         mangle: sector.mangle,
134                         r: r,
135                         value: values[j],
136                         j : j,
137                         total: total,
138                         label: that.labels && that.labels[j]
139                     };
140                     cover.mouseover(function () {
141                         fin.call(o);
142                     }).mouseout(function () {
143                         fout.call(o);
144                     });
145                 })(series[i], covers[i], i);
146             }
147             return this;
148         };
149
150         // x: where label could be put
151         // y: where label could be put
152         // value: value to show
153         // total: total number to count %
154         chart.each = function (f) {
155             var that = this;
156
157             for (var i = 0; i < len; i++) {
158                 (function (sector, cover, j) {
159                     var o = {
160                         sector: sector,
161                         cover: cover,
162                         cx: cx,
163                         cy: cy,
164                         x: sector.middle.x,
165                         y: sector.middle.y,
166                         mangle: sector.mangle,
167                         r: r,
168                          j : j,
169                         value: values[j],
170                         total: total,
171                         label: that.labels && that.labels[j]
172                     };
173                     f.call(o);
174                 })(series[i], covers[i], i);
175             }
176             return this;
177         };
178
179         chart.click = function (f) {
180             var that = this;
181
182             for (var i = 0; i < len; i++) {
183                 (function (sector, cover, j) {
184                     var o = {
185                         sector: sector,
186                         cover: cover,
187                         cx: cx,
188                         cy: cy,
189                         mx: sector.middle.x,
190                         my: sector.middle.y,
191                         mangle: sector.mangle,
192                         r: r,
193                         j : j,
194                         value: values[j],
195                         total: total,
196                         label: that.labels && that.labels[j]
197                     };
198                     cover.click(function () { f.call(o); });
199                 })(series[i], covers[i], i);
200             }
201             return this;
202         };
203
204         chart.inject = function (element) {
205             element.insertBefore(covers[0]);
206         };
207
208         var legend = function (labels, otherslabel, mark, dir) {
209             var x = cx + r + r / 5,
210                 y = cy,
211                 h = y + 10;
212
213             labels = labels || [];
214             dir = (dir && dir.toLowerCase && dir.toLowerCase()) || "east";
215             mark = paper[mark && mark.toLowerCase()] || "circle";
216             chart.labels = paper.set();
217
218             for (var i = 0; i < len; i++) {
219                 var clr = series[i].attr("fill"),
220                     j = values[i].order,
221                     txt;
222
223                 values[i].others && (labels[j] = otherslabel || "Others");
224                 labels[j] = chartinst.labelise(labels[j], values[i], total);
225                 Roo.log(labels[j]);
226                 chart.labels.push(paper.set());
227                 chart.labels[i].push(paper[mark](x + 5, h, 5).attr({ fill: clr, stroke: "none" }));
228                 chart.labels[i].push(txt = paper.text(x + 20, h, labels[j] || values[j]).attr(chartinst.txtattr).attr({ fill: opts.legendcolor || "#000", "text-anchor": "start"}));
229                 covers[i].label = chart.labels[i];
230                 h += txt.getBBox().height * 1.2;
231             }
232
233             var bb = chart.labels.getBBox(),
234                 tr = {
235                     east: [0, -bb.height / 2],
236                     west: [-bb.width - 2 * r - 20, -bb.height / 2],
237                     north: [-r - bb.width / 2, -r - bb.height - 10],
238                     south: [-r - bb.width / 2, r + 10]
239                 }[dir];
240
241             chart.labels.translate.apply(chart.labels, tr);
242             chart.push(chart.labels);
243         };
244
245         if (opts.legend) {
246             legend(opts.legend, opts.legendothers, opts.legendmark, opts.legendpos);
247         }
248
249         chart.push(series, covers);
250         chart.series = series;
251         chart.covers = covers;
252         
253         chart.sector = sector;
254         chart.cx = cx;
255         chart.cy = cy;
256         chart.r = r;
257         return chart;
258     };
259     
260     //inheritance
261     var F = function() {};
262     F.prototype = Raphael.g;
263     Piechart.prototype = new F;
264     
265     //public
266     Raphael.fn.piechart = function(cx, cy, r, values, opts) {
267         return new Piechart(this, cx, cy, r, values, opts);
268     }
269     
270 })();