g.raphael-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }