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