diff -r 322d0feea350 -r 89ef5ed3c48b src/cm/media/js/lib/yui/yui_3.10.3/build/axis/axis.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/yui/yui_3.10.3/build/axis/axis.js Tue Jul 16 14:29:46 2013 +0200 @@ -0,0 +1,3177 @@ +/* +YUI 3.10.3 (build 2fb5187) +Copyright 2013 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +http://yuilibrary.com/license/ +*/ + +YUI.add('axis', function (Y, NAME) { + +/** + * Provides base functionality for drawing chart axes. + * + * @module charts + * @submodule axis + */ +var CONFIG = Y.config, + DOCUMENT = CONFIG.doc, + Y_Lang = Y.Lang, + IS_STRING = Y_Lang.isString, + Y_DOM = Y.DOM, + LeftAxisLayout, + RightAxisLayout, + BottomAxisLayout, + TopAxisLayout; +/** + * Algorithmic strategy for rendering a left axis. + * + * @class LeftAxisLayout + * @constructor + * @submodule axis + */ +LeftAxisLayout = function() {}; + +LeftAxisLayout.prototype = { + /** + * Default margins for text fields. + * + * @private + * @method _getDefaultMargins + * @return Object + */ + _getDefaultMargins: function() + { + return { + top: 0, + left: 0, + right: 4, + bottom: 0 + }; + }, + + /** + * Sets the length of the tick on either side of the axis line. + * + * @method setTickOffset + * @protected + */ + setTickOffsets: function() + { + var host = this, + majorTicks = host.get("styles").majorTicks, + tickLength = majorTicks.length, + halfTick = tickLength * 0.5, + display = majorTicks.display; + host.set("topTickOffset", 0); + host.set("bottomTickOffset", 0); + + switch(display) + { + case "inside" : + host.set("rightTickOffset", tickLength); + host.set("leftTickOffset", 0); + break; + case "outside" : + host.set("rightTickOffset", 0); + host.set("leftTickOffset", tickLength); + break; + case "cross": + host.set("rightTickOffset", halfTick); + host.set("leftTickOffset", halfTick); + break; + default: + host.set("rightTickOffset", 0); + host.set("leftTickOffset", 0); + break; + } + }, + + /** + * Draws a tick + * + * @method drawTick + * @param {Path} path reference to the path `Path` element in which to draw the tick. + * @param {Object} pt Point on the axis in which the tick will intersect. + * @param {Object} tickStyle Hash of properties to apply to the tick. + * @protected + */ + drawTick: function(path, pt, tickStyles) + { + var host = this, + style = host.get("styles"), + padding = style.padding, + tickLength = tickStyles.length, + start = {x:padding.left, y:pt.y}, + end = {x:tickLength + padding.left, y:pt.y}; + host.drawLine(path, start, end); + }, + + /** + * Calculates the coordinates for the first point on an axis. + * + * @method getLineStart + * @return {Object} + * @protected + */ + getLineStart: function() + { + var style = this.get("styles"), + padding = style.padding, + majorTicks = style.majorTicks, + tickLength = majorTicks.length, + display = majorTicks.display, + pt = {x:padding.left, y:0}; + if(display === "outside") + { + pt.x += tickLength; + } + else if(display === "cross") + { + pt.x += tickLength/2; + } + return pt; + }, + + /** + * Calculates the point for a label. + * + * @method getLabelPoint + * @param {Object} point Point on the axis in which the tick will intersect. + * @return {Object} + * @protected + */ + getLabelPoint: function(point) + { + return {x:point.x - this.get("leftTickOffset"), y:point.y}; + }, + + /** + * Updates the value for the `maxLabelSize` for use in calculating total size. + * + * @method updateMaxLabelSize + * @param {HTMLElement} label to measure + * @protected + */ + updateMaxLabelSize: function(labelWidth, labelHeight) + { + var host = this, + props = this._labelRotationProps, + rot = props.rot, + absRot = props.absRot, + sinRadians = props.sinRadians, + cosRadians = props.cosRadians, + max; + if(rot === 0) + { + max = labelWidth; + } + else if(absRot === 90) + { + max = labelHeight; + } + else + { + max = (cosRadians * labelWidth) + (sinRadians * labelHeight); + } + host._maxLabelSize = Math.max(host._maxLabelSize, max); + }, + + /** + * Determines the available label width when the axis width has been explicitly set. + * + * @method getExplicitlySized + * @return Boolean + * @protected + */ + getExplicitlySized: function(styles) + { + if(this._explicitWidth) + { + var host = this, + w = host._explicitWidth, + totalTitleSize = host._totalTitleSize, + leftTickOffset = host.get("leftTickOffset"), + margin = styles.label.margin.right; + host._maxLabelSize = w - (leftTickOffset + margin + totalTitleSize); + return true; + } + return false; + }, + + /** + * Rotate and position title. + * + * @method positionTitle + * @param {HTMLElement} label to rotate position + * @protected + */ + positionTitle: function(label) + { + var host = this, + bounds = host._titleBounds, + margin = host.get("styles").title.margin, + props = host._titleRotationProps, + w = bounds.right - bounds.left, + labelWidth = label.offsetWidth, + labelHeight = label.offsetHeight, + x = (labelWidth * -0.5) + (w * 0.5), + y = (host.get("height") * 0.5) - (labelHeight * 0.5); + props.labelWidth = labelWidth; + props.labelHeight = labelHeight; + if(margin && margin.left) + { + x += margin.left; + } + props.x = x; + props.y = y; + props.transformOrigin = [0.5, 0.5]; + host._rotate(label, props); + }, + + /** + * Rotate and position labels. + * + * @method positionLabel + * @param {HTMLElement} label to rotate position + * @param {Object} pt hash containing the x and y coordinates in which the label will be positioned + * against. + * @protected + */ + positionLabel: function(label, pt, styles, i) + { + var host = this, + tickOffset = host.get("leftTickOffset"), + totalTitleSize = this._totalTitleSize, + leftOffset = pt.x + totalTitleSize - tickOffset, + topOffset = pt.y, + props = this._labelRotationProps, + rot = props.rot, + absRot = props.absRot, + maxLabelSize = host._maxLabelSize, + labelWidth = this._labelWidths[i], + labelHeight = this._labelHeights[i]; + if(rot === 0) + { + leftOffset -= labelWidth; + topOffset -= labelHeight * 0.5; + } + else if(rot === 90) + { + leftOffset -= labelWidth * 0.5; + } + else if(rot === -90) + { + leftOffset -= labelWidth * 0.5; + topOffset -= labelHeight; + } + else + { + leftOffset -= labelWidth + (labelHeight * absRot/360); + topOffset -= labelHeight * 0.5; + } + props.labelWidth = labelWidth; + props.labelHeight = labelHeight; + props.x = Math.round(maxLabelSize + leftOffset); + props.y = Math.round(topOffset); + this._rotate(label, props); + }, + + /** + * Adjusts the coordinates of an axis label based on the rotation. + * + * @method _setRotationCoords + * @param {Object} props Coordinates, dimension and rotation properties of the label. + * @protected + */ + _setRotationCoords: function(props) + { + var rot = props.rot, + absRot = props.absRot, + leftOffset, + topOffset, + labelWidth = props.labelWidth, + labelHeight = props.labelHeight; + if(rot === 0) + { + leftOffset = labelWidth; + topOffset = labelHeight * 0.5; + } + else if(rot === 90) + { + topOffset = 0; + leftOffset = labelWidth * 0.5; + } + else if(rot === -90) + { + leftOffset = labelWidth * 0.5; + topOffset = labelHeight; + } + else + { + leftOffset = labelWidth + (labelHeight * absRot/360); + topOffset = labelHeight * 0.5; + } + props.x -= leftOffset; + props.y -= topOffset; + }, + + /** + * Returns the transformOrigin to use for an axis label based on the position of the axis + * and the rotation of the label. + * + * @method _getTransformOrigin + * @param {Number} rot The rotation (in degrees) of the label. + * @return Array + * @protected + */ + _getTransformOrigin: function(rot) + { + var transformOrigin; + if(rot === 0) + { + transformOrigin = [0, 0]; + } + else if(rot === 90) + { + transformOrigin = [0.5, 0]; + } + else if(rot === -90) + { + transformOrigin = [0.5, 1]; + } + else + { + transformOrigin = [1, 0.5]; + } + return transformOrigin; + }, + + /** + * Adjust the position of the Axis widget's content box for internal axes. + * + * @method offsetNodeForTick + * @param {Node} cb contentBox of the axis + * @protected + */ + offsetNodeForTick: function() + { + }, + + /** + * Sets the width of the axis based on its contents. + * + * @method setCalculatedSize + * @protected + */ + setCalculatedSize: function() + { + var host = this, + graphic = this.get("graphic"), + style = host.get("styles"), + label = style.label, + tickOffset = host.get("leftTickOffset"), + max = host._maxLabelSize, + totalTitleSize = this._totalTitleSize, + ttl = Math.round(totalTitleSize + tickOffset + max + label.margin.right); + if(this._explicitWidth) + { + ttl = this._explicitWidth; + } + this.set("calculatedWidth", ttl); + graphic.set("x", ttl - tickOffset); + } +}; + +Y.LeftAxisLayout = LeftAxisLayout; +/** + * RightAxisLayout contains algorithms for rendering a right axis. + * + * @class RightAxisLayout + * @constructor + * @submodule axis + */ +RightAxisLayout = function(){}; + +RightAxisLayout.prototype = { + /** + * Default margins for text fields. + * + * @private + * @method _getDefaultMargins + * @return Object + */ + _getDefaultMargins: function() + { + return { + top: 0, + left: 4, + right: 0, + bottom: 0 + }; + }, + + /** + * Sets the length of the tick on either side of the axis line. + * + * @method setTickOffset + * @protected + */ + setTickOffsets: function() + { + var host = this, + majorTicks = host.get("styles").majorTicks, + tickLength = majorTicks.length, + halfTick = tickLength * 0.5, + display = majorTicks.display; + host.set("topTickOffset", 0); + host.set("bottomTickOffset", 0); + + switch(display) + { + case "inside" : + host.set("leftTickOffset", tickLength); + host.set("rightTickOffset", 0); + break; + case "outside" : + host.set("leftTickOffset", 0); + host.set("rightTickOffset", tickLength); + break; + case "cross" : + host.set("rightTickOffset", halfTick); + host.set("leftTickOffset", halfTick); + break; + default: + host.set("leftTickOffset", 0); + host.set("rightTickOffset", 0); + break; + } + }, + + /** + * Draws a tick + * + * @method drawTick + * @param {Path} path reference to the path `Path` element in which to draw the tick. + * @param {Object} pt Point on the axis in which the tick will intersect. + * @param {Object) tickStyle Hash of properties to apply to the tick. + * @protected + */ + drawTick: function(path, pt, tickStyles) + { + var host = this, + style = host.get("styles"), + padding = style.padding, + tickLength = tickStyles.length, + start = {x:padding.left, y:pt.y}, + end = {x:padding.left + tickLength, y:pt.y}; + host.drawLine(path, start, end); + }, + + /** + * Calculates the coordinates for the first point on an axis. + * + * @method getLineStart + * @return {Object} + * @protected + */ + getLineStart: function() + { + var host = this, + style = host.get("styles"), + padding = style.padding, + majorTicks = style.majorTicks, + tickLength = majorTicks.length, + display = majorTicks.display, + pt = {x:padding.left, y:padding.top}; + if(display === "inside") + { + pt.x += tickLength; + } + else if(display === "cross") + { + pt.x += tickLength/2; + } + return pt; + }, + + /** + * Calculates the point for a label. + * + * @method getLabelPoint + * @param {Object} point Point on the axis in which the tick will intersect. + * @return {Object} + * @protected + */ + getLabelPoint: function(point) + { + return {x:point.x + this.get("rightTickOffset"), y:point.y}; + }, + + /** + * Updates the value for the `maxLabelSize` for use in calculating total size. + * + * @method updateMaxLabelSize + * @param {HTMLElement} label to measure + * @protected + */ + updateMaxLabelSize: function(labelWidth, labelHeight) + { + var host = this, + props = this._labelRotationProps, + rot = props.rot, + absRot = props.absRot, + sinRadians = props.sinRadians, + cosRadians = props.cosRadians, + max; + if(rot === 0) + { + max = labelWidth; + } + else if(absRot === 90) + { + max = labelHeight; + } + else + { + max = (cosRadians * labelWidth) + (sinRadians * labelHeight); + } + host._maxLabelSize = Math.max(host._maxLabelSize, max); + }, + + /** + * Determines the available label width when the axis width has been explicitly set. + * + * @method getExplicitlySized + * @return Boolean + * @protected + */ + getExplicitlySized: function(styles) + { + if(this._explicitWidth) + { + var host = this, + w = host._explicitWidth, + totalTitleSize = this._totalTitleSize, + rightTickOffset = host.get("rightTickOffset"), + margin = styles.label.margin.right; + host._maxLabelSize = w - (rightTickOffset + margin + totalTitleSize); + return true; + } + return false; + }, + + /** + * Rotate and position title. + * + * @method positionTitle + * @param {HTMLElement} label to rotate position + * @protected + */ + positionTitle: function(label) + { + var host = this, + bounds = host._titleBounds, + margin = host.get("styles").title.margin, + props = host._titleRotationProps, + labelWidth = label.offsetWidth, + labelHeight = label.offsetHeight, + w = bounds.right - bounds.left, + x = this.get("width") - (labelWidth * 0.5) - (w * 0.5), + y = (host.get("height") * 0.5) - (labelHeight * 0.5); + props.labelWidth = labelWidth; + props.labelHeight = labelHeight; + if(margin && margin.right) + { + x -= margin.left; + } + props.x = x; + props.y = y; + props.transformOrigin = [0.5, 0.5]; + host._rotate(label, props); + }, + + /** + * Rotate and position labels. + * + * @method positionLabel + * @param {HTMLElement} label to rotate position + * @param {Object} pt hash containing the x and y coordinates in which the label will be positioned + * against. + * @protected + */ + positionLabel: function(label, pt, styles, i) + { + var host = this, + tickOffset = host.get("rightTickOffset"), + labelStyles = styles.label, + margin = 0, + leftOffset = pt.x, + topOffset = pt.y, + props = this._labelRotationProps, + rot = props.rot, + absRot = props.absRot, + labelWidth = this._labelWidths[i], + labelHeight = this._labelHeights[i]; + if(labelStyles.margin && labelStyles.margin.left) + { + margin = labelStyles.margin.left; + } + if(rot === 0) + { + topOffset -= labelHeight * 0.5; + } + else if(rot === 90) + { + leftOffset -= labelWidth * 0.5; + topOffset -= labelHeight; + } + else if(rot === -90) + { + leftOffset -= labelWidth * 0.5; + } + else + { + topOffset -= labelHeight * 0.5; + leftOffset += labelHeight/2 * absRot/90; + } + leftOffset += margin; + leftOffset += tickOffset; + props.labelWidth = labelWidth; + props.labelHeight = labelHeight; + props.x = Math.round(leftOffset); + props.y = Math.round(topOffset); + this._rotate(label, props); + }, + + /** + * Adjusts the coordinates of an axis label based on the rotation. + * + * @method _setRotationCoords + * @param {Object} props Coordinates, dimension and rotation properties of the label. + * @protected + */ + _setRotationCoords: function(props) + { + var rot = props.rot, + absRot = props.absRot, + leftOffset = 0, + topOffset = 0, + labelWidth = props.labelWidth, + labelHeight = props.labelHeight; + if(rot === 0) + { + topOffset = labelHeight * 0.5; + } + else if(rot === 90) + { + leftOffset = labelWidth * 0.5; + topOffset = labelHeight; + } + else if(rot === -90) + { + leftOffset = labelWidth * 0.5; + } + else + { + topOffset = labelHeight * 0.5; + leftOffset = labelHeight/2 * absRot/90; + } + props.x -= leftOffset; + props.y -= topOffset; + }, + + /** + * Returns the transformOrigin to use for an axis label based on the position of the axis + * and the rotation of the label. + * + * @method _getTransformOrigin + * @param {Number} rot The rotation (in degrees) of the label. + * @return Array + * @protected + */ + _getTransformOrigin: function(rot) + { + var transformOrigin; + if(rot === 0) + { + transformOrigin = [0, 0]; + } + else if(rot === 90) + { + transformOrigin = [0.5, 1]; + } + else if(rot === -90) + { + transformOrigin = [0.5, 0]; + } + else + { + transformOrigin = [0, 0.5]; + } + return transformOrigin; + }, + + /** + * Adjusts position for inner ticks. + * + * @method offsetNodeForTick + * @param {Node} cb contentBox of the axis + * @protected + */ + offsetNodeForTick: function(cb) + { + var host = this, + tickOffset = host.get("leftTickOffset"), + offset = 0 - tickOffset; + cb.setStyle("left", offset); + }, + + /** + * Assigns a height based on the size of the contents. + * + * @method setCalculatedSize + * @protected + */ + setCalculatedSize: function() + { + var host = this, + styles = host.get("styles"), + labelStyle = styles.label, + totalTitleSize = this._totalTitleSize, + ttl = Math.round(host.get("rightTickOffset") + host._maxLabelSize + totalTitleSize + labelStyle.margin.left); + if(this._explicitWidth) + { + ttl = this._explicitWidth; + } + host.set("calculatedWidth", ttl); + host.get("contentBox").setStyle("width", ttl); + } +}; + +Y.RightAxisLayout = RightAxisLayout; +/** + * Contains algorithms for rendering a bottom axis. + * + * @class BottomAxisLayout + * @Constructor + * @submodule axis + */ +BottomAxisLayout = function(){}; + +BottomAxisLayout.prototype = { + /** + * Default margins for text fields. + * + * @private + * @method _getDefaultMargins + * @return Object + */ + _getDefaultMargins: function() + { + return { + top: 4, + left: 0, + right: 0, + bottom: 0 + }; + }, + + /** + * Sets the length of the tick on either side of the axis line. + * + * @method setTickOffsets + * @protected + */ + setTickOffsets: function() + { + var host = this, + majorTicks = host.get("styles").majorTicks, + tickLength = majorTicks.length, + halfTick = tickLength * 0.5, + display = majorTicks.display; + host.set("leftTickOffset", 0); + host.set("rightTickOffset", 0); + + switch(display) + { + case "inside" : + host.set("topTickOffset", tickLength); + host.set("bottomTickOffset", 0); + break; + case "outside" : + host.set("topTickOffset", 0); + host.set("bottomTickOffset", tickLength); + break; + case "cross": + host.set("topTickOffset", halfTick); + host.set("bottomTickOffset", halfTick); + break; + default: + host.set("topTickOffset", 0); + host.set("bottomTickOffset", 0); + break; + } + }, + + /** + * Calculates the coordinates for the first point on an axis. + * + * @method getLineStart + * @protected + */ + getLineStart: function() + { + var style = this.get("styles"), + padding = style.padding, + majorTicks = style.majorTicks, + tickLength = majorTicks.length, + display = majorTicks.display, + pt = {x:0, y:padding.top}; + if(display === "inside") + { + pt.y += tickLength; + } + else if(display === "cross") + { + pt.y += tickLength/2; + } + return pt; + }, + + /** + * Draws a tick + * + * @method drawTick + * @param {Path} path reference to the path `Path` element in which to draw the tick. + * @param {Object} pt hash containing x and y coordinates + * @param {Object} tickStyles hash of properties used to draw the tick + * @protected + */ + drawTick: function(path, pt, tickStyles) + { + var host = this, + style = host.get("styles"), + padding = style.padding, + tickLength = tickStyles.length, + start = {x:pt.x, y:padding.top}, + end = {x:pt.x, y:tickLength + padding.top}; + host.drawLine(path, start, end); + }, + + /** + * Calculates the point for a label. + * + * @method getLabelPoint + * @param {Object} pt Object containing x and y coordinates + * @return Object + * @protected + */ + getLabelPoint: function(point) + { + return {x:point.x, y:point.y + this.get("bottomTickOffset")}; + }, + + /** + * Updates the value for the `maxLabelSize` for use in calculating total size. + * + * @method updateMaxLabelSize + * @param {HTMLElement} label to measure + * @protected + */ + updateMaxLabelSize: function(labelWidth, labelHeight) + { + var host = this, + props = this._labelRotationProps, + rot = props.rot, + absRot = props.absRot, + sinRadians = props.sinRadians, + cosRadians = props.cosRadians, + max; + if(rot === 0) + { + max = labelHeight; + } + else if(absRot === 90) + { + max = labelWidth; + } + else + { + max = (sinRadians * labelWidth) + (cosRadians * labelHeight); + } + host._maxLabelSize = Math.max(host._maxLabelSize, max); + }, + + /** + * Determines the available label height when the axis width has been explicitly set. + * + * @method getExplicitlySized + * @return Boolean + * @protected + */ + getExplicitlySized: function(styles) + { + if(this._explicitHeight) + { + var host = this, + h = host._explicitHeight, + totalTitleSize = host._totalTitleSize, + bottomTickOffset = host.get("bottomTickOffset"), + margin = styles.label.margin.right; + host._maxLabelSize = h - (bottomTickOffset + margin + totalTitleSize); + return true; + } + return false; + }, + + /** + * Rotate and position title. + * + * @method positionTitle + * @param {HTMLElement} label to rotate position + * @protected + */ + positionTitle: function(label) + { + var host = this, + bounds = host._titleBounds, + margin = host.get("styles").title.margin, + props = host._titleRotationProps, + h = bounds.bottom - bounds.top, + labelWidth = label.offsetWidth, + labelHeight = label.offsetHeight, + x = (host.get("width") * 0.5) - (labelWidth * 0.5), + y = host.get("height") - labelHeight/2 - h/2; + props.labelWidth = labelWidth; + props.labelHeight = labelHeight; + if(margin && margin.bottom) + { + y -= margin.bottom; + } + props.x = x; + props.y = y; + props.transformOrigin = [0.5, 0.5]; + host._rotate(label, props); + }, + + /** + * Rotate and position labels. + * + * @method positionLabel + * @param {HTMLElement} label to rotate position + * @param {Object} pt hash containing the x and y coordinates in which the label will be positioned + * against. + * @protected + */ + positionLabel: function(label, pt, styles, i) + { + var host = this, + tickOffset = host.get("bottomTickOffset"), + labelStyles = styles.label, + margin = 0, + props = host._labelRotationProps, + rot = props.rot, + absRot = props.absRot, + leftOffset = Math.round(pt.x), + topOffset = Math.round(pt.y), + labelWidth = host._labelWidths[i], + labelHeight = host._labelHeights[i]; + if(labelStyles.margin && labelStyles.margin.top) + { + margin = labelStyles.margin.top; + } + if(rot > 0) + { + topOffset -= labelHeight/2 * rot/90; + } + else if(rot < 0) + { + leftOffset -= labelWidth; + topOffset -= labelHeight/2 * absRot/90; + } + else + { + leftOffset -= labelWidth * 0.5; + } + topOffset += margin; + topOffset += tickOffset; + props.labelWidth = labelWidth; + props.labelHeight = labelHeight; + props.x = leftOffset; + props.y = topOffset; + host._rotate(label, props); + }, + + /** + * Adjusts the coordinates of an axis label based on the rotation. + * + * @method _setRotationCoords + * @param {Object} props Coordinates, dimension and rotation properties of the label. + * @protected + */ + _setRotationCoords: function(props) + { + var rot = props.rot, + absRot = props.absRot, + labelWidth = props.labelWidth, + labelHeight = props.labelHeight, + leftOffset, + topOffset; + + if(rot > 0) + { + leftOffset = 0; + topOffset = labelHeight/2 * rot/90; + } + else if(rot < 0) + { + leftOffset = labelWidth; + topOffset = labelHeight/2 * absRot/90; + } + else + { + leftOffset = labelWidth * 0.5; + topOffset = 0; + } + props.x -= leftOffset; + props.y -= topOffset; + }, + + /** + * Returns the transformOrigin to use for an axis label based on the position of the axis + * and the rotation of the label. + * + * @method _getTransformOrigin + * @param {Number} rot The rotation (in degrees) of the label. + * @return Array + * @protected + */ + _getTransformOrigin: function(rot) + { + var transformOrigin; + if(rot > 0) + { + transformOrigin = [0, 0.5]; + } + else if(rot < 0) + { + transformOrigin = [1, 0.5]; + } + else + { + transformOrigin = [0, 0]; + } + return transformOrigin; + }, + + /** + * Adjusts position for inner ticks. + * + * @method offsetNodeForTick + * @param {Node} cb contentBox of the axis + * @protected + */ + offsetNodeForTick: function(cb) + { + var host = this; + cb.setStyle("top", 0 - host.get("topTickOffset")); + }, + + /** + * Assigns a height based on the size of the contents. + * + * @method setCalculatedSize + * @protected + */ + setCalculatedSize: function() + { + var host = this, + styles = host.get("styles"), + labelStyle = styles.label, + totalTitleSize = host._totalTitleSize, + ttl = Math.round(host.get("bottomTickOffset") + host._maxLabelSize + labelStyle.margin.top + totalTitleSize); + if(host._explicitHeight) + { + ttl = host._explicitHeight; + } + host.set("calculatedHeight", ttl); + } +}; +Y.BottomAxisLayout = BottomAxisLayout; +/** + * Contains algorithms for rendering a top axis. + * + * @class TopAxisLayout + * @constructor + * @submodule axis + */ +TopAxisLayout = function(){}; + +TopAxisLayout.prototype = { + /** + * Default margins for text fields. + * + * @private + * @method _getDefaultMargins + * @return Object + */ + _getDefaultMargins: function() + { + return { + top: 0, + left: 0, + right: 0, + bottom: 4 + }; + }, + + /** + * Sets the length of the tick on either side of the axis line. + * + * @method setTickOffsets + * @protected + */ + setTickOffsets: function() + { + var host = this, + majorTicks = host.get("styles").majorTicks, + tickLength = majorTicks.length, + halfTick = tickLength * 0.5, + display = majorTicks.display; + host.set("leftTickOffset", 0); + host.set("rightTickOffset", 0); + switch(display) + { + case "inside" : + host.set("bottomTickOffset", tickLength); + host.set("topTickOffset", 0); + break; + case "outside" : + host.set("bottomTickOffset", 0); + host.set("topTickOffset", tickLength); + break; + case "cross" : + host.set("topTickOffset", halfTick); + host.set("bottomTickOffset", halfTick); + break; + default: + host.set("topTickOffset", 0); + host.set("bottomTickOffset", 0); + break; + } + }, + + /** + * Calculates the coordinates for the first point on an axis. + * + * @method getLineStart + * @protected + */ + getLineStart: function() + { + var host = this, + style = host.get("styles"), + padding = style.padding, + majorTicks = style.majorTicks, + tickLength = majorTicks.length, + display = majorTicks.display, + pt = {x:0, y:padding.top}; + if(display === "outside") + { + pt.y += tickLength; + } + else if(display === "cross") + { + pt.y += tickLength/2; + } + return pt; + }, + + /** + * Draws a tick + * + * @method drawTick + * @param {Path} path reference to the path `Path` element in which to draw the tick. + * @param {Object} pt hash containing x and y coordinates + * @param {Object} tickStyles hash of properties used to draw the tick + * @protected + */ + drawTick: function(path, pt, tickStyles) + { + var host = this, + style = host.get("styles"), + padding = style.padding, + tickLength = tickStyles.length, + start = {x:pt.x, y:padding.top}, + end = {x:pt.x, y:tickLength + padding.top}; + host.drawLine(path, start, end); + }, + + /** + * Calculates the point for a label. + * + * @method getLabelPoint + * @param {Object} pt hash containing x and y coordinates + * @return Object + * @protected + */ + getLabelPoint: function(pt) + { + return {x:pt.x, y:pt.y - this.get("topTickOffset")}; + }, + + /** + * Updates the value for the `maxLabelSize` for use in calculating total size. + * + * @method updateMaxLabelSize + * @param {HTMLElement} label to measure + * @protected + */ + updateMaxLabelSize: function(labelWidth, labelHeight) + { + var host = this, + props = this._labelRotationProps, + rot = props.rot, + absRot = props.absRot, + sinRadians = props.sinRadians, + cosRadians = props.cosRadians, + max; + if(rot === 0) + { + max = labelHeight; + } + else if(absRot === 90) + { + max = labelWidth; + } + else + { + max = (sinRadians * labelWidth) + (cosRadians * labelHeight); + } + host._maxLabelSize = Math.max(host._maxLabelSize, max); + }, + + /** + * Determines the available label height when the axis width has been explicitly set. + * + * @method getExplicitlySized + * @return Boolean + * @protected + */ + getExplicitlySized: function(styles) + { + if(this._explicitHeight) + { + var host = this, + h = host._explicitHeight, + totalTitleSize = host._totalTitleSize, + topTickOffset = host.get("topTickOffset"), + margin = styles.label.margin.right; + host._maxLabelSize = h - (topTickOffset + margin + totalTitleSize); + return true; + } + return false; + }, + + /** + * Rotate and position title. + * + * @method positionTitle + * @param {HTMLElement} label to rotate position + * @protected + */ + positionTitle: function(label) + { + var host = this, + bounds = host._titleBounds, + margin = host.get("styles").title.margin, + props = host._titleRotationProps, + labelWidth = label.offsetWidth, + labelHeight = label.offsetHeight, + h = bounds.bottom - bounds.top, + x = (host.get("width") * 0.5) - (labelWidth * 0.5), + y = h/2 - labelHeight/2; + props.labelWidth = labelWidth; + props.labelHeight = labelHeight; + if(margin && margin.top) + { + y += margin.top; + } + props.x = x; + props.y = y; + props.transformOrigin = [0.5, 0.5]; + host._rotate(label, props); + }, + + /** + * Rotate and position labels. + * + * @method positionLabel + * @param {HTMLElement} label to rotate position + * @param {Object} pt hash containing the x and y coordinates in which the label will be positioned + * against. + * @protected + */ + positionLabel: function(label, pt, styles, i) + { + var host = this, + totalTitleSize = this._totalTitleSize, + maxLabelSize = host._maxLabelSize, + leftOffset = pt.x, + topOffset = pt.y + totalTitleSize + maxLabelSize, + props = this._labelRotationProps, + rot = props.rot, + absRot = props.absRot, + labelWidth = this._labelWidths[i], + labelHeight = this._labelHeights[i]; + if(rot === 0) + { + leftOffset -= labelWidth * 0.5; + topOffset -= labelHeight; + } + else + { + if(rot === 90) + { + leftOffset -= labelWidth; + topOffset -= (labelHeight * 0.5); + } + else if (rot === -90) + { + topOffset -= (labelHeight * 0.5); + } + else if(rot > 0) + { + leftOffset -= labelWidth; + topOffset -= labelHeight - (labelHeight * rot/180); + } + else + { + topOffset -= labelHeight - (labelHeight * absRot/180); + } + } + props.x = Math.round(leftOffset); + props.y = Math.round(topOffset); + props.labelWidth = labelWidth; + props.labelHeight = labelHeight; + this._rotate(label, props); + }, + + /** + * Adjusts the coordinates of an axis label based on the rotation. + * + * @method _setRotationCoords + * @param {Object} props Coordinates, dimension and rotation properties of the label. + * @protected + */ + _setRotationCoords: function(props) + { + var rot = props.rot, + absRot = props.absRot, + labelWidth = props.labelWidth, + labelHeight = props.labelHeight, + leftOffset, + topOffset; + if(rot === 0) + { + leftOffset = labelWidth * 0.5; + topOffset = labelHeight; + } + else + { + if(rot === 90) + { + leftOffset = labelWidth; + topOffset = (labelHeight * 0.5); + } + else if (rot === -90) + { + topOffset = (labelHeight * 0.5); + } + else if(rot > 0) + { + leftOffset = labelWidth; + topOffset = labelHeight - (labelHeight * rot/180); + } + else + { + topOffset = labelHeight - (labelHeight * absRot/180); + } + } + props.x -= leftOffset; + props.y -= topOffset; + }, + + /** + * Returns the transformOrigin to use for an axis label based on the position of the axis + * and the rotation of the label. + * + * @method _getTransformOrigin + * @param {Number} rot The rotation (in degrees) of the label. + * @return Array + * @protected + */ + _getTransformOrigin: function(rot) + { + var transformOrigin; + if(rot === 0) + { + transformOrigin = [0, 0]; + } + else + { + if(rot === 90) + { + transformOrigin = [1, 0.5]; + } + else if (rot === -90) + { + transformOrigin = [0, 0.5]; + } + else if(rot > 0) + { + transformOrigin = [1, 0.5]; + } + else + { + transformOrigin = [0, 0.5]; + } + } + return transformOrigin; + }, + + /** + * Adjusts position for inner ticks. + * + * @method offsetNodeForTick + * @param {Node} cb contentBox of the axis + * @protected + */ + offsetNodeForTick: function() + { + }, + + /** + * Assigns a height based on the size of the contents. + * + * @method setCalculatedSize + * @protected + */ + setCalculatedSize: function() + { + var host = this, + graphic = host.get("graphic"), + styles = host.get("styles"), + labelMargin = styles.label.margin, + totalLabelSize = labelMargin.bottom + host._maxLabelSize, + totalTitleSize = host._totalTitleSize, + topTickOffset = this.get("topTickOffset"), + ttl = Math.round(topTickOffset + totalLabelSize + totalTitleSize); + if(this._explicitHeight) + { + ttl = this._explicitHeight; + } + host.set("calculatedHeight", ttl); + graphic.set("y", ttl - topTickOffset); + } +}; +Y.TopAxisLayout = TopAxisLayout; + +/** + * An abstract class that provides the core functionality for draw a chart axis. Axis is used by the following classes: + * + * + * @class Axis + * @extends Widget + * @uses AxisBase + * @uses TopAxisLayout + * @uses RightAxisLayout + * @uses BottomAxisLayout + * @uses LeftAxisLayout + * @constructor + * @param {Object} config (optional) Configuration parameters. + * @submodule axis + */ +Y.Axis = Y.Base.create("axis", Y.Widget, [Y.AxisBase], { + /** + * Calculates and returns a value based on the number of labels and the index of + * the current label. + * + * @method getLabelByIndex + * @param {Number} i Index of the label. + * @param {Number} l Total number of labels. + * @return String + */ + getLabelByIndex: function(i, l) + { + var position = this.get("position"), + direction = position === "left" || position === "right" ? "vertical" : "horizontal"; + return this._getLabelByIndex(i, l, direction); + }, + + /** + * @method bindUI + * @private + */ + bindUI: function() + { + this.after("dataReady", Y.bind(this._dataChangeHandler, this)); + this.after("dataUpdate", Y.bind(this._dataChangeHandler, this)); + this.after("stylesChange", this._updateHandler); + this.after("overlapGraphChange", this._updateHandler); + this.after("positionChange", this._positionChangeHandler); + this.after("widthChange", this._handleSizeChange); + this.after("heightChange", this._handleSizeChange); + this.after("calculatedWidthChange", this._handleSizeChange); + this.after("calculatedHeightChange", this._handleSizeChange); + }, + /** + * Storage for calculatedWidth value. + * + * @property _calculatedWidth + * @type Number + * @private + */ + _calculatedWidth: 0, + + /** + * Storage for calculatedHeight value. + * + * @property _calculatedHeight + * @type Number + * @private + */ + _calculatedHeight: 0, + + /** + * Handles change to the dataProvider + * + * @method _dataChangeHandler + * @param {Object} e Event object + * @private + */ + _dataChangeHandler: function() + { + if(this.get("rendered")) + { + this._drawAxis(); + } + }, + + /** + * Handles change to the position attribute + * + * @method _positionChangeHandler + * @param {Object} e Event object + * @private + */ + _positionChangeHandler: function(e) + { + this._updateGraphic(e.newVal); + this._updateHandler(); + }, + + /** + * Updates the the Graphic instance + * + * @method _updateGraphic + * @param {String} position Position of axis + * @private + */ + _updateGraphic: function(position) + { + var graphic = this.get("graphic"); + if(position === "none") + { + if(graphic) + { + graphic.destroy(); + } + } + else + { + if(!graphic) + { + this._setCanvas(); + } + } + }, + + /** + * Handles changes to axis. + * + * @method _updateHandler + * @param {Object} e Event object + * @private + */ + _updateHandler: function() + { + if(this.get("rendered")) + { + this._drawAxis(); + } + }, + + /** + * @method renderUI + * @private + */ + renderUI: function() + { + this._updateGraphic(this.get("position")); + }, + + /** + * @method syncUI + * @private + */ + syncUI: function() + { + var layout = this._layout, + defaultMargins, + styles, + label, + title, + i; + if(layout) + { + defaultMargins = layout._getDefaultMargins(); + styles = this.get("styles"); + label = styles.label.margin; + title =styles.title.margin; + //need to defaultMargins method to the layout classes. + for(i in defaultMargins) + { + if(defaultMargins.hasOwnProperty(i)) + { + label[i] = label[i] === undefined ? defaultMargins[i] : label[i]; + title[i] = title[i] === undefined ? defaultMargins[i] : title[i]; + } + } + } + this._drawAxis(); + }, + + /** + * Creates a graphic instance to be used for the axis line and ticks. + * + * @method _setCanvas + * @private + */ + _setCanvas: function() + { + var cb = this.get("contentBox"), + bb = this.get("boundingBox"), + p = this.get("position"), + pn = this._parentNode, + w = this.get("width"), + h = this.get("height"); + bb.setStyle("position", "absolute"); + bb.setStyle("zIndex", 2); + w = w ? w + "px" : pn.getStyle("width"); + h = h ? h + "px" : pn.getStyle("height"); + if(p === "top" || p === "bottom") + { + cb.setStyle("width", w); + } + else + { + cb.setStyle("height", h); + } + cb.setStyle("position", "relative"); + cb.setStyle("left", "0px"); + cb.setStyle("top", "0px"); + this.set("graphic", new Y.Graphic()); + this.get("graphic").render(cb); + }, + + /** + * Gets the default value for the `styles` attribute. Overrides + * base implementation. + * + * @method _getDefaultStyles + * @return Object + * @protected + */ + _getDefaultStyles: function() + { + var axisstyles = { + majorTicks: { + display:"inside", + length:4, + color:"#dad8c9", + weight:1, + alpha:1 + }, + minorTicks: { + display:"none", + length:2, + color:"#dad8c9", + weight:1 + }, + line: { + weight:1, + color:"#dad8c9", + alpha:1 + }, + majorUnit: { + determinant:"count", + count:11, + distance:75 + }, + top: "0px", + left: "0px", + width: "100px", + height: "100px", + label: { + color:"#808080", + alpha: 1, + fontSize:"85%", + rotation: 0, + margin: { + top: undefined, + right: undefined, + bottom: undefined, + left: undefined + } + }, + title: { + color:"#808080", + alpha: 1, + fontSize:"85%", + rotation: undefined, + margin: { + top: undefined, + right: undefined, + bottom: undefined, + left: undefined + } + }, + hideOverlappingLabelTicks: false + }; + + return Y.merge(Y.Renderer.prototype._getDefaultStyles(), axisstyles); + }, + + /** + * Updates the axis when the size changes. + * + * @method _handleSizeChange + * @param {Object} e Event object. + * @private + */ + _handleSizeChange: function(e) + { + var attrName = e.attrName, + pos = this.get("position"), + vert = pos === "left" || pos === "right", + cb = this.get("contentBox"), + hor = pos === "bottom" || pos === "top"; + cb.setStyle("width", this.get("width")); + cb.setStyle("height", this.get("height")); + if((hor && attrName === "width") || (vert && attrName === "height")) + { + this._drawAxis(); + } + }, + + /** + * Maps key values to classes containing layout algorithms + * + * @property _layoutClasses + * @type Object + * @private + */ + _layoutClasses: + { + top : TopAxisLayout, + bottom: BottomAxisLayout, + left: LeftAxisLayout, + right : RightAxisLayout + }, + + /** + * Draws a line segment between 2 points + * + * @method drawLine + * @param {Object} startPoint x and y coordinates for the start point of the line segment + * @param {Object} endPoint x and y coordinates for the for the end point of the line segment + * @param {Object} line styles (weight, color and alpha to be applied to the line segment) + * @private + */ + drawLine: function(path, startPoint, endPoint) + { + path.moveTo(startPoint.x, startPoint.y); + path.lineTo(endPoint.x, endPoint.y); + }, + + /** + * Generates the properties necessary for rotating and positioning a text field. + * + * @method _getTextRotationProps + * @param {Object} styles properties for the text field + * @return Object + * @private + */ + _getTextRotationProps: function(styles) + { + if(styles.rotation === undefined) + { + switch(this.get("position")) + { + case "left" : + styles.rotation = -90; + break; + case "right" : + styles.rotation = 90; + break; + default : + styles.rotation = 0; + break; + } + } + var rot = Math.min(90, Math.max(-90, styles.rotation)), + absRot = Math.abs(rot), + radCon = Math.PI/180, + sinRadians = parseFloat(parseFloat(Math.sin(absRot * radCon)).toFixed(8)), + cosRadians = parseFloat(parseFloat(Math.cos(absRot * radCon)).toFixed(8)); + return { + rot: rot, + absRot: absRot, + radCon: radCon, + sinRadians: sinRadians, + cosRadians: cosRadians, + textAlpha: styles.alpha + }; + }, + + /** + * Draws an axis. + * + * @method _drawAxis + * @private + */ + _drawAxis: function () + { + if(this._drawing) + { + this._callLater = true; + return; + } + this._drawing = true; + this._callLater = false; + if(this._layout) + { + var styles = this.get("styles"), + line = styles.line, + labelStyles = styles.label, + majorTickStyles = styles.majorTicks, + drawTicks = majorTickStyles.display !== "none", + tickPoint, + majorUnit = styles.majorUnit, + len, + majorUnitDistance, + i = 0, + layout = this._layout, + layoutLength, + lineStart, + label, + labelWidth, + labelHeight, + labelFunction = this.get("labelFunction"), + labelFunctionScope = this.get("labelFunctionScope"), + labelFormat = this.get("labelFormat"), + graphic = this.get("graphic"), + path = this.get("path"), + tickPath, + explicitlySized, + position = this.get("position"), + labelData, + labelValues, + point, + points, + edgeOffset, + direction = (position === "left" || position === "right") ? "vertical" : "horizontal"; + this._labelWidths = []; + this._labelHeights = []; + graphic.set("autoDraw", false); + path.clear(); + path.set("stroke", { + weight: line.weight, + color: line.color, + opacity: line.alpha + }); + this._labelRotationProps = this._getTextRotationProps(labelStyles); + this._labelRotationProps.transformOrigin = layout._getTransformOrigin(this._labelRotationProps.rot); + layout.setTickOffsets.apply(this); + layoutLength = this.getLength(); + + len = this.getTotalMajorUnits(); + edgeOffset = this.getEdgeOffset(len, layoutLength); + this.set("edgeOffset", edgeOffset); + lineStart = layout.getLineStart.apply(this); + + //if labelValues are explicitly set, get the points based on the calculated positions of the labelValues + //if not, get the points based on the axis length, number of ticks and majorUnit values + if(this._labelValuesExplicitlySet) + { + labelData = this._getDataFromLabelValues(lineStart, this.get("labelValues"), edgeOffset, layoutLength, direction); + points = labelData.points; + labelValues = labelData.values; + len = points.length; + } + else + { + majorUnitDistance = this.getMajorUnitDistance(len, layoutLength, majorUnit); + points = this._getPoints(lineStart, len, edgeOffset, majorUnitDistance, direction); + labelValues = []; + for(i = 0; i < len; i = i + 1) + { + labelValues.push(this._getLabelByIndex(i, len, direction)); + } + + //Don't set labelValues fix for #2533172 is available + //this.set("labelValues", labelValues, {src: internal}); + } + + //Don't create the last label or tick. + if(this.get("hideFirstMajorUnit")) + { + points.shift(); + labelValues.shift(); + len = len - 1; + } + + //Don't create the last label or tick. + if(this.get("hideLastMajorUnit")) + { + points.pop(); + labelValues.pop(); + len = len - 1; + } + + if(len < 1) + { + this._clearLabelCache(); + } + else + { + tickPoint = this.getFirstPoint(lineStart); + this.drawLine(path, lineStart, this.getLineEnd(tickPoint)); + if(drawTicks) + { + tickPath = this.get("tickPath"); + tickPath.clear(); + tickPath.set("stroke", { + weight: majorTickStyles.weight, + color: majorTickStyles.color, + opacity: majorTickStyles.alpha + }); + for(i = 0; i < len; i = i + 1) + { + point = points[i]; + if(point) + { + layout.drawTick.apply(this, [tickPath, points[i], majorTickStyles]); + } + } + } + this._createLabelCache(); + this._tickPoints = points; + this._maxLabelSize = 0; + this._totalTitleSize = 0; + this._titleSize = 0; + this._setTitle(); + explicitlySized = layout.getExplicitlySized.apply(this, [styles]); + for(i = 0; i < len; i = i + 1) + { + point = points[i]; + if(point) + { + label = this.getLabel(point, labelStyles); + this._labels.push(label); + this.get("appendLabelFunction")(label, labelFunction.apply(labelFunctionScope, [labelValues[i], labelFormat])); + labelWidth = Math.round(label.offsetWidth); + labelHeight = Math.round(label.offsetHeight); + if(!explicitlySized) + { + this._layout.updateMaxLabelSize.apply(this, [labelWidth, labelHeight]); + } + this._labelWidths.push(labelWidth); + this._labelHeights.push(labelHeight); + } + } + this._clearLabelCache(); + if(this.get("overlapGraph")) + { + layout.offsetNodeForTick.apply(this, [this.get("contentBox")]); + } + layout.setCalculatedSize.apply(this); + if(this._titleTextField) + { + this._layout.positionTitle.apply(this, [this._titleTextField]); + } + len = this._labels.length; + for(i = 0; i < len; ++i) + { + layout.positionLabel.apply(this, [this.get("labels")[i], this._tickPoints[i], styles, i]); + } + } + } + this._drawing = false; + if(this._callLater) + { + this._drawAxis(); + } + else + { + this._updatePathElement(); + this.fire("axisRendered"); + } + }, + + /** + * Calculates and sets the total size of a title. + * + * @method _setTotalTitleSize + * @param {Object} styles Properties for the title field. + * @private + */ + _setTotalTitleSize: function(styles) + { + var title = this._titleTextField, + w = title.offsetWidth, + h = title.offsetHeight, + rot = this._titleRotationProps.rot, + bounds, + size, + margin = styles.margin, + position = this.get("position"), + matrix = new Y.Matrix(); + matrix.rotate(rot); + bounds = matrix.getContentRect(w, h); + if(position === "left" || position === "right") + { + size = bounds.right - bounds.left; + if(margin) + { + size += margin.left + margin.right; + } + } + else + { + size = bounds.bottom - bounds.top; + if(margin) + { + size += margin.top + margin.bottom; + } + } + this._titleBounds = bounds; + this._totalTitleSize = size; + }, + + /** + * Updates path. + * + * @method _updatePathElement + * @private + */ + _updatePathElement: function() + { + var path = this._path, + tickPath = this._tickPath, + redrawGraphic = false, + graphic = this.get("graphic"); + if(path) + { + redrawGraphic = true; + path.end(); + } + if(tickPath) + { + redrawGraphic = true; + tickPath.end(); + } + if(redrawGraphic) + { + graphic._redraw(); + } + }, + + /** + * Updates the content and style properties for a title field. + * + * @method _updateTitle + * @private + */ + _setTitle: function() + { + var i, + styles, + customStyles, + title = this.get("title"), + titleTextField = this._titleTextField, + parentNode; + if(title !== null && title !== undefined) + { + customStyles = { + rotation: "rotation", + margin: "margin", + alpha: "alpha" + }; + styles = this.get("styles").title; + if(!titleTextField) + { + titleTextField = DOCUMENT.createElement('span'); + titleTextField.style.display = "block"; + titleTextField.style.whiteSpace = "nowrap"; + titleTextField.setAttribute("class", "axisTitle"); + this.get("contentBox").append(titleTextField); + } + else if(!DOCUMENT.createElementNS) + { + if(titleTextField.style.filter) + { + titleTextField.style.filter = null; + } + } + titleTextField.style.position = "absolute"; + for(i in styles) + { + if(styles.hasOwnProperty(i) && !customStyles.hasOwnProperty(i)) + { + titleTextField.style[i] = styles[i]; + } + } + this.get("appendTitleFunction")(titleTextField, title); + this._titleTextField = titleTextField; + this._titleRotationProps = this._getTextRotationProps(styles); + this._setTotalTitleSize(styles); + } + else if(titleTextField) + { + parentNode = titleTextField.parentNode; + if(parentNode) + { + parentNode.removeChild(titleTextField); + } + this._titleTextField = null; + this._totalTitleSize = 0; + } + }, + + /** + * Creates or updates an axis label. + * + * @method getLabel + * @param {Object} pt x and y coordinates for the label + * @param {Object} styles styles applied to label + * @return HTMLElement + * @private + */ + getLabel: function(pt, styles) + { + var i, + label, + labelCache = this._labelCache, + customStyles = { + rotation: "rotation", + margin: "margin", + alpha: "alpha" + }; + if(labelCache && labelCache.length > 0) + { + label = labelCache.shift(); + } + else + { + label = DOCUMENT.createElement("span"); + label.className = Y.Lang.trim([label.className, "axisLabel"].join(' ')); + this.get("contentBox").append(label); + } + if(!DOCUMENT.createElementNS) + { + if(label.style.filter) + { + label.style.filter = null; + } + } + label.style.display = "block"; + label.style.whiteSpace = "nowrap"; + label.style.position = "absolute"; + for(i in styles) + { + if(styles.hasOwnProperty(i) && !customStyles.hasOwnProperty(i)) + { + label.style[i] = styles[i]; + } + } + return label; + }, + + /** + * Creates a cache of labels that can be re-used when the axis redraws. + * + * @method _createLabelCache + * @private + */ + _createLabelCache: function() + { + if(this._labels) + { + while(this._labels.length > 0) + { + this._labelCache.push(this._labels.shift()); + } + } + else + { + this._clearLabelCache(); + } + this._labels = []; + }, + + /** + * Removes axis labels from the dom and clears the label cache. + * + * @method _clearLabelCache + * @private + */ + _clearLabelCache: function() + { + if(this._labelCache) + { + var len = this._labelCache.length, + i = 0, + label; + for(; i < len; ++i) + { + label = this._labelCache[i]; + this._removeChildren(label); + Y.Event.purgeElement(label, true); + label.parentNode.removeChild(label); + } + } + this._labelCache = []; + }, + + /** + * Gets the end point of an axis. + * + * @method getLineEnd + * @return Object + * @private + */ + getLineEnd: function(pt) + { + var w = this.get("width"), + h = this.get("height"), + pos = this.get("position"); + if(pos === "top" || pos === "bottom") + { + return {x:w, y:pt.y}; + } + else + { + return {x:pt.x, y:h}; + } + }, + + /** + * Calcuates the width or height of an axis depending on its direction. + * + * @method getLength + * @return Number + * @private + */ + getLength: function() + { + var l, + style = this.get("styles"), + padding = style.padding, + w = this.get("width"), + h = this.get("height"), + pos = this.get("position"); + if(pos === "top" || pos === "bottom") + { + l = w - (padding.left + padding.right); + } + else + { + l = h - (padding.top + padding.bottom); + } + return l; + }, + + /** + * Gets the position of the first point on an axis. + * + * @method getFirstPoint + * @param {Object} pt Object containing x and y coordinates. + * @return Object + * @private + */ + getFirstPoint:function(pt) + { + var style = this.get("styles"), + pos = this.get("position"), + padding = style.padding, + np = {x:pt.x, y:pt.y}; + if(pos === "top" || pos === "bottom") + { + np.x += padding.left + this.get("edgeOffset"); + } + else + { + np.y += this.get("height") - (padding.top + this.get("edgeOffset")); + } + return np; + }, + + /** + * Calculates points based off the majorUnit count or distance of the Axis. + * + * @method _getPoints + * @param {Object} startPoint An object literal containing the x and y coordinates of the first + * point on the axis. + * @param {Number} len The number of points on an axis. + * @param {Number} edgeOffset The distance from the start of the axis and the point. + * @param {Number} majorUnitDistance The distance between points on an axis. + * @param {String} direction Indicates whether the axis is horizontal or vertical. + * @return Array + * @private + */ + _getPoints: function(startPoint, len, edgeOffset, majorUnitDistance, direction) + { + var points = [], + i, + style = this.get("styles"), + staticCoord, + dynamicCoord, + constantVal, + newPoint, + padding, + coord; + if(direction === "vertical") + { + staticCoord = "x"; + dynamicCoord = "y"; + padding = style.padding.top; + } + else + { + staticCoord = "y"; + dynamicCoord = "x"; + padding = style.padding.left; + } + constantVal = startPoint[staticCoord]; + coord = edgeOffset + padding; + for(i = 0; i < len; i = i + 1) + { + newPoint = {}; + newPoint[staticCoord] = constantVal; + newPoint[dynamicCoord] = coord; + points.push(newPoint); + coord = coord + majorUnitDistance; + } + return points; + }, + + /** + * Rotates and positions a text field. + * + * @method _rotate + * @param {HTMLElement} label text field to rotate and position + * @param {Object} props properties to be applied to the text field. + * @private + */ + _rotate: function(label, props) + { + var rot = props.rot, + x = props.x, + y = props.y, + filterString, + textAlpha, + matrix = new Y.Matrix(), + transformOrigin = props.transformOrigin || [0, 0], + offsetRect; + if(DOCUMENT.createElementNS) + { + matrix.translate(x, y); + matrix.rotate(rot); + Y_DOM.setStyle(label, "transformOrigin", (transformOrigin[0] * 100) + "% " + (transformOrigin[1] * 100) + "%"); + Y_DOM.setStyle(label, "transform", matrix.toCSSText()); + } + else + { + textAlpha = props.textAlpha; + if(Y_Lang.isNumber(textAlpha) && textAlpha < 1 && textAlpha > -1 && !isNaN(textAlpha)) + { + filterString = "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + Math.round(textAlpha * 100) + ")"; + } + if(rot !== 0) + { + //ms filters kind of, sort of uses a transformOrigin of 0, 0. + //we'll translate the difference to create a true 0, 0 origin. + matrix.rotate(rot); + offsetRect = matrix.getContentRect(props.labelWidth, props.labelHeight); + matrix.init(); + matrix.translate(offsetRect.left, offsetRect.top); + matrix.translate(x, y); + this._simulateRotateWithTransformOrigin(matrix, rot, transformOrigin, props.labelWidth, props.labelHeight); + if(filterString) + { + filterString += " "; + } + else + { + filterString = ""; + } + filterString += matrix.toFilterText(); + label.style.left = matrix.dx + "px"; + label.style.top = matrix.dy + "px"; + } + else + { + label.style.left = x + "px"; + label.style.top = y + "px"; + } + if(filterString) + { + label.style.filter = filterString; + } + } + }, + + /** + * Simulates a rotation with a specified transformOrigin. + * + * @method _simulateTransformOrigin + * @param {Matrix} matrix Reference to a `Matrix` instance. + * @param {Number} rot The rotation (in degrees) that will be performed on a matrix. + * @param {Array} transformOrigin An array represeniting the origin in which to perform the transform. The first + * index represents the x origin and the second index represents the y origin. + * @param {Number} w The width of the object that will be transformed. + * @param {Number} h The height of the object that will be transformed. + * @private + */ + _simulateRotateWithTransformOrigin: function(matrix, rot, transformOrigin, w, h) + { + var transformX = transformOrigin[0] * w, + transformY = transformOrigin[1] * h; + transformX = !isNaN(transformX) ? transformX : 0; + transformY = !isNaN(transformY) ? transformY : 0; + matrix.translate(transformX, transformY); + matrix.rotate(rot); + matrix.translate(-transformX, -transformY); + }, + + /** + * Returns the coordinates (top, right, bottom, left) for the bounding box of the last label. + * + * @method getMaxLabelBounds + * @return Object + */ + getMaxLabelBounds: function() + { + return this._getLabelBounds(this.getMaximumValue()); + }, + + /** + * Returns the coordinates (top, right, bottom, left) for the bounding box of the first label. + * + * @method getMinLabelBounds + * @return Object + */ + getMinLabelBounds: function() + { + return this._getLabelBounds(this.getMinimumValue()); + }, + + /** + * Returns the coordinates (top, right, bottom, left) for the bounding box of a label. + * + * @method _getLabelBounds + * @param {String} Value of the label + * @return Object + * @private + */ + _getLabelBounds: function(val) + { + var layout = this._layout, + labelStyles = this.get("styles").label, + matrix = new Y.Matrix(), + label, + props = this._getTextRotationProps(labelStyles); + props.transformOrigin = layout._getTransformOrigin(props.rot); + label = this.getLabel({x: 0, y: 0}, labelStyles); + this.get("appendLabelFunction")(label, this.get("labelFunction").apply(this, [val, this.get("labelFormat")])); + props.labelWidth = label.offsetWidth; + props.labelHeight = label.offsetHeight; + this._removeChildren(label); + Y.Event.purgeElement(label, true); + label.parentNode.removeChild(label); + props.x = 0; + props.y = 0; + layout._setRotationCoords(props); + matrix.translate(props.x, props.y); + this._simulateRotateWithTransformOrigin(matrix, props.rot, props.transformOrigin, props.labelWidth, props.labelHeight); + return matrix.getContentRect(props.labelWidth, props.labelHeight); + }, + + /** + * Removes all DOM elements from an HTML element. Used to clear out labels during detruction + * phase. + * + * @method _removeChildren + * @private + */ + _removeChildren: function(node) + { + if(node.hasChildNodes()) + { + var child; + while(node.firstChild) + { + child = node.firstChild; + this._removeChildren(child); + node.removeChild(child); + } + } + }, + + /** + * Destructor implementation Axis class. Removes all labels and the Graphic instance from the widget. + * + * @method destructor + * @protected + */ + destructor: function() + { + var cb = this.get("contentBox").getDOMNode(), + labels = this.get("labels"), + graphic = this.get("graphic"), + label, + len = labels ? labels.length : 0; + if(len > 0) + { + while(labels.length > 0) + { + label = labels.shift(); + this._removeChildren(label); + cb.removeChild(label); + label = null; + } + } + if(graphic) + { + graphic.destroy(); + } + }, + + /** + * Length in pixels of largest text bounding box. Used to calculate the height of the axis. + * + * @property maxLabelSize + * @type Number + * @protected + */ + _maxLabelSize: 0, + + /** + * Updates the content of text field. This method writes a value into a text field using + * `appendChild`. If the value is a `String`, it is converted to a `TextNode` first. + * + * @method _setText + * @param label {HTMLElement} label to be updated + * @param val {String} value with which to update the label + * @private + */ + _setText: function(textField, val) + { + textField.innerHTML = ""; + if(Y_Lang.isNumber(val)) + { + val = val + ""; + } + else if(!val) + { + val = ""; + } + if(IS_STRING(val)) + { + val = DOCUMENT.createTextNode(val); + } + textField.appendChild(val); + }, + + /** + * Returns the total number of majorUnits that will appear on an axis. + * + * @method getTotalMajorUnits + * @return Number + */ + getTotalMajorUnits: function() + { + var units, + majorUnit = this.get("styles").majorUnit, + len = this.getLength(); + if(majorUnit.determinant === "count") + { + units = majorUnit.count; + } + else if(majorUnit.determinant === "distance") + { + units = (len/majorUnit.distance) + 1; + } + return units; + }, + + /** + * Returns the distance between major units on an axis. + * + * @method getMajorUnitDistance + * @param {Number} len Number of ticks + * @param {Number} uiLen Size of the axis. + * @param {Object} majorUnit Hash of properties used to determine the majorUnit + * @return Number + */ + getMajorUnitDistance: function(len, uiLen, majorUnit) + { + var dist; + if(majorUnit.determinant === "count") + { + if(!this.get("calculateEdgeOffset")) + { + len = len - 1; + } + dist = uiLen/len; + } + else if(majorUnit.determinant === "distance") + { + dist = majorUnit.distance; + } + return dist; + }, + + /** + * Checks to see if data extends beyond the range of the axis. If so, + * that data will need to be hidden. This method is internal, temporary and subject + * to removal in the future. + * + * @method _hasDataOverflow + * @protected + * @return Boolean + */ + _hasDataOverflow: function() + { + if(this.get("setMin") || this.get("setMax")) + { + return true; + } + return false; + }, + + /** + * Returns a string corresponding to the first label on an + * axis. + * + * @method getMinimumValue + * @return String + */ + getMinimumValue: function() + { + return this.get("minimum"); + }, + + /** + * Returns a string corresponding to the last label on an + * axis. + * + * @method getMaximumValue + * @return String + */ + getMaximumValue: function() + { + return this.get("maximum"); + } +}, { + ATTRS: + { + /** + * When set, defines the width of a vertical axis instance. By default, vertical axes automatically size based + * on their contents. When the width attribute is set, the axis will not calculate its width. When the width + * attribute is explicitly set, axis labels will postion themselves off of the the inner edge of the axis and the + * title, if present, will position itself off of the outer edge. If a specified width is less than the sum of + * the axis' contents, excess content will overflow. + * + * @attribute width + * @type Number + */ + width: { + lazyAdd: false, + + getter: function() + { + if(this._explicitWidth) + { + return this._explicitWidth; + } + return this._calculatedWidth; + }, + + setter: function(val) + { + this._explicitWidth = val; + return val; + } + }, + + /** + * When set, defines the height of a horizontal axis instance. By default, horizontal axes automatically size based + * on their contents. When the height attribute is set, the axis will not calculate its height. When the height + * attribute is explicitly set, axis labels will postion themselves off of the the inner edge of the axis and the + * title, if present, will position itself off of the outer edge. If a specified height is less than the sum of + * the axis' contents, excess content will overflow. + * + * @attribute height + * @type Number + */ + height: { + lazyAdd: false, + + getter: function() + { + if(this._explicitHeight) + { + return this._explicitHeight; + } + return this._calculatedHeight; + }, + + setter: function(val) + { + this._explicitHeight = val; + return val; + } + }, + + /** + * Calculated value of an axis' width. By default, the value is used internally for vertical axes. If the `width` + * attribute is explicitly set, this value will be ignored. + * + * @attribute calculatedWidth + * @type Number + * @private + */ + calculatedWidth: { + getter: function() + { + return this._calculatedWidth; + }, + + setter: function(val) + { + this._calculatedWidth = val; + return val; + } + }, + + /** + * Calculated value of an axis' height. By default, the value is used internally for horizontal axes. If the `height` + * attribute is explicitly set, this value will be ignored. + * + * @attribute calculatedHeight + * @type Number + * @private + */ + calculatedHeight: { + getter: function() + { + return this._calculatedHeight; + }, + + setter: function(val) + { + this._calculatedHeight = val; + return val; + } + }, + + /** + * Difference between the first/last tick and edge of axis. + * + * @attribute edgeOffset + * @type Number + * @protected + */ + edgeOffset: + { + value: 0 + }, + + /** + * The graphic in which the axis line and ticks will be rendered. + * + * @attribute graphic + * @type Graphic + */ + graphic: {}, + + /** + * @attribute path + * @type Shape + * @readOnly + * @private + */ + path: { + readOnly: true, + + getter: function() + { + if(!this._path) + { + var graphic = this.get("graphic"); + if(graphic) + { + this._path = graphic.addShape({type:"path"}); + } + } + return this._path; + } + }, + + /** + * @attribute tickPath + * @type Shape + * @readOnly + * @private + */ + tickPath: { + readOnly: true, + + getter: function() + { + if(!this._tickPath) + { + var graphic = this.get("graphic"); + if(graphic) + { + this._tickPath = graphic.addShape({type:"path"}); + } + } + return this._tickPath; + } + }, + + /** + * Contains the contents of the axis. + * + * @attribute node + * @type HTMLElement + */ + node: {}, + + /** + * Direction of the axis. + * + * @attribute position + * @type String + */ + position: { + lazyAdd: false, + + setter: function(val) + { + var LayoutClass = this._layoutClasses[val]; + if(val && val !== "none") + { + this._layout = new LayoutClass(); + } + return val; + } + }, + + /** + * Distance determined by the tick styles used to calculate the distance between the axis + * line in relation to the top of the axis. + * + * @attribute topTickOffset + * @type Number + */ + topTickOffset: { + value: 0 + }, + + /** + * Distance determined by the tick styles used to calculate the distance between the axis + * line in relation to the bottom of the axis. + * + * @attribute bottomTickOffset + * @type Number + */ + bottomTickOffset: { + value: 0 + }, + + /** + * Distance determined by the tick styles used to calculate the distance between the axis + * line in relation to the left of the axis. + * + * @attribute leftTickOffset + * @type Number + */ + leftTickOffset: { + value: 0 + }, + + /** + * Distance determined by the tick styles used to calculate the distance between the axis + * line in relation to the right side of the axis. + * + * @attribute rightTickOffset + * @type Number + */ + rightTickOffset: { + value: 0 + }, + + /** + * Collection of labels used to render the axis. + * + * @attribute labels + * @type Array + */ + labels: { + readOnly: true, + getter: function() + { + return this._labels; + } + }, + + /** + * Collection of points used for placement of labels and ticks along the axis. + * + * @attribute tickPoints + * @type Array + */ + tickPoints: { + readOnly: true, + + getter: function() + { + if(this.get("position") === "none") + { + return this.get("styles").majorUnit.count; + } + return this._tickPoints; + } + }, + + /** + * Indicates whether the axis overlaps the graph. If an axis is the inner most axis on a given + * position and the tick position is inside or cross, the axis will need to overlap the graph. + * + * @attribute overlapGraph + * @type Boolean + */ + overlapGraph: { + value:true, + + validator: function(val) + { + return Y_Lang.isBoolean(val); + } + }, + + /** + * Length in pixels of largest text bounding box. Used to calculate the height of the axis. + * + * @attribute maxLabelSize + * @type Number + * @protected + */ + maxLabelSize: { + getter: function() + { + return this._maxLabelSize; + }, + + setter: function(val) + { + this._maxLabelSize = val; + return val; + } + }, + + /** + * Title for the axis. When specified, the title will display. The position of the title is determined by the axis position. + *
+ *
top
Appears above the axis and it labels. The default rotation is 0.
+ *
right
Appears to the right of the axis and its labels. The default rotation is 90.
+ *
bottom
Appears below the axis and its labels. The default rotation is 0.
+ *
left
Appears to the left of the axis and its labels. The default rotation is -90.
+ *
+ * + * @attribute title + * @type String + */ + title: { + value: null + }, + + /** + * Function used to append an axis value to an axis label. This function has the following signature: + *
+ *
textField
The axis label to be appended. (`HTMLElement`)
+ *
val
The value to attach to the text field. This method will accept an `HTMLELement` + * or a `String`. This method does not use (`HTMLElement` | `String`)
+ *
+ * The default method appends a value to the `HTMLElement` using the `appendChild` method. If the given + * value is a `String`, the method will convert the the value to a `textNode` before appending to the + * `HTMLElement`. This method will not convert an `HTMLString` to an `HTMLElement`. + * + * @attribute appendLabelFunction + * @type Function + */ + appendLabelFunction: { + valueFn: function() + { + return this._setText; + } + }, + + /** + * Function used to append a title value to the title object. This function has the following signature: + *
+ *
textField
The title text field to be appended. (`HTMLElement`)
+ *
val
The value to attach to the text field. This method will accept an `HTMLELement` + * or a `String`. This method does not use (`HTMLElement` | `String`)
+ *
+ * The default method appends a value to the `HTMLElement` using the `appendChild` method. If the given + * value is a `String`, the method will convert the the value to a `textNode` before appending to the + * `HTMLElement` element. This method will not convert an `HTMLString` to an `HTMLElement`. + * + * @attribute appendTitleFunction + * @type Function + */ + appendTitleFunction: { + valueFn: function() + { + return this._setText; + } + }, + + /** + * An array containing the unformatted values of the axis labels. By default, TimeAxis, NumericAxis and + * StackedAxis labelValues are determined by the majorUnit style. By default, CategoryAxis labels are + * determined by the values of the dataProvider. + *

When the labelValues attribute is explicitly set, the labelValues are dictated by the set value and + * the position of ticks and labels are determined by where those values would fall on the axis.

+ * + * @attribute labelValues + * @type Array + */ + labelValues: { + lazyAdd: false, + + setter: function(val) + { + var opts = arguments[2]; + if(!val || (opts && opts.src && opts.src === "internal")) + { + this._labelValuesExplicitlySet = false; + } + else + { + this._labelValuesExplicitlySet = true; + } + return val; + } + }, + + /** + * Suppresses the creation of the the first visible label and tick. + * + * @attribute hideFirstMajorUnit + * @type Boolean + */ + hideFirstMajorUnit: { + value: false + }, + + /** + * Suppresses the creation of the the last visible label and tick. + * + * @attribute hideLastMajorUnit + * @type Boolean + */ + hideLastMajorUnit: { + value: false + } + + /** + * Style properties used for drawing an axis. This attribute is inherited from `Renderer`. Below are the default values: + *
+ *
majorTicks
Properties used for drawing ticks. + *
+ *
display
Position of the tick. Possible values are `inside`, `outside`, `cross` and `none`. + * The default value is `inside`.
+ *
length
The length (in pixels) of the tick. The default value is 4.
+ *
color
The color of the tick. The default value is `#dad8c9`
+ *
weight
Number indicating the width of the tick. The default value is 1.
+ *
alpha
Number from 0 to 1 indicating the opacity of the tick. The default value is 1.
+ *
+ *
+ *
line
Properties used for drawing the axis line. + *
+ *
weight
Number indicating the width of the axis line. The default value is 1.
+ *
color
The color of the axis line. The default value is `#dad8c9`.
+ *
alpha
Number from 0 to 1 indicating the opacity of the tick. The default value is 1.
+ *
+ *
+ *
majorUnit
Properties used to calculate the `majorUnit` for the axis. + *
+ *
determinant
The algorithm used for calculating distance between ticks. The possible options are + * `count` and `distance`. If the `determinant` is `count`, the axis ticks will spaced so that a specified number + * of ticks appear on the axis. If the `determinant` is `distance`, the axis ticks will spaced out according to + * the specified distance. The default value is `count`.
+ *
count
Number of ticks to appear on the axis when the `determinant` is `count`. The default value is 11.
+ *
distance
The distance (in pixels) between ticks when the `determinant` is `distance`. The default + * value is 75.
+ *
+ *
+ *
label
Properties and styles applied to the axis labels. + *
+ *
color
The color of the labels. The default value is `#808080`.
+ *
alpha
Number between 0 and 1 indicating the opacity of the labels. The default value is 1.
+ *
fontSize
The font-size of the labels. The default value is 85%
+ *
rotation
The rotation, in degrees (between -90 and 90) of the labels. The default value is 0.
+ *
margin
The distance between the label and the axis/tick. Depending on the position of the `Axis`, + * only one of the properties used. + *
+ *
top
Pixel value used for an axis with a `position` of `bottom`. The default value is 4.
+ *
right
Pixel value used for an axis with a `position` of `left`. The default value is 4.
+ *
bottom
Pixel value used for an axis with a `position` of `top`. The default value is 4.
+ *
left
Pixel value used for an axis with a `position` of `right`. The default value is 4.
+ *
+ *
+ *
+ *
+ *
+ * + * @attribute styles + * @type Object + */ + } +}); +Y.AxisType = Y.Base.create("baseAxis", Y.Axis, [], {}); + + +}, '3.10.3', {"requires": ["dom", "widget", "widget-position", "widget-stack", "graphics", "axis-base"]});