# Yahoo! UI Library

### charts 3.3.0

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

    /**
     * VMLGraphics is a fallback drawing api used for basic drawing operations when SVG is not available.
     *
     * @class VMLGraphics
     * @constructor
     */
    var VMLGraphics = function(config) {

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

    VMLGraphics.prototype = {
        /**
         * Indicates whether or not the instance will size itself based on its contents.
         *
         * @property autoSize
         * @type String
         */
        initializer: function(config) {
            config = config || {};
            var w = config.width || 0,
                h = config.height || 0;
            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) {
                if (Y.Lang.isNumber(alpha)) {
                    this._fillProps = {
                        type:"solid",
                        opacity: alpha
                    };
                }
                this._fillColor = color;
                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 type = config.type,
                colors = config.colors,
                alphas = config.alphas || [],
                ratios = config.ratios || [],
                fill = {
                    colors:colors,
                    ratios:ratios
                },
                len = alphas.length,
                i = 0,
                alpha,
                oi,
                rotation = config.rotation || 0;

            for(;i < len; ++i)
            {
                alpha = alphas[i];
                alpha = Y.Lang.isNumber(alpha) ? alpha : 1;
                oi = i > 0 ? i + 1 : "";
                alphas[i] = Math.round(alpha * 100) + "%";
                fill["opacity" + oi] = alpha;
            }
            if(type === "linear")
            {
                if(config)
                {
                }
                if(rotation > 0 && rotation <= 90)
                {
                    rotation = 450 - rotation;
                }
                else if(rotation <= 270)
                {
                    rotation = 270 - rotation;
                }
                else if(rotation <= 360)
                {
                    rotation = 630 - rotation;
                }
                else
                {
                    rotation = 270;
                }
                fill.type = "gradientunscaled";
                fill.angle = rotation;
            }
            else if(type === "radial")
            {
                fill.alignshape = false;
                fill.type = "gradientradial";
                fill.focus = "100%";
                fill.focusposition = "50%,50%";
            }
            fill.ratios = ratios || [];

            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;
            }
            this._fillProps = fill;
        },

        /**
         * Clears the graphics object.
         *
         * @method clear
         */
        clear: function() {
            this._path = '';
            this._removeChildren(this.node);
        },

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

        /**
         * Removes all child nodes.
         *
         * @method _removeChildren
         * @param 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.one(node).get("children"),
                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;
        },

        /**
         * 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._shape = "shape";
            this._path += ' c ' + 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) {
            this._path += ' qb ' + cpx + ", " + cpy + ", " + x + ", " + 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._width = this._height = r * 2;
            this._x = x - r;
            this._y = y - r;
            this._shape = "oval";
            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._width = w;
            this._height = h;
            this._x = x;
            this._y = y;
            this._shape = "oval";
            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._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._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._width = radius;
            this._height = radius;
            yRadius = yRadius || radius;
            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._shape = "shape";
            this._draw();
        },

        /**
         * 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,
                path;
            if(Math.abs(arc) > 360)
            {
                arc = 360;
            }
            startAngle *= -65535;
            arc *= 65536;
            path = " m " + x + " " + y + " ae " + x + " " + y + " " + radius + " " + yRadius + " " + startAngle + " " + arc;
            return path;
        },

        /**
         * Completes a drawing operation.
         *
         * @method end
         */
        end: function() {
            if(this._shape)
            {
                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 * 0.7;
            this._strokeColor = color;
            this._strokeOpacity = 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._shape = "shape";
            this._path += ' l ';
            for (i = 0; i < len; ++i) {
                this._path += ' ' + Math.round(args[i][0]) + ', ' + Math.round(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._path += ' m ' + Math.round(x) + ', ' + Math.round(y);
        },

        /**
         * 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) {
            w = Math.round(w);
            h = Math.round(h);
            this.node.style.width = w + 'px';
            this.node.style.height = h + 'px';
            this.node.coordSize = w + ' ' + h;
            this._canvasWidth = w;
            this._canvasHeight = 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)
        {
            x = Math.round(x);
            y = Math.round(y);
            this.node.style.left = x + "px";
            this.node.style.top = y + "px";
        },

        /**
         * 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 = Math.max(parentNode.offsetWidth || 0, this._canvasWidth),
                h = Math.max(parentNode.offsetHeight || 0, this._canvasHeight);
            parentNode = parentNode || Y.config.doc.body;
            parentNode.appendChild(this.node);
            this.setSize(w, h);
            this._initProps();
            return this;
        },

        /**
         * @private
         */
        _shape: null,

        /**
         * 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;
            }
        },

        /**
         * Clears the properties
         *
         * @method _initProps
         * @private
         */
        _initProps: function() {
            this._fillColor = null;
            this._strokeColor = null;
            this._strokeOpacity = null;
            this._strokeWeight = 0;
            this._fillProps = null;
            this._path = '';
            this._width = 0;
            this._height = 0;
            this._x = 0;
            this._y = 0;
            this._fill = null;
            this._stroke = 0;
            this._stroked = false;
        },

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

        /**
         * Completes a shape
         *
         * @method _draw
         * @private
         */
        _draw: function()
        {
            var shape = this._createGraphicNode(this._shape),
                w = Math.round(this._width),
                h = Math.round(this._height),
                strokeNode,
                fillProps = this._fillProps;
                this.setSize(w, h);
            if(this._path)
            {
                if(this._fill || this._fillProps)
                {
                    this._path += ' x';
                }
                if(this._stroke)
                {
                    this._path += ' e';
                }
                shape.path = this._path;
                shape.coordSize = w + ', ' + h;
            }
            else
            {
                shape.style.display = "block";
                shape.style.position = "absolute";
                shape.style.left = this._x + "px";
                shape.style.top = this._y + "px";
            }

            if (this._fill) {
                shape.fillColor = this._fillColor;
            }
            else
            {
                shape.filled = false;
            }
            if (this._stroke && this._strokeWeight > 0) {
                shape.strokeColor = this._strokeColor;
                shape.strokeWeight = this._strokeWeight;
                if(Y.Lang.isNumber(this._strokeOpacity) && this._strokeOpacity < 1)
                {
                    strokeNode = this._createGraphicNode("stroke");
                    shape.appendChild(strokeNode);
                    strokeNode.opacity = this._strokeOpacity;
                }
            } else {
                shape.stroked = false;
            }
            shape.style.width = w + 'px';
            shape.style.height = h + 'px';
            if (fillProps) {
                shape.filled = true;
                shape.appendChild(this._getFill());
            }
            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 fill = this._createGraphicNode("fill"),
                w = this._width,
                h = this._height,
                fillProps = this._fillProps,
                prop,
                pct,
                i = 0,
                colors,
                colorstring = "",
                len,
                ratios,
                hyp = Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2)),
                cx = 50,
                cy = 50;
            if(this._gradientBox)
            {
                cx= Math.round( (this._gradientBox.width/2 - ((this._x - this._gradientBox.tx) * hyp/w))/(w * w/hyp) * 100);
                cy = Math.round( (this._gradientBox.height/2 - ((this._y - this._gradientBox.ty) * hyp/h))/(h * h/hyp) * 100);
                fillProps.focussize = (this._gradientBox.width/w)/10 + " " + (this._gradientBox.height/h)/10;
            }
            if(fillProps.colors)
            {
                colors = fillProps.colors.concat();
                ratios = fillProps.ratios.concat();
                len = colors.length;
                for(;i < len; ++i) {
                    pct = ratios[i] || i/(len-1);
                    pct = Math.round(100 * pct) + "%";
                    colorstring += ", " + pct + " " + colors[i];
                }
                if(parseInt(pct, 10) < 100)
                {
                    colorstring += ", 100% " + colors[len-1];
                }
            }
            for (prop in fillProps) {
                if(fillProps.hasOwnProperty(prop)) {
                    fill.setAttribute(prop, fillProps[prop]);
               }
            }
            fill.colors = colorstring.substr(2);
            if(fillProps.type === "gradientradial")
            {
                fill.focusposition = cx + "%," + cy + "%";
            }
            return fill;
        },

        /**
         * Creates a group element
         *
         * @method _createGraphics
         * @private
         */
        _createGraphics: function() {
            var group = this._createGraphicNode("group");
            group.style.display = "inline-block";
            group.style.position = 'absolute';
            return group;
        },

        /**
         * 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)
        {
            return document.createElement('<' + type + ' xmlns="urn:schemas-microsft.com:vml" class="vml' + type + '"/>');

        },

        /**
         * Converts a shape type to the appropriate vml node type.
         *
         * @method _getNodeShapeType
         * @param {String} type The shape to convert.
         * @return String
         * @private
         */
        _getNodeShapeType: function(type)
        {
            var shape = "shape";
            if(this._typeConversionHash.hasOwnProperty(type))
            {
                shape = this._typeConversionHash[type];
            }
            return shape;
        },

        /**
         * Used to convert certain shape types to the appropriate vml node type.
         *
         * @property _typeConversionHash
         * @type Object
         * @private
         */
        _typeConversionHash: {
            circle: "oval",
            ellipse: "oval",
            rect: "rect"
        },

        /**
         * 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);
        },

        /**
         * Adds a child to the <code>node</code>.
         *
         * @method addChild
         * @param {HTMLElement} element to add
         * @private
         */
        addChild: function(child)
        {
            this.node.appendChild(child);
        }
    };

    if(DRAWINGAPI == "vml")
    {
        var sheet = document.createStyleSheet();
        sheet.addRule(".vmlgroup", "behavior:url(#default#VML)", sheet.rules.length);
        sheet.addRule(".vmlgroup", "display:inline-block", sheet.rules.length);
        sheet.addRule(".vmlgroup", "zoom:1", sheet.rules.length);
        sheet.addRule(".vmlshape", "behavior:url(#default#VML)", sheet.rules.length);
        sheet.addRule(".vmlshape", "display:inline-block", sheet.rules.length);
        sheet.addRule(".vmloval", "behavior:url(#default#VML)", sheet.rules.length);
        sheet.addRule(".vmloval", "display:inline-block", sheet.rules.length);
        sheet.addRule(".vmlrect", "behavior:url(#default#VML)", sheet.rules.length);
        sheet.addRule(".vmlrect", "display:block", sheet.rules.length);
        sheet.addRule(".vmlfill", "behavior:url(#default#VML)", sheet.rules.length);
        sheet.addRule(".vmlstroke", "behavior:url(#default#VML)", sheet.rules.length);
        Y.log('using VML');
        Y.Graphic = VMLGraphics;
    }

Copyright © 2011 Yahoo! Inc. All rights reserved.