/* YUI 3.17.2 (build 9c3c78e) Copyright 2014 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, offset = parseFloat(styles.label.offset), 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 * offset; } else if(rot === 90) { leftOffset -= labelWidth * 0.5; topOffset = topOffset + labelWidth/2 - (labelWidth * offset); } else if(rot === -90) { leftOffset -= labelWidth * 0.5; topOffset = topOffset - labelHeight + labelWidth/2 - (labelWidth * offset); } else { leftOffset -= labelWidth + (labelHeight * absRot/360); topOffset -= labelHeight * offset; } 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, offset = parseFloat(styles.label.offset), 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 * offset; } else if(rot === 90) { leftOffset -= labelWidth * 0.5; topOffset = topOffset - labelHeight + labelWidth/2 - (labelWidth * offset); } else if(rot === -90) { topOffset = topOffset + labelWidth/2 - (labelWidth * offset); leftOffset -= labelWidth * 0.5; } else { topOffset -= labelHeight * offset; 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, offset = parseFloat(styles.label.offset), 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 === 90) { topOffset -= labelHeight/2 * rot/90; leftOffset = leftOffset + labelHeight/2 - (labelHeight * offset); } else if(rot === -90) { topOffset -= labelHeight/2 * absRot/90; leftOffset = leftOffset - labelWidth + labelHeight/2 - (labelHeight * offset); } else if(rot > 0) { leftOffset = leftOffset + labelHeight/2 - (labelHeight * offset); topOffset -= labelHeight/2 * rot/90; } else if(rot < 0) { leftOffset = leftOffset - labelWidth + labelHeight/2 - (labelHeight * offset); topOffset -= labelHeight/2 * absRot/90; } else { leftOffset -= labelWidth * offset; } 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, offset = parseFloat(styles.label.offset), 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 * offset; topOffset -= labelHeight; } else { if(rot === 90) { leftOffset = leftOffset - labelWidth + labelHeight/2 - (labelHeight * offset); topOffset -= (labelHeight * 0.5); } else if (rot === -90) { leftOffset = leftOffset + labelHeight/2 - (labelHeight * offset); topOffset -= (labelHeight * 0.5); } else if(rot > 0) { leftOffset = leftOffset - labelWidth + labelHeight/2 - (labelHeight * offset); topOffset -= labelHeight - (labelHeight * rot/180); } else { leftOffset = leftOffset + labelHeight/2 - (labelHeight * offset); 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, offset: 0.5, 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", len, 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, firstPoint, lastPoint, firstLabel, lastLabel, staticCoord, dynamicCoord, edgeOffset, explicitLabels = this._labelValuesExplicitlySet ? this.get("labelValues") : null, 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(direction === "vertical") { staticCoord = "x"; dynamicCoord = "y"; } else { staticCoord = "y"; dynamicCoord = "x"; } labelData = this._getLabelData( lineStart[staticCoord], staticCoord, dynamicCoord, this.get("minimum"), this.get("maximum"), edgeOffset, layoutLength - edgeOffset - edgeOffset, len, explicitLabels ); points = labelData.points; labelValues = labelData.values; len = points.length; if(!this._labelValuesExplicitlySet) { this.set("labelValues", labelValues, {src: "internal"}); } //Don't create the last label or tick. if(this.get("hideFirstMajorUnit")) { firstPoint = points.shift(); firstLabel = labelValues.shift(); len = len - 1; } //Don't create the last label or tick. if(this.get("hideLastMajorUnit")) { lastPoint = points.pop(); lastLabel = labelValues.pop(); len = len - 1; } if(len < 1) { this._clearLabelCache(); } else { this.drawLine(path, lineStart, this.getLineEnd(lineStart)); 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._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(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], points[i], styles, i]); } if(firstPoint) { points.unshift(firstPoint); } if(lastPoint) { points.push(lastPoint); } if(firstLabel) { labelValues.unshift(firstLabel); } if(lastLabel) { labelValues.push(lastLabel); } this._tickPoints = points; } } 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} styles styles applied to label * @return HTMLElement * @private */ getLabel: function(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; }, /** * 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(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; if(majorUnit.determinant === "count") { units = majorUnit.count; } else if(majorUnit.determinant === "distance") { len = this.getLength(); 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.
*
offset
A number between 0 and 1 indicating the relationship of the label to a tick. For a horizontal axis * label, a value of 0 will position the label's left side even to the the tick. A position of 1 would position the * right side of the label with the tick. A position of 0.5 would center the label horizontally with the tick. For a * vertical axis, a value of 0 would position the top of the label with the tick, a value of 1 would position the bottom * of the label with the tick and a value 0 would center the label vertically with the tick. The default value is 0.5.
*
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.17.2', {"requires": ["dom", "widget", "widget-position", "widget-stack", "graphics", "axis-base"]});