/*!
 * g.Raphael 0.5 - Charting library, based on Raphaël
 *
 * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 */

/*
 * Tooltips on Element prototype
 */
/*\
 * Element.popup
 [ method ]
 **
 * Puts the context Element in a 'popup' tooltip. Can also be used on sets.
 **
 > Parameters
 **
 - dir (string) location of Element relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
 - size (number) amount of bevel/padding around the Element, as well as half the width and height of the tail [default: `5`]
 - x (number) x coordinate of the popup's tail [default: Element's `x` or `cx`]
 - y (number) y coordinate of the popup's tail [default: Element's `y` or `cy`]
 **
 = (object) path element of the popup
 > Usage
 | paper.circle(50, 50, 5).attr({
 |     stroke: "#fff",
 |     fill: "0-#c9de96-#8ab66b:44-#398235"
 | }).popup();
 \*/
Raphael.el.popup = function (dir, size, x, y) {
    var paper = this.paper || this[0].paper,
        bb, xy, center, cw, ch;

    if (!paper) return;

    switch (this.type) {
        case 'text':
        case 'circle':
        case 'ellipse': center = true; break;
        default: center = false;
    }

    dir = dir == null ? 'up' : dir;
    size = size || 5;
    bb = this.getBBox();

    x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
    y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
    cw = Math.max(bb.width / 2 - size, 0);
    ch = Math.max(bb.height / 2 - size, 0);

    this.translate(x - bb.x - (center ? bb.width / 2 : 0), y - bb.y - (center ? bb.height / 2 : 0));
    bb = this.getBBox();

    var paths = {
        up: [
            'M', x, y,
            'l', -size, -size, -cw, 0,
            'a', size, size, 0, 0, 1, -size, -size,
            'l', 0, -bb.height,
            'a', size, size, 0, 0, 1, size, -size,
            'l', size * 2 + cw * 2, 0,
            'a', size, size, 0, 0, 1, size, size,
            'l', 0, bb.height,
            'a', size, size, 0, 0, 1, -size, size,
            'l', -cw, 0,
            'z'
        ].join(','),
        down: [
            'M', x, y,
            'l', size, size, cw, 0,
            'a', size, size, 0, 0, 1, size, size,
            'l', 0, bb.height,
            'a', size, size, 0, 0, 1, -size, size,
            'l', -(size * 2 + cw * 2), 0,
            'a', size, size, 0, 0, 1, -size, -size,
            'l', 0, -bb.height,
            'a', size, size, 0, 0, 1, size, -size,
            'l', cw, 0,
            'z'
        ].join(','),
        left: [
            'M', x, y,
            'l', -size, size, 0, ch,
            'a', size, size, 0, 0, 1, -size, size,
            'l', -bb.width, 0,
            'a', size, size, 0, 0, 1, -size, -size,
            'l', 0, -(size * 2 + ch * 2),
            'a', size, size, 0, 0, 1, size, -size,
            'l', bb.width, 0,
            'a', size, size, 0, 0, 1, size, size,
            'l', 0, ch,
            'z'
        ].join(','),
        right: [
            'M', x, y,
            'l', size, -size, 0, -ch,
            'a', size, size, 0, 0, 1, size, -size,
            'l', bb.width, 0,
            'a', size, size, 0, 0, 1, size, size,
            'l', 0, size * 2 + ch * 2,
            'a', size, size, 0, 0, 1, -size, size,
            'l', -bb.width, 0,
            'a', size, size, 0, 0, 1, -size, -size,
            'l', 0, -ch,
            'z'
        ].join(',')
    };

    xy = {
        up: { x: -!center * (bb.width / 2), y: -size * 2 - (center ? bb.height / 2 : bb.height) },
        down: { x: -!center * (bb.width / 2), y: size * 2 + (center ? bb.height / 2 : bb.height) },
        left: { x: -size * 2 - (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) },
        right: { x: size * 2 + (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) }
    }[dir];

    this.translate(xy.x, xy.y);
    return paper.path(paths[dir]).attr({ fill: "#000", stroke: "none" }).insertBefore(this.node ? this : this[0]);
};

/*\
 * Element.tag
 [ method ]
 **
 * Puts the context Element in a 'tag' tooltip. Can also be used on sets.
 **
 > Parameters
 **
 - angle (number) angle of orientation in degrees [default: `0`]
 - r (number) radius of the loop [default: `5`]
 - x (number) x coordinate of the center of the tag loop [default: Element's `x` or `cx`]
 - y (number) y coordinate of the center of the tag loop [default: Element's `x` or `cx`]
 **
 = (object) path element of the tag
 > Usage
 | paper.circle(50, 50, 15).attr({
 |     stroke: "#fff",
 |     fill: "0-#c9de96-#8ab66b:44-#398235"
 | }).tag(60);
 \*/
Raphael.el.tag = function (angle, r, x, y) {
    var d = 3,
        paper = this.paper || this[0].paper;

    if (!paper) return;

    var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
        bb = this.getBBox(),
        dx, R, center, tmp;

    switch (this.type) {
        case 'text':
        case 'circle':
        case 'ellipse': center = true; break;
        default: center = false;
    }

    angle = angle || 0;
    x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
    y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
    r = r == null ? 5 : r;
    R = .5522 * r;

    if (bb.height >= r * 2) {
        p.attr({
            path: [
                "M", x, y + r,
                "a", r, r, 0, 1, 1, 0, -r * 2, r, r, 0, 1, 1, 0, r * 2,
                "m", 0, -r * 2 -d,
                "a", r + d, r + d, 0, 1, 0, 0, (r + d) * 2,
                "L", x + r + d, y + bb.height / 2 + d,
                "l", bb.width + 2 * d, 0, 0, -bb.height - 2 * d, -bb.width - 2 * d, 0,
                "L", x, y - r - d
            ].join(",")
        });
    } else {
        dx = Math.sqrt(Math.pow(r + d, 2) - Math.pow(bb.height / 2 + d, 2));
        p.attr({
            path: [
                "M", x, y + r,
                "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,
                "M", x + dx, y - bb.height / 2 - d,
                "a", r + d, r + d, 0, 1, 0, 0, bb.height + 2 * d,
                "l", r + d - dx + bb.width + 2 * d, 0, 0, -bb.height - 2 * d,
                "L", x + dx, y - bb.height / 2 - d
            ].join(",")
        });
    }

    angle = 360 - angle;
    p.rotate(angle, x, y);

    if (this.attrs) {
        //elements
        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);
        this.rotate(angle, x, y);
        angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - r - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
    } else {
        //sets
        if (angle > 90 && angle < 270) {
            this.translate(x - bb.x - bb.width - r - d, y - bb.y - bb.height / 2);
            this.rotate(angle - 180, bb.x + bb.width + r + d, bb.y + bb.height / 2);
        } else {
            this.translate(x - bb.x + r + d, y - bb.y - bb.height / 2);
            this.rotate(angle, bb.x - r - d, bb.y + bb.height / 2); 
        }
    }

    return p.insertBefore(this.node ? this : this[0]);
};

/*\
 * Element.drop
 [ method ]
 **
 * Puts the context Element in a 'drop' tooltip. Can also be used on sets.
 **
 > Parameters
 **
 - angle (number) angle of orientation in degrees [default: `0`]
 - x (number) x coordinate of the drop's point [default: Element's `x` or `cx`]
 - y (number) y coordinate of the drop's point [default: Element's `x` or `cx`]
 **
 = (object) path element of the drop
 > Usage
 | paper.circle(50, 50, 8).attr({
 |     stroke: "#fff",
 |     fill: "0-#c9de96-#8ab66b:44-#398235"
 | }).drop(60);
 \*/
Raphael.el.drop = function (angle, x, y) {
    var bb = this.getBBox(),
        paper = this.paper || this[0].paper,
        center, size, p, dx, dy;

    if (!paper) return;

    switch (this.type) {
        case 'text':
        case 'circle':
        case 'ellipse': center = true; break;
        default: center = false;
    }

    angle = angle || 0;

    x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
    y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
    size = Math.max(bb.width, bb.height) + Math.min(bb.width, bb.height);
    p = paper.path([
        "M", x, y,
        "l", size, 0,
        "A", size * .4, size * .4, 0, 1, 0, x + size * .7, y - size * .7,
        "z"
    ]).attr({fill: "#000", stroke: "none"}).rotate(22.5 - angle, x, y);

    angle = (angle + 90) * Math.PI / 180;
    dx = (x + size * Math.sin(angle)) - (center ? 0 : bb.width / 2);
    dy = (y + size * Math.cos(angle)) - (center ? 0 : bb.height / 2);

    this.attrs ?
        this.attr(this.attrs.x ? 'x' : 'cx', dx).attr(this.attrs.y ? 'y' : 'cy', dy) :
        this.translate(dx - bb.x, dy - bb.y);

    return p.insertBefore(this.node ? this : this[0]);
};

/*\
 * Element.flag
 [ method ]
 **
 * Puts the context Element in a 'flag' tooltip. Can also be used on sets.
 **
 > Parameters
 **
 - angle (number) angle of orientation in degrees [default: `0`]
 - x (number) x coordinate of the flag's point [default: Element's `x` or `cx`]
 - y (number) y coordinate of the flag's point [default: Element's `x` or `cx`]
 **
 = (object) path element of the flag
 > Usage
 | paper.circle(50, 50, 10).attr({
 |     stroke: "#fff",
 |     fill: "0-#c9de96-#8ab66b:44-#398235"
 | }).flag(60);
 \*/
Raphael.el.flag = function (angle, x, y) {
    var d = 3,
        paper = this.paper || this[0].paper;

    if (!paper) return;

    var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
        bb = this.getBBox(),
        h = bb.height / 2,
        center;

    switch (this.type) {
        case 'text':
        case 'circle':
        case 'ellipse': center = true; break;
        default: center = false;
    }

    angle = angle || 0;
    x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
    y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2: bb.y);

    p.attr({
        path: [
            "M", x, y,
            "l", h + d, -h - d, bb.width + 2 * d, 0, 0, bb.height + 2 * d, -bb.width - 2 * d, 0,
            "z"
        ].join(",")
    });

    angle = 360 - angle;
    p.rotate(angle, x, y);

    if (this.attrs) {
        //elements
        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);
        this.rotate(angle, x, y);
        angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - h - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
    } else {
        //sets
        if (angle > 90 && angle < 270) {
            this.translate(x - bb.x - bb.width - h - d, y - bb.y - bb.height / 2);
            this.rotate(angle - 180, bb.x + bb.width + h + d, bb.y + bb.height / 2);
        } else {
            this.translate(x - bb.x + h + d, y - bb.y - bb.height / 2);
            this.rotate(angle, bb.x - h - d, bb.y + bb.height / 2);
        }
    }

    return p.insertBefore(this.node ? this : this[0]);
};

/*\
 * Element.label
 [ method ]
 **
 * Puts the context Element in a 'label' tooltip. Can also be used on sets.
 **
 = (object) path element of the label.
 > Usage
 | paper.circle(50, 50, 10).attr({
 |     stroke: "#fff",
 |     fill: "0-#c9de96-#8ab66b:44-#398235"
 | }).label();
 \*/
Raphael.el.label = function () {
    var bb = this.getBBox(),
        paper = this.paper || this[0].paper,
        r = Math.min(20, bb.width + 10, bb.height + 10) / 2;

    if (!paper) return;

    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]);
};

/*\
 * Element.blob
 [ method ]
 **
 * Puts the context Element in a 'blob' tooltip. Can also be used on sets.
 **
 > Parameters
 **
 - angle (number) angle of orientation in degrees [default: `0`]
 - x (number) x coordinate of the blob's tail [default: Element's `x` or `cx`]
 - y (number) y coordinate of the blob's tail [default: Element's `x` or `cx`]
 **
 = (object) path element of the blob
 > Usage
 | paper.circle(50, 50, 8).attr({
 |     stroke: "#fff",
 |     fill: "0-#c9de96-#8ab66b:44-#398235"
 | }).blob(60);
 \*/
Raphael.el.blob = function (angle, x, y) {
    var bb = this.getBBox(),
        rad = Math.PI / 180,
        paper = this.paper || this[0].paper,
        p, center, size;

    if (!paper) return;

    switch (this.type) {
        case 'text':
        case 'circle':
        case 'ellipse': center = true; break;
        default: center = false;
    }

    p = paper.path().attr({ fill: "#000", stroke: "none" });
    angle = (+angle + 1 ? angle : 45) + 90;
    size = Math.min(bb.height, bb.width);
    x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
    y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);

    var w = Math.max(bb.width + size, size * 25 / 12),
        h = Math.max(bb.height + size, size * 25 / 12),
        x2 = x + size * Math.sin((angle - 22.5) * rad),
        y2 = y + size * Math.cos((angle - 22.5) * rad),
        x1 = x + size * Math.sin((angle + 22.5) * rad),
        y1 = y + size * Math.cos((angle + 22.5) * rad),
        dx = (x1 - x2) / 2,
        dy = (y1 - y2) / 2,
        rx = w / 2,
        ry = h / 2,
        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)),
        cx = k * rx * dy / ry + (x1 + x2) / 2,
        cy = k * -ry * dx / rx + (y1 + y2) / 2;

    p.attr({
        x: cx,
        y: cy,
        path: [
            "M", x, y,
            "L", x1, y1,
            "A", rx, ry, 0, 1, 1, x2, y2,
            "z"
        ].join(",")
    });

    this.translate(cx - bb.x - bb.width / 2, cy - bb.y - bb.height / 2);

    return p.insertBefore(this.node ? this : this[0]);
};

/*
 * Tooltips on Paper prototype
 */
/*\
 * Paper.label
 [ method ]
 **
 * Puts the given `text` into a 'label' tooltip. The text is given a default style according to @g.txtattr. See @Element.label
 **
 > Parameters
 **
 - x (number) x coordinate of the center of the label
 - y (number) y coordinate of the center of the label
 - text (string) text to place inside the label
 **
 = (object) set containing the label path and the text element
 > Usage
 | paper.label(50, 50, "$9.99");
 \*/
Raphael.fn.label = function (x, y, text) {
    var set = this.set();

    text = this.text(x, y, text).attr(Raphael.g.txtattr);
    return set.push(text.label(), text);
};

/*\
 * Paper.popup
 [ method ]
 **
 * Puts the given `text` into a 'popup' tooltip. The text is given a default style according to @g.txtattr. See @Element.popup
 *
 * 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.
 **
 > Parameters
 **
 - x (number) x coordinate of the popup's tail
 - y (number) y coordinate of the popup's tail
 - text (string) text to place inside the popup
 - dir (string) location of the text relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
 - size (number) amount of padding around the Element [default: `5`]
 **
 = (object) set containing the popup path and the text element
 > Usage
 | paper.popup(50, 50, "$9.99", 'down');
 \*/
Raphael.fn.popup = function (x, y, text, dir, size) {
    var set = this.set();

    text = this.text(x, y, text).attr(Raphael.g.txtattr);
    return set.push(text.popup(dir, size), text);
};

/*\
 * Paper.tag
 [ method ]
 **
 * Puts the given text into a 'tag' tooltip. The text is given a default style according to @g.txtattr. See @Element.tag
 **
 > Parameters
 **
 - x (number) x coordinate of the center of the tag loop
 - y (number) y coordinate of the center of the tag loop
 - text (string) text to place inside the tag
 - angle (number) angle of orientation in degrees [default: `0`]
 - r (number) radius of the loop [default: `5`]
 **
 = (object) set containing the tag path and the text element
 > Usage
 | paper.tag(50, 50, "$9.99", 60);
 \*/
Raphael.fn.tag = function (x, y, text, angle, r) {
    var set = this.set();

    text = this.text(x, y, text).attr(Raphael.g.txtattr);
    return set.push(text.tag(angle, r), text);
};

/*\
 * Paper.flag
 [ method ]
 **
 * Puts the given `text` into a 'flag' tooltip. The text is given a default style according to @g.txtattr. See @Element.flag
 **
 > Parameters
 **
 - x (number) x coordinate of the flag's point
 - y (number) y coordinate of the flag's point
 - text (string) text to place inside the flag
 - angle (number) angle of orientation in degrees [default: `0`]
 **
 = (object) set containing the flag path and the text element
 > Usage
 | paper.flag(50, 50, "$9.99", 60);
 \*/
Raphael.fn.flag = function (x, y, text, angle) {
    var set = this.set();

    text = this.text(x, y, text).attr(Raphael.g.txtattr);
    return set.push(text.flag(angle), text);
};

/*\
 * Paper.drop
 [ method ]
 **
 * Puts the given text into a 'drop' tooltip. The text is given a default style according to @g.txtattr. See @Element.drop
 **
 > Parameters
 **
 - x (number) x coordinate of the drop's point
 - y (number) y coordinate of the drop's point
 - text (string) text to place inside the drop
 - angle (number) angle of orientation in degrees [default: `0`]
 **
 = (object) set containing the drop path and the text element
 > Usage
 | paper.drop(50, 50, "$9.99", 60);
 \*/
Raphael.fn.drop = function (x, y, text, angle) {
    var set = this.set();

    text = this.text(x, y, text).attr(Raphael.g.txtattr);
    return set.push(text.drop(angle), text);
};

/*\
 * Paper.blob
 [ method ]
 **
 * Puts the given text into a 'blob' tooltip. The text is given a default style according to @g.txtattr. See @Element.blob
 **
 > Parameters
 **
 - x (number) x coordinate of the blob's tail
 - y (number) y coordinate of the blob's tail
 - text (string) text to place inside the blob
 - angle (number) angle of orientation in degrees [default: `0`]
 **
 = (object) set containing the blob path and the text element
 > Usage
 | paper.blob(50, 50, "$9.99", 60);
 \*/
Raphael.fn.blob = function (x, y, text, angle) {
    var set = this.set();

    text = this.text(x, y, text).attr(Raphael.g.txtattr);
    return set.push(text.blob(angle), text);
};

/**
 * Brightness functions on the Element prototype
 */
/*\
 * Element.lighter
 [ method ]
 **
 * Makes the context element lighter by increasing the brightness and reducing the saturation by a given factor. Can be called on Sets.
 **
 > Parameters
 **
 - times (number) adjustment factor [default: `2`]
 **
 = (object) Element
 > Usage
 | paper.circle(50, 50, 20).attr({
 |     fill: "#ff0000",
 |     stroke: "#fff",
 |     "stroke-width": 2
 | }).lighter(6);
 \*/
Raphael.el.lighter = function (times) {
    times = times || 2;

    var fs = [this.attrs.fill, this.attrs.stroke];

    this.fs = this.fs || [fs[0], fs[1]];

    fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
    fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
    fs[0].b = Math.min(fs[0].b * times, 1);
    fs[0].s = fs[0].s / times;
    fs[1].b = Math.min(fs[1].b * times, 1);
    fs[1].s = fs[1].s / times;

    this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
    return this;
};

/*\
 * Element.darker
 [ method ]
 **
 * Makes the context element darker by decreasing the brightness and increasing the saturation by a given factor. Can be called on Sets.
 **
 > Parameters
 **
 - times (number) adjustment factor [default: `2`]
 **
 = (object) Element
 > Usage
 | paper.circle(50, 50, 20).attr({
 |     fill: "#ff0000",
 |     stroke: "#fff",
 |     "stroke-width": 2
 | }).darker(6);
 \*/
Raphael.el.darker = function (times) {
    times = times || 2;

    var fs = [this.attrs.fill, this.attrs.stroke];

    this.fs = this.fs || [fs[0], fs[1]];

    fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
    fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
    fs[0].s = Math.min(fs[0].s * times, 1);
    fs[0].b = fs[0].b / times;
    fs[1].s = Math.min(fs[1].s * times, 1);
    fs[1].b = fs[1].b / times;

    this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
    return this;
};

/*\
 * Element.resetBrightness
 [ method ]
 **
 * Resets brightness and saturation levels to their original values. See @Element.lighter and @Element.darker. Can be called on Sets.
 **
 = (object) Element
 > Usage
 | paper.circle(50, 50, 20).attr({
 |     fill: "#ff0000",
 |     stroke: "#fff",
 |     "stroke-width": 2
 | }).lighter(6).resetBrightness();
 \*/
Raphael.el.resetBrightness = function () {
    if (this.fs) {
        this.attr({ fill: this.fs[0], stroke: this.fs[1] });
        delete this.fs;
    }
    return this;
};

//alias to set prototype
(function () {
    var brightness = ['lighter', 'darker', 'resetBrightness'],
        tooltips = ['popup', 'tag', 'flag', 'label', 'drop', 'blob'];

    for (var f in tooltips) (function (name) {
        Raphael.st[name] = function () {
            return Raphael.el[name].apply(this, arguments);
        };
    })(tooltips[f]);

    for (var f in brightness) (function (name) {
        Raphael.st[name] = function () {
            for (var i = 0; i < this.length; i++) {
                this[i][name].apply(this[i], arguments);
            }

            return this;
        };
    })(brightness[f]);
})();

//chart prototype for storing common functions
Raphael.g = {
    /*\
     * g.shim
     [ object ]
     **
     * An attribute object that charts will set on all generated shims (shims being the invisible objects that mouse events are bound to)
     **
     > Default value
     | { stroke: 'none', fill: '#000', 'fill-opacity': 0 }
     \*/
    shim: { stroke: 'none', fill: '#000', 'fill-opacity': 0 },

    /*\
     * g.txtattr
     [ object ]
     **
     * An attribute object that charts and tooltips will set on any generated text
     **
     > Default value
     | { font: '12px Arial, sans-serif', fill: '#fff' }
     \*/  
    txtattr: { font: '12px Arial, sans-serif', fill: '#fff' },

    /*\
     * g.colors
     [ array ]
     **
     * An array of color values that charts will iterate through when drawing chart data values.
     **
     \*/
    colors: (function () {
            var hues = [.6, .2, .05, .1333, .75, 0],
                colors = [];

            for (var i = 0; i < 10; i++) {
                if (i < hues.length) {
                    colors.push('hsb(' + hues[i] + ',.75, .75)');
                } else {
                    colors.push('hsb(' + hues[i - hues.length] + ', 1, .5)');
                }
            }

            return colors;
    })(),
    
    snapEnds: function(from, to, steps) {
        var f = from,
            t = to;

        if (f == t) {
            return {from: f, to: t, power: 0};
        }

        var d = (t - f) / steps,
            mag = Math.floor(Math.log(d) / Math.log(10)),
            magPow = Math.pow(10, mag),
            magMsd = Math.floor(d / magPow + 0.5);

        // promote the MSD to either 1, 2, or 5
        if (magMsd > 5.0)
            magMsd = 10.0;
        else if (magMsd > 2.0)
            magMsd = 5.0;
        else if (magMsd > 1.0)
            magMsd = 2.0;

        var step = magMsd * magPow;

        return { from: f - (f % step), to: t - (-t % step), i: mag }

    },

    axis: function (x, y, length, from, to, steps, orientation, labels, type, dashsize, paper) {
        dashsize = dashsize == null ? 2 : dashsize;
        type = type || "t";
        steps = steps || 10;
        paper = arguments[arguments.length-1] //paper is always last argument

        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],
            ends = this.snapEnds(from, to, steps),
            f = ends.from,
            t = ends.to,
            i = ends.power,
            j = 0,
            txtattr = { font: "11px 'Fontin Sans', Fontin-Sans, sans-serif" },
            text = paper.set(),
            d;

        d = (t - f) / steps;

        var label = f,
            rnd = i > 0 ? i : 0;
            dx = length / steps;

        if (+orientation == 1 || +orientation == 3) {
            var Y = y,
                addon = (orientation - 1 ? 1 : -1) * (dashsize + 3 + !!(orientation - 1));

            while (Y >= y - length) {
                type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), Y + .5, "l", dashsize * 2 + 1, 0]));
                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" }));
                label += d;
                Y -= dx;
            }

            if (Math.round(Y + dx - (y - length))) {
                type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), y - length + .5, "l", dashsize * 2 + 1, 0]));
                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" }));
            }
        } else {
            label = f;
            rnd = (i > 0) * i;
            addon = (orientation ? -1 : 1) * (dashsize + 9 + !orientation);

            var X = x,
                dx = length / steps,
                txt = 0,
                prev = 0;

            while (X <= x + length) {
                type != "-" && type != " " && (path = path.concat(["M", X + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
                text.push(txt = paper.text(X, y + addon, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));

                var bb = txt.getBBox();

                if (prev >= bb.x - 5) {
                    text.pop(text.length - 1).remove();
                } else {
                    prev = bb.x + bb.width;
                }

                label += d;
                X += dx;
            }

            if (Math.round(X - dx - x - length)) {
                type != "-" && type != " " && (path = path.concat(["M", x + length + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
                text.push(paper.text(x + length, y + addon, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
            }
        }

        var res = paper.path(path);

        res.text = text;
        res.all = paper.set([res, text]);
        res.remove = function () {
            this.text.remove();
            this.constructor.prototype.remove.call(this);
        };

        return res;
    },
    
    labelise: function(label, val, total) {
        if (label) {
            return (label + "").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g, function (all, value, percent) {
                if (value) {
                    return (+val).toFixed(value.replace(/^#+\.?/g, "").length);
                }
                if (percent) {
                    return (val * 100 / total).toFixed(percent.replace(/^%+\.?/g, "").length) + "%";
                }
            });
        } else {
            return (+val).toFixed(0);
        }
    }
}
