/**
* The PieChart class creates a pie chart
*
* @class PieChart
* @extends ChartBase
* @constructor
* @submodule charts-base
*/
Y.PieChart = Y.Base.create("pieChart", Y.Widget, [Y.ChartBase], {
/**
* Calculates and returns a `seriesCollection`.
*
* @method _getSeriesCollection
* @return Array
* @private
*/
_getSeriesCollection: function()
{
if(this._seriesCollection)
{
return this._seriesCollection;
}
var axes = this.get("axes"),
sc = [],
seriesKeys,
i = 0,
l,
type = this.get("type"),
key,
catAxis = "categoryAxis",
catKey = "categoryKey",
valAxis = "valueAxis",
seriesKey = "valueKey";
if(axes)
{
seriesKeys = axes.values.get("keyCollection");
key = axes.category.get("keyCollection")[0];
l = seriesKeys.length;
for(; i < l; ++i)
{
sc[i] = {type:type};
sc[i][catAxis] = "category";
sc[i][valAxis] = "values";
sc[i][catKey] = key;
sc[i][seriesKey] = seriesKeys[i];
}
}
this._seriesCollection = sc;
return sc;
},
/**
* Creates `Axis` instances.
*
* @method _parseAxes
* @param {Object} val Object containing `Axis` instances or objects in which to construct `Axis` instances.
* @return Object
* @private
*/
_parseAxes: function(hash)
{
if(!this._axes)
{
this._axes = {};
}
var i, pos, axis, dh, config, AxisClass,
type = this.get("type"),
w = this.get("width"),
h = this.get("height"),
node = Y.Node.one(this._parentNode);
if(!w)
{
this.set("width", node.get("offsetWidth"));
w = this.get("width");
}
if(!h)
{
this.set("height", node.get("offsetHeight"));
h = this.get("height");
}
for(i in hash)
{
if(hash.hasOwnProperty(i))
{
dh = hash[i];
pos = type === "pie" ? "none" : dh.position;
AxisClass = this._getAxisClass(dh.type);
config = {dataProvider:this.get("dataProvider")};
if(dh.hasOwnProperty("roundingUnit"))
{
config.roundingUnit = dh.roundingUnit;
}
config.keys = dh.keys;
config.width = w;
config.height = h;
config.position = pos;
config.styles = dh.styles;
axis = new AxisClass(config);
axis.on("axisRendered", Y.bind(this._itemRendered, this));
this._axes[i] = axis;
}
}
},
/**
* Adds axes to the chart.
*
* @method _addAxes
* @private
*/
_addAxes: function()
{
var axes = this.get("axes"),
i,
axis,
p;
if(!axes)
{
this.set("axes", this._getDefaultAxes());
axes = this.get("axes");
}
if(!this._axesCollection)
{
this._axesCollection = [];
}
for(i in axes)
{
if(axes.hasOwnProperty(i))
{
axis = axes[i];
p = axis.get("position");
if(!this.get(p + "AxesCollection"))
{
this.set(p + "AxesCollection", [axis]);
}
else
{
this.get(p + "AxesCollection").push(axis);
}
this._axesCollection.push(axis);
}
}
},
/**
* Renders the Graph.
*
* @method _addSeries
* @private
*/
_addSeries: function()
{
var graph = this.get("graph"),
seriesCollection = this.get("seriesCollection");
this._parseSeriesAxes(seriesCollection);
graph.set("showBackground", false);
graph.set("width", this.get("width"));
graph.set("height", this.get("height"));
graph.set("seriesCollection", seriesCollection);
this._seriesCollection = graph.get("seriesCollection");
graph.render(this.get("contentBox"));
},
/**
* Parse and sets the axes for the chart.
*
* @method _parseSeriesAxes
* @param {Array} c A collection `PieSeries` instance.
* @private
*/
_parseSeriesAxes: function(c)
{
var i = 0,
len = c.length,
s,
axes = this.get("axes"),
axis;
for(; i < len; ++i)
{
s = c[i];
if(s)
{
//If series is an actual series instance,
//replace axes attribute string ids with axes
if(s instanceof Y.PieSeries)
{
axis = s.get("categoryAxis");
if(axis && !(axis instanceof Y.Axis))
{
s.set("categoryAxis", axes[axis]);
}
axis = s.get("valueAxis");
if(axis && !(axis instanceof Y.Axis))
{
s.set("valueAxis", axes[axis]);
}
continue;
}
s.categoryAxis = axes.category;
s.valueAxis = axes.values;
if(!s.type)
{
s.type = this.get("type");
}
}
}
},
/**
* Generates and returns a key-indexed object containing `Axis` instances or objects used to create `Axis` instances.
*
* @method _getDefaultAxes
* @return Object
* @private
*/
_getDefaultAxes: function()
{
var catKey = this.get("categoryKey"),
seriesKeys = this.get("seriesKeys").concat(),
seriesAxis = "numeric";
return {
values:{
keys:seriesKeys,
type:seriesAxis
},
category:{
keys:[catKey],
type:this.get("categoryType")
}
};
},
/**
* Returns an object literal containing a categoryItem and a valueItem for a given series index.
*
* @method getSeriesItem
* @param series Reference to a series.
* @param index Index of the specified item within a series.
* @return Object
*/
getSeriesItems: function(series, index)
{
var categoryItem = {
axis: series.get("categoryAxis"),
key: series.get("categoryKey"),
displayName: series.get("categoryDisplayName")
},
valueItem = {
axis: series.get("valueAxis"),
key: series.get("valueKey"),
displayName: series.get("valueDisplayName")
};
categoryItem.value = categoryItem.axis.getKeyValueAt(categoryItem.key, index);
valueItem.value = valueItem.axis.getKeyValueAt(valueItem.key, index);
return {category:categoryItem, value:valueItem};
},
/**
* Handler for sizeChanged event.
*
* @method _sizeChanged
* @param {Object} e Event object.
* @private
*/
_sizeChanged: function()
{
this._redraw();
},
/**
* Redraws the chart instance.
*
* @method _redraw
* @private
*/
_redraw: function()
{
var graph = this.get("graph"),
w = this.get("width"),
h = this.get("height"),
dimension;
if(graph)
{
dimension = Math.min(w, h);
graph.set("width", dimension);
graph.set("height", dimension);
}
},
/**
* Formats tooltip text for a pie chart.
*
* @method _tooltipLabelFunction
* @param {Object} categoryItem An object containing the following:
* <dl>
* <dt>axis</dt><dd>The axis to which the category is bound.</dd>
* <dt>displayName</dt><dd>The display name set to the category (defaults to key if not provided)</dd>
* <dt>key</dt><dd>The key of the category.</dd>
* <dt>value</dt><dd>The value of the category</dd>
* </dl>
* @param {Object} valueItem An object containing the following:
* <dl>
* <dt>axis</dt><dd>The axis to which the item's series is bound.</dd>
* <dt>displayName</dt><dd>The display name of the series. (defaults to key if not provided)</dd>
* <dt>key</dt><dd>The key for the series.</dd>
* <dt>value</dt><dd>The value for the series item.</dd>
* </dl>
* @param {Number} itemIndex The index of the item within the series.
* @param {CartesianSeries} series The `PieSeries` instance of the item.
* @return {HTMLElement}
* @private
*/
_tooltipLabelFunction: function(categoryItem, valueItem, itemIndex, series)
{
var msg = DOCUMENT.createElement("div"),
total = series.getTotalValues(),
pct = Math.round((valueItem.value / total) * 10000)/100;
msg.appendChild(DOCUMENT.createTextNode(categoryItem.displayName +
": " + categoryItem.axis.get("labelFunction").apply(this, [categoryItem.value, categoryItem.axis.get("labelFormat")])));
msg.appendChild(DOCUMENT.createElement("br"));
msg.appendChild(DOCUMENT.createTextNode(valueItem.displayName +
": " + valueItem.axis.get("labelFunction").apply(this, [valueItem.value, valueItem.axis.get("labelFormat")])));
msg.appendChild(DOCUMENT.createElement("br"));
msg.appendChild(DOCUMENT.createTextNode(pct + "%"));
return msg;
},
/**
* Returns the appropriate message based on the key press.
*
* @method _getAriaMessage
* @param {Number} key The keycode that was pressed.
* @return String
*/
_getAriaMessage: function(key)
{
var msg = "",
categoryItem,
items,
series,
valueItem,
seriesIndex = 0,
itemIndex = this._itemIndex,
len,
total,
pct,
markers;
series = this.getSeries(parseInt(seriesIndex, 10));
markers = series.get("markers");
len = markers && markers.length ? markers.length : 0;
if(key === 37)
{
itemIndex = itemIndex > 0 ? itemIndex - 1 : len - 1;
}
else if(key === 39)
{
itemIndex = itemIndex >= len - 1 ? 0 : itemIndex + 1;
}
this._itemIndex = itemIndex;
items = this.getSeriesItems(series, itemIndex);
categoryItem = items.category;
valueItem = items.value;
total = series.getTotalValues();
pct = Math.round((valueItem.value / total) * 10000)/100;
if(categoryItem && valueItem)
{
msg += categoryItem.displayName +
": " +
categoryItem.axis.formatLabel.apply(this, [categoryItem.value, categoryItem.axis.get("labelFormat")]) +
", ";
msg += valueItem.displayName +
": " + valueItem.axis.formatLabel.apply(this, [valueItem.value, valueItem.axis.get("labelFormat")]) +
", ";
msg += "Percent of total " + valueItem.displayName + ": " + pct + "%,";
}
else
{
msg += "No data available,";
}
msg += (itemIndex + 1) + " of " + len + ". ";
return msg;
},
/**
* Destructor implementation for the PieChart class.
*
* @method destructor
* @protected
*/
destructor: function()
{
var series,
axis,
tooltip = this.get("tooltip"),
tooltipNode = tooltip.node,
graph = this.get("graph"),
axesCollection = this._axesCollection,
seriesCollection = this.get("seriesCollection");
while(seriesCollection.length > 0)
{
series = seriesCollection.shift();
series.destroy(true);
}
while(axesCollection.length > 0)
{
axis = axesCollection.shift();
if(axis instanceof Y.Axis)
{
axis.destroy(true);
}
}
if(this._description)
{
this._description.empty();
this._description.remove(true);
}
if(this._liveRegion)
{
this._liveRegion.empty();
this._liveRegion.remove(true);
}
if(graph)
{
graph.destroy(true);
}
if(tooltipNode)
{
tooltipNode.empty();
tooltipNode.remove(true);
}
}
}, {
ATTRS: {
/**
* Sets the aria description for the chart.
*
* @attribute ariaDescription
* @type String
*/
ariaDescription: {
value: "Use the left and right keys to navigate through items.",
setter: function(val)
{
if(this._description)
{
this._description.set("text", val);
}
return val;
}
},
/**
* Axes to appear in the chart.
*
* @attribute axes
* @type Object
*/
axes: {
getter: function()
{
return this._axes;
},
setter: function(val)
{
this._parseAxes(val);
}
},
/**
* Collection of series to appear on the chart. This can be an array of Series instances or object literals
* used to describe a Series instance.
*
* @attribute seriesCollection
* @type Array
*/
seriesCollection: {
lazyAdd: false,
getter: function()
{
return this._getSeriesCollection();
},
setter: function(val)
{
return this._setSeriesCollection(val);
}
},
/**
* Type of chart when there is no series collection specified.
*
* @attribute type
* @type String
*/
type: {
value: "pie"
}
}
});