g.raphael-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,296 @@
1
+ /*!
2
+ * g.Raphael 0.51 - Charting library, based on Raphaël
3
+ *
4
+ * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
5
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
6
+ */
7
+
8
+ /*
9
+ * piechart method on paper
10
+ */
11
+ /*\
12
+ * Paper.piechart
13
+ [ method ]
14
+ **
15
+ * Creates a pie chart
16
+ **
17
+ > Parameters
18
+ **
19
+ - cx (number) x coordinate of the chart
20
+ - cy (number) y coordinate of the chart
21
+ - r (integer) radius of the chart
22
+ - values (array) values used to plot
23
+ - opts (object) options for the chart
24
+ o {
25
+ o minPercent (number) minimal percent threshold which will have a slice rendered. Sliced corresponding to data points below this threshold will be collapsed into 1 additional slice. [default `1`]
26
+ o maxSlices (number) a threshold for how many slices should be rendered before collapsing all remaining slices into 1 additional slice (to focus on most important data points). [default `100`]
27
+ o stroke (string) color of the circle stroke in HTML color format [default `"#FFF"`]
28
+ o strokewidth (integer) width of the chart stroke [default `1`]
29
+ o init (boolean) whether or not to show animation when the chart is ready [default `false`]
30
+ o colors (array) colors be used to plot the chart
31
+ o href (array) urls to to set up clicks on chart slices
32
+ o legend (array) array containing strings that will be used in a legend. Other label options work if legend is defined.
33
+ o legendcolor (string) color of text in legend [default `"#000"`]
34
+ o legendothers (string) text that will be used in legend to describe options that are collapsed into 1 slice, because they are too small to render [default `"Others"`]
35
+ o legendmark (string) symbol used as a bullet point in legend that has the same colour as the chart slice [default `"circle"`]
36
+ o legendpos (string) position of the legend on the chart [default `"east"`]. Other options are `"north"`, `"south"`, `"west"`
37
+ o }
38
+ **
39
+ = (object) path element of the popup
40
+ > Usage
41
+ | r.piechart(cx, cy, r, values, opts)
42
+ \*/
43
+
44
+ (function () {
45
+
46
+ function Piechart(paper, cx, cy, r, values, opts) {
47
+ opts = opts || {};
48
+
49
+ var chartinst = this,
50
+ sectors = [],
51
+ covers = paper.set(),
52
+ chart = paper.set(),
53
+ series = paper.set(),
54
+ order = [],
55
+ len = values.length,
56
+ angle = 0,
57
+ total = 0,
58
+ others = 0,
59
+ cut = opts.maxSlices || 100,
60
+ minPercent = parseFloat(opts.minPercent) || 1,
61
+ defcut = Boolean( minPercent );
62
+
63
+ function sector(cx, cy, r, startAngle, endAngle, fill) {
64
+ var rad = Math.PI / 180,
65
+ x1 = cx + r * Math.cos(-startAngle * rad),
66
+ x2 = cx + r * Math.cos(-endAngle * rad),
67
+ xm = cx + r / 2 * Math.cos(-(startAngle + (endAngle - startAngle) / 2) * rad),
68
+ y1 = cy + r * Math.sin(-startAngle * rad),
69
+ y2 = cy + r * Math.sin(-endAngle * rad),
70
+ ym = cy + r / 2 * Math.sin(-(startAngle + (endAngle - startAngle) / 2) * rad),
71
+ res = [
72
+ "M", cx, cy,
73
+ "L", x1, y1,
74
+ "A", r, r, 0, +(Math.abs(endAngle - startAngle) > 180), 1, x2, y2,
75
+ "z"
76
+ ];
77
+
78
+ res.middle = { x: xm, y: ym };
79
+ return res;
80
+ }
81
+
82
+ chart.covers = covers;
83
+
84
+ if (len == 1) {
85
+ series.push(paper.circle(cx, cy, r).attr({ fill: opts.colors && opts.colors[0] || chartinst.colors[0], stroke: opts.stroke || "#fff", "stroke-width": opts.strokewidth == null ? 1 : opts.strokewidth }));
86
+ covers.push(paper.circle(cx, cy, r).attr(chartinst.shim));
87
+ total = values[0];
88
+ values[0] = { value: values[0], order: 0, valueOf: function () { return this.value; } };
89
+ opts.href && opts.href[0] && covers[0].attr({ href: opts.href[0] });
90
+ series[0].middle = {x: cx, y: cy};
91
+ series[0].mangle = 180;
92
+ } else {
93
+ for (var i = 0; i < len; i++) {
94
+ total += values[i];
95
+ values[i] = { value: values[i], order: i, valueOf: function () { return this.value; } };
96
+ }
97
+
98
+ //values are sorted numerically
99
+ values.sort(function (a, b) {
100
+ return b.value - a.value;
101
+ });
102
+
103
+ for (i = 0; i < len; i++) {
104
+ if (defcut && values[i] * 100 / total < minPercent) {
105
+ cut = i;
106
+ defcut = false;
107
+ }
108
+
109
+ if (i > cut) {
110
+ defcut = false;
111
+ values[cut].value += values[i];
112
+ values[cut].others = true;
113
+ others = values[cut].value;
114
+ }
115
+ }
116
+
117
+ len = Math.min(cut + 1, values.length);
118
+ others && values.splice(len) && (values[cut].others = true);
119
+
120
+ for (i = 0; i < len; i++) {
121
+ var mangle = angle - 360 * values[i] / total / 2;
122
+
123
+ if (!i) {
124
+ angle = 90 - mangle;
125
+ mangle = angle - 360 * values[i] / total / 2;
126
+ }
127
+
128
+ if (opts.init) {
129
+ var ipath = sector(cx, cy, 1, angle, angle - 360 * values[i] / total).join(",");
130
+ }
131
+
132
+ var path = sector(cx, cy, r, angle, angle -= 360 * values[i] / total);
133
+ var j = (opts.matchColors && opts.matchColors == true) ? values[i].order : i;
134
+ var p = paper.path(opts.init ? ipath : path).attr({ fill: opts.colors && opts.colors[j] || chartinst.colors[j] || "#666", stroke: opts.stroke || "#fff", "stroke-width": (opts.strokewidth == null ? 1 : opts.strokewidth), "stroke-linejoin": "round" });
135
+
136
+ p.value = values[i];
137
+ p.middle = path.middle;
138
+ p.mangle = mangle;
139
+ sectors.push(p);
140
+ series.push(p);
141
+ opts.init && p.animate({ path: path.join(",") }, (+opts.init - 1) || 1000, ">");
142
+ }
143
+
144
+ for (i = 0; i < len; i++) {
145
+ p = paper.path(sectors[i].attr("path")).attr(chartinst.shim);
146
+ opts.href && opts.href[i] && p.attr({ href: opts.href[i] });
147
+ p.attr = function () {};
148
+ covers.push(p);
149
+ series.push(p);
150
+ }
151
+ }
152
+
153
+ chart.hover = function (fin, fout) {
154
+ fout = fout || function () {};
155
+
156
+ var that = this;
157
+
158
+ for (var i = 0; i < len; i++) {
159
+ (function (sector, cover, j) {
160
+ var o = {
161
+ sector: sector,
162
+ cover: cover,
163
+ cx: cx,
164
+ cy: cy,
165
+ mx: sector.middle.x,
166
+ my: sector.middle.y,
167
+ mangle: sector.mangle,
168
+ r: r,
169
+ value: values[j],
170
+ total: total,
171
+ label: that.labels && that.labels[j]
172
+ };
173
+ cover.mouseover(function () {
174
+ fin.call(o);
175
+ }).mouseout(function () {
176
+ fout.call(o);
177
+ });
178
+ })(series[i], covers[i], i);
179
+ }
180
+ return this;
181
+ };
182
+
183
+ // x: where label could be put
184
+ // y: where label could be put
185
+ // value: value to show
186
+ // total: total number to count %
187
+ chart.each = function (f) {
188
+ var that = this;
189
+
190
+ for (var i = 0; i < len; i++) {
191
+ (function (sector, cover, j) {
192
+ var o = {
193
+ sector: sector,
194
+ cover: cover,
195
+ cx: cx,
196
+ cy: cy,
197
+ x: sector.middle.x,
198
+ y: sector.middle.y,
199
+ mangle: sector.mangle,
200
+ r: r,
201
+ value: values[j],
202
+ total: total,
203
+ label: that.labels && that.labels[j]
204
+ };
205
+ f.call(o);
206
+ })(series[i], covers[i], i);
207
+ }
208
+ return this;
209
+ };
210
+
211
+ chart.click = function (f) {
212
+ var that = this;
213
+
214
+ for (var i = 0; i < len; i++) {
215
+ (function (sector, cover, j) {
216
+ var o = {
217
+ sector: sector,
218
+ cover: cover,
219
+ cx: cx,
220
+ cy: cy,
221
+ mx: sector.middle.x,
222
+ my: sector.middle.y,
223
+ mangle: sector.mangle,
224
+ r: r,
225
+ value: values[j],
226
+ total: total,
227
+ label: that.labels && that.labels[j]
228
+ };
229
+ cover.click(function () { f.call(o); });
230
+ })(series[i], covers[i], i);
231
+ }
232
+ return this;
233
+ };
234
+
235
+ chart.inject = function (element) {
236
+ element.insertBefore(covers[0]);
237
+ };
238
+
239
+ var legend = function (labels, otherslabel, mark, dir) {
240
+ var x = cx + r + r / 5,
241
+ y = cy,
242
+ h = y + 10;
243
+
244
+ labels = labels || [];
245
+ dir = (dir && dir.toLowerCase && dir.toLowerCase()) || "east";
246
+ mark = paper[mark && mark.toLowerCase()] || "circle";
247
+ chart.labels = paper.set();
248
+
249
+ for (var i = 0; i < len; i++) {
250
+ var clr = series[i].attr("fill"),
251
+ j = values[i].order,
252
+ txt;
253
+
254
+ values[i].others && (labels[j] = otherslabel || "Others");
255
+ labels[j] = chartinst.labelise(labels[j], values[i], total);
256
+ chart.labels.push(paper.set());
257
+ chart.labels[i].push(paper[mark](x + 5, h, 5).attr({ fill: clr, stroke: "none" }));
258
+ 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"}));
259
+ covers[i].label = chart.labels[i];
260
+ h += txt.getBBox().height * 1.2;
261
+ }
262
+
263
+ var bb = chart.labels.getBBox(),
264
+ tr = {
265
+ east: [0, -bb.height / 2],
266
+ west: [-bb.width - 2 * r - 20, -bb.height / 2],
267
+ north: [-r - bb.width / 2, -r - bb.height - 10],
268
+ south: [-r - bb.width / 2, r + 10]
269
+ }[dir];
270
+
271
+ chart.labels.translate.apply(chart.labels, tr);
272
+ chart.push(chart.labels);
273
+ };
274
+
275
+ if (opts.legend) {
276
+ legend(opts.legend, opts.legendothers, opts.legendmark, opts.legendpos);
277
+ }
278
+
279
+ chart.push(series, covers);
280
+ chart.series = series;
281
+ chart.covers = covers;
282
+
283
+ return chart;
284
+ };
285
+
286
+ //inheritance
287
+ var F = function() {};
288
+ F.prototype = Raphael.g;
289
+ Piechart.prototype = new F;
290
+
291
+ //public
292
+ Raphael.fn.piechart = function(cx, cy, r, values, opts) {
293
+ return new Piechart(this, cx, cy, r, values, opts);
294
+ }
295
+
296
+ })();
@@ -0,0 +1,861 @@
1
+ /*!
2
+ * g.Raphael 0.51 - Charting library, based on Raphaël
3
+ *
4
+ * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
5
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
6
+ */
7
+
8
+ /*
9
+ * Tooltips on Element prototype
10
+ */
11
+ /*\
12
+ * Element.popup
13
+ [ method ]
14
+ **
15
+ * Puts the context Element in a 'popup' tooltip. Can also be used on sets.
16
+ **
17
+ > Parameters
18
+ **
19
+ - dir (string) location of Element relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
20
+ - size (number) amount of bevel/padding around the Element, as well as half the width and height of the tail [default: `5`]
21
+ - x (number) x coordinate of the popup's tail [default: Element's `x` or `cx`]
22
+ - y (number) y coordinate of the popup's tail [default: Element's `y` or `cy`]
23
+ **
24
+ = (object) path element of the popup
25
+ \*/
26
+ Raphael.el.popup = function (dir, size, x, y) {
27
+ var paper = this.paper || this[0].paper,
28
+ bb, xy, center, cw, ch;
29
+
30
+ if (!paper) return;
31
+
32
+ switch (this.type) {
33
+ case 'text':
34
+ case 'circle':
35
+ case 'ellipse': center = true; break;
36
+ default: center = false;
37
+ }
38
+
39
+ dir = dir == null ? 'up' : dir;
40
+ size = size || 5;
41
+ bb = this.getBBox();
42
+
43
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
44
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
45
+ cw = Math.max(bb.width / 2 - size, 0);
46
+ ch = Math.max(bb.height / 2 - size, 0);
47
+
48
+ this.translate(x - bb.x - (center ? bb.width / 2 : 0), y - bb.y - (center ? bb.height / 2 : 0));
49
+ bb = this.getBBox();
50
+
51
+ var paths = {
52
+ up: [
53
+ 'M', x, y,
54
+ 'l', -size, -size, -cw, 0,
55
+ 'a', size, size, 0, 0, 1, -size, -size,
56
+ 'l', 0, -bb.height,
57
+ 'a', size, size, 0, 0, 1, size, -size,
58
+ 'l', size * 2 + cw * 2, 0,
59
+ 'a', size, size, 0, 0, 1, size, size,
60
+ 'l', 0, bb.height,
61
+ 'a', size, size, 0, 0, 1, -size, size,
62
+ 'l', -cw, 0,
63
+ 'z'
64
+ ].join(','),
65
+ down: [
66
+ 'M', x, y,
67
+ 'l', size, size, cw, 0,
68
+ 'a', size, size, 0, 0, 1, size, size,
69
+ 'l', 0, bb.height,
70
+ 'a', size, size, 0, 0, 1, -size, size,
71
+ 'l', -(size * 2 + cw * 2), 0,
72
+ 'a', size, size, 0, 0, 1, -size, -size,
73
+ 'l', 0, -bb.height,
74
+ 'a', size, size, 0, 0, 1, size, -size,
75
+ 'l', cw, 0,
76
+ 'z'
77
+ ].join(','),
78
+ left: [
79
+ 'M', x, y,
80
+ 'l', -size, size, 0, ch,
81
+ 'a', size, size, 0, 0, 1, -size, size,
82
+ 'l', -bb.width, 0,
83
+ 'a', size, size, 0, 0, 1, -size, -size,
84
+ 'l', 0, -(size * 2 + ch * 2),
85
+ 'a', size, size, 0, 0, 1, size, -size,
86
+ 'l', bb.width, 0,
87
+ 'a', size, size, 0, 0, 1, size, size,
88
+ 'l', 0, ch,
89
+ 'z'
90
+ ].join(','),
91
+ right: [
92
+ 'M', x, y,
93
+ 'l', size, -size, 0, -ch,
94
+ 'a', size, size, 0, 0, 1, size, -size,
95
+ 'l', bb.width, 0,
96
+ 'a', size, size, 0, 0, 1, size, size,
97
+ 'l', 0, size * 2 + ch * 2,
98
+ 'a', size, size, 0, 0, 1, -size, size,
99
+ 'l', -bb.width, 0,
100
+ 'a', size, size, 0, 0, 1, -size, -size,
101
+ 'l', 0, -ch,
102
+ 'z'
103
+ ].join(',')
104
+ };
105
+
106
+ xy = {
107
+ up: { x: -!center * (bb.width / 2), y: -size * 2 - (center ? bb.height / 2 : bb.height) },
108
+ down: { x: -!center * (bb.width / 2), y: size * 2 + (center ? bb.height / 2 : bb.height) },
109
+ left: { x: -size * 2 - (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) },
110
+ right: { x: size * 2 + (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) }
111
+ }[dir];
112
+
113
+ this.translate(xy.x, xy.y);
114
+ return paper.path(paths[dir]).attr({ fill: "#000", stroke: "none" }).insertBefore(this.node ? this : this[0]);
115
+ };
116
+
117
+ /*\
118
+ * Element.tag
119
+ [ method ]
120
+ **
121
+ * Puts the context Element in a 'tag' tooltip. Can also be used on sets.
122
+ **
123
+ > Parameters
124
+ **
125
+ - angle (number) angle of orientation in degrees [default: `0`]
126
+ - r (number) radius of the loop [default: `5`]
127
+ - x (number) x coordinate of the center of the tag loop [default: Element's `x` or `cx`]
128
+ - y (number) y coordinate of the center of the tag loop [default: Element's `x` or `cx`]
129
+ **
130
+ = (object) path element of the tag
131
+ \*/
132
+ Raphael.el.tag = function (angle, r, x, y) {
133
+ var d = 3,
134
+ paper = this.paper || this[0].paper;
135
+
136
+ if (!paper) return;
137
+
138
+ var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
139
+ bb = this.getBBox(),
140
+ dx, R, center, tmp;
141
+
142
+ switch (this.type) {
143
+ case 'text':
144
+ case 'circle':
145
+ case 'ellipse': center = true; break;
146
+ default: center = false;
147
+ }
148
+
149
+ angle = angle || 0;
150
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
151
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
152
+ r = r == null ? 5 : r;
153
+ R = .5522 * r;
154
+
155
+ if (bb.height >= r * 2) {
156
+ p.attr({
157
+ path: [
158
+ "M", x, y + r,
159
+ "a", r, r, 0, 1, 1, 0, -r * 2, r, r, 0, 1, 1, 0, r * 2,
160
+ "m", 0, -r * 2 -d,
161
+ "a", r + d, r + d, 0, 1, 0, 0, (r + d) * 2,
162
+ "L", x + r + d, y + bb.height / 2 + d,
163
+ "l", bb.width + 2 * d, 0, 0, -bb.height - 2 * d, -bb.width - 2 * d, 0,
164
+ "L", x, y - r - d
165
+ ].join(",")
166
+ });
167
+ } else {
168
+ dx = Math.sqrt(Math.pow(r + d, 2) - Math.pow(bb.height / 2 + d, 2));
169
+ p.attr({
170
+ path: [
171
+ "M", x, y + r,
172
+ "c", -R, 0, -r, R - r, -r, -r, 0, -R, r - R, -r, r, -r, R, 0, r, r - R, r, r, 0, R, R - r, r, -r, r,
173
+ "M", x + dx, y - bb.height / 2 - d,
174
+ "a", r + d, r + d, 0, 1, 0, 0, bb.height + 2 * d,
175
+ "l", r + d - dx + bb.width + 2 * d, 0, 0, -bb.height - 2 * d,
176
+ "L", x + dx, y - bb.height / 2 - d
177
+ ].join(",")
178
+ });
179
+ }
180
+
181
+ angle = 360 - angle;
182
+ p.rotate(angle, x, y);
183
+
184
+ if (this.attrs) {
185
+ //elements
186
+ this.attr(this.attrs.x ? 'x' : 'cx', x + r + d + (!center ? this.type == 'text' ? bb.width : 0 : bb.width / 2)).attr('y', center ? y : y - bb.height / 2);
187
+ this.rotate(angle, x, y);
188
+ angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - r - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
189
+ } else {
190
+ //sets
191
+ if (angle > 90 && angle < 270) {
192
+ this.translate(x - bb.x - bb.width - r - d, y - bb.y - bb.height / 2);
193
+ this.rotate(angle - 180, bb.x + bb.width + r + d, bb.y + bb.height / 2);
194
+ } else {
195
+ this.translate(x - bb.x + r + d, y - bb.y - bb.height / 2);
196
+ this.rotate(angle, bb.x - r - d, bb.y + bb.height / 2);
197
+ }
198
+ }
199
+
200
+ return p.insertBefore(this.node ? this : this[0]);
201
+ };
202
+
203
+ /*\
204
+ * Element.drop
205
+ [ method ]
206
+ **
207
+ * Puts the context Element in a 'drop' tooltip. Can also be used on sets.
208
+ **
209
+ > Parameters
210
+ **
211
+ - angle (number) angle of orientation in degrees [default: `0`]
212
+ - x (number) x coordinate of the drop's point [default: Element's `x` or `cx`]
213
+ - y (number) y coordinate of the drop's point [default: Element's `x` or `cx`]
214
+ **
215
+ = (object) path element of the drop
216
+ \*/
217
+ Raphael.el.drop = function (angle, x, y) {
218
+ var bb = this.getBBox(),
219
+ paper = this.paper || this[0].paper,
220
+ center, size, p, dx, dy;
221
+
222
+ if (!paper) return;
223
+
224
+ switch (this.type) {
225
+ case 'text':
226
+ case 'circle':
227
+ case 'ellipse': center = true; break;
228
+ default: center = false;
229
+ }
230
+
231
+ angle = angle || 0;
232
+
233
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
234
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
235
+ size = Math.max(bb.width, bb.height) + Math.min(bb.width, bb.height);
236
+ p = paper.path([
237
+ "M", x, y,
238
+ "l", size, 0,
239
+ "A", size * .4, size * .4, 0, 1, 0, x + size * .7, y - size * .7,
240
+ "z"
241
+ ]).attr({fill: "#000", stroke: "none"}).rotate(22.5 - angle, x, y);
242
+
243
+ angle = (angle + 90) * Math.PI / 180;
244
+ dx = (x + size * Math.sin(angle)) - (center ? 0 : bb.width / 2);
245
+ dy = (y + size * Math.cos(angle)) - (center ? 0 : bb.height / 2);
246
+
247
+ this.attrs ?
248
+ this.attr(this.attrs.x ? 'x' : 'cx', dx).attr(this.attrs.y ? 'y' : 'cy', dy) :
249
+ this.translate(dx - bb.x, dy - bb.y);
250
+
251
+ return p.insertBefore(this.node ? this : this[0]);
252
+ };
253
+
254
+ /*\
255
+ * Element.flag
256
+ [ method ]
257
+ **
258
+ * Puts the context Element in a 'flag' tooltip. Can also be used on sets.
259
+ **
260
+ > Parameters
261
+ **
262
+ - angle (number) angle of orientation in degrees [default: `0`]
263
+ - x (number) x coordinate of the flag's point [default: Element's `x` or `cx`]
264
+ - y (number) y coordinate of the flag's point [default: Element's `x` or `cx`]
265
+ **
266
+ = (object) path element of the flag
267
+ \*/
268
+ Raphael.el.flag = function (angle, x, y) {
269
+ var d = 3,
270
+ paper = this.paper || this[0].paper;
271
+
272
+ if (!paper) return;
273
+
274
+ var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
275
+ bb = this.getBBox(),
276
+ h = bb.height / 2,
277
+ center;
278
+
279
+ switch (this.type) {
280
+ case 'text':
281
+ case 'circle':
282
+ case 'ellipse': center = true; break;
283
+ default: center = false;
284
+ }
285
+
286
+ angle = angle || 0;
287
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
288
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2: bb.y);
289
+
290
+ p.attr({
291
+ path: [
292
+ "M", x, y,
293
+ "l", h + d, -h - d, bb.width + 2 * d, 0, 0, bb.height + 2 * d, -bb.width - 2 * d, 0,
294
+ "z"
295
+ ].join(",")
296
+ });
297
+
298
+ angle = 360 - angle;
299
+ p.rotate(angle, x, y);
300
+
301
+ if (this.attrs) {
302
+ //elements
303
+ this.attr(this.attrs.x ? 'x' : 'cx', x + h + d + (!center ? this.type == 'text' ? bb.width : 0 : bb.width / 2)).attr('y', center ? y : y - bb.height / 2);
304
+ this.rotate(angle, x, y);
305
+ angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - h - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
306
+ } else {
307
+ //sets
308
+ if (angle > 90 && angle < 270) {
309
+ this.translate(x - bb.x - bb.width - h - d, y - bb.y - bb.height / 2);
310
+ this.rotate(angle - 180, bb.x + bb.width + h + d, bb.y + bb.height / 2);
311
+ } else {
312
+ this.translate(x - bb.x + h + d, y - bb.y - bb.height / 2);
313
+ this.rotate(angle, bb.x - h - d, bb.y + bb.height / 2);
314
+ }
315
+ }
316
+
317
+ return p.insertBefore(this.node ? this : this[0]);
318
+ };
319
+
320
+ /*\
321
+ * Element.label
322
+ [ method ]
323
+ **
324
+ * Puts the context Element in a 'label' tooltip. Can also be used on sets.
325
+ **
326
+ = (object) path element of the label.
327
+ \*/
328
+ Raphael.el.label = function () {
329
+ var bb = this.getBBox(),
330
+ paper = this.paper || this[0].paper,
331
+ r = Math.min(20, bb.width + 10, bb.height + 10) / 2;
332
+
333
+ if (!paper) return;
334
+
335
+ return paper.rect(bb.x - r / 2, bb.y - r / 2, bb.width + r, bb.height + r, r).attr({ stroke: 'none', fill: '#000' }).insertBefore(this.node ? this : this[0]);
336
+ };
337
+
338
+ /*\
339
+ * Element.blob
340
+ [ method ]
341
+ **
342
+ * Puts the context Element in a 'blob' tooltip. Can also be used on sets.
343
+ **
344
+ > Parameters
345
+ **
346
+ - angle (number) angle of orientation in degrees [default: `0`]
347
+ - x (number) x coordinate of the blob's tail [default: Element's `x` or `cx`]
348
+ - y (number) y coordinate of the blob's tail [default: Element's `x` or `cx`]
349
+ **
350
+ = (object) path element of the blob
351
+ \*/
352
+ Raphael.el.blob = function (angle, x, y) {
353
+ var bb = this.getBBox(),
354
+ rad = Math.PI / 180,
355
+ paper = this.paper || this[0].paper,
356
+ p, center, size;
357
+
358
+ if (!paper) return;
359
+
360
+ switch (this.type) {
361
+ case 'text':
362
+ case 'circle':
363
+ case 'ellipse': center = true; break;
364
+ default: center = false;
365
+ }
366
+
367
+ p = paper.path().attr({ fill: "#000", stroke: "none" });
368
+ angle = (+angle + 1 ? angle : 45) + 90;
369
+ size = Math.min(bb.height, bb.width);
370
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
371
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
372
+
373
+ var w = Math.max(bb.width + size, size * 25 / 12),
374
+ h = Math.max(bb.height + size, size * 25 / 12),
375
+ x2 = x + size * Math.sin((angle - 22.5) * rad),
376
+ y2 = y + size * Math.cos((angle - 22.5) * rad),
377
+ x1 = x + size * Math.sin((angle + 22.5) * rad),
378
+ y1 = y + size * Math.cos((angle + 22.5) * rad),
379
+ dx = (x1 - x2) / 2,
380
+ dy = (y1 - y2) / 2,
381
+ rx = w / 2,
382
+ ry = h / 2,
383
+ k = -Math.sqrt(Math.abs(rx * rx * ry * ry - rx * rx * dy * dy - ry * ry * dx * dx) / (rx * rx * dy * dy + ry * ry * dx * dx)),
384
+ cx = k * rx * dy / ry + (x1 + x2) / 2,
385
+ cy = k * -ry * dx / rx + (y1 + y2) / 2;
386
+
387
+ p.attr({
388
+ x: cx,
389
+ y: cy,
390
+ path: [
391
+ "M", x, y,
392
+ "L", x1, y1,
393
+ "A", rx, ry, 0, 1, 1, x2, y2,
394
+ "z"
395
+ ].join(",")
396
+ });
397
+
398
+ this.translate(cx - bb.x - bb.width / 2, cy - bb.y - bb.height / 2);
399
+
400
+ return p.insertBefore(this.node ? this : this[0]);
401
+ };
402
+
403
+ /*
404
+ * Tooltips on Paper prototype
405
+ */
406
+ /*\
407
+ * Paper.label
408
+ [ method ]
409
+ **
410
+ * Puts the given `text` into a 'label' tooltip. The text is given a default style according to @g.txtattr. See @Element.label
411
+ **
412
+ > Parameters
413
+ **
414
+ - x (number) x coordinate of the center of the label
415
+ - y (number) y coordinate of the center of the label
416
+ - text (string) text to place inside the label
417
+ **
418
+ = (object) set containing the label path and the text element
419
+ > Usage
420
+ | paper.label(50, 50, "$9.99");
421
+ \*/
422
+ Raphael.fn.label = function (x, y, text) {
423
+ var set = this.set();
424
+
425
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
426
+ return set.push(text.label(), text);
427
+ };
428
+
429
+ /*\
430
+ * Paper.popup
431
+ [ method ]
432
+ **
433
+ * Puts the given `text` into a 'popup' tooltip. The text is given a default style according to @g.txtattr. See @Element.popup
434
+ *
435
+ * Note: The `dir` parameter has changed from g.Raphael 0.4.1 to 0.5. The options `0`, `1`, `2`, and `3` has been changed to `'down'`, `'left'`, `'up'`, and `'right'` respectively.
436
+ **
437
+ > Parameters
438
+ **
439
+ - x (number) x coordinate of the popup's tail
440
+ - y (number) y coordinate of the popup's tail
441
+ - text (string) text to place inside the popup
442
+ - dir (string) location of the text relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
443
+ - size (number) amount of padding around the Element [default: `5`]
444
+ **
445
+ = (object) set containing the popup path and the text element
446
+ > Usage
447
+ | paper.popup(50, 50, "$9.99", 'down');
448
+ \*/
449
+ Raphael.fn.popup = function (x, y, text, dir, size) {
450
+ var set = this.set();
451
+
452
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
453
+ return set.push(text.popup(dir, size), text);
454
+ };
455
+
456
+ /*\
457
+ * Paper.tag
458
+ [ method ]
459
+ **
460
+ * Puts the given text into a 'tag' tooltip. The text is given a default style according to @g.txtattr. See @Element.tag
461
+ **
462
+ > Parameters
463
+ **
464
+ - x (number) x coordinate of the center of the tag loop
465
+ - y (number) y coordinate of the center of the tag loop
466
+ - text (string) text to place inside the tag
467
+ - angle (number) angle of orientation in degrees [default: `0`]
468
+ - r (number) radius of the loop [default: `5`]
469
+ **
470
+ = (object) set containing the tag path and the text element
471
+ > Usage
472
+ | paper.tag(50, 50, "$9.99", 60);
473
+ \*/
474
+ Raphael.fn.tag = function (x, y, text, angle, r) {
475
+ var set = this.set();
476
+
477
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
478
+ return set.push(text.tag(angle, r), text);
479
+ };
480
+
481
+ /*\
482
+ * Paper.flag
483
+ [ method ]
484
+ **
485
+ * Puts the given `text` into a 'flag' tooltip. The text is given a default style according to @g.txtattr. See @Element.flag
486
+ **
487
+ > Parameters
488
+ **
489
+ - x (number) x coordinate of the flag's point
490
+ - y (number) y coordinate of the flag's point
491
+ - text (string) text to place inside the flag
492
+ - angle (number) angle of orientation in degrees [default: `0`]
493
+ **
494
+ = (object) set containing the flag path and the text element
495
+ > Usage
496
+ | paper.flag(50, 50, "$9.99", 60);
497
+ \*/
498
+ Raphael.fn.flag = function (x, y, text, angle) {
499
+ var set = this.set();
500
+
501
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
502
+ return set.push(text.flag(angle), text);
503
+ };
504
+
505
+ /*\
506
+ * Paper.drop
507
+ [ method ]
508
+ **
509
+ * Puts the given text into a 'drop' tooltip. The text is given a default style according to @g.txtattr. See @Element.drop
510
+ **
511
+ > Parameters
512
+ **
513
+ - x (number) x coordinate of the drop's point
514
+ - y (number) y coordinate of the drop's point
515
+ - text (string) text to place inside the drop
516
+ - angle (number) angle of orientation in degrees [default: `0`]
517
+ **
518
+ = (object) set containing the drop path and the text element
519
+ > Usage
520
+ | paper.drop(50, 50, "$9.99", 60);
521
+ \*/
522
+ Raphael.fn.drop = function (x, y, text, angle) {
523
+ var set = this.set();
524
+
525
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
526
+ return set.push(text.drop(angle), text);
527
+ };
528
+
529
+ /*\
530
+ * Paper.blob
531
+ [ method ]
532
+ **
533
+ * Puts the given text into a 'blob' tooltip. The text is given a default style according to @g.txtattr. See @Element.blob
534
+ **
535
+ > Parameters
536
+ **
537
+ - x (number) x coordinate of the blob's tail
538
+ - y (number) y coordinate of the blob's tail
539
+ - text (string) text to place inside the blob
540
+ - angle (number) angle of orientation in degrees [default: `0`]
541
+ **
542
+ = (object) set containing the blob path and the text element
543
+ > Usage
544
+ | paper.blob(50, 50, "$9.99", 60);
545
+ \*/
546
+ Raphael.fn.blob = function (x, y, text, angle) {
547
+ var set = this.set();
548
+
549
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
550
+ return set.push(text.blob(angle), text);
551
+ };
552
+
553
+ /**
554
+ * Brightness functions on the Element prototype
555
+ */
556
+ /*\
557
+ * Element.lighter
558
+ [ method ]
559
+ **
560
+ * Makes the context element lighter by increasing the brightness and reducing the saturation by a given factor. Can be called on Sets.
561
+ **
562
+ > Parameters
563
+ **
564
+ - times (number) adjustment factor [default: `2`]
565
+ **
566
+ = (object) Element
567
+ > Usage
568
+ | paper.circle(50, 50, 20).attr({
569
+ | fill: "#ff0000",
570
+ | stroke: "#fff",
571
+ | "stroke-width": 2
572
+ | }).lighter(6);
573
+ \*/
574
+ Raphael.el.lighter = function (times) {
575
+ times = times || 2;
576
+
577
+ var fs = [this.attrs.fill, this.attrs.stroke];
578
+
579
+ this.fs = this.fs || [fs[0], fs[1]];
580
+
581
+ fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
582
+ fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
583
+ fs[0].b = Math.min(fs[0].b * times, 1);
584
+ fs[0].s = fs[0].s / times;
585
+ fs[1].b = Math.min(fs[1].b * times, 1);
586
+ fs[1].s = fs[1].s / times;
587
+
588
+ this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
589
+ return this;
590
+ };
591
+
592
+ /*\
593
+ * Element.darker
594
+ [ method ]
595
+ **
596
+ * Makes the context element darker by decreasing the brightness and increasing the saturation by a given factor. Can be called on Sets.
597
+ **
598
+ > Parameters
599
+ **
600
+ - times (number) adjustment factor [default: `2`]
601
+ **
602
+ = (object) Element
603
+ > Usage
604
+ | paper.circle(50, 50, 20).attr({
605
+ | fill: "#ff0000",
606
+ | stroke: "#fff",
607
+ | "stroke-width": 2
608
+ | }).darker(6);
609
+ \*/
610
+ Raphael.el.darker = function (times) {
611
+ times = times || 2;
612
+
613
+ var fs = [this.attrs.fill, this.attrs.stroke];
614
+
615
+ this.fs = this.fs || [fs[0], fs[1]];
616
+
617
+ fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
618
+ fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
619
+ fs[0].s = Math.min(fs[0].s * times, 1);
620
+ fs[0].b = fs[0].b / times;
621
+ fs[1].s = Math.min(fs[1].s * times, 1);
622
+ fs[1].b = fs[1].b / times;
623
+
624
+ this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
625
+ return this;
626
+ };
627
+
628
+ /*\
629
+ * Element.resetBrightness
630
+ [ method ]
631
+ **
632
+ * Resets brightness and saturation levels to their original values. See @Element.lighter and @Element.darker. Can be called on Sets.
633
+ **
634
+ = (object) Element
635
+ > Usage
636
+ | paper.circle(50, 50, 20).attr({
637
+ | fill: "#ff0000",
638
+ | stroke: "#fff",
639
+ | "stroke-width": 2
640
+ | }).lighter(6).resetBrightness();
641
+ \*/
642
+ Raphael.el.resetBrightness = function () {
643
+ if (this.fs) {
644
+ this.attr({ fill: this.fs[0], stroke: this.fs[1] });
645
+ delete this.fs;
646
+ }
647
+ return this;
648
+ };
649
+
650
+ //alias to set prototype
651
+ (function () {
652
+ var brightness = ['lighter', 'darker', 'resetBrightness'],
653
+ tooltips = ['popup', 'tag', 'flag', 'label', 'drop', 'blob'];
654
+
655
+ for (var f in tooltips) (function (name) {
656
+ Raphael.st[name] = function () {
657
+ return Raphael.el[name].apply(this, arguments);
658
+ };
659
+ })(tooltips[f]);
660
+
661
+ for (var f in brightness) (function (name) {
662
+ Raphael.st[name] = function () {
663
+ for (var i = 0; i < this.length; i++) {
664
+ this[i][name].apply(this[i], arguments);
665
+ }
666
+
667
+ return this;
668
+ };
669
+ })(brightness[f]);
670
+ })();
671
+
672
+ //chart prototype for storing common functions
673
+ Raphael.g = {
674
+ /*\
675
+ * g.shim
676
+ [ object ]
677
+ **
678
+ * An attribute object that charts will set on all generated shims (shims being the invisible objects that mouse events are bound to)
679
+ **
680
+ > Default value
681
+ | { stroke: 'none', fill: '#000', 'fill-opacity': 0 }
682
+ \*/
683
+ shim: { stroke: 'none', fill: '#000', 'fill-opacity': 0 },
684
+
685
+ /*\
686
+ * g.txtattr
687
+ [ object ]
688
+ **
689
+ * An attribute object that charts and tooltips will set on any generated text
690
+ **
691
+ > Default value
692
+ | { font: '12px Arial, sans-serif', fill: '#fff' }
693
+ \*/
694
+ txtattr: { font: '12px Arial, sans-serif', fill: '#fff' },
695
+
696
+ /*\
697
+ * g.colors
698
+ [ array ]
699
+ **
700
+ * An array of color values that charts will iterate through when drawing chart data values.
701
+ **
702
+ \*/
703
+ colors: (function () {
704
+ var hues = [.6, .2, .05, .1333, .75, 0],
705
+ colors = [];
706
+
707
+ for (var i = 0; i < 10; i++) {
708
+ if (i < hues.length) {
709
+ colors.push('hsb(' + hues[i] + ',.75, .75)');
710
+ } else {
711
+ colors.push('hsb(' + hues[i - hues.length] + ', 1, .5)');
712
+ }
713
+ }
714
+
715
+ return colors;
716
+ })(),
717
+
718
+ snapEnds: function(from, to, steps) {
719
+ var f = from,
720
+ t = to;
721
+
722
+ if (f == t) {
723
+ return {from: f, to: t, power: 0};
724
+ }
725
+
726
+ function round(a) {
727
+ return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
728
+ }
729
+
730
+ var d = (t - f) / steps,
731
+ r = ~~(d),
732
+ R = r,
733
+ i = 0;
734
+
735
+ if (r) {
736
+ while (R) {
737
+ i--;
738
+ R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
739
+ }
740
+
741
+ i ++;
742
+ } else {
743
+ if(d == 0 || !isFinite(d)) {
744
+ i = 1;
745
+ } else {
746
+ while (!r) {
747
+ i = i || 1;
748
+ r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
749
+ i++;
750
+ }
751
+ }
752
+
753
+ i && i--;
754
+ }
755
+
756
+ t = round(to * Math.pow(10, i)) / Math.pow(10, i);
757
+
758
+ if (t < to) {
759
+ t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
760
+ }
761
+
762
+ f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
763
+ return { from: f, to: t, power: i };
764
+ },
765
+
766
+ axis: function (x, y, length, from, to, steps, orientation, labels, type, dashsize, paper) {
767
+ dashsize = dashsize == null ? 2 : dashsize;
768
+ type = type || "t";
769
+ steps = steps || 10;
770
+ paper = arguments[arguments.length-1] //paper is always last argument
771
+
772
+ var path = type == "|" || type == " " ? ["M", x + .5, y, "l", 0, .001] : orientation == 1 || orientation == 3 ? ["M", x + .5, y, "l", 0, -length] : ["M", x, y + .5, "l", length, 0],
773
+ ends = this.snapEnds(from, to, steps),
774
+ f = ends.from,
775
+ t = ends.to,
776
+ i = ends.power,
777
+ j = 0,
778
+ txtattr = { font: "11px 'Fontin Sans', Fontin-Sans, sans-serif" },
779
+ text = paper.set(),
780
+ d;
781
+
782
+ d = (t - f) / steps;
783
+
784
+ var label = f,
785
+ rnd = i > 0 ? i : 0;
786
+ dx = length / steps;
787
+
788
+ if (+orientation == 1 || +orientation == 3) {
789
+ var Y = y,
790
+ addon = (orientation - 1 ? 1 : -1) * (dashsize + 3 + !!(orientation - 1));
791
+
792
+ while (Y >= y - length) {
793
+ type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), Y + .5, "l", dashsize * 2 + 1, 0]));
794
+ text.push(paper.text(x + addon, Y, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr).attr({ "text-anchor": orientation - 1 ? "start" : "end" }));
795
+ label += d;
796
+ Y -= dx;
797
+ }
798
+
799
+ if (Math.round(Y + dx - (y - length))) {
800
+ type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), y - length + .5, "l", dashsize * 2 + 1, 0]));
801
+ text.push(paper.text(x + addon, y - length, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr).attr({ "text-anchor": orientation - 1 ? "start" : "end" }));
802
+ }
803
+ } else {
804
+ label = f;
805
+ rnd = (i > 0) * i;
806
+ addon = (orientation ? -1 : 1) * (dashsize + 9 + !orientation);
807
+
808
+ var X = x,
809
+ dx = length / steps,
810
+ txt = 0,
811
+ prev = 0;
812
+
813
+ while (X <= x + length) {
814
+ type != "-" && type != " " && (path = path.concat(["M", X + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
815
+ text.push(txt = paper.text(X, y + addon, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
816
+
817
+ var bb = txt.getBBox();
818
+
819
+ if (prev >= bb.x - 5) {
820
+ text.pop(text.length - 1).remove();
821
+ } else {
822
+ prev = bb.x + bb.width;
823
+ }
824
+
825
+ label += d;
826
+ X += dx;
827
+ }
828
+
829
+ if (Math.round(X - dx - x - length)) {
830
+ type != "-" && type != " " && (path = path.concat(["M", x + length + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
831
+ text.push(paper.text(x + length, y + addon, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
832
+ }
833
+ }
834
+
835
+ var res = paper.path(path);
836
+
837
+ res.text = text;
838
+ res.all = paper.set([res, text]);
839
+ res.remove = function () {
840
+ this.text.remove();
841
+ this.constructor.prototype.remove.call(this);
842
+ };
843
+
844
+ return res;
845
+ },
846
+
847
+ labelise: function(label, val, total) {
848
+ if (label) {
849
+ return (label + "").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g, function (all, value, percent) {
850
+ if (value) {
851
+ return (+val).toFixed(value.replace(/^#+\.?/g, "").length);
852
+ }
853
+ if (percent) {
854
+ return (val * 100 / total).toFixed(percent.replace(/^%+\.?/g, "").length) + "%";
855
+ }
856
+ });
857
+ } else {
858
+ return (+val).toFixed(0);
859
+ }
860
+ }
861
+ }