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: + * + *
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. + *If a `linear` or `radial` is specified as the fill type. The following additional property is used: + *
Linear gradients also have the following property:
+ *Radial gradients have the following additional properties:
+ *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.
+ *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.
+ *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.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.autoSize is set to sizeContentToGraphic.
+ *
+ *