/**
* 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
*/
}
});