diff -r 322d0feea350 -r 89ef5ed3c48b src/cm/media/js/lib/yui/yui_3.10.3/build/graphics-svg/graphics-svg.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/yui/yui_3.10.3/build/graphics-svg/graphics-svg.js Tue Jul 16 14:29:46 2013 +0200 @@ -0,0 +1,3520 @@ +/* +YUI 3.10.3 (build 2fb5187) +Copyright 2013 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +http://yuilibrary.com/license/ +*/ + +YUI.add('graphics-svg', function (Y, NAME) { + +var IMPLEMENTATION = "svg", + SHAPE = "shape", + SPLITPATHPATTERN = /[a-z][^a-z]*/ig, + SPLITARGSPATTERN = /[\-]?[0-9]*[0-9|\.][0-9]*/g, + Y_LANG = Y.Lang, + AttributeLite = Y.AttributeLite, + SVGGraphic, + SVGShape, + SVGCircle, + SVGRect, + SVGPath, + SVGEllipse, + SVGPieSlice, + DOCUMENT = Y.config.doc, + _getClassName = Y.ClassNameManager.getClassName; + +function SVGDrawing(){} + +/** + * SVG implementation of the `Drawing` class. + * `SVGDrawing` is not intended to be used directly. Instead, use the `Drawing` class. + * If the browser has SVG capabilities, the `Drawing` + * class will point to the `SVGDrawing` class. + * + * @module graphics + * @class SVGDrawing + * @constructor + */ +SVGDrawing.prototype = { + /** + * Rounds a value to the nearest hundredth. + * + * @method _round + * @param {Number} val Value to be rounded. + * @return Number + * @private + */ + _round: function(val) { + return Math.round(val * 100)/100; + }, + + /** + * Maps path to methods + * + * @property _pathSymbolToMethod + * @type Object + * @private + */ + _pathSymbolToMethod: { + M: "moveTo", + m: "relativeMoveTo", + L: "lineTo", + l: "relativeLineTo", + C: "curveTo", + c: "relativeCurveTo", + Q: "quadraticCurveTo", + q: "relativeQuadraticCurveTo", + z: "closePath", + Z: "closePath" + }, + + /** + * Current x position of the drawing. + * + * @property _currentX + * @type Number + * @private + */ + _currentX: 0, + + /** + * Current y position of the drqwing. + * + * @property _currentY + * @type Number + * @private + */ + _currentY: 0, + + /** + * Indicates the type of shape + * + * @private + * @property _type + * @readOnly + * @type String + */ + _type: "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. + * @chainable + */ + curveTo: function() { + this._curveTo.apply(this, [Y.Array(arguments), false]); + return this; + }, + + /** + * Draws a bezier curve relative to the current coordinates. + * + * @method relativeCurveTo + * @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. + * @chainable + */ + relativeCurveTo: function() { + this._curveTo.apply(this, [Y.Array(arguments), true]); + return this; + }, + + /** + * Implements curveTo methods. + * + * @method _curveTo + * @param {Array} args The arguments to be used. + * @param {Boolean} relative Indicates whether or not to use relative coordinates. + * @private + */ + _curveTo: function(args, relative) { + var w, + h, + pts, + cp1x, + cp1y, + cp2x, + cp2y, + x, + y, + right, + left, + bottom, + top, + i, + len, + pathArrayLen, + currentArray, + command = relative ? "c" : "C", + relativeX = relative ? parseFloat(this._currentX) : 0, + relativeY = relative ? parseFloat(this._currentY) : 0; + this._pathArray = this._pathArray || []; + if(this._pathType !== command) + { + this._pathType = command; + currentArray = [command]; + this._pathArray.push(currentArray); + } + else + { + currentArray = this._pathArray[Math.max(0, this._pathArray.length - 1)]; + if(!currentArray) + { + currentArray = []; + this._pathArray.push(currentArray); + } + } + pathArrayLen = this._pathArray.length - 1; + this._pathArray[pathArrayLen] = this._pathArray[pathArrayLen].concat(args); + len = args.length - 5; + for(i = 0; i < len; i = i + 6) + { + cp1x = parseFloat(args[i]) + relativeX; + cp1y = parseFloat(args[i + 1]) + relativeY; + cp2x = parseFloat(args[i + 2]) + relativeX; + cp2y = parseFloat(args[i + 3]) + relativeY; + x = parseFloat(args[i + 4]) + relativeX; + y = parseFloat(args[i + 5]) + relativeY; + right = Math.max(x, Math.max(cp1x, cp2x)); + bottom = Math.max(y, Math.max(cp1y, cp2y)); + left = Math.min(x, Math.min(cp1x, cp2x)); + top = Math.min(y, Math.min(cp1y, cp2y)); + w = Math.abs(right - left); + h = Math.abs(bottom - top); + pts = [[this._currentX, this._currentY] , [cp1x, cp1y], [cp2x, cp2y], [x, y]]; + this._setCurveBoundingBox(pts, w, h); + this._currentX = x; + this._currentY = 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. + * @chainable + */ + quadraticCurveTo: function() { + this._quadraticCurveTo.apply(this, [Y.Array(arguments), false]); + return this; + }, + + /** + * Draws a quadratic bezier curve relative to the current position. + * + * @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. + * @chainable + */ + relativeQuadraticCurveTo: function() { + this._quadraticCurveTo.apply(this, [Y.Array(arguments), true]); + return this; + }, + + /** + * Implements quadraticCurveTo methods. + * + * @method _quadraticCurveTo + * @param {Array} args The arguments to be used. + * @param {Boolean} relative Indicates whether or not to use relative coordinates. + * @private + */ + _quadraticCurveTo: function(args, relative) { + var cpx, + cpy, + x, + y, + pathArrayLen, + currentArray, + w, + h, + pts, + right, + left, + bottom, + top, + i, + len, + command = relative ? "q" : "Q", + relativeX = relative ? parseFloat(this._currentX) : 0, + relativeY = relative ? parseFloat(this._currentY) : 0; + this._pathArray = this._pathArray || []; + if(this._pathType !== command) + { + this._pathType = command; + currentArray = [command]; + this._pathArray.push(currentArray); + } + else + { + currentArray = this._pathArray[Math.max(0, this._pathArray.length - 1)]; + if(!currentArray) + { + currentArray = []; + this._pathArray.push(currentArray); + } + } + pathArrayLen = this._pathArray.length - 1; + this._pathArray[pathArrayLen] = this._pathArray[pathArrayLen].concat(args); + len = args.length - 3; + for(i = 0; i < len; i = i + 4) + { + cpx = parseFloat(args[i]) + relativeX; + cpy = parseFloat(args[i + 1]) + relativeY; + x = parseFloat(args[i + 2]) + relativeX; + y = parseFloat(args[i + 3]) + relativeY; + right = Math.max(x, cpx); + bottom = Math.max(y, cpy); + left = Math.min(x, cpx); + top = Math.min(y, cpy); + w = Math.abs(right - left); + h = Math.abs(bottom - top); + pts = [[this._currentX, this._currentY] , [cpx, cpy], [x, y]]; + this._setCurveBoundingBox(pts, w, h); + this._currentX = x; + this._currentY = y; + } + }, + + /** + * Draws a rectangle. + * + * @method drawRect + * @param {Number} x x-coordinate + * @param {Number} y y-coordinate + * @param {Number} w width + * @param {Number} h height + * @chainable + */ + drawRect: function(x, y, w, 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); + return this; + }, + + /** + * Draws a rectangle with rounded corners. + * + * @method drawRoundRect + * @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 + * @chainable + */ + drawRoundRect: function(x, y, w, h, ew, eh) { + 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); + return this; + }, + + /** + * Draws a circle. + * + * @method drawCircle + * @param {Number} x y-coordinate + * @param {Number} y x-coordinate + * @param {Number} r radius + * @chainable + * @protected + */ + drawCircle: function(x, y, radius) { + var circum = radius * 2; + this._drawingComplete = false; + this._trackSize(x, y); + this._trackSize(x + circum, y + circum); + this._pathArray = this._pathArray || []; + this._pathArray.push(["M", x + radius, y]); + this._pathArray.push(["A", radius, radius, 0, 1, 0, x + radius, y + circum]); + this._pathArray.push(["A", radius, radius, 0, 1, 0, x + radius, y]); + this._currentX = x; + this._currentY = y; + return this; + }, + + /** + * Draws an ellipse. + * + * @method drawEllipse + * @param {Number} x x-coordinate + * @param {Number} y y-coordinate + * @param {Number} w width + * @param {Number} h height + * @chainable + * @protected + */ + drawEllipse: function(x, y, w, h) { + var radius = w * 0.5, + yRadius = h * 0.5; + this._drawingComplete = false; + this._trackSize(x, y); + this._trackSize(x + w, y + h); + this._pathArray = this._pathArray || []; + this._pathArray.push(["M", x + radius, y]); + this._pathArray.push(["A", radius, yRadius, 0, 1, 0, x + radius, y + h]); + this._pathArray.push(["A", radius, yRadius, 0, 1, 0, x + radius, y]); + this._currentX = x; + this._currentY = y; + return this; + }, + + /** + * Draws a diamond. + * + * @method drawDiamond + * @param {Number} x y-coordinate + * @param {Number} y x-coordinate + * @param {Number} width width + * @param {Number} height height + * @chainable + * @protected + */ + drawDiamond: function(x, y, width, height) + { + var midWidth = width * 0.5, + midHeight = height * 0.5; + this.moveTo(x + midWidth, y); + this.lineTo(x + width, y + midHeight); + this.lineTo(x + midWidth, y + height); + this.lineTo(x, y + midHeight); + this.lineTo(x + midWidth, y); + return this; + }, + + /** + * Draws a wedge. + * + * @method drawWedge + * @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. + * @chainable + * @private + */ + drawWedge: function(x, y, startAngle, arc, radius, yRadius) + { + var segs, + segAngle, + theta, + angle, + angleMid, + ax, + ay, + bx, + by, + cx, + cy, + i, + diameter = radius * 2, + currentArray, + pathArrayLen; + this._pathArray = this._pathArray || []; + yRadius = yRadius || radius; + if(this._pathType !== "M") + { + this._pathType = "M"; + currentArray = ["M"]; + this._pathArray.push(currentArray); + } + else + { + currentArray = this._getCurrentArray(); + } + pathArrayLen = this._pathArray.length - 1; + this._pathArray[pathArrayLen].push(x); + this._pathArray[pathArrayLen].push(x); + + // 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; + this._pathType = "L"; + pathArrayLen++; + this._pathArray[pathArrayLen] = ["L"]; + this._pathArray[pathArrayLen].push(this._round(ax)); + this._pathArray[pathArrayLen].push(this._round(ay)); + pathArrayLen++; + this._pathType = "Q"; + this._pathArray[pathArrayLen] = ["Q"]; + for(i = 0; 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)); + this._pathArray[pathArrayLen].push(this._round(cx)); + this._pathArray[pathArrayLen].push(this._round(cy)); + this._pathArray[pathArrayLen].push(this._round(bx)); + this._pathArray[pathArrayLen].push(this._round(by)); + } + } + this._currentX = x; + this._currentY = y; + this._trackSize(diameter, diameter); + return this; + }, + + /** + * 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. + * @chainable + */ + lineTo: function() + { + this._lineTo.apply(this, [Y.Array(arguments), false]); + return this; + }, + + /** + * Draws a line segment using the current line style from the current drawing position to the relative x and y coordinates. + * + * @method relativeLineTo + * @param {Number} point1 x-coordinate for the end point. + * @param {Number} point2 y-coordinate for the end point. + * @chainable + */ + relativeLineTo: function() + { + this._lineTo.apply(this, [Y.Array(arguments), true]); + return this; + }, + + /** + * Implements lineTo methods. + * + * @method _lineTo + * @param {Array} args The arguments to be used. + * @param {Boolean} relative Indicates whether or not to use relative coordinates. + * @private + */ + _lineTo: function(args, relative) { + var point1 = args[0], + i, + len, + pathArrayLen, + currentArray, + x, + y, + command = relative ? "l" : "L", + relativeX = relative ? parseFloat(this._currentX) : 0, + relativeY = relative ? parseFloat(this._currentY) : 0; + this._pathArray = this._pathArray || []; + this._shapeType = "path"; + len = args.length; + if(this._pathType !== command) + { + this._pathType = command; + currentArray = [command]; + this._pathArray.push(currentArray); + } + else + { + currentArray = this._getCurrentArray(); + } + pathArrayLen = this._pathArray.length - 1; + if (typeof point1 === 'string' || typeof point1 === 'number') { + for (i = 0; i < len; i = i + 2) { + x = parseFloat(args[i]); + y = parseFloat(args[i + 1]); + this._pathArray[pathArrayLen].push(x); + this._pathArray[pathArrayLen].push(y); + x = x + relativeX; + y = y + relativeY; + this._currentX = x; + this._currentY = y; + this._trackSize.apply(this, [x, y]); + } + } + else + { + for (i = 0; i < len; ++i) { + x = parseFloat(args[i][0]); + y = parseFloat(args[i][1]); + this._pathArray[pathArrayLen].push(x); + this._pathArray[pathArrayLen].push(y); + this._currentX = x; + this._currentY = y; + x = x + relativeX; + y = y + relativeY; + this._trackSize.apply(this, [x, y]); + } + } + }, + + /** + * 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. + * @chainable + */ + moveTo: function() + { + this._moveTo.apply(this, [Y.Array(arguments), false]); + return this; + }, + + /** + * Moves the current drawing position relative to specified x and y coordinates. + * + * @method relativeMoveTo + * @param {Number} x x-coordinate for the end point. + * @param {Number} y y-coordinate for the end point. + * @chainable + */ + relativeMoveTo: function() + { + this._moveTo.apply(this, [Y.Array(arguments), true]); + return this; + }, + + /** + * Implements moveTo methods. + * + * @method _moveTo + * @param {Array} args The arguments to be used. + * @param {Boolean} relative Indicates whether or not to use relative coordinates. + * @private + */ + _moveTo: function(args, relative) { + var pathArrayLen, + currentArray, + x = parseFloat(args[0]), + y = parseFloat(args[1]), + command = relative ? "m" : "M", + relativeX = relative ? parseFloat(this._currentX) : 0, + relativeY = relative ? parseFloat(this._currentY) : 0; + this._pathArray = this._pathArray || []; + this._pathType = command; + currentArray = [command]; + this._pathArray.push(currentArray); + pathArrayLen = this._pathArray.length - 1; + this._pathArray[pathArrayLen] = this._pathArray[pathArrayLen].concat([x, y]); + x = x + relativeX; + y = y + relativeY; + this._currentX = x; + this._currentY = y; + this._trackSize(x, y); + }, + + /** + * Completes a drawing operation. + * + * @method end + * @chainable + */ + end: function() + { + this._closePath(); + return this; + }, + + /** + * Clears the path. + * + * @method clear + * @chainable + */ + clear: function() + { + this._currentX = 0; + this._currentY = 0; + this._width = 0; + this._height = 0; + this._left = 0; + this._right = 0; + this._top = 0; + this._bottom = 0; + this._pathArray = []; + this._path = ""; + this._pathType = ""; + return this; + }, + + /** + * Draws the path. + * + * @method _closePath + * @private + */ + _closePath: function() + { + var pathArray, + segmentArray, + pathType, + len, + val, + i, + path = "", + node = this.node, + left = parseFloat(this._left), + top = parseFloat(this._top), + fill = this.get("fill"); + if(this._pathArray) + { + pathArray = this._pathArray.concat(); + while(pathArray && pathArray.length > 0) + { + segmentArray = pathArray.shift(); + len = segmentArray.length; + pathType = segmentArray[0]; + if(pathType === "A") + { + path += pathType + segmentArray[1] + "," + segmentArray[2]; + } + else if(pathType === "z" || pathType === "Z") + { + path += " z "; + } + else if(pathType === "C" || pathType === "c") + { + path += pathType + (segmentArray[1] - left)+ "," + (segmentArray[2] - top); + } + else + { + path += " " + pathType + parseFloat(segmentArray[1] - left); + } + switch(pathType) + { + case "L" : + case "l" : + case "M" : + case "m" : + case "Q" : + case "q" : + for(i = 2; i < len; ++i) + { + val = (i % 2 === 0) ? top : left; + val = segmentArray[i] - val; + path += ", " + parseFloat(val); + } + break; + case "A" : + val = " " + parseFloat(segmentArray[3]) + " " + parseFloat(segmentArray[4]); + val += "," + parseFloat(segmentArray[5]) + " " + parseFloat(segmentArray[6] - left); + val += "," + parseFloat(segmentArray[7] - top); + path += " " + val; + break; + case "C" : + case "c" : + for(i = 3; i < len - 1; i = i + 2) + { + val = parseFloat(segmentArray[i] - left); + val = val + ", "; + val = val + parseFloat(segmentArray[i + 1] - top); + path += " " + val; + } + break; + } + } + if(fill && fill.color) + { + path += 'z'; + } + Y.Lang.trim(path); + if(path) + { + node.setAttribute("d", path); + } + + this._path = path; + this._fillChangeHandler(); + this._strokeChangeHandler(); + this._updateTransform(); + } + }, + + /** + * Ends a fill and stroke + * + * @method closePath + * @chainable + */ + closePath: function() + { + this._pathArray.push(["z"]); + return this; + }, + + /** + * Returns the current array of drawing commands. + * + * @method _getCurrentArray + * @return Array + * @private + */ + _getCurrentArray: function() + { + var currentArray = this._pathArray[Math.max(0, this._pathArray.length - 1)]; + if(!currentArray) + { + currentArray = []; + this._pathArray.push(currentArray); + } + return currentArray; + }, + + /** + * Returns the points on a curve + * + * @method getBezierData + * @param Array points Array containing the begin, end and control points of a curve. + * @param Number t The value for incrementing the next set of points. + * @return Array + * @private + */ + getBezierData: function(points, t) { + var n = points.length, + tmp = [], + i, + j; + + for (i = 0; i < n; ++i){ + tmp[i] = [points[i][0], points[i][1]]; // save input + } + + for (j = 1; j < n; ++j) { + for (i = 0; i < n - j; ++i) { + tmp[i][0] = (1 - t) * tmp[i][0] + t * tmp[parseInt(i + 1, 10)][0]; + tmp[i][1] = (1 - t) * tmp[i][1] + t * tmp[parseInt(i + 1, 10)][1]; + } + } + return [ tmp[0][0], tmp[0][1] ]; + }, + + /** + * Calculates the bounding box for a curve + * + * @method _setCurveBoundingBox + * @param Array pts Array containing points for start, end and control points of a curve. + * @param Number w Width used to calculate the number of points to describe the curve. + * @param Number h Height used to calculate the number of points to describe the curve. + * @private + */ + _setCurveBoundingBox: function(pts, w, h) + { + var i, + left = this._currentX, + right = left, + top = this._currentY, + bottom = top, + len = Math.round(Math.sqrt((w * w) + (h * h))), + t = 1/len, + xy; + for(i = 0; i < len; ++i) + { + xy = this.getBezierData(pts, t * i); + left = isNaN(left) ? xy[0] : Math.min(xy[0], left); + right = isNaN(right) ? xy[0] : Math.max(xy[0], right); + top = isNaN(top) ? xy[1] : Math.min(xy[1], top); + bottom = isNaN(bottom) ? xy[1] : Math.max(xy[1], bottom); + } + left = Math.round(left * 10)/10; + right = Math.round(right * 10)/10; + top = Math.round(top * 10)/10; + bottom = Math.round(bottom * 10)/10; + this._trackSize(right, bottom); + this._trackSize(left, top); + }, + + /** + * 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._right) { + this._right = w; + } + if(w < this._left) + { + this._left = w; + } + if (h < this._top) + { + this._top = h; + } + if (h > this._bottom) + { + this._bottom = h; + } + this._width = this._right - this._left; + this._height = this._bottom - this._top; + } +}; +Y.SVGDrawing = SVGDrawing; +/** + * SVG implementation of the `Shape` class. + * `SVGShape` is not intended to be used directly. Instead, use the `Shape` class. + * If the browser has SVG capabilities, the `Shape` + * class will point to the `SVGShape` class. + * + * @module graphics + * @class SVGShape + * @constructor + * @param {Object} cfg (optional) Attribute configs + */ +SVGShape = function() +{ + this._transforms = []; + this.matrix = new Y.Matrix(); + this._normalizedMatrix = new Y.Matrix(); + SVGShape.superclass.constructor.apply(this, arguments); +}; + +SVGShape.NAME = "shape"; + +Y.extend(SVGShape, Y.GraphicBase, Y.mix({ + /** + * Storage for x attribute. + * + * @property _x + * @protected + */ + _x: 0, + + /** + * Storage for y attribute. + * + * @property _y + * @protected + */ + _y: 0, + + /** + * Init method, invoked during construction. + * Calls `initializer` method. + * + * @method init + * @protected + */ + init: function() + { + this.initializer.apply(this, arguments); + }, + + /** + * Initializes the shape + * + * @private + * @method initializer + */ + initializer: function(cfg) + { + var host = this, + graphic = cfg.graphic, + data = this.get("data"); + host.createNode(); + if(graphic) + { + host._setGraphic(graphic); + } + if(data) + { + host._parsePathData(data); + } + host._updateHandler(); + }, + + /** + * Set the Graphic instance for the shape. + * + * @method _setGraphic + * @param {Graphic | Node | HTMLElement | String} render This param is used to determine the graphic instance. If it is a + * `Graphic` instance, it will be assigned to the `graphic` attribute. Otherwise, a new Graphic instance will be created + * and rendered into the dom element that the render represents. + * @private + */ + _setGraphic: function(render) + { + var graphic; + if(render instanceof Y.SVGGraphic) + { + this._graphic = render; + } + else + { + render = Y.one(render); + graphic = new Y.SVGGraphic({ + render: render + }); + graphic._appendShape(this); + this._graphic = graphic; + } + }, + + /** + * Add a class name to each node. + * + * @method addClass + * @param {String} className the class name to add to the node's class attribute + */ + addClass: function(className) + { + var node = this.node; + node.className.baseVal = Y_LANG.trim([node.className.baseVal, className].join(' ')); + }, + + /** + * Removes a class name from each node. + * + * @method removeClass + * @param {String} className the class name to remove from the node's class attribute + */ + removeClass: function(className) + { + var node = this.node, + classString = node.className.baseVal; + classString = classString.replace(new RegExp(className + ' '), className).replace(new RegExp(className), ''); + node.className.baseVal = classString; + }, + + /** + * Gets the current position of the node in page coordinates. + * + * @method getXY + * @return Array The XY position of the shape. + */ + getXY: function() + { + var graphic = this._graphic, + parentXY = graphic.getXY(), + x = this._x, + y = this._y; + return [parentXY[0] + x, parentXY[1] + y]; + }, + + /** + * Set the position of the shape in page coordinates, regardless of how the node is positioned. + * + * @method setXY + * @param {Array} Contains x & y values for new position (coordinates are page-based) + */ + setXY: function(xy) + { + var graphic = this._graphic, + parentXY = graphic.getXY(); + this._x = xy[0] - parentXY[0]; + this._y = xy[1] - parentXY[1]; + this.set("transform", this.get("transform")); + }, + + /** + * Determines whether the node is an ancestor of another HTML element in the DOM hierarchy. + * + * @method contains + * @param {SVGShape | HTMLElement} needle The possible node or descendent + * @return Boolean Whether or not this shape is the needle or its ancestor. + */ + contains: function(needle) + { + return needle === Y.one(this.node); + }, + + /** + * Compares nodes to determine if they match. + * Node instances can be compared to each other and/or HTMLElements. + * @method compareTo + * @param {HTMLElement | Node} refNode The reference node to compare to the node. + * @return {Boolean} True if the nodes match, false if they do not. + */ + compareTo: function(refNode) { + var node = this.node; + + return node === refNode; + }, + + /** + * Test if the supplied node matches the supplied selector. + * + * @method test + * @param {String} selector The CSS selector to test against. + * @return Boolean Wheter or not the shape matches the selector. + */ + test: function(selector) + { + return Y.Selector.test(this.node, selector); + }, + + /** + * Value function for fill attribute + * + * @private + * @method _getDefaultFill + * @return Object + */ + _getDefaultFill: function() { + return { + type: "solid", + opacity: 1, + cx: 0.5, + cy: 0.5, + fx: 0.5, + fy: 0.5, + r: 0.5 + }; + }, + + /** + * Value function for stroke attribute + * + * @private + * @method _getDefaultStroke + * @return Object + */ + _getDefaultStroke: function() + { + return { + weight: 1, + dashstyle: "none", + color: "#000", + opacity: 1.0 + }; + }, + + /** + * Creates the dom node for the shape. + * + * @method createNode + * @return HTMLElement + * @private + */ + createNode: function() + { + var host = this, + node = DOCUMENT.createElementNS("http://www.w3.org/2000/svg", "svg:" + this._type), + id = host.get("id"), + name = host.name, + concat = host._camelCaseConcat, + pointerEvents = host.get("pointerEvents"); + host.node = node; + host.addClass( + _getClassName(SHAPE) + + " " + + _getClassName(concat(IMPLEMENTATION, SHAPE)) + + " " + + _getClassName(name) + + " " + + _getClassName(concat(IMPLEMENTATION, name)) + ); + if(id) + { + node.setAttribute("id", id); + } + if(pointerEvents) + { + node.setAttribute("pointer-events", pointerEvents); + } + if(!host.get("visible")) + { + Y.one(node).setStyle("visibility", "hidden"); + } + }, + + + /** + * Overrides default `on` method. Checks to see if its a dom interaction event. If so, + * return an event attached to the `node` element. If not, return the normal functionality. + * + * @method on + * @param {String} type event type + * @param {Object} callback function + * @private + */ + on: function(type, fn) + { + if(Y.Node.DOM_EVENTS[type]) + { + return Y.one("#" + this.get("id")).on(type, fn); + } + return Y.on.apply(this, arguments); + }, + + /** + * Adds a stroke to the shape node. + * + * @method _strokeChangeHandler + * @private + */ + _strokeChangeHandler: function() + { + var node = this.node, + stroke = this.get("stroke"), + strokeOpacity, + dashstyle, + dash, + linejoin; + if(stroke && stroke.weight && stroke.weight > 0) + { + linejoin = stroke.linejoin || "round"; + strokeOpacity = parseFloat(stroke.opacity); + dashstyle = stroke.dashstyle || "none"; + dash = Y_LANG.isArray(dashstyle) ? dashstyle.toString() : dashstyle; + stroke.color = stroke.color || "#000000"; + stroke.weight = stroke.weight || 1; + stroke.opacity = Y_LANG.isNumber(strokeOpacity) ? strokeOpacity : 1; + stroke.linecap = stroke.linecap || "butt"; + node.setAttribute("stroke-dasharray", dash); + node.setAttribute("stroke", stroke.color); + node.setAttribute("stroke-linecap", stroke.linecap); + node.setAttribute("stroke-width", stroke.weight); + node.setAttribute("stroke-opacity", stroke.opacity); + if(linejoin === "round" || linejoin === "bevel") + { + node.setAttribute("stroke-linejoin", linejoin); + } + else + { + linejoin = parseInt(linejoin, 10); + if(Y_LANG.isNumber(linejoin)) + { + node.setAttribute("stroke-miterlimit", Math.max(linejoin, 1)); + node.setAttribute("stroke-linejoin", "miter"); + } + } + } + else + { + node.setAttribute("stroke", "none"); + } + }, + + /** + * Adds a fill to the shape node. + * + * @method _fillChangeHandler + * @private + */ + _fillChangeHandler: function() + { + var node = this.node, + fill = this.get("fill"), + fillOpacity, + type; + if(fill) + { + type = fill.type; + if(type === "linear" || type === "radial") + { + this._setGradientFill(fill); + node.setAttribute("fill", "url(#grad" + this.get("id") + ")"); + } + else if(!fill.color) + { + node.setAttribute("fill", "none"); + } + else + { + fillOpacity = parseFloat(fill.opacity); + fillOpacity = Y_LANG.isNumber(fillOpacity) ? fillOpacity : 1; + node.setAttribute("fill", fill.color); + node.setAttribute("fill-opacity", fillOpacity); + } + } + else + { + node.setAttribute("fill", "none"); + } + }, + + /** + * Creates a gradient fill + * + * @method _setGradientFill + * @param {String} type gradient type + * @private + */ + _setGradientFill: function(fill) { + var offset, + opacity, + color, + stopNode, + newStop, + isNumber = Y_LANG.isNumber, + graphic = this._graphic, + type = fill.type, + gradientNode = graphic.getGradientNode("grad" + this.get("id"), type), + stops = fill.stops, + w = this.get("width"), + h = this.get("height"), + rotation = fill.rotation || 0, + radCon = Math.PI/180, + tanRadians = parseFloat(parseFloat(Math.tan(rotation * radCon)).toFixed(8)), + i, + len, + def, + stop, + x1 = "0%", + x2 = "100%", + y1 = "0%", + y2 = "0%", + cx = fill.cx, + cy = fill.cy, + fx = fill.fx, + fy = fill.fy, + r = fill.r, + stopNodes = []; + if(type === "linear") + { + cx = w/2; + cy = h/2; + if(Math.abs(tanRadians) * w/2 >= h/2) + { + if(rotation < 180) + { + y1 = 0; + y2 = h; + } + else + { + y1 = h; + y2 = 0; + } + x1 = cx - ((cy - y1)/tanRadians); + x2 = cx - ((cy - y2)/tanRadians); + } + else + { + if(rotation > 90 && rotation < 270) + { + x1 = w; + x2 = 0; + } + else + { + x1 = 0; + x2 = w; + } + y1 = ((tanRadians * (cx - x1)) - cy) * -1; + y2 = ((tanRadians * (cx - x2)) - cy) * -1; + } + + x1 = Math.round(100 * x1/w); + x2 = Math.round(100 * x2/w); + y1 = Math.round(100 * y1/h); + y2 = Math.round(100 * y2/h); + + //Set default value if not valid + x1 = isNumber(x1) ? x1 : 0; + x2 = isNumber(x2) ? x2 : 100; + y1 = isNumber(y1) ? y1 : 0; + y2 = isNumber(y2) ? y2 : 0; + + gradientNode.setAttribute("spreadMethod", "pad"); + gradientNode.setAttribute("width", w); + gradientNode.setAttribute("height", h); + gradientNode.setAttribute("x1", x1 + "%"); + gradientNode.setAttribute("x2", x2 + "%"); + gradientNode.setAttribute("y1", y1 + "%"); + gradientNode.setAttribute("y2", y2 + "%"); + } + else + { + gradientNode.setAttribute("cx", (cx * 100) + "%"); + gradientNode.setAttribute("cy", (cy * 100) + "%"); + gradientNode.setAttribute("fx", (fx * 100) + "%"); + gradientNode.setAttribute("fy", (fy * 100) + "%"); + gradientNode.setAttribute("r", (r * 100) + "%"); + } + + len = stops.length; + def = 0; + for(i = 0; i < len; ++i) + { + if(this._stops && this._stops.length > 0) + { + stopNode = this._stops.shift(); + newStop = false; + } + else + { + stopNode = graphic._createGraphicNode("stop"); + newStop = true; + } + stop = stops[i]; + opacity = stop.opacity; + color = stop.color; + offset = stop.offset || i/(len - 1); + offset = Math.round(offset * 100) + "%"; + opacity = isNumber(opacity) ? opacity : 1; + opacity = Math.max(0, Math.min(1, opacity)); + def = (i + 1) / len; + stopNode.setAttribute("offset", offset); + stopNode.setAttribute("stop-color", color); + stopNode.setAttribute("stop-opacity", opacity); + if(newStop) + { + gradientNode.appendChild(stopNode); + } + stopNodes.push(stopNode); + } + while(this._stops && this._stops.length > 0) + { + gradientNode.removeChild(this._stops.shift()); + } + this._stops = stopNodes; + }, + + _stops: null, + + /** + * Sets the value of an attribute. + * + * @method set + * @param {String|Object} name The name of the attribute. Alternatively, an object of key value pairs can + * be passed in to set multiple attributes at once. + * @param {Any} value The value to set the attribute to. This value is ignored if an object is received as + * the name param. + */ + set: function() + { + var host = this; + AttributeLite.prototype.set.apply(host, arguments); + if(host.initialized) + { + host._updateHandler(); + } + }, + + /** + * Specifies a 2d translation. + * + * @method translate + * @param {Number} x The value to transate on the x-axis. + * @param {Number} y The value to translate on the y-axis. + */ + translate: function() + { + this._addTransform("translate", arguments); + }, + + /** + * Translates the shape along the x-axis. When translating x and y coordinates, + * use the `translate` method. + * + * @method translateX + * @param {Number} x The value to translate. + */ + translateX: function() + { + this._addTransform("translateX", arguments); + }, + + /** + * Translates the shape along the y-axis. When translating x and y coordinates, + * use the `translate` method. + * + * @method translateY + * @param {Number} y The value to translate. + */ + translateY: function() + { + this._addTransform("translateY", arguments); + }, + + /** + * Skews the shape around the x-axis and y-axis. + * + * @method skew + * @param {Number} x The value to skew on the x-axis. + * @param {Number} y The value to skew on the y-axis. + */ + skew: function() + { + this._addTransform("skew", arguments); + }, + + /** + * Skews the shape around the x-axis. + * + * @method skewX + * @param {Number} x x-coordinate + */ + skewX: function() + { + this._addTransform("skewX", arguments); + }, + + /** + * Skews the shape around the y-axis. + * + * @method skewY + * @param {Number} y y-coordinate + */ + skewY: function() + { + this._addTransform("skewY", arguments); + }, + + /** + * Rotates the shape clockwise around it transformOrigin. + * + * @method rotate + * @param {Number} deg The degree of the rotation. + */ + rotate: function() + { + this._addTransform("rotate", arguments); + }, + + /** + * Specifies a 2d scaling operation. + * + * @method scale + * @param {Number} val + */ + scale: function() + { + this._addTransform("scale", arguments); + }, + + /** + * Adds a transform to the shape. + * + * @method _addTransform + * @param {String} type The transform being applied. + * @param {Array} args The arguments for the transform. + * @private + */ + _addTransform: function(type, args) + { + args = Y.Array(args); + this._transform = Y_LANG.trim(this._transform + " " + type + "(" + args.join(", ") + ")"); + args.unshift(type); + this._transforms.push(args); + if(this.initialized) + { + this._updateTransform(); + } + }, + + /** + * Applies all transforms. + * + * @method _updateTransform + * @private + */ + _updateTransform: function() + { + var isPath = this._type === "path", + node = this.node, + key, + transform, + transformOrigin, + x, + y, + tx, + ty, + matrix = this.matrix, + normalizedMatrix = this._normalizedMatrix, + i, + len = this._transforms.length; + + if(isPath || (this._transforms && this._transforms.length > 0)) + { + x = this._x; + y = this._y; + transformOrigin = this.get("transformOrigin"); + tx = x + (transformOrigin[0] * this.get("width")); + ty = y + (transformOrigin[1] * this.get("height")); + //need to use translate for x/y coords + if(isPath) + { + //adjust origin for custom shapes + if(!(this instanceof Y.SVGPath)) + { + tx = this._left + (transformOrigin[0] * this.get("width")); + ty = this._top + (transformOrigin[1] * this.get("height")); + } + normalizedMatrix.init({dx: x + this._left, dy: y + this._top}); + } + normalizedMatrix.translate(tx, ty); + for(i = 0; i < len; ++i) + { + key = this._transforms[i].shift(); + if(key) + { + normalizedMatrix[key].apply(normalizedMatrix, this._transforms[i]); + matrix[key].apply(matrix, this._transforms[i]); + } + if(isPath) + { + this._transforms[i].unshift(key); + } + } + normalizedMatrix.translate(-tx, -ty); + transform = "matrix(" + normalizedMatrix.a + "," + + normalizedMatrix.b + "," + + normalizedMatrix.c + "," + + normalizedMatrix.d + "," + + normalizedMatrix.dx + "," + + normalizedMatrix.dy + ")"; + } + this._graphic.addToRedrawQueue(this); + if(transform) + { + node.setAttribute("transform", transform); + } + if(!isPath) + { + this._transforms = []; + } + }, + + /** + * Draws the shape. + * + * @method _draw + * @private + */ + _draw: function() + { + var node = this.node; + node.setAttribute("width", this.get("width")); + node.setAttribute("height", this.get("height")); + node.setAttribute("x", this._x); + node.setAttribute("y", this._y); + node.style.left = this._x + "px"; + node.style.top = this._y + "px"; + this._fillChangeHandler(); + this._strokeChangeHandler(); + this._updateTransform(); + }, + + /** + * Updates `Shape` based on attribute changes. + * + * @method _updateHandler + * @private + */ + _updateHandler: function() + { + this._draw(); + }, + + /** + * Storage for the transform attribute. + * + * @property _transform + * @type String + * @private + */ + _transform: "", + + /** + * Returns the bounds for a shape. + * + * Calculates the a new bounding box from the original corner coordinates (base on size and position) and the transform matrix. + * The calculated bounding box is used by the graphic instance to calculate its viewBox. + * + * @method getBounds + * @return Object + */ + getBounds: function() + { + var type = this._type, + stroke = this.get("stroke"), + w = this.get("width"), + h = this.get("height"), + x = type === "path" ? 0 : this._x, + y = type === "path" ? 0 : this._y, + wt = 0; + if(type !== "path") + { + if(stroke && stroke.weight) + { + wt = stroke.weight; + } + w = (x + w + wt) - (x - wt); + h = (y + h + wt) - (y - wt); + x -= wt; + y -= wt; + } + return this._normalizedMatrix.getContentRect(w, h, x, y); + }, + + /** + * Places the shape above all other shapes. + * + * @method toFront + */ + toFront: function() + { + var graphic = this.get("graphic"); + if(graphic) + { + graphic._toFront(this); + } + }, + + /** + * Places the shape underneath all other shapes. + * + * @method toFront + */ + toBack: function() + { + var graphic = this.get("graphic"); + if(graphic) + { + graphic._toBack(this); + } + }, + + /** + * Parses path data string and call mapped methods. + * + * @method _parsePathData + * @param {String} val The path data + * @private + */ + _parsePathData: function(val) + { + var method, + methodSymbol, + args, + commandArray = Y.Lang.trim(val.match(SPLITPATHPATTERN)), + i, + len, + str, + symbolToMethod = this._pathSymbolToMethod; + if(commandArray) + { + this.clear(); + len = commandArray.length || 0; + for(i = 0; i < len; i = i + 1) + { + str = commandArray[i]; + methodSymbol = str.substr(0, 1); + args = str.substr(1).match(SPLITARGSPATTERN); + method = symbolToMethod[methodSymbol]; + if(method) + { + if(args) + { + this[method].apply(this, args); + } + else + { + this[method].apply(this); + } + } + } + this.end(); + } + }, + + /** + * Destroys the shape instance. + * + * @method destroy + */ + destroy: function() + { + var graphic = this.get("graphic"); + if(graphic) + { + graphic.removeShape(this); + } + else + { + this._destroy(); + } + }, + + /** + * Implementation for shape destruction + * + * @method destroy + * @protected + */ + _destroy: function() + { + if(this.node) + { + Y.Event.purgeElement(this.node, true); + if(this.node.parentNode) + { + this.node.parentNode.removeChild(this.node); + } + this.node = null; + } + } + }, Y.SVGDrawing.prototype)); + +SVGShape.ATTRS = { + /** + * An array of x, y values which indicates the transformOrigin in which to rotate the shape. Valid values range between 0 and 1 representing a + * fraction of the shape's corresponding bounding box dimension. The default value is [0.5, 0.5]. + * + * @config transformOrigin + * @type Array + */ + transformOrigin: { + valueFn: function() + { + return [0.5, 0.5]; + } + }, + + /** + *

A string containing, in order, transform operations applied to the shape instance. The `transform` string can contain the following values: + * + *

+ *
rotate
Rotates the shape clockwise around it transformOrigin.
+ *
translate
Specifies a 2d translation.
+ *
skew
Skews the shape around the x-axis and y-axis.
+ *
scale
Specifies a 2d scaling operation.
+ *
translateX
Translates the shape along the x-axis.
+ *
translateY
Translates the shape along the y-axis.
+ *
skewX
Skews the shape around the x-axis.
+ *
skewY
Skews the shape around the y-axis.
+ *
matrix
Specifies a 2D transformation matrix comprised of the specified six values.
+ *
+ *

+ *

Applying transforms through the transform attribute will reset the transform matrix and apply a new transform. The shape class also contains + * corresponding methods for each transform that will apply the transform to the current matrix. The below code illustrates how you might use the + * `transform` attribute to instantiate a recangle with a rotation of 45 degrees.

+ var myRect = new Y.Rect({ + type:"rect", + width: 50, + height: 40, + transform: "rotate(45)" + }; + *

The code below would apply `translate` and `rotate` to an existing shape.

+ + myRect.set("transform", "translate(40, 50) rotate(45)"); + * @config transform + * @type String + */ + transform: { + setter: function(val) + { + this.matrix.init(); + this._normalizedMatrix.init(); + this._transforms = this.matrix.getTransformArray(val); + this._transform = val; + return val; + }, + + getter: function() + { + return this._transform; + } + }, + + /** + * Unique id for class instance. + * + * @config id + * @type String + */ + id: { + valueFn: function() + { + return Y.guid(); + }, + + setter: function(val) + { + var node = this.node; + if(node) + { + node.setAttribute("id", val); + } + return val; + } + }, + + /** + * Indicates the x position of shape. + * + * @config x + * @type Number + */ + x: { + getter: function() + { + return this._x; + }, + + setter: function(val) + { + var transform = this.get("transform"); + this._x = val; + if(transform) + { + this.set("transform", transform); + } + } + }, + + /** + * Indicates the y position of shape. + * + * @config y + * @type Number + */ + y: { + getter: function() + { + return this._y; + }, + + setter: function(val) + { + var transform = this.get("transform"); + this._y = val; + if(transform) + { + this.set("transform", transform); + } + } + }, + + /** + * Indicates the width of the shape + * + * @config width + * @type Number + */ + width: { + value: 0 + }, + + /** + * Indicates the height of the shape + * + * @config height + * @type Number + */ + height: { + value: 0 + }, + + /** + * Indicates whether the shape is visible. + * + * @config visible + * @type Boolean + */ + visible: { + value: true, + + setter: function(val){ + var visibility = val ? "visible" : "hidden"; + if(this.node) + { + this.node.style.visibility = visibility; + } + return val; + } + }, + + /** + * Contains information about the fill of the shape. + *
+ *
color
The color of the fill.
+ *
opacity
Number between 0 and 1 that indicates the opacity of the fill. The default value is 1.
+ *
type
Type of fill. + *
+ *
solid
Solid single color fill. (default)
+ *
linear
Linear gradient fill.
+ *
radial
Radial gradient fill.
+ *
+ *
+ *
+ *

If a `linear` or `radial` is specified as the fill type. The following additional property is used: + *

+ *
stops
An array of objects containing the following properties: + *
+ *
color
The color of the stop.
+ *
opacity
Number between 0 and 1 that indicates the opacity of the stop. The default value is 1. + * Note: No effect for IE 6 - 8
+ *
offset
Number between 0 and 1 indicating where the color stop is positioned.
+ *
+ *
+ *

Linear gradients also have the following property:

+ *
rotation
Linear gradients flow left to right by default. The rotation property allows you to change the + * flow by rotation. (e.g. A rotation of 180 would make the gradient pain from right to left.)
+ *

Radial gradients have the following additional properties:

+ *
r
Radius of the gradient circle.
+ *
fx
Focal point x-coordinate of the gradient.
+ *
fy
Focal point y-coordinate of the gradient.
+ *
cx
+ *

The x-coordinate of the center of the gradient circle. Determines where the color stop begins. The default value 0.5.

+ *

Note: Currently, this property is not implemented for corresponding `CanvasShape` and + * `VMLShape` classes which are used on Android or IE 6 - 8.

+ *
+ *
cy
+ *

The y-coordinate of the center of the gradient circle. Determines where the color stop begins. The default value 0.5.

+ *

Note: Currently, this property is not implemented for corresponding `CanvasShape` and `VMLShape` + * classes which are used on Android or IE 6 - 8.

+ *
+ *
+ * + * @config fill + * @type Object + */ + fill: { + valueFn: "_getDefaultFill", + + setter: function(val) + { + var fill, + tmpl = this.get("fill") || this._getDefaultFill(); + fill = (val) ? Y.merge(tmpl, val) : null; + if(fill && fill.color) + { + if(fill.color === undefined || fill.color === "none") + { + fill.color = null; + } + } + return fill; + } + }, + + /** + * Contains information about the stroke of the shape. + *
+ *
color
The color of the stroke.
+ *
weight
Number that indicates the width of the stroke.
+ *
opacity
Number between 0 and 1 that indicates the opacity of the stroke. The default value is 1.
+ *
dashstyle
Indicates whether to draw a dashed stroke. When set to "none", a solid stroke is drawn. When set + * to an array, the first index indicates the length of the dash. The second index indicates the length of gap. + *
linecap
Specifies the linecap for the stroke. The following values can be specified: + *
+ *
butt (default)
Specifies a butt linecap.
+ *
square
Specifies a sqare linecap.
+ *
round
Specifies a round linecap.
+ *
+ *
+ *
linejoin
Specifies a linejoin for the stroke. The following values can be specified: + *
+ *
round (default)
Specifies that the linejoin will be round.
+ *
bevel
Specifies a bevel for the linejoin.
+ *
miter limit
An integer specifying the miter limit of a miter linejoin. If you want to specify a linejoin + * of miter, you simply specify the limit as opposed to having separate miter and miter limit values.
+ *
+ *
+ *
+ * + * @config stroke + * @type Object + */ + stroke: { + valueFn: "_getDefaultStroke", + + setter: function(val) + { + var tmpl = this.get("stroke") || this._getDefaultStroke(), + wt; + if(val && val.hasOwnProperty("weight")) + { + wt = parseInt(val.weight, 10); + if(!isNaN(wt)) + { + val.weight = wt; + } + } + return (val) ? Y.merge(tmpl, val) : null; + } + }, + + // Only implemented in SVG + // Determines whether the instance will receive mouse events. + // + // @config pointerEvents + // @type string + // + pointerEvents: { + valueFn: function() + { + var val = "visiblePainted", + node = this.node; + if(node) + { + node.setAttribute("pointer-events", val); + } + return val; + }, + + setter: function(val) + { + var node = this.node; + if(node) + { + node.setAttribute("pointer-events", val); + } + return val; + } + }, + + /** + * Dom node for the shape. + * + * @config node + * @type HTMLElement + * @readOnly + */ + node: { + readOnly: true, + + getter: function() + { + return this.node; + } + }, + + /** + * Represents an SVG Path string. This will be parsed and added to shape's API to represent the SVG data across all + * implementations. Note that when using VML or SVG implementations, part of this content will be added to the DOM using + * respective VML/SVG attributes. If your content comes from an untrusted source, you will need to ensure that no + * malicious code is included in that content. + * + * @config data + * @type String + */ + data: { + setter: function(val) + { + if(this.get("node")) + { + this._parsePathData(val); + } + return val; + } + }, + + /** + * Reference to the parent graphic instance + * + * @config graphic + * @type SVGGraphic + * @readOnly + */ + graphic: { + readOnly: true, + + getter: function() + { + return this._graphic; + } + } +}; +Y.SVGShape = SVGShape; + +/** + * SVG implementation of the `Path` class. + * `SVGPath` is not intended to be used directly. Instead, use the `Path` class. + * If the browser has SVG capabilities, the `Path` + * class will point to the `SVGPath` class. + * + * @module graphics + * @class SVGPath + * @extends SVGShape + * @constructor + */ +SVGPath = function() +{ + SVGPath.superclass.constructor.apply(this, arguments); +}; +SVGPath.NAME = "path"; +Y.extend(SVGPath, Y.SVGShape, { + /** + * Left edge of the path + * + * @property _left + * @type Number + * @private + */ + _left: 0, + + /** + * Right edge of the path + * + * @property _right + * @type Number + * @private + */ + _right: 0, + + /** + * Top edge of the path + * + * @property _top + * @type Number + * @private + */ + _top: 0, + + /** + * Bottom edge of the path + * + * @property _bottom + * @type Number + * @private + */ + _bottom: 0, + + /** + * Indicates the type of shape + * + * @property _type + * @readOnly + * @type String + * @private + */ + _type: "path", + + /** + * Storage for path + * + * @property _path + * @type String + * @private + */ + _path: "" +}); + +SVGPath.ATTRS = Y.merge(Y.SVGShape.ATTRS, { + /** + * Indicates the path used for the node. + * + * @config path + * @type String + * @readOnly + */ + path: { + readOnly: true, + + getter: function() + { + return this._path; + } + }, + + /** + * Indicates the width of the shape + * + * @config width + * @type Number + */ + width: { + getter: function() + { + var val = Math.max(this._right - this._left, 0); + return val; + } + }, + + /** + * Indicates the height of the shape + * + * @config height + * @type Number + */ + height: { + getter: function() + { + return Math.max(this._bottom - this._top, 0); + } + } +}); +Y.SVGPath = SVGPath; +/** + * SVG implementation of the `Rect` class. + * `SVGRect` is not intended to be used directly. Instead, use the `Rect` class. + * If the browser has SVG capabilities, the `Rect` + * class will point to the `SVGRect` class. + * + * @module graphics + * @class SVGRect + * @constructor + */ +SVGRect = function() +{ + SVGRect.superclass.constructor.apply(this, arguments); +}; +SVGRect.NAME = "rect"; +Y.extend(SVGRect, Y.SVGShape, { + /** + * Indicates the type of shape + * + * @property _type + * @type String + * @private + */ + _type: "rect" + }); +SVGRect.ATTRS = Y.SVGShape.ATTRS; +Y.SVGRect = SVGRect; +/** + * SVG implementation of the `Ellipse` class. + * `SVGEllipse` is not intended to be used directly. Instead, use the `Ellipse` class. + * If the browser has SVG capabilities, the `Ellipse` + * class will point to the `SVGEllipse` class. + * + * @module graphics + * @class SVGEllipse + * @constructor + */ +SVGEllipse = function() +{ + SVGEllipse.superclass.constructor.apply(this, arguments); +}; + +SVGEllipse.NAME = "ellipse"; + +Y.extend(SVGEllipse, SVGShape, { + /** + * Indicates the type of shape + * + * @property _type + * @type String + * @private + */ + _type: "ellipse", + + /** + * Updates the shape. + * + * @method _draw + * @private + */ + _draw: function() + { + var node = this.node, + w = this.get("width"), + h = this.get("height"), + x = this.get("x"), + y = this.get("y"), + xRadius = w * 0.5, + yRadius = h * 0.5, + cx = x + xRadius, + cy = y + yRadius; + node.setAttribute("rx", xRadius); + node.setAttribute("ry", yRadius); + node.setAttribute("cx", cx); + node.setAttribute("cy", cy); + this._fillChangeHandler(); + this._strokeChangeHandler(); + this._updateTransform(); + } +}); + +SVGEllipse.ATTRS = Y.merge(SVGShape.ATTRS, { + /** + * Horizontal radius for the ellipse. + * + * @config xRadius + * @type Number + */ + xRadius: { + setter: function(val) + { + this.set("width", val * 2); + }, + + getter: function() + { + var val = this.get("width"); + if(val) + { + val *= 0.5; + } + return val; + } + }, + + /** + * Vertical radius for the ellipse. + * + * @config yRadius + * @type Number + * @readOnly + */ + yRadius: { + setter: function(val) + { + this.set("height", val * 2); + }, + + getter: function() + { + var val = this.get("height"); + if(val) + { + val *= 0.5; + } + return val; + } + } +}); +Y.SVGEllipse = SVGEllipse; +/** + * SVG implementation of the `Circle` class. + * `SVGCircle` is not intended to be used directly. Instead, use the `Circle` class. + * If the browser has SVG capabilities, the `Circle` + * class will point to the `SVGCircle` class. + * + * @module graphics + * @class SVGCircle + * @constructor + */ + SVGCircle = function() + { + SVGCircle.superclass.constructor.apply(this, arguments); + }; + + SVGCircle.NAME = "circle"; + + Y.extend(SVGCircle, Y.SVGShape, { + + /** + * Indicates the type of shape + * + * @property _type + * @type String + * @private + */ + _type: "circle", + + /** + * Updates the shape. + * + * @method _draw + * @private + */ + _draw: function() + { + var node = this.node, + x = this.get("x"), + y = this.get("y"), + radius = this.get("radius"), + cx = x + radius, + cy = y + radius; + node.setAttribute("r", radius); + node.setAttribute("cx", cx); + node.setAttribute("cy", cy); + this._fillChangeHandler(); + this._strokeChangeHandler(); + this._updateTransform(); + } + }); + +SVGCircle.ATTRS = Y.merge(Y.SVGShape.ATTRS, { + /** + * Indicates the width of the shape + * + * @config width + * @type Number + */ + width: { + setter: function(val) + { + this.set("radius", val/2); + return val; + }, + + getter: function() + { + return this.get("radius") * 2; + } + }, + + /** + * Indicates the height of the shape + * + * @config height + * @type Number + */ + height: { + setter: function(val) + { + this.set("radius", val/2); + return val; + }, + + getter: function() + { + return this.get("radius") * 2; + } + }, + + /** + * Radius of the circle + * + * @config radius + * @type Number + */ + radius: { + value: 0 + } +}); +Y.SVGCircle = SVGCircle; +/** + * Draws pie slices + * + * @module graphics + * @class SVGPieSlice + * @constructor + */ +SVGPieSlice = function() +{ + SVGPieSlice.superclass.constructor.apply(this, arguments); +}; +SVGPieSlice.NAME = "svgPieSlice"; +Y.extend(SVGPieSlice, Y.SVGShape, Y.mix({ + /** + * Indicates the type of shape + * + * @property _type + * @type String + * @private + */ + _type: "path", + + /** + * Change event listener + * + * @private + * @method _updateHandler + */ + _draw: function() + { + var x = this.get("cx"), + y = this.get("cy"), + startAngle = this.get("startAngle"), + arc = this.get("arc"), + radius = this.get("radius"); + this.clear(); + this.drawWedge(x, y, startAngle, arc, radius); + this.end(); + } + }, Y.SVGDrawing.prototype)); +SVGPieSlice.ATTRS = Y.mix({ + cx: { + value: 0 + }, + + cy: { + value: 0 + }, + /** + * Starting angle in relation to a circle in which to begin the pie slice drawing. + * + * @config startAngle + * @type Number + */ + startAngle: { + value: 0 + }, + + /** + * Arc of the slice. + * + * @config arc + * @type Number + */ + arc: { + value: 0 + }, + + /** + * Radius of the circle in which the pie slice is drawn + * + * @config radius + * @type Number + */ + radius: { + value: 0 + } +}, Y.SVGShape.ATTRS); +Y.SVGPieSlice = SVGPieSlice; +/** + * SVG implementation of the `Graphic` class. + * `SVGGraphic` is not intended to be used directly. Instead, use the `Graphic` class. + * If the browser has SVG capabilities, the `Graphic` + * class will point to the `SVGGraphic` class. + * + * @module graphics + * @class SVGGraphic + * @constructor + */ +SVGGraphic = function() { + SVGGraphic.superclass.constructor.apply(this, arguments); +}; + +SVGGraphic.NAME = "svgGraphic"; + +SVGGraphic.ATTRS = { + /** + * Whether or not to render the `Graphic` automatically after to a specified parent node after init. This can be a Node + * instance or a CSS selector string. + * + * @config render + * @type Node | String + */ + render: {}, + + /** + * Unique id for class instance. + * + * @config id + * @type String + */ + id: { + valueFn: function() + { + return Y.guid(); + }, + + setter: function(val) + { + var node = this._node; + if(node) + { + node.setAttribute("id", val); + } + return val; + } + }, + + /** + * Key value pairs in which a shape instance is associated with its id. + * + * @config shapes + * @type Object + * @readOnly + */ + shapes: { + readOnly: true, + + getter: function() + { + return this._shapes; + } + }, + + /** + * Object containing size and coordinate data for the content of a Graphic in relation to the coordSpace node. + * + * @config contentBounds + * @type Object + * @readOnly + */ + contentBounds: { + readOnly: true, + + getter: function() + { + return this._contentBounds; + } + }, + + /** + * The html element that represents to coordinate system of the Graphic instance. + * + * @config node + * @type HTMLElement + * @readOnly + */ + node: { + readOnly: true, + + getter: function() + { + return this._node; + } + }, + + /** + * Indicates the width of the `Graphic`. + * + * @config width + * @type Number + */ + width: { + setter: function(val) + { + if(this._node) + { + this._node.style.width = val + "px"; + } + return val; + } + }, + + /** + * Indicates the height of the `Graphic`. + * + * @config height + * @type Number + */ + height: { + setter: function(val) + { + if(this._node) + { + this._node.style.height = val + "px"; + } + return val; + } + }, + + /** + * Determines the sizing of the Graphic. + * + *
+ *
sizeContentToGraphic
The Graphic's width and height attributes are, either explicitly set through the + * width and height attributes or are determined by the dimensions of the parent element. The + * content contained in the Graphic will be sized to fit with in the Graphic instance's dimensions. When using this + * setting, the preserveAspectRatio attribute will determine how the contents are sized.
+ *
sizeGraphicToContent
(Also accepts a value of true) The Graphic's width and height are determined by the + * size and positioning of the content.
+ *
false
The Graphic's width and height attributes are, either explicitly set through the width + * and height attributes or are determined by the dimensions of the parent element. The contents of the + * Graphic instance are not affected by this setting.
+ *
+ * + * + * @config autoSize + * @type Boolean | String + * @default false + */ + autoSize: { + value: false + }, + + /** + * Determines how content is sized when autoSize is set to sizeContentToGraphic. + * + *
+ *
none
Do not force uniform scaling. Scale the graphic content of the given element non-uniformly if necessary + * such that the element's bounding box exactly matches the viewport rectangle.
+ *
xMinYMin
Force uniform scaling position along the top left of the Graphic's node.
+ *
xMidYMin
Force uniform scaling horizontally centered and positioned at the top of the Graphic's node.
+ *
xMaxYMin
Force uniform scaling positioned horizontally from the right and vertically from the top.
+ *
xMinYMid
Force uniform scaling positioned horizontally from the left and vertically centered. + *
xMidYMid (the default)
Force uniform scaling with the content centered.
+ *
xMaxYMid
Force uniform scaling positioned horizontally from the right and vertically centered.
+ *
xMinYMax
Force uniform scaling positioned horizontally from the left and vertically from the bottom.
+ *
xMidYMax
Force uniform scaling horizontally centered and position vertically from the bottom.
+ *
xMaxYMax
Force uniform scaling positioned horizontally from the right and vertically from the bottom.
+ *
+ * + * @config preserveAspectRatio + * @type String + * @default xMidYMid + */ + preserveAspectRatio: { + value: "xMidYMid" + }, + + /** + * The contentBounds will resize to greater values but not to smaller values. (for performance) + * When resizing the contentBounds down is desirable, set the resizeDown value to true. + * + * @config resizeDown + * @type Boolean + */ + resizeDown: { + value: false + }, + + /** + * Indicates the x-coordinate for the instance. + * + * @config x + * @type Number + */ + x: { + getter: function() + { + return this._x; + }, + + setter: function(val) + { + this._x = val; + if(this._node) + { + this._node.style.left = val + "px"; + } + return val; + } + }, + + /** + * Indicates the y-coordinate for the instance. + * + * @config y + * @type Number + */ + y: { + getter: function() + { + return this._y; + }, + + setter: function(val) + { + this._y = val; + if(this._node) + { + this._node.style.top = val + "px"; + } + return val; + } + }, + + /** + * Indicates whether or not the instance will automatically redraw after a change is made to a shape. + * This property will get set to false when batching operations. + * + * @config autoDraw + * @type Boolean + * @default true + * @private + */ + autoDraw: { + value: true + }, + + visible: { + value: true, + + setter: function(val) + { + this._toggleVisible(val); + return val; + } + }, + + // + // Indicates the pointer-events setting for the svg:svg element. + // + // @config pointerEvents + // @type String + // + pointerEvents: { + value: "none" + } +}; + +Y.extend(SVGGraphic, Y.GraphicBase, { + /** + * Sets the value of an attribute. + * + * @method set + * @param {String|Object} name The name of the attribute. Alternatively, an object of key value pairs can + * be passed in to set multiple attributes at once. + * @param {Any} value The value to set the attribute to. This value is ignored if an object is received as + * the name param. + */ + set: function() + { + var host = this, + attr = arguments[0], + redrawAttrs = { + autoDraw: true, + autoSize: true, + preserveAspectRatio: true, + resizeDown: true + }, + key, + forceRedraw = false; + AttributeLite.prototype.set.apply(host, arguments); + if(host._state.autoDraw === true && Y.Object.size(this._shapes) > 0) + { + if(Y_LANG.isString && redrawAttrs[attr]) + { + forceRedraw = true; + } + else if(Y_LANG.isObject(attr)) + { + for(key in redrawAttrs) + { + if(redrawAttrs.hasOwnProperty(key) && attr[key]) + { + forceRedraw = true; + break; + } + } + } + } + if(forceRedraw) + { + host._redraw(); + } + }, + + /** + * Storage for `x` attribute. + * + * @property _x + * @type Number + * @private + */ + _x: 0, + + /** + * Storage for `y` attribute. + * + * @property _y + * @type Number + * @private + */ + _y: 0, + + /** + * Gets the current position of the graphic instance in page coordinates. + * + * @method getXY + * @return Array The XY position of the shape. + */ + getXY: function() + { + var node = Y.one(this._node), + xy; + if(node) + { + xy = node.getXY(); + } + return xy; + }, + + /** + * Initializes the class. + * + * @method initializer + * @private + */ + initializer: function() { + var render = this.get("render"), + visibility = this.get("visible") ? "visible" : "hidden"; + this._shapes = {}; + this._contentBounds = { + left: 0, + top: 0, + right: 0, + bottom: 0 + }; + this._gradients = {}; + this._node = DOCUMENT.createElement('div'); + this._node.style.position = "absolute"; + this._node.style.left = this.get("x") + "px"; + this._node.style.top = this.get("y") + "px"; + this._node.style.visibility = visibility; + this._contentNode = this._createGraphics(); + this._contentNode.style.visibility = visibility; + this._contentNode.setAttribute("id", this.get("id")); + this._node.appendChild(this._contentNode); + if(render) + { + this.render(render); + } + }, + + /** + * Adds the graphics node to the dom. + * + * @method render + * @param {HTMLElement} parentNode node in which to render the graphics node into. + */ + render: function(render) { + var parentNode = Y.one(render), + w = this.get("width") || parseInt(parentNode.getComputedStyle("width"), 10), + h = this.get("height") || parseInt(parentNode.getComputedStyle("height"), 10); + parentNode = parentNode || Y.one(DOCUMENT.body); + parentNode.append(this._node); + this.parentNode = parentNode; + this.set("width", w); + this.set("height", h); + return this; + }, + + /** + * Removes all nodes. + * + * @method destroy + */ + destroy: function() + { + this.removeAllShapes(); + if(this._contentNode) + { + this._removeChildren(this._contentNode); + if(this._contentNode.parentNode) + { + this._contentNode.parentNode.removeChild(this._contentNode); + } + this._contentNode = null; + } + if(this._node) + { + this._removeChildren(this._node); + Y.one(this._node).remove(true); + this._node = null; + } + }, + + /** + * Generates a shape instance by type. + * + * @method addShape + * @param {Object} cfg attributes for the shape + * @return Shape + */ + addShape: function(cfg) + { + cfg.graphic = this; + if(!this.get("visible")) + { + cfg.visible = false; + } + var ShapeClass = this._getShapeClass(cfg.type), + shape = new ShapeClass(cfg); + this._appendShape(shape); + return shape; + }, + + /** + * Adds a shape instance to the graphic instance. + * + * @method _appendShape + * @param {Shape} shape The shape instance to be added to the graphic. + * @private + */ + _appendShape: function(shape) + { + var node = shape.node, + parentNode = this._frag || this._contentNode; + if(this.get("autoDraw")) + { + parentNode.appendChild(node); + } + else + { + this._getDocFrag().appendChild(node); + } + }, + + /** + * Removes a shape instance from from the graphic instance. + * + * @method removeShape + * @param {Shape|String} shape The instance or id of the shape to be removed. + */ + removeShape: function(shape) + { + if(!(shape instanceof SVGShape)) + { + if(Y_LANG.isString(shape)) + { + shape = this._shapes[shape]; + } + } + if(shape && shape instanceof SVGShape) + { + shape._destroy(); + delete this._shapes[shape.get("id")]; + } + if(this.get("autoDraw")) + { + this._redraw(); + } + return shape; + }, + + /** + * Removes all shape instances from the dom. + * + * @method removeAllShapes + */ + removeAllShapes: function() + { + var shapes = this._shapes, + i; + for(i in shapes) + { + if(shapes.hasOwnProperty(i)) + { + shapes[i]._destroy(); + } + } + this._shapes = {}; + }, + + /** + * 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); + } + } + }, + + /** + * Clears the graphics object. + * + * @method clear + */ + clear: function() { + this.removeAllShapes(); + }, + + /** + * Toggles visibility + * + * @method _toggleVisible + * @param {Boolean} val indicates visibilitye + * @private + */ + _toggleVisible: function(val) + { + var i, + shapes = this._shapes, + visibility = val ? "visible" : "hidden"; + if(shapes) + { + for(i in shapes) + { + if(shapes.hasOwnProperty(i)) + { + shapes[i].set("visible", val); + } + } + } + if(this._contentNode) + { + this._contentNode.style.visibility = visibility; + } + if(this._node) + { + this._node.style.visibility = visibility; + } + }, + + /** + * Returns a shape class. Used by `addShape`. + * + * @method _getShapeClass + * @param {Shape | String} val Indicates which shape class. + * @return Function + * @private + */ + _getShapeClass: function(val) + { + var shape = this._shapeClass[val]; + if(shape) + { + return shape; + } + return val; + }, + + /** + * Look up for shape classes. Used by `addShape` to retrieve a class for instantiation. + * + * @property _shapeClass + * @type Object + * @private + */ + _shapeClass: { + circle: Y.SVGCircle, + rect: Y.SVGRect, + path: Y.SVGPath, + ellipse: Y.SVGEllipse, + pieslice: Y.SVGPieSlice + }, + + /** + * Returns a shape based on the id of its dom node. + * + * @method getShapeById + * @param {String} id Dom id of the shape's node attribute. + * @return Shape + */ + getShapeById: function(id) + { + var shape = this._shapes[id]; + return shape; + }, + + /** + * Allows for creating multiple shapes in order to batch appending and redraw operations. + * + * @method batch + * @param {Function} method Method to execute. + */ + batch: function(method) + { + var autoDraw = this.get("autoDraw"); + this.set("autoDraw", false); + method(); + this.set("autoDraw", autoDraw); + }, + + /** + * Returns a document fragment to for attaching shapes. + * + * @method _getDocFrag + * @return DocumentFragment + * @private + */ + _getDocFrag: function() + { + if(!this._frag) + { + this._frag = DOCUMENT.createDocumentFragment(); + } + return this._frag; + }, + + /** + * Redraws all shapes. + * + * @method _redraw + * @private + */ + _redraw: function() + { + var autoSize = this.get("autoSize"), + preserveAspectRatio = this.get("preserveAspectRatio"), + box = this.get("resizeDown") ? this._getUpdatedContentBounds() : this._contentBounds, + left = box.left, + right = box.right, + top = box.top, + bottom = box.bottom, + width = right - left, + height = bottom - top, + computedWidth, + computedHeight, + computedLeft, + computedTop, + node; + if(autoSize) + { + if(autoSize === "sizeContentToGraphic") + { + node = Y.one(this._node); + computedWidth = parseFloat(node.getComputedStyle("width")); + computedHeight = parseFloat(node.getComputedStyle("height")); + computedLeft = computedTop = 0; + this._contentNode.setAttribute("preserveAspectRatio", preserveAspectRatio); + } + else + { + computedWidth = width; + computedHeight = height; + computedLeft = left; + computedTop = top; + this._state.width = width; + this._state.height = height; + if(this._node) + { + this._node.style.width = width + "px"; + this._node.style.height = height + "px"; + } + } + } + else + { + computedWidth = width; + computedHeight = height; + computedLeft = left; + computedTop = top; + } + if(this._contentNode) + { + this._contentNode.style.left = computedLeft + "px"; + this._contentNode.style.top = computedTop + "px"; + this._contentNode.setAttribute("width", computedWidth); + this._contentNode.setAttribute("height", computedHeight); + this._contentNode.style.width = computedWidth + "px"; + this._contentNode.style.height = computedHeight + "px"; + this._contentNode.setAttribute("viewBox", "" + left + " " + top + " " + width + " " + height + ""); + } + if(this._frag) + { + if(this._contentNode) + { + this._contentNode.appendChild(this._frag); + } + this._frag = null; + } + }, + + /** + * Adds a shape to the redraw queue and calculates the contentBounds. Used internally + * by `Shape` instances. + * + * @method addToRedrawQueue + * @param shape {SVGShape} + * @protected + */ + addToRedrawQueue: function(shape) + { + var shapeBox, + box; + this._shapes[shape.get("id")] = shape; + if(!this.get("resizeDown")) + { + shapeBox = shape.getBounds(); + box = this._contentBounds; + box.left = box.left < shapeBox.left ? box.left : shapeBox.left; + box.top = box.top < shapeBox.top ? box.top : shapeBox.top; + box.right = box.right > shapeBox.right ? box.right : shapeBox.right; + box.bottom = box.bottom > shapeBox.bottom ? box.bottom : shapeBox.bottom; + box.width = box.right - box.left; + box.height = box.bottom - box.top; + this._contentBounds = box; + } + if(this.get("autoDraw")) + { + this._redraw(); + } + }, + + /** + * Recalculates and returns the `contentBounds` for the `Graphic` instance. + * + * @method _getUpdatedContentBounds + * @return {Object} + * @private + */ + _getUpdatedContentBounds: function() + { + var bounds, + i, + shape, + queue = this._shapes, + box = {}; + for(i in queue) + { + if(queue.hasOwnProperty(i)) + { + shape = queue[i]; + bounds = shape.getBounds(); + box.left = Y_LANG.isNumber(box.left) ? Math.min(box.left, bounds.left) : bounds.left; + box.top = Y_LANG.isNumber(box.top) ? Math.min(box.top, bounds.top) : bounds.top; + box.right = Y_LANG.isNumber(box.right) ? Math.max(box.right, bounds.right) : bounds.right; + box.bottom = Y_LANG.isNumber(box.bottom) ? Math.max(box.bottom, bounds.bottom) : bounds.bottom; + } + } + box.left = Y_LANG.isNumber(box.left) ? box.left : 0; + box.top = Y_LANG.isNumber(box.top) ? box.top : 0; + box.right = Y_LANG.isNumber(box.right) ? box.right : 0; + box.bottom = Y_LANG.isNumber(box.bottom) ? box.bottom : 0; + this._contentBounds = box; + return box; + }, + + /** + * Creates a contentNode element + * + * @method _createGraphics + * @private + */ + _createGraphics: function() { + var contentNode = this._createGraphicNode("svg"), + pointerEvents = this.get("pointerEvents"); + contentNode.style.position = "absolute"; + contentNode.style.top = "0px"; + contentNode.style.left = "0px"; + contentNode.style.overflow = "auto"; + contentNode.setAttribute("overflow", "auto"); + contentNode.setAttribute("pointer-events", pointerEvents); + return contentNode; + }, + + /** + * 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" && type !== "radialGradient") + { + node.setAttribute("pointer-events", v); + } + return node; + }, + + /** + * Returns a reference to a gradient definition based on an id and type. + * + * @method getGradientNode + * @param {String} key id that references the gradient definition + * @param {String} type description of the gradient type + * @return HTMLElement + * @protected + */ + getGradientNode: function(key, type) + { + var gradients = this._gradients, + gradient, + nodeType = type + "Gradient"; + if(gradients.hasOwnProperty(key) && gradients[key].tagName.indexOf(type) > -1) + { + gradient = this._gradients[key]; + } + else + { + gradient = this._createGraphicNode(nodeType); + if(!this._defs) + { + this._defs = this._createGraphicNode("defs"); + this._contentNode.appendChild(this._defs); + } + this._defs.appendChild(gradient); + key = key || "gradient" + Math.round(100000 * Math.random()); + gradient.setAttribute("id", key); + if(gradients.hasOwnProperty(key)) + { + this._defs.removeChild(gradients[key]); + } + gradients[key] = gradient; + } + return gradient; + }, + + /** + * Inserts shape on the top of the tree. + * + * @method _toFront + * @param {SVGShape} Shape to add. + * @private + */ + _toFront: function(shape) + { + var contentNode = this._contentNode; + if(shape instanceof Y.SVGShape) + { + shape = shape.get("node"); + } + if(contentNode && shape) + { + contentNode.appendChild(shape); + } + }, + + /** + * Inserts shape as the first child of the content node. + * + * @method _toBack + * @param {SVGShape} Shape to add. + * @private + */ + _toBack: function(shape) + { + var contentNode = this._contentNode, + targetNode; + if(shape instanceof Y.SVGShape) + { + shape = shape.get("node"); + } + if(contentNode && shape) + { + targetNode = contentNode.firstChild; + if(targetNode) + { + contentNode.insertBefore(shape, targetNode); + } + else + { + contentNode.appendChild(shape); + } + } + } +}); + +Y.SVGGraphic = SVGGraphic; + + + +}, '3.10.3', {"requires": ["graphics"]});