/**
* AxisType is an abstract class that manages the data for an axis.
*
* @module charts
* @submodule charts-base
* @class AxisType
* @constructor
* @extends Axis
*/
Y.AxisType = Y.Base.create("baseAxis", Y.Axis, [], {
/**
* @method initializer
* @private
*/
initializer: function()
{
this.after("dataReady", Y.bind(this._dataChangeHandler, this));
this.after("dataUpdate", Y.bind(this._dataChangeHandler, this));
this.after("minimumChange", Y.bind(this._keyChangeHandler, this));
this.after("maximumChange", Y.bind(this._keyChangeHandler, this));
this.after("keysChange", this._keyChangeHandler);
this.after("dataProviderChange", this._dataProviderChangeHandler);
this.after("alwaysShowZeroChange", this._keyChangeHandler);
this.after("roundingMethodChange", this._keyChangeHandler);
},
/**
* @method bindUI
* @private
*/
bindUI: function()
{
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);
},
/**
* Handles changes to `dataProvider`.
*
* @method _dataProviderChangeHandler
* @param {Object} e Event object.
* @private
*/
_dataProviderChangeHandler: function(e)
{
var keyCollection = this.get("keyCollection").concat(),
keys = this.get("keys"),
i;
if(keys)
{
for(i in keys)
{
if(keys.hasOwnProperty(i))
{
delete keys[i];
}
}
}
if(keyCollection && keyCollection.length)
{
this.set("keys", keyCollection);
}
},
/**
* Constant used to generate unique id.
*
* @property GUID
* @type String
* @private
*/
GUID: "yuibaseaxis",
/**
* Type of data used in `Axis`.
*
* @property _type
* @type String
* @readOnly
* @private
*/
_type: null,
/**
* Storage for `setMaximum` attribute.
*
* @property _setMaximum
* @type Object
* @private
*/
_setMaximum: null,
/**
* Storage for `dataMaximum` attribute.
*
* @property _dataMaximum
* @type Object
* @private
*/
_dataMaximum: null,
/**
* Storage for `setMinimum` attribute.
*
* @property _setMinimum
* @type Object
* @private
*/
_setMinimum: null,
/**
* Reference to data array.
*
* @property _data
* @type Array
* @private
*/
_data: null,
/**
* Indicates whether the all data is up to date.
*
* @property _updateTotalDataFlag
* @type Boolean
* @private
*/
_updateTotalDataFlag: true,
/**
* Storage for `dataReady` attribute.
*
* @property _dataReady
* @type Boolean
* @readOnly
* @private
*/
_dataReady: false,
/**
* Adds an array to the key hash.
*
* @method addKey
* @param value Indicates what key to use in retrieving
* the array.
*/
addKey: function (value)
{
this.set("keys", value);
},
/**
* Gets an array of values based on a key.
*
* @method _getKeyArray
* @param {String} key Value key associated with the data array.
* @param {Array} data Array in which the data resides.
* @return Array
* @private
*/
_getKeyArray: function(key, data)
{
var i = 0,
obj,
keyArray = [],
len = data.length;
for(; i < len; ++i)
{
obj = data[i];
keyArray[i] = obj[key];
}
return keyArray;
},
/**
* Sets data by key
*
* @method _setDataByKey
* @param {String} key Key value to use.
* @param {Array} data Array to use.
* @private
*/
_setDataByKey: function(key, data)
{
var i,
obj,
arr = [],
dv = this._dataClone.concat(),
len = dv.length;
for(i = 0; i < len; ++i)
{
obj = dv[i];
arr[i] = obj[key];
}
this.get("keys")[key] = arr;
this._updateTotalDataFlag = true;
},
/**
* Updates the total data array.
*
* @method _updateTotalData
* @private
*/
_updateTotalData: function()
{
var keys = this.get("keys"),
i;
this._data = [];
for(i in keys)
{
if(keys.hasOwnProperty(i))
{
this._data = this._data.concat(keys[i]);
}
}
this._updateTotalDataFlag = false;
},
/**
* Removes an array from the key hash.
*
* @method removeKey
* @param {String} value Indicates what key to use in removing from
* the hash.
*/
removeKey: function(value)
{
var keys = this.get("keys");
if(keys.hasOwnProperty(value))
{
delete keys[value];
this._keyChangeHandler();
}
},
/**
* Returns a value based of a key value and an index.
*
* @method getKeyValueAt
* @param {String} key value used to look up the correct array
* @param {Number} index within the array
* @return Number
*/
getKeyValueAt: function(key, index)
{
var value = NaN,
keys = this.get("keys");
if(keys[key] && Y_Lang.isNumber(parseFloat(keys[key][index])))
{
value = keys[key][index];
}
return parseFloat(value);
},
/**
* Returns an array of values based on an identifier key.
*
* @method getDataByKey
* @param {String} value value used to identify the array
* @return Object
*/
getDataByKey: function (value)
{
var keys = this.get("keys");
if(keys[value])
{
return keys[value];
}
return null;
},
/**
* Calculates the maximum and minimum values for the `Axis`.
*
* @method _updateMinAndMax
* @private
*/
_updateMinAndMax: function()
{
var data = this.get("data"),
max = 0,
min = 0,
len,
num,
i;
if(data && data.length && data.length > 0)
{
len = data.length;
max = min = data[0];
if(len > 1)
{
for(i = 1; i < len; i++)
{
num = data[i];
if(isNaN(num))
{
continue;
}
max = Math.max(num, max);
min = Math.min(num, min);
}
}
}
this._dataMaximum = max;
this._dataMinimum = min;
},
/**
* Returns the total number of majorUnits that will appear on an axis.
*
* @method getTotalMajorUnits
* @return Number
*/
getTotalMajorUnits: function()
{
var units,
majorUnit = this.get("styles").majorUnit,
len = this.get("length");
if(majorUnit.determinant === "count")
{
units = majorUnit.count;
}
else if(majorUnit.determinant === "distance")
{
units = (len/majorUnit.distance) + 1;
}
return units;
},
/**
* Returns the distance between major units on an axis.
*
* @method getMajorUnitDistance
* @param {Number} len Number of ticks
* @param {Number} uiLen Size of the axis.
* @param {Object} majorUnit Hash of properties used to determine the majorUnit
* @return Number
*/
getMajorUnitDistance: function(len, uiLen, majorUnit)
{
var dist;
if(majorUnit.determinant === "count")
{
dist = uiLen/(len - 1);
}
else if(majorUnit.determinant === "distance")
{
dist = majorUnit.distance;
}
return dist;
},
/**
* Gets the distance that the first and last ticks are offset from there respective
* edges.
*
* @method getEdgeOffset
* @param {Number} ct Number of ticks on the axis.
* @param {Number} l Length (in pixels) of the axis.
* @return Number
*/
getEdgeOffset: function(ct, l)
{
return 0;
},
/**
* 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 min = this.get("minimum"),
max = this.get("maximum"),
increm = (max - min)/(l-1),
label;
l -= 1;
label = min + (i * increm);
return label;
},
/**
* Updates the `Axis` after a change in keys.
*
* @method _keyChangeHandler
* @param {Object} e Event object.
* @private
*/
_keyChangeHandler: function(e)
{
this._updateMinAndMax();
this.fire("dataUpdate");
},
/**
* 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: {
/**
* Hash of array identifed by a string value.
*
* @attribute keys
* @type Object
*/
keys: {
value: {},
setter: function(val)
{
var keys = {},
i,
len,
data = this.get("dataProvider");
if(Y_Lang.isArray(val))
{
len = val.length;
for(i = 0; i < len; ++i)
{
keys[val[i]] = this._getKeyArray(val[i], data);
}
}
else if(Y_Lang.isString(val))
{
keys = this.get("keys");
keys[val] = this._getKeyArray(val, data);
}
else
{
for(i in val)
{
if(val.hasOwnProperty(i))
{
keys[i] = this._getKeyArray(i, data);
}
}
}
this._updateTotalDataFlag = true;
return keys;
}
},
/**
*Indicates how to round unit values.
* <dl>
* <dt>niceNumber</dt><dd>Units will be smoothed based on the number of ticks and data range.</dd>
* <dt>auto</dt><dd>If the range is greater than 1, the units will be rounded.</dd>
* <dt>numeric value</dt><dd>Units will be equal to the numeric value.</dd>
* <dt>null</dt><dd>No rounding will occur.</dd>
* </dl>
*
* @attribute roundingMethod
* @type String
* @default niceNumber
*/
roundingMethod: {
value: "niceNumber"
},
/**
*Returns the type of axis data
* <dl>
* <dt>time</dt><dd>Manages time data</dd>
* <dt>stacked</dt><dd>Manages stacked numeric data</dd>
* <dt>numeric</dt><dd>Manages numeric data</dd>
* <dt>category</dt><dd>Manages categorical data</dd>
* </dl>
*
* @attribute type
* @type String
*/
type:
{
readOnly: true,
getter: function ()
{
return this._type;
}
},
/**
* Instance of `ChartDataProvider` that the class uses
* to build its own data.
*
* @attribute dataProvider
* @type Array
*/
dataProvider:{
setter: function (value)
{
return value;
}
},
/**
* The maximum value contained in the `data` array. Used for
* `maximum` when `autoMax` is true.
*
* @attribute dataMaximum
* @type Number
*/
dataMaximum: {
getter: function ()
{
if(!this._dataMaximum)
{
this._updateMinAndMax();
}
return this._dataMaximum;
}
},
/**
* The maximum value that will appear on an axis.
*
* @attribute maximum
* @type Number
*/
maximum: {
lazyAdd: false,
getter: function ()
{
var max = this.get("dataMaximum"),
min = this.get("minimum");
//If all values are zero, force a range so that the Axis and related series
//will still render.
if(min === 0 && max === 0)
{
max = 10;
}
if(Y_Lang.isNumber(this._setMaximum))
{
max = this._setMaximum;
}
return parseFloat(max);
},
setter: function (value)
{
this._setMaximum = parseFloat(value);
return value;
}
},
/**
* The minimum value contained in the `data` array. Used for
* `minimum` when `autoMin` is true.
*
* @attribute dataMinimum
* @type Number
*/
dataMinimum: {
getter: function ()
{
if(!this._dataMinimum)
{
this._updateMinAndMax();
}
return this._dataMinimum;
}
},
/**
* The minimum value that will appear on an axis.
*
* @attribute minimum
* @type Number
*/
minimum: {
lazyAdd: false,
getter: function ()
{
var min = this.get("dataMinimum");
if(Y_Lang.isNumber(this._setMinimum))
{
min = this._setMinimum;
}
return parseFloat(min);
},
setter: function(val)
{
this._setMinimum = parseFloat(val);
return val;
}
},
/**
* Determines whether the maximum is calculated or explicitly
* set by the user.
*
* @attribute setMax
* @type Boolean
*/
setMax: {
readOnly: true,
getter: function()
{
return Y_Lang.isNumber(this._setMaximum);
}
},
/**
* Determines whether the minimum is calculated or explicitly
* set by the user.
*
* @attribute setMin
* @type Boolean
*/
setMin: {
readOnly: true,
getter: function()
{
return Y_Lang.isNumber(this._setMinimum);
}
},
/**
* Array of axis data
*
* @attribute data
* @type Array
*/
data: {
getter: function ()
{
if(!this._data || this._updateTotalDataFlag)
{
this._updateTotalData();
}
return this._data;
}
},
/**
* Array containing all the keys in the axis.
* @attribute keyCollection
* @type Array
*/
keyCollection: {
getter: function()
{
var keys = this.get("keys"),
i,
col = [];
for(i in keys)
{
if(keys.hasOwnProperty(i))
{
col.push(i);
}
}
return col;
},
readOnly: true
}
}
});