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