fix #8056 - more refinements to checking data
[g.raphael] / g.raphael.0.51.js
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      * @param x = starting postion.
767      * @param orentation //0=top 1=right 2=bottom 3=left
768      *
769      */
770     axis: function (x, y, length, from, to, steps, orientation, labels, type, dashsize, paper)
771     {
772         var opts = {};
773         Roo.log(x);
774         
775         if (Raphael.is(x,"object")) {
776             opts = x;
777             x = opts.x;
778             y = opts.y;
779             length = opts.length;
780             from = opts.from;
781             to = opts.to;
782             steps = opts.steps;
783             orientation = opts.orientation;
784             labels = opts.labels,
785             type = opts.type,
786             dashsize = opts.dashsize;
787             paper = opts.paper;
788         } else {
789             paper = arguments[arguments.length-1] //paper is always last argument
790         }
791         opts.loffset = (opts.loffset || 0);
792         opts.roffset = (opts.roffset || 0);
793         
794         dashsize = dashsize == null ? 2 : dashsize;
795         type = type || "t";
796         steps = steps || 10;
797         
798
799         var path = type == "|" || type == " " ? ["M", x + .5, y, "l", 0, .001] :  // this is the main bar line.
800                 orientation == 1 || orientation == 3 ?
801                     ["M", x + .5, y, "l", 0, -length] :
802                     ["M", x, y + .5, "l", length, 0],
803             ends = this.snapEnds(from, to, steps),
804             f = ends.from,
805             t = ends.to,
806             i = ends.power,
807             j = 0,
808             txtattr = { font: "11px 'Fontin Sans', Fontin-Sans, sans-serif" },
809             text = paper.set(),
810             d;
811
812         d = (t - f) / steps;
813
814         var label = f,
815             rnd = i > 0 ? i : 0;
816             dx = length / steps;
817         
818         
819         // left or right
820         if (+orientation == 1 || +orientation == 3) {
821             var Y = y,
822                 addon = (orientation - 1 ? 1 : -1) * (dashsize + 3 + !!(orientation - 1));
823
824             while (Y >= y - length) {
825                 type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), Y + .5, "l", dashsize * 2 + 1, 0]));
826                 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" }));
827                 label += d;
828                 Y -= dx;
829             }
830
831             if (Math.round(Y + dx - (y - length))) {
832                 type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), y - length + .5, "l", dashsize * 2 + 1, 0]));
833                 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" }));
834             }
835         } else {
836             
837             // top or bottom..
838             
839             label = f;
840             rnd = (i > 0) * i;
841             addon = (orientation ? -1 : 1) * (dashsize + 9 + !orientation);
842
843             var X = x + opts.loffset,
844                 dx = (length - (opts.loffset + opts.roffset)) / steps,
845                 txt = 0,
846                 prev = 0;
847         
848             if(dx < 1){
849                 dx = 1;
850             }
851         
852             while (X <= x + length) {
853                 Roo.log('label ' + j + ":" + labels[j]);
854                 
855                 if (typeof(labels[j]) == 'undefined') {
856                     label += d;
857                     X += dx;
858                     continue;
859                 }
860                 // add the line
861                 type != "-" && type != " " && (path = path.concat(["M", X + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
862                 // add the text...
863                 text.push(txt = paper.text(X, y + addon, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
864
865                 var bb = txt.getBBox();
866
867                 if (prev >= bb.x - 5) {
868                     text.pop(text.length - 1).remove();
869                 } else {
870                     prev = bb.x + bb.width;
871                 }
872
873                 label += d;
874                 X += dx;
875             }
876
877             if (Math.round(X - dx - x - length)  && typeof(labels[j]) != 'undefined') {
878                     
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 }