/** * An abstract class that provides the core functionality for draw a chart axis. Axis is used by the following classes: * <ul> * <li>{{#crossLink "CategoryAxis"}}{{/crossLink}}</li> * <li>{{#crossLink "NumericAxis"}}{{/crossLink}}</li> * <li>{{#crossLink "StackedAxis"}}{{/crossLink}}</li> * <li>{{#crossLink "TimeAxis"}}{{/crossLink}}</li> * </ul> * * @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. * <dl> * <dt>top</dt><dd>Appears above the axis and it labels. The default rotation is 0.</dd> * <dt>right</dt><dd>Appears to the right of the axis and its labels. The default rotation is 90.</dd> * <dt>bottom</dt><dd>Appears below the axis and its labels. The default rotation is 0.</dd> * <dt>left</dt><dd>Appears to the left of the axis and its labels. The default rotation is -90.</dd> * </dl> * * @attribute title * @type String */ title: { value: null }, /** * Function used to append an axis value to an axis label. This function has the following signature: * <dl> * <dt>textField</dt><dd>The axis label to be appended. (`HTMLElement`)</dd> * <dt>val</dt><dd>The value to attach to the text field. This method will accept an `HTMLELement` * or a `String`. This method does not use (`HTMLElement` | `String`)</dd> * </dl> * 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: * <dl> * <dt>textField</dt><dd>The title text field to be appended. (`HTMLElement`)</dd> * <dt>val</dt><dd>The value to attach to the text field. This method will accept an `HTMLELement` * or a `String`. This method does not use (`HTMLElement` | `String`)</dd> * </dl> * 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. * <p>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. </p> * * @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: * <dl> * <dt>majorTicks</dt><dd>Properties used for drawing ticks. * <dl> * <dt>display</dt><dd>Position of the tick. Possible values are `inside`, `outside`, `cross` and `none`. * The default value is `inside`.</dd> * <dt>length</dt><dd>The length (in pixels) of the tick. The default value is 4.</dd> * <dt>color</dt><dd>The color of the tick. The default value is `#dad8c9`</dd> * <dt>weight</dt><dd>Number indicating the width of the tick. The default value is 1.</dd> * <dt>alpha</dt><dd>Number from 0 to 1 indicating the opacity of the tick. The default value is 1.</dd> * </dl> * </dd> * <dt>line</dt><dd>Properties used for drawing the axis line. * <dl> * <dt>weight</dt><dd>Number indicating the width of the axis line. The default value is 1.</dd> * <dt>color</dt><dd>The color of the axis line. The default value is `#dad8c9`.</dd> * <dt>alpha</dt><dd>Number from 0 to 1 indicating the opacity of the tick. The default value is 1.</dd> * </dl> * </dd> * <dt>majorUnit</dt><dd>Properties used to calculate the `majorUnit` for the axis. * <dl> * <dt>determinant</dt><dd>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`.</dd> * <dt>count</dt><dd>Number of ticks to appear on the axis when the `determinant` is `count`. The default value is 11.</dd> * <dt>distance</dt><dd>The distance (in pixels) between ticks when the `determinant` is `distance`. The default * value is 75.</dd> * </dl> * </dd> * <dt>label</dt><dd>Properties and styles applied to the axis labels. * <dl> * <dt>color</dt><dd>The color of the labels. The default value is `#808080`.</dd> * <dt>alpha</dt><dd>Number between 0 and 1 indicating the opacity of the labels. The default value is 1.</dd> * <dt>fontSize</dt><dd>The font-size of the labels. The default value is 85%</dd> * <dt>rotation</dt><dd>The rotation, in degrees (between -90 and 90) of the labels. The default value is 0.</dd> * <dt>offset</td><dd>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.</dd> * <dt>margin</dt><dd>The distance between the label and the axis/tick. Depending on the position of the `Axis`, * only one of the properties used. * <dl> * <dt>top</dt><dd>Pixel value used for an axis with a `position` of `bottom`. The default value is 4.</dd> * <dt>right</dt><dd>Pixel value used for an axis with a `position` of `left`. The default value is 4.</dd> * <dt>bottom</dt><dd>Pixel value used for an axis with a `position` of `top`. The default value is 4.</dd> * <dt>left</dt><dd>Pixel value used for an axis with a `position` of `right`. The default value is 4.</dd> * </dl> * </dd> * </dl> * </dd> * </dl> * * @attribute styles * @type Object */ } });