Axis.js revision 87a49173dbc22a145cb87e605cca83fd42524377
/**
* The Axis class. Generates axes for a chart.
*
* @class Axis
* @extends Renderer
* @constructor
*/
Y.Axis = Y.Base.create("axis", Y.Widget, [Y.Renderer], {
/**
* @private
*/
_dataChangeHandler: function(e)
{
if(this.get("rendered"))
{
this._drawAxis();
}
},
/**
* @private
*/
_updateHandler: function(e)
{
if(this.get("rendered"))
{
this._drawAxis();
}
},
/**
* @private
*/
_positionChangeHandler: function(e)
{
var position = this.get("position");
if(position == "none")
{
return;
}
this._layout =this.getLayout(this.get("position"));
if(this.get("rendered"))
{
this._drawAxis();
}
},
/**
* @private
*/
renderUI: function()
{
var pos = this.get("position");
if(pos && pos != "none")
{
this._layout =this.getLayout(pos);
this._setCanvas();
}
},
/**
* @private
*/
syncUI: function()
{
this._drawAxis();
},
/**
* @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");
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);
},
/**
* @protected
*
* Gets the default value for the <code>styles</code> attribute. Overrides
* base implementation.
*
* @method _getDefaultStyles
* @return Object
*/
_getDefaultStyles: function()
{
var axisstyles = {
majorTicks: {
display:"inside",
length:4,
color:"#dad8c9",
weight:1,
alpha:1
},
minorTicks: {
display:"none",
length:2,
color:"#dad8c9",
weight:1
},
line: {
weight:1,
color:"#dad8c9",
alpha:1
},
majorUnit: {
determinant:"count",
count:11,
distance:75
},
top: "0px",
left: "0px",
width: "100px",
height: "100px",
label: {
color:"#808080",
alpha: 1,
fontSize:"85%",
rotation: 0,
margin: {
top:4,
right:4,
bottom:4,
left:4
}
},
hideOverlappingLabelTicks: false
};
return Y.merge(Y.Renderer.prototype._getDefaultStyles(), axisstyles);
},
/**
* @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();
}
},
/**
* @private
*/
_layout: null,
/**
* @private
*/
getLayout: function(pos)
{
var l;
switch(pos)
{
case "top" :
l = new Y.TopAxisLayout({axisRenderer:this});
break;
case "bottom" :
l = new Y.BottomAxisLayout({axisRenderer:this});
break;
case "left" :
l = new Y.LeftAxisLayout({axisRenderer:this});
break;
case "right" :
l = new Y.RightAxisLayout({axisRenderer:this});
break;
}
return l;
},
/**
* @private
*/
drawLine: function(startPoint, endPoint, line)
{
var graphic = this.get("graphic");
graphic.lineStyle(line.weight, line.color, line.alpha);
graphic.moveTo(startPoint.x, startPoint.y);
graphic.lineTo(endPoint.x, endPoint.y);
graphic.end();
},
/**
* @private
*/
_drawAxis: function ()
{
if(this._drawing)
{
this._callLater = true;
return;
}
this._drawing = true;
this._callLater = false;
if(this.get("position") != "none")
{
var styles = this.get("styles"),
majorTickStyles = styles.majorTicks,
drawTicks = majorTickStyles.display != "none",
tickPoint,
majorUnit = styles.majorUnit,
len,
majorUnitDistance,
i = 0,
layoutLength,
position,
lineStart,
label,
layout = this._layout,
labelFunction = this.get("labelFunction"),
labelFunctionScope = this.get("labelFunctionScope"),
labelFormat = this.get("labelFormat"),
graphic = this.get("graphic");
graphic.clear();
layout.setTickOffsets();
layoutLength = this.getLength();
lineStart = layout.getLineStart();
len = this.getTotalMajorUnits(majorUnit);
majorUnitDistance = this.getMajorUnitDistance(len, layoutLength, majorUnit);
this.set("edgeOffset", this.getEdgeOffset(len, layoutLength) * 0.5);
tickPoint = this.getFirstPoint(lineStart);
this.drawLine(lineStart, this.getLineEnd(tickPoint), styles.line);
if(drawTicks)
{
layout.drawTick(tickPoint, majorTickStyles);
}
if(len < 1)
{
this._clearLabelCache();
return;
}
this._createLabelCache();
this._tickPoints = [];
layout.set("maxLabelSize", 0);
for(; i < len; ++i)
{
if(drawTicks)
{
layout.drawTick(tickPoint, majorTickStyles);
}
position = this.getPosition(tickPoint);
label = this.getLabel(tickPoint);
label.innerHTML = labelFunction.apply(labelFunctionScope, [this.getLabelByIndex(i, len), labelFormat]);
tickPoint = this.getNextPoint(tickPoint, majorUnitDistance);
}
this._clearLabelCache();
layout.setSizeAndPosition();
if(this.get("overlapGraph"))
{
layout.offsetNodeForTick(this.get("contentBox"));
}
layout.setCalculatedSize();
for(i = 0; i < len; ++i)
{
layout.positionLabel(this.get("labels")[i], this._tickPoints[i]);
}
}
this._drawing = false;
if(this._callLater)
{
this._drawAxis();
}
else
{
this.fire("axisRendered");
}
},
/**
* @private
*/
_labels: null,
/**
* @private
*/
_labelCache: null,
/**
* @private
*/
getLabel: function(pt, pos)
{
var i,
label,
customStyles = {
rotation: "rotation",
margin: "margin",
alpha: "alpha"
},
cache = this._labelCache,
styles = this.get("styles").label;
if(cache.length > 0)
{
label = cache.shift();
}
else
{
label = document.createElement("span");
label.style.display = "block";
label.style.whiteSpace = "nowrap";
Y.one(label).addClass("axisLabel");
this.get("contentBox").appendChild(label);
}
label.style.position = "absolute";
this._labels.push(label);
this._tickPoints.push({x:pt.x, y:pt.y});
this._layout.updateMaxLabelSize(label);
for(i in styles)
{
if(styles.hasOwnProperty(i) && !customStyles.hasOwnProperty(i))
{
label.style[i] = styles[i];
}
}
return label;
},
/**
* @private
*/
_createLabelCache: function()
{
if(this._labels)
{
if(this._labelCache)
{
this._labelCache = this._labels.concat(this._labelCache);
}
else
{
this._labelCache = this._labels.concat();
}
}
else
{
this._clearLabelCache();
}
this._labels = [];
},
/**
* @private
*/
_clearLabelCache: function()
{
if(this._labelCache)
{
var len = this._labelCache.length,
i = 0,
label,
labelCache = this._labelCache;
for(; i < len; ++i)
{
label = labelCache[i];
label.parentNode.removeChild(label);
}
}
this._labelCache = [];
},
/**
* @private
*/
_calculateSizeByTickLength: true,
/**
* @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};
}
},
/**
* @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;
},
/**
* @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;
},
/**
* @private
*/
getNextPoint: function(point, majorUnitDistance)
{
var pos = this.get("position");
if(pos === "top" || pos === "bottom")
{
point.x = point.x + majorUnitDistance;
}
else
{
point.y = point.y - majorUnitDistance;
}
return point;
},
/**
* @private
*/
getLastPoint: function()
{
var style = this.get("styles"),
padding = style.padding,
w = this.get("width"),
pos = this.get("position");
if(pos === "top" || pos === "bottom")
{
return {x:w - padding.right, y:padding.top};
}
else
{
return {x:padding.left, y:padding.top};
}
},
/**
* @private
*/
getPosition: function(point)
{
var p,
h = this.get("height"),
style = this.get("styles"),
padding = style.padding,
pos = this.get("position"),
dataType = this.get("dataType");
if(pos === "left" || pos === "right")
{
//Numeric data on a vertical axis is displayed from bottom to top.
//Categorical and Timeline data is displayed from top to bottom.
if(dataType === "numeric")
{
p = (h - (padding.top + padding.bottom)) - (point.y - padding.top);
}
else
{
p = point.y - padding.top;
}
}
else
{
p = point.x - padding.left;
}
return p;
}
}, {
ATTRS:
{
/**
* @protected
*
* Difference betweend the first/last tick and edge of axis.
*
* @attribute edgeOffset
* @type Number
*/
edgeOffset:
{
value: 0
},
/**
* The graphic in which the axis line and ticks will be rendered.
*
* @attribute graphic
* @type Graphic
*/
graphic: {},
/**
* Contains the contents of the axis.
*
* @attribute node
* @type HTMLElement
*/
node: {},
/**
* Direction of the axis.
*
* @attribute position
* @type String
*/
position: {
lazyAdd: false,
setOnce: true,
setter: function(val)
{
if(val == "none")
{
this.bindUI();
}
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);
}
},
/**
* Object which should have by the labelFunction
*
* @attribute labelFunctionScope
* @type Object
*/
labelFunctionScope: {}
/**
* Style properties used for drawing an axis. This attribute is inherited from <code>Renderer</code>. 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 <code>inside</code>, <code>outside</code>, <code>cross</code> and <code>none</code>. The
* default value is <code>inside</code>.</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 <code>#dad8c9</code></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 <code>#dad8c9</code>.</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 <code>majorUnit</code> for the axis.
* <dl>
* <dt>determinant</dt><dd>The algorithm used for calculating distance between ticks. The possible options are <code>count</code> and <code>distance</code>. If
* the <code>determinant</code> is <code>count</code>, the axis ticks will spaced so that a specified number of ticks appear on the axis. If the <code>determinant</code>
* is <code>distance</code>, the axis ticks will spaced out according to the specified distance. The default value is <code>count</code>.</dd>
* <dt>count</dt><dd>Number of ticks to appear on the axis when the <code>determinant</code> is <code>count</code>. The default value is 11.</dd>
* <dt>distance</dt><dd>The distance (in pixels) between ticks when the <code>determinant</code> is <code>distance</code>. 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 <code>#808080</code>.</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>margin</dt><dd>The distance between the label and the axis/tick. Depending on the position of the <code>Axis</code>, only one of the properties used.
* <dl>
* <dt>top</dt><dd>Pixel value used for an axis with a <code>position</code> of <code>bottom</code>. The default value is 4.</dd>
* <dt>right</dt><dd>Pixel value used for an axis with a <code>position</code> of <code>left</code>. The default value is 4.</dd>
* <dt>bottom</dt><dd>Pixel value used for an axis with a <code>position</code> of <code>top</code>. The default value is 4.</dd>
* <dt>left</dt><dd>Pixel value used for an axis with a <code>position</code> of <code>right</code>. The default value is 4.</dd>
* </dl>
* </dd>
* </dl>
* </dd>
* </dl>
*
* @attribute styles
* @type Object
*/
}
});