# Yahoo! UI Library

### charts 3.3.0

Yahoo! UI Library > charts > graphics-svg.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    /**
     * The Charts widget provides an api for displaying data
     * graphically.
     *
     * @module charts
     */
    var ISCHROME = Y.UA.chrome,
        DRAWINGAPI,
        canvas = document.createElement("canvas");
    if(document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"))
    {
        DRAWINGAPI = "svg";
    }
    else if(canvas && canvas.getContext && canvas.getContext("2d"))
    {
        DRAWINGAPI = "canvas";
    }
    else
    {
        DRAWINGAPI = "vml";
    }

    /**
     * Graphic is a simple drawing api that allows for basic drawing operations.
     *
     * @class Graphic
     * @constructor
     */
    var Graphic = function(config) {

        this.initializer.apply(this, arguments);
    };

    Graphic.prototype = {
        /**
         * Indicates whether or not the instance will size itself based on its contents.
         *
         * @property autoSize
         * @type String
         */
        autoSize: true,

        /**
         * Initializes the class.
         *
         * @method initializer
         * @private
         */
        initializer: function(config) {
            config = config || {};
            var w = config.width || 0,
                h = config.height || 0;
            if(config.node)
            {
                this.node = config.node;
                this._styleGroup(this.node);
            }
            else
            {
                this.node = this._createGraphics();
                this.setSize(w, h);
            }
            this._initProps();
        },

        /**
         * Specifies a bitmap fill used by subsequent calls to other drawing methods.
         *
         * @method beginBitmapFill
         * @param {Object} config
         */
        beginBitmapFill: function(config) {

            var fill = {};
            fill.src = config.bitmap.src;
            fill.type = "tile";
            this._fillProps = fill;
            if(!isNaN(config.tx) ||
                !isNaN(config.ty) ||
                !isNaN(config.width) ||
                !isNaN(config.height))
            {
                this._gradientBox = {
                    tx:config.tx,
                    ty:config.ty,
                    width:config.width,
                    height:config.height
                };
            }
            else
            {
                this._gradientBox = null;
            }
        },

        /**
         * Specifes a solid fill used by subsequent calls to other drawing methods.
         *
         * @method beginFill
         * @param {String} color Hex color value for the fill.
         * @param {Number} alpha Value between 0 and 1 used to specify the opacity of the fill.
         */
        beginFill: function(color, alpha) {
            if (color) {
                this._fillAlpha = Y.Lang.isNumber(alpha) ? alpha : 1;
                this._fillColor = color;
                this._fillType = 'solid';
                this._fill = 1;
            }
            return this;
        },

        /**
         * Specifies a gradient fill used by subsequent calls to other drawing methods.
         *
         * @method beginGradientFill
         * @param {Object} config
         */
        beginGradientFill: function(config) {
            var alphas = config.alphas || [];
            if(!this._defs)
            {
                this._defs = this._createGraphicNode("defs");
                this.node.appendChild(this._defs);
            }
            this._fillAlphas = alphas;
            this._fillColors = config.colors;
            this._fillType =  config.type || "linear";
            this._fillRatios = config.ratios || [];
            this._fillRotation = config.rotation || 0;
            this._fillWidth = config.width || null;
            this._fillHeight = config.height || null;
            this._fillX = !isNaN(config.tx) ? config.tx : NaN;
            this._fillY = !isNaN(config.ty) ? config.ty : NaN;
            this._gradientId = "lg" + Math.round(100000 * Math.random());
            return this;
        },

        /**
         * Removes all nodes.
         *
         * @method destroy
         */
        destroy: function()
        {
            this._removeChildren(this.node);
            if(this.node && this.node.parentNode)
            {
                this.node.parentNode.removeChild(this.node);
            }
        },

        /**
         * Removes all child nodes.
         *
         * @method _removeChildren
         * @param {HTMLElement} node
         * @private
         */
        _removeChildren: function(node)
        {
            if(node.hasChildNodes())
            {
                var child;
                while(node.firstChild)
                {
                    child = node.firstChild;
                    this._removeChildren(child);
                    node.removeChild(child);
                }
            }
        },

        /**
         * Shows and and hides a the graphic instance.
         *
         * @method toggleVisible
         * @param val {Boolean} indicates whether the instance should be visible.
         */
        toggleVisible: function(val)
        {
            this._toggleVisible(this.node, val);
        },

        /**
         * Toggles visibility
         *
         * @method _toggleVisible
         * @param {HTMLElement} node element to toggle
         * @param {Boolean} val indicates visibilitye
         * @private
         */
        _toggleVisible: function(node, val)
        {
            var children = Y.Selector.query(">/*", node),
                visibility = val ? "visible" : "hidden",
                i = 0,
                len;
            if(children)
            {
                len = children.length;
                for(; i < len; ++i)
                {
                    this._toggleVisible(children[i], val);
                }
            }
            node.style.visibility = visibility;
        },

        /**
         * Clears the graphics object.
         *
         * @method clear
         */
        clear: function() {
            if(this._graphicsList)
            {
                while(this._graphicsList.length > 0)
                {
                    this.node.removeChild(this._graphicsList.shift());
                }
            }
            this.path = '';
        },

        /**
         * Draws a bezier curve.
         *
         * @method curveTo
         * @param {Number} cp1x x-coordinate for the first control point.
         * @param {Number} cp1y y-coordinate for the first control point.
         * @param {Number} cp2x x-coordinate for the second control point.
         * @param {Number} cp2y y-coordinate for the second control point.
         * @param {Number} x x-coordinate for the end point.
         * @param {Number} y y-coordinate for the end point.
         */
        curveTo: function(cp1x, cp1y, cp2x, cp2y, x, y) {
            this._shapeType = "path";
            if(this.path.indexOf("C") < 0 || this._pathType !== "C")
            {
                this._pathType = "C";
                this.path += ' C';
            }
            this.path += Math.round(cp1x) + ", " + Math.round(cp1y) + ", " + Math.round(cp2x) + ", " + Math.round(cp2y) + ", " + x + ", " + y + " ";
            this._trackSize(x, y);
        },

        /**
         * Draws a quadratic bezier curve.
         *
         * @method quadraticCurveTo
         * @param {Number} cpx x-coordinate for the control point.
         * @param {Number} cpy y-coordinate for the control point.
         * @param {Number} x x-coordinate for the end point.
         * @param {Number} y y-coordinate for the end point.
         */
        quadraticCurveTo: function(cpx, cpy, x, y) {
            if(this.path.indexOf("Q") < 0 || this._pathType !== "Q")
            {
                this._pathType = "Q";
                this.path += " Q";
            }
            this.path +=  Math.round(cpx) + " " + Math.round(cpy) + " " + Math.round(x) + " " + Math.round(y);
        },

        /**
         * Draws a circle.
         *
         * @method drawCircle
         * @param {Number} x y-coordinate
         * @param {Number} y x-coordinate
         * @param {Number} r radius
         */
    	drawCircle: function(x, y, r) {
            this._shape = {
                x:x - r,
                y:y - r,
                w:r * 2,
                h:r * 2
            };
            this._attributes = {cx:x, cy:y, r:r};
            this._width = this._height = r * 2;
            this._x = x - r;
            this._y = y - r;
            this._shapeType = "circle";
            this._draw();
    	},

        /**
         * Draws an ellipse.
         *
         * @method drawEllipse
         * @param {Number} x x-coordinate
         * @param {Number} y y-coordinate
         * @param {Number} w width
         * @param {Number} h height
         */
        drawEllipse: function(x, y, w, h) {
            this._shape = {
                x:x,
                y:y,
                w:w,
                h:h
            };
            this._width = w;
            this._height = h;
            this._x = x;
            this._y = y;
            this._shapeType = "ellipse";
            this._draw();
        },

        /**
         * Draws a rectangle.
         *
         * @method drawRect
         * @param {Number} x x-coordinate
         * @param {Number} y y-coordinate
         * @param {Number} w width
         * @param {Number} h height
         */
        drawRect: function(x, y, w, h) {
            this._shape = {
                x:x,
                y:y,
                w:w,
                h:h
            };
            this._x = x;
            this._y = y;
            this._width = w;
            this._height = h;
            this.moveTo(x, y);
            this.lineTo(x + w, y);
            this.lineTo(x + w, y + h);
            this.lineTo(x, y + h);
            this.lineTo(x, y);
            this._draw();
        },

        /**
         * Draws a rectangle with rounded corners.
         *
         * @method drawRect
         * @param {Number} x x-coordinate
         * @param {Number} y y-coordinate
         * @param {Number} w width
         * @param {Number} h height
         * @param {Number} ew width of the ellipse used to draw the rounded corners
         * @param {Number} eh height of the ellipse used to draw the rounded corners
         */
        drawRoundRect: function(x, y, w, h, ew, eh) {
            this._shape = {
                x:x,
                y:y,
                w:w,
                h:h
            };
            this._x = x;
            this._y = y;
            this._width = w;
            this._height = h;
            this.moveTo(x, y + eh);
            this.lineTo(x, y + h - eh);
            this.quadraticCurveTo(x, y + h, x + ew, y + h);
            this.lineTo(x + w - ew, y + h);
            this.quadraticCurveTo(x + w, y + h, x + w, y + h - eh);
            this.lineTo(x + w, y + eh);
            this.quadraticCurveTo(x + w, y, x + w - ew, y);
            this.lineTo(x + ew, y);
            this.quadraticCurveTo(x, y, x, y + eh);
            this._draw();
    	},

        /**
         * Draws a wedge.
         *
         * @param {Number} x			x-coordinate of the wedge's center point
         * @param {Number} y			y-coordinate of the wedge's center point
         * @param {Number} startAngle	starting angle in degrees
         * @param {Number} arc			sweep of the wedge. Negative values draw clockwise.
         * @param {Number} radius		radius of wedge. If [optional] yRadius is defined, then radius is the x radius.
         * @param {Number} yRadius		[optional] y radius for wedge.
         */
        drawWedge: function(x, y, startAngle, arc, radius, yRadius)
        {
            this._drawingComplete = false;
            this.path = this._getWedgePath({x:x, y:y, startAngle:startAngle, arc:arc, radius:radius, yRadius:yRadius});
            this._width = radius * 2;
            this._height = this._width;
            this._shapeType = "path";
            this._draw();

        },

        /**
         * Completes a drawing operation.
         *
         * @method end
         */
        end: function() {
            if(this._shapeType)
            {
                this._draw();
            }
            this._initProps();
        },

        /**
         * Specifies a gradient to use for the stroke when drawing lines.
         * Not implemented
         *
         * @method lineGradientStyle
         * @private
         */
        lineGradientStyle: function() {
            Y.log('lineGradientStyle not implemented', 'warn', 'graphics-canvas');
        },

        /**
         * Specifies a line style used for subsequent calls to drawing methods.
         *
         * @method lineStyle
         * @param {Number} thickness indicates the thickness of the line
         * @param {String} color hex color value for the line
         * @param {Number} alpha Value between 0 and 1 used to specify the opacity of the fill.
         */
        lineStyle: function(thickness, color, alpha, pixelHinting, scaleMode, caps, joints, miterLimit) {
            this._stroke = 1;
            this._strokeWeight = thickness;
            if (color) {
                this._strokeColor = color;
            }
            this._strokeAlpha = Y.Lang.isNumber(alpha) ? alpha : 1;
        },

        /**
         * Draws a line segment using the current line style from the current drawing position to the specified x and y coordinates.
         *
         * @method lineTo
         * @param {Number} point1 x-coordinate for the end point.
         * @param {Number} point2 y-coordinate for the end point.
         */
        lineTo: function(point1, point2, etc) {
            var args = arguments,
                i,
                len;
            if (typeof point1 === 'string' || typeof point1 === 'number') {
                args = [[point1, point2]];
            }
            len = args.length;
            this._shapeType = "path";
            if(this.path.indexOf("L") < 0 || this._pathType !== "L")
            {
                this._pathType = "L";
                this.path += ' L';
            }
            for (i = 0; i < len; ++i) {
                this.path += args[i][0] + ', ' + args[i][1] + " ";

                this._trackSize.apply(this, args[i]);
            }
        },

        /**
         * Moves the current drawing position to specified x and y coordinates.
         *
         * @method moveTo
         * @param {Number} x x-coordinate for the end point.
         * @param {Number} y y-coordinate for the end point.
         */
        moveTo: function(x, y) {
            this._pathType = "M";
            this.path += ' M' + x + ', ' + y;
        },

        /**
         * Generates a path string for a wedge shape
         *
         * @method _getWedgePath
         * @param {Object} config attributes used to create the path
         * @return String
         * @private
         */
        _getWedgePath: function(config)
        {
            var x = config.x,
                y = config.y,
                startAngle = config.startAngle,
                arc = config.arc,
                radius = config.radius,
                yRadius = config.yRadius || radius,
                segs,
                segAngle,
                theta,
                angle,
                angleMid,
                ax,
                ay,
                bx,
                by,
                cx,
                cy,
                i = 0,
                path = ' M' + x + ', ' + y;

            // limit sweep to reasonable numbers
            if(Math.abs(arc) > 360)
            {
                arc = 360;
            }

            // First we calculate how many segments are needed
            // for a smooth arc.
            segs = Math.ceil(Math.abs(arc) / 45);

            // Now calculate the sweep of each segment.
            segAngle = arc / segs;

            // The math requires radians rather than degrees. To convert from degrees
            // use the formula (degrees/180)*Math.PI to get radians.
            theta = -(segAngle / 180) * Math.PI;

            // convert angle startAngle to radians
            angle = (startAngle / 180) * Math.PI;
            if(segs > 0)
            {
                // draw a line from the center to the start of the curve
                ax = x + Math.cos(startAngle / 180 * Math.PI) * radius;
                ay = y + Math.sin(startAngle / 180 * Math.PI) * yRadius;
                path += " L" + Math.round(ax) + ", " +  Math.round(ay);
                path += " Q";
                for(; i < segs; ++i)
                {
                    angle += theta;
                    angleMid = angle - (theta / 2);
                    bx = x + Math.cos(angle) * radius;
                    by = y + Math.sin(angle) * yRadius;
                    cx = x + Math.cos(angleMid) * (radius / Math.cos(theta / 2));
                    cy = y + Math.sin(angleMid) * (yRadius / Math.cos(theta / 2));
                    path +=  Math.round(cx) + " " + Math.round(cy) + " " + Math.round(bx) + " " + Math.round(by) + " ";
                }
                path += ' L' + x + ", " + y;
            }
            return path;
        },

        /**
         * Sets the size of the graphics object.
         *
         * @method setSize
         * @param w {Number} width to set for the instance.
         * @param h {Number} height to set for the instance.
         */
        setSize: function(w, h) {
            if(this.autoSize)
            {
                if(w > this.node.getAttribute("width"))
                {
                    this.node.setAttribute("width",  w);
                }
                if(h > this.node.getAttribute("height"))
                {
                    this.node.setAttribute("height", h);
                }
            }
        },

        /**
         * Updates the size of the graphics object
         *
         * @method _trackSize
         * @param {Number} w width
         * @param {Number} h height
         * @private
         */
        _trackSize: function(w, h) {
            if (w > this._width) {
                this._width = w;
            }
            if (h > this._height) {
                this._height = h;
            }
            this.setSize(w, h);
        },

        /**
         * Sets the positon of the graphics object.
         *
         * @method setPosition
         * @param {Number} x x-coordinate for the object.
         * @param {Number} y y-coordinate for the object.
         */
        setPosition: function(x, y)
        {
            this.node.setAttribute("x", x);
            this.node.setAttribute("y", y);
        },

        /**
         * Adds the graphics node to the dom.
         *
         * @method render
         * @param {HTMLElement} parentNode node in which to render the graphics node into.
         */
        render: function(parentNode) {
            var w = parentNode.get("width") || parentNode.get("offsetWidth"),
                h = parentNode.get("height") || parentNode.get("offsetHeight");
            parentNode = parentNode || Y.config.doc.body;
            parentNode.appendChild(this.node);
            this.setSize(w, h);
            this._initProps();
            return this;
        },

        /**
         * Clears the properties
         *
         * @method _initProps
         * @private
         */
        _initProps: function() {
            this._shape = null;
            this._fillColor = null;
            this._strokeColor = null;
            this._strokeWeight = 0;
            this._fillProps = null;
            this._fillAlphas = null;
            this._fillColors = null;
            this._fillType =  null;
            this._fillRatios = null;
            this._fillRotation = null;
            this._fillWidth = null;
            this._fillHeight = null;
            this._fillX = NaN;
            this._fillY = NaN;
            this.path = '';
            this._width = 0;
            this._height = 0;
            this._x = 0;
            this._y = 0;
            this._fill = null;
            this._stroke = 0;
            this._stroked = false;
            this._pathType = null;
            this._attributes = {};
        },

        /**
         * Clears path properties
         *
         * @method _clearPath
         * @private
         */
        _clearPath: function()
        {
            this._shape = null;
            this._shapeType = null;
            this.path = '';
            this._width = 0;
            this._height = 0;
            this._x = 0;
            this._y = 0;
            this._pathType = null;
            this._attributes = {};
        },

        /**
         * Completes a shape
         *
         * @method _draw
         * @private
         */
        _draw: function()
        {
            var shape = this._createGraphicNode(this._shapeType),
                i,
                gradFill;
            if(this.path)
            {
                if(this._fill)
                {
                    this.path += 'z';
                }
                shape.setAttribute("d", this.path);
            }
            else
            {
                for(i in this._attributes)
                {
                    if(this._attributes.hasOwnProperty(i))
                    {
                        shape.setAttribute(i, this._attributes[i]);
                    }
                }
            }
            shape.setAttribute("stroke-width",  this._strokeWeight);
            if(this._strokeColor)
            {
                shape.setAttribute("stroke", this._strokeColor);
                shape.setAttribute("stroke-opacity", this._strokeAlpha);
            }
            if(!this._fillType || this._fillType === "solid")
            {
                if(this._fillColor)
                {
                   shape.setAttribute("fill", this._fillColor);
                   shape.setAttribute("fill-opacity", this._fillAlpha);
                }
                else
                {
                    shape.setAttribute("fill", "none");
                }
            }
            else if(this._fillType === "linear")
            {
                gradFill = this._getFill();
                gradFill.setAttribute("id", this._gradientId);
                this._defs.appendChild(gradFill);
                shape.setAttribute("fill", "url(#" + this._gradientId + ")");

            }
            this.node.appendChild(shape);
            this._clearPath();
        },

        /**
         * Returns ths actual fill object to be used in a drawing or shape
         *
         * @method _getFill
         * @private
         */
        _getFill: function() {
            var type = this._fillType,
                fill;

            switch (type) {
                case 'linear':
                    fill = this._getLinearGradient('fill');
                    break;
                case 'radial':
                    //fill = this._getRadialGradient('fill');
                    break;
                case 'bitmap':
                    //fill = this._bitmapFill;
                    break;
            }
            return fill;
        },

        /**
         * Returns a linear gradient fill
         *
         * @method _getLinearGradient
         * @param {String} type gradient type
         * @private
         */
        _getLinearGradient: function(type) {
            var fill = this._createGraphicNode("linearGradient"),
                prop = '_' + type,
                colors = this[prop + 'Colors'],
                ratios = this[prop + 'Ratios'],
                alphas = this[prop + 'Alphas'],
                w = this._fillWidth || (this._shape.w),
                h = this._fillHeight || (this._shape.h),
                r = this[prop + 'Rotation'],
                i,
                l,
                color,
                ratio,
                alpha,
                def,
                stop,
                x1, x2, y1, y2,
                cx = w/2,
                cy = h/2,
                radCon,
                tanRadians;
            /*
            if(r > 0 && r < 90)
            {
                r *= h/w;
            }
            else if(r > 90 && r < 180)
            {

                r =  90 + ((r-90) * w/h);
            }
    */
            radCon = Math.PI/180;
            tanRadians = parseFloat(parseFloat(Math.tan(r * radCon)).toFixed(8));
            if(Math.abs(tanRadians) * w/2 >= h/2)
            {
                if(r < 180)
                {
                    y1 = 0;
                    y2 = h;
                }
                else
                {
                    y1 = h;
                    y2 = 0;
                }
                x1 = cx - ((cy - y1)/tanRadians);
                x2 = cx - ((cy - y2)/tanRadians);
            }
            else
            {
                if(r > 90 && r < 270)
                {
                    x1 = w;
                    x2 = 0;
                }
                else
                {
                    x1 = 0;
                    x2 = w;
                }
                y1 = ((tanRadians * (cx - x1)) - cy) * -1;
                y2 = ((tanRadians * (cx - x2)) - cy) * -1;
            }
            /*
            fill.setAttribute("spreadMethod", "pad");

            fill.setAttribute("x1", Math.round(100 * x1/w) + "%");
            fill.setAttribute("y1", Math.round(100 * y1/h) + "%");
            fill.setAttribute("x2", Math.round(100 * x2/w) + "%");
            fill.setAttribute("y2", Math.round(100 * y2/h) + "%");
            */
            fill.setAttribute("gradientTransform", "rotate(" + r + ")");//," + (w/2) + ", " + (h/2) + ")");
            fill.setAttribute("width", w);
            fill.setAttribute("height", h);
            fill.setAttribute("gradientUnits", "userSpaceOnUse");
            l = colors.length;
            def = 0;
            for(i = 0; i < l; ++i)
            {
                alpha = alphas[i];
                color = colors[i];
                ratio = ratios[i] || i/(l - 1);
                ratio = Math.round(ratio * 100) + "%";
                alpha = Y.Lang.isNumber(alpha) ? alpha : "1";
                def = (i + 1) / l;
                stop = this._createGraphicNode("stop");
                stop.setAttribute("offset", ratio);
                stop.setAttribute("stop-color", color);
                stop.setAttribute("stop-opacity", alpha);
                fill.appendChild(stop);
            }
            return fill;
        },

        /**
         * Creates a group element
         *
         * @method _createGraphics
         * @private
         */
        _createGraphics: function() {
            var group = this._createGraphicNode("svg");
            this._styleGroup(group);
            return group;
        },

        /**
         * Styles a group element
         *
         * @method _styleGroup
         * @private
         */
        _styleGroup: function(group)
        {
            group.style.position = "absolute";
            group.style.top = "0px";
            group.style.overflow = "visible";
            group.style.left = "0px";
            group.setAttribute("pointer-events", "none");
        },

        /**
         * Creates a graphic node
         *
         * @method _createGraphicNode
         * @param {String} type node type to create
         * @param {String} pe specified pointer-events value
         * @return HTMLElement
         * @private
         */
        _createGraphicNode: function(type, pe)
        {
            var node = document.createElementNS("http://www.w3.org/2000/svg", "svg:" + type),
                v = pe || "none";
            if(type !== "defs" && type !== "stop" && type !== "linearGradient")
            {
                node.setAttribute("pointer-events", v);
            }
            if(type != "svg")
            {
                if(!this._graphicsList)
                {
                    this._graphicsList = [];
                }
                this._graphicsList.push(node);
            }
            return node;
        },

        /**
         * Creates a Shape instance and adds it to the graphics object.
         *
         * @method getShape
         * @param {Object} config Object literal of properties used to construct a Shape.
         * @return Shape
         */
        getShape: function(config) {
            config.graphic = this;
            return new Y.Shape(config);
        }

    };
    Y.Graphic = Graphic;

Copyright © 2011 Yahoo! Inc. All rights reserved.