/** * The CartesianChart class creates a chart with horizontal and vertical axes. * * @class CartesianChart * @extends ChartBase * @constructor * @submodule charts-base */ Y.CartesianChart = Y.Base.create("cartesianChart", Y.Widget, [Y.ChartBase, Y.Renderer], { /** * @method renderUI * @private */ renderUI: function() { var bb = this.get("boundingBox"), cb = this.get("contentBox"), tt = this.get("tooltip"), overlayClass = _getClassName("overlay"); //move the position = absolute logic to a class file bb.setStyle("position", "absolute"); cb.setStyle("position", "absolute"); this._addAxes(); this._addGridlines(); this._addSeries(); if(tt && tt.show) { this._addTooltip(); } if(this.get("interactionType") === "planar") { this._overlay = Y.Node.create("<div></div>"); this._overlay.set("id", this.get("id") + "_overlay"); this._overlay.setStyle("position", "absolute"); this._overlay.setStyle("background", "#fff"); this._overlay.setStyle("opacity", 0); this._overlay.addClass(overlayClass); this._overlay.setStyle("zIndex", 4); cb.append(this._overlay); } this._setAriaElements(bb, cb); this._redraw(); }, /** * When `interactionType` is set to `planar`, listens for mouse move events and fires `planarEvent:mouseover` or `planarEvent:mouseout` * depending on the position of the mouse in relation to data points on the `Chart`. * * @method _planarEventDispatcher * @param {Object} e Event object. * @private */ _planarEventDispatcher: function(e) { var graph = this.get("graph"), bb = this.get("boundingBox"), cb = graph.get("contentBox"), isTouch = e && e.hasOwnProperty("changedTouches"), pageX = isTouch ? e.changedTouches[0].pageX : e.pageX, pageY = isTouch ? e.changedTouches[0].pageY : e.pageY, posX = pageX - bb.getX(), posY = pageY - bb.getY(), offset = { x: pageX - cb.getX(), y: pageY - cb.getY() }, sc = graph.get("seriesCollection"), series, i = 0, index, oldIndex = this._selectedIndex, item, items = [], categoryItems = [], valueItems = [], direction = this.get("direction"), hasMarkers, catAxis, valAxis, coord, //data columns and area data could be created on a graph level markerPlane, len, coords; e.halt(true); if(direction === "horizontal") { catAxis = "x"; valAxis = "y"; } else { valAxis = "x"; catAxis = "y"; } coord = offset[catAxis]; if(sc) { len = sc.length; while(i < len && !markerPlane) { if(sc[i]) { markerPlane = sc[i].get(catAxis + "MarkerPlane"); } i++; } } if(markerPlane) { len = markerPlane.length; for(i = 0; i < len; ++i) { if(coord <= markerPlane[i].end && coord >= markerPlane[i].start) { index = i; break; } } len = sc.length; for(i = 0; i < len; ++i) { series = sc[i]; coords = series.get(valAxis + "coords"); hasMarkers = series.get("markers"); if(hasMarkers && !isNaN(oldIndex) && oldIndex > -1) { series.updateMarkerState("mouseout", oldIndex); } if(coords && coords[index] > -1) { if(hasMarkers && !isNaN(index) && index > -1) { series.updateMarkerState("mouseover", index); } item = this.getSeriesItems(series, index); categoryItems.push(item.category); valueItems.push(item.value); items.push(series); } } this._selectedIndex = index; /** * Broadcasts when `interactionType` is set to `planar` and a series' marker plane has received a mouseover event. * * * @event planarEvent:mouseover * @preventable false * @param {EventFacade} e Event facade with the following additional * properties: * <dl> * <dt>categoryItem</dt><dd>An array of hashes, each containing information about the category `Axis` of each marker * whose plane has been intersected.</dd> * <dt>valueItem</dt><dd>An array of hashes, each containing information about the value `Axis` of each marker whose * plane has been intersected.</dd> * <dt>x</dt><dd>The x-coordinate of the mouse in relation to the Chart.</dd> * <dt>y</dt><dd>The y-coordinate of the mouse in relation to the Chart.</dd> * <dt>pageX</dt><dd>The x location of the event on the page (including scroll)</dd> * <dt>pageY</dt><dd>The y location of the event on the page (including scroll)</dd> * <dt>items</dt><dd>An array including all the series which contain a marker whose plane has been intersected.</dd> * <dt>index</dt><dd>Index of the markers in their respective series.</dd> * <dt>originEvent</dt><dd>Underlying dom event.</dd> * </dl> */ /** * Broadcasts when `interactionType` is set to `planar` and a series' marker plane has received a mouseout event. * * @event planarEvent:mouseout * @preventable false * @param {EventFacade} e */ if(index > -1) { this.fire("planarEvent:mouseover", { categoryItem:categoryItems, valueItem:valueItems, x:posX, y:posY, pageX:pageX, pageY:pageY, items:items, index:index, originEvent:e }); } else { this.fire("planarEvent:mouseout"); } } }, /** * Indicates the default series type for the chart. * * @property _type * @type {String} * @private */ _type: "combo", /** * Queue of axes instances that will be updated. This method is used internally to determine when all axes have been updated. * * @property _itemRenderQueue * @type Array * @private */ _itemRenderQueue: null, /** * Adds an `Axis` instance to the `_itemRenderQueue`. * * @method _addToAxesRenderQueue * @param {Axis} axis An `Axis` instance. * @private */ _addToAxesRenderQueue: function(axis) { if(!this._itemRenderQueue) { this._itemRenderQueue = []; } if(Y.Array.indexOf(this._itemRenderQueue, axis) < 0) { this._itemRenderQueue.push(axis); } }, /** * Adds axis instance to the appropriate array based on position * * @method _addToAxesCollection * @param {String} position The position of the axis * @param {Axis} axis The `Axis` instance */ _addToAxesCollection: function(position, axis) { var axesCollection = this.get(position + "AxesCollection"); if(!axesCollection) { axesCollection = []; this.set(position + "AxesCollection", axesCollection); } axesCollection.push(axis); }, /** * Returns the default value for the `seriesCollection` attribute. * * @method _getDefaultSeriesCollection * @param {Array} val Array containing either `CartesianSeries` instances or objects containing data to construct series instances. * @return Array * @private */ _getDefaultSeriesCollection: function() { var seriesCollection, dataProvider = this.get("dataProvider"); if(dataProvider) { seriesCollection = this._parseSeriesCollection(); } return seriesCollection; }, /** * Parses and returns a series collection from an object and default properties. * * @method _parseSeriesCollection * @param {Object} val Object contain properties for series being set. * @return Object * @private */ _parseSeriesCollection: function(val) { var dir = this.get("direction"), seriesStyles = this.get("styles").series, stylesAreArray = seriesStyles && Y_Lang.isArray(seriesStyles), stylesIndex, setStyles, globalStyles, sc = [], catAxis, valAxis, tempKeys = [], series, seriesKeys = this.get("seriesKeys").concat(), i, index, l, type = this.get("type"), key, catKey, seriesKey, graph, orphans = [], categoryKey = this.get("categoryKey"), showMarkers = this.get("showMarkers"), showAreaFill = this.get("showAreaFill"), showLines = this.get("showLines"); val = val ? val.concat() : []; if(dir === "vertical") { catAxis = "yAxis"; catKey = "yKey"; valAxis = "xAxis"; seriesKey = "xKey"; } else { catAxis = "xAxis"; catKey = "xKey"; valAxis = "yAxis"; seriesKey = "yKey"; } l = val.length; while(val && val.length > 0) { series = val.shift(); key = this._getBaseAttribute(series, seriesKey); if(key) { index = Y.Array.indexOf(seriesKeys, key); if(index > -1) { seriesKeys.splice(index, 1); tempKeys.push(key); sc.push(series); } else { orphans.push(series); } } else { orphans.push(series); } } while(orphans.length > 0) { series = orphans.shift(); if(seriesKeys.length > 0) { key = seriesKeys.shift(); this._setBaseAttribute(series, seriesKey, key); tempKeys.push(key); sc.push(series); } else if(series instanceof Y.CartesianSeries) { series.destroy(true); } } if(seriesKeys.length > 0) { tempKeys = tempKeys.concat(seriesKeys); } l = tempKeys.length; for(i = 0; i < l; ++i) { series = sc[i] || {type:type}; if(series instanceof Y.CartesianSeries) { this._parseSeriesAxes(series); } else { series[catKey] = series[catKey] || categoryKey; series[seriesKey] = series[seriesKey] || seriesKeys.shift(); series[catAxis] = this._getCategoryAxis(); series[valAxis] = this._getSeriesAxis(series[seriesKey]); series.type = series.type || type; series.direction = series.direction || dir; if(series.type === "combo" || series.type === "stackedcombo" || series.type === "combospline" || series.type === "stackedcombospline") { if(showAreaFill !== null) { series.showAreaFill = (series.showAreaFill !== null && series.showAreaFill !== undefined) ? series.showAreaFill : showAreaFill; } if(showMarkers !== null) { series.showMarkers = (series.showMarkers !== null && series.showMarkers !== undefined) ? series.showMarkers : showMarkers; } if(showLines !== null) { series.showLines = (series.showLines !== null && series.showLines !== undefined) ? series.showLines : showLines; } } if(seriesStyles) { stylesIndex = stylesAreArray ? i : series[seriesKey]; globalStyles = seriesStyles[stylesIndex]; if(globalStyles) { setStyles = series.styles; if(setStyles) { series.styles = this._mergeStyles(setStyles, globalStyles); } else { series.styles = globalStyles; } } } sc[i] = series; } } if(sc) { graph = this.get("graph"); graph.set("seriesCollection", sc); sc = graph.get("seriesCollection"); } return sc; }, /** * Parse and sets the axes for a series instance. * * @method _parseSeriesAxes * @param {CartesianSeries} series A `CartesianSeries` instance. * @private */ _parseSeriesAxes: function(series) { var axes = this.get("axes"), xAxis = series.get("xAxis"), yAxis = series.get("yAxis"), YAxis = Y.Axis, axis; if(xAxis && !(xAxis instanceof YAxis) && Y_Lang.isString(xAxis) && axes.hasOwnProperty(xAxis)) { axis = axes[xAxis]; if(axis instanceof YAxis) { series.set("xAxis", axis); } } if(yAxis && !(yAxis instanceof YAxis) && Y_Lang.isString(yAxis) && axes.hasOwnProperty(yAxis)) { axis = axes[yAxis]; if(axis instanceof YAxis) { series.set("yAxis", axis); } } }, /** * Returns the category axis instance for the chart. * * @method _getCategoryAxis * @return Axis * @private */ _getCategoryAxis: function() { var axis, axes = this.get("axes"), categoryAxisName = this.get("categoryAxisName") || this.get("categoryKey"); axis = axes[categoryAxisName]; return axis; }, /** * Returns the value axis for a series. * * @method _getSeriesAxis * @param {String} key The key value used to determine the axis instance. * @return Axis * @private */ _getSeriesAxis:function(key, axisName) { var axes = this.get("axes"), i, keys, axis; if(axes) { if(axisName && axes.hasOwnProperty(axisName)) { axis = axes[axisName]; } else { for(i in axes) { if(axes.hasOwnProperty(i)) { keys = axes[i].get("keys"); if(keys && keys.hasOwnProperty(key)) { axis = axes[i]; break; } } } } } return axis; }, /** * Gets an attribute from an object, using a getter for Base objects and a property for object * literals. Used for determining attributes from series/axis references which can be an actual class instance * or a hash of properties that will be used to create a class instance. * * @method _getBaseAttribute * @param {Object} item Object or instance in which the attribute resides. * @param {String} key Attribute whose value will be returned. * @return Object * @private */ _getBaseAttribute: function(item, key) { if(item instanceof Y.Base) { return item.get(key); } if(item.hasOwnProperty(key)) { return item[key]; } return null; }, /** * Sets an attribute on an object, using a setter of Base objects and a property for object * literals. Used for setting attributes on a Base class, either directly or to be stored in an object literal * for use at instantiation. * * @method _setBaseAttribute * @param {Object} item Object or instance in which the attribute resides. * @param {String} key Attribute whose value will be assigned. * @param {Object} value Value to be assigned to the attribute. * @private */ _setBaseAttribute: function(item, key, value) { if(item instanceof Y.Base) { item.set(key, value); } else { item[key] = value; } }, /** * Creates `Axis` instances. * * @method _setAxes * @param {Object} val Object containing `Axis` instances or objects in which to construct `Axis` instances. * @return Object * @private */ _setAxes: function(val) { var hash = this._parseAxes(val), axes = {}, axesAttrs = { edgeOffset: "edgeOffset", calculateEdgeOffset: "calculateEdgeOffset", position: "position", overlapGraph:"overlapGraph", labelValues: "labelValues", hideFirstMajorUnit: "hideFirstMajorUnit", hideLastMajorUnit: "hideLastMajorUnit", labelFunction:"labelFunction", labelFunctionScope:"labelFunctionScope", labelFormat:"labelFormat", appendLabelFunction: "appendLabelFunction", appendTitleFunction: "appendTitleFunction", maximum:"maximum", minimum:"minimum", roundingMethod:"roundingMethod", alwaysShowZero:"alwaysShowZero", scaleType: "scaleType", title:"title", width:"width", height:"height" }, dp = this.get("dataProvider"), ai, i, pos, axis, axisPosition, dh, AxisClass, config, axesCollection; for(i in hash) { if(hash.hasOwnProperty(i)) { dh = hash[i]; if(dh instanceof Y.Axis) { axis = dh; } else { axis = null; config = {}; config.dataProvider = dh.dataProvider || dp; config.keys = dh.keys; if(dh.hasOwnProperty("roundingUnit")) { config.roundingUnit = dh.roundingUnit; } pos = dh.position; if(dh.styles) { config.styles = dh.styles; } config.position = dh.position; for(ai in axesAttrs) { if(axesAttrs.hasOwnProperty(ai) && dh.hasOwnProperty(ai)) { config[ai] = dh[ai]; } } //only check for existing axis if we constructed the default axes already if(val) { axis = this.getAxisByKey(i); } if(axis && axis instanceof Y.Axis) { axisPosition = axis.get("position"); if(pos !== axisPosition) { if(axisPosition !== "none") { axesCollection = this.get(axisPosition + "AxesCollection"); axesCollection.splice(Y.Array.indexOf(axesCollection, axis), 1); } if(pos !== "none") { this._addToAxesCollection(pos, axis); } } axis.setAttrs(config); } else { AxisClass = this._getAxisClass(dh.type); axis = new AxisClass(config); axis.after("axisRendered", Y.bind(this._itemRendered, this)); } } if(axis) { axesCollection = this.get(pos + "AxesCollection"); if(axesCollection && Y.Array.indexOf(axesCollection, axis) > 0) { axis.set("overlapGraph", false); } axes[i] = axis; } } } return axes; }, /** * Adds axes to the chart. * * @method _addAxes * @private */ _addAxes: function() { var axes = this.get("axes"), i, axis, pos, w = this.get("width"), h = this.get("height"), node = Y.Node.one(this._parentNode); if(!this._axesCollection) { this._axesCollection = []; } for(i in axes) { if(axes.hasOwnProperty(i)) { axis = axes[i]; if(axis instanceof Y.Axis) { if(!w) { this.set("width", node.get("offsetWidth")); w = this.get("width"); } if(!h) { this.set("height", node.get("offsetHeight")); h = this.get("height"); } this._addToAxesRenderQueue(axis); pos = axis.get("position"); if(!this.get(pos + "AxesCollection")) { this.set(pos + "AxesCollection", [axis]); } else { this.get(pos + "AxesCollection").push(axis); } this._axesCollection.push(axis); if(axis.get("keys").hasOwnProperty(this.get("categoryKey"))) { this.set("categoryAxis", axis); } axis.render(this.get("contentBox")); } } } }, /** * Renders the Graph. * * @method _addSeries * @private */ _addSeries: function() { var graph = this.get("graph"); graph.render(this.get("contentBox")); }, /** * Adds gridlines to the chart. * * @method _addGridlines * @private */ _addGridlines: function() { var graph = this.get("graph"), hgl = this.get("horizontalGridlines"), vgl = this.get("verticalGridlines"), direction = this.get("direction"), leftAxesCollection = this.get("leftAxesCollection"), rightAxesCollection = this.get("rightAxesCollection"), bottomAxesCollection = this.get("bottomAxesCollection"), topAxesCollection = this.get("topAxesCollection"), seriesAxesCollection, catAxis = this.get("categoryAxis"), hAxis, vAxis; if(this._axesCollection) { seriesAxesCollection = this._axesCollection.concat(); seriesAxesCollection.splice(Y.Array.indexOf(seriesAxesCollection, catAxis), 1); } if(hgl) { if(leftAxesCollection && leftAxesCollection[0]) { hAxis = leftAxesCollection[0]; } else if(rightAxesCollection && rightAxesCollection[0]) { hAxis = rightAxesCollection[0]; } else { hAxis = direction === "horizontal" ? catAxis : seriesAxesCollection[0]; } if(!this._getBaseAttribute(hgl, "axis") && hAxis) { this._setBaseAttribute(hgl, "axis", hAxis); } if(this._getBaseAttribute(hgl, "axis")) { graph.set("horizontalGridlines", hgl); } } if(vgl) { if(bottomAxesCollection && bottomAxesCollection[0]) { vAxis = bottomAxesCollection[0]; } else if (topAxesCollection && topAxesCollection[0]) { vAxis = topAxesCollection[0]; } else { vAxis = direction === "vertical" ? catAxis : seriesAxesCollection[0]; } if(!this._getBaseAttribute(vgl, "axis") && vAxis) { this._setBaseAttribute(vgl, "axis", vAxis); } if(this._getBaseAttribute(vgl, "axis")) { graph.set("verticalGridlines", vgl); } } }, /** * Default Function for the axes attribute. * * @method _getDefaultAxes * @return Object * @private */ _getDefaultAxes: function() { var axes; if(this.get("dataProvider")) { axes = this._parseAxes(); } return axes; }, /** * Generates and returns a key-indexed object containing `Axis` instances or objects used to create `Axis` instances. * * @method _parseAxes * @param {Object} axes Object containing `Axis` instances or `Axis` attributes. * @return Object * @private */ _parseAxes: function(axes) { var catKey = this.get("categoryKey"), axis, attr, keys, newAxes = {}, claimedKeys = [], newKeys = [], categoryAxisName = this.get("categoryAxisName") || this.get("categoryKey"), valueAxisName = this.get("valueAxisName"), seriesKeys = this.get("seriesKeys").concat(), i, l, ii, ll, cIndex, direction = this.get("direction"), seriesPosition, categoryPosition, valueAxes = [], seriesAxis = this.get("stacked") ? "stacked" : "numeric"; if(direction === "vertical") { seriesPosition = "bottom"; categoryPosition = "left"; } else { seriesPosition = "left"; categoryPosition = "bottom"; } if(axes) { for(i in axes) { if(axes.hasOwnProperty(i)) { axis = axes[i]; keys = this._getBaseAttribute(axis, "keys"); attr = this._getBaseAttribute(axis, "type"); if(attr === "time" || attr === "category") { categoryAxisName = i; this.set("categoryAxisName", i); if(Y_Lang.isArray(keys) && keys.length > 0) { catKey = keys[0]; this.set("categoryKey", catKey); } newAxes[i] = axis; } else if(i === categoryAxisName) { newAxes[i] = axis; } else { newAxes[i] = axis; if(i !== valueAxisName && keys && Y_Lang.isArray(keys)) { ll = keys.length; for(ii = 0; ii < ll; ++ii) { claimedKeys.push(keys[ii]); } valueAxes.push(newAxes[i]); } if(!(this._getBaseAttribute(newAxes[i], "type"))) { this._setBaseAttribute(newAxes[i], "type", seriesAxis); } if(!(this._getBaseAttribute(newAxes[i], "position"))) { this._setBaseAttribute( newAxes[i], "position", this._getDefaultAxisPosition(newAxes[i], valueAxes, seriesPosition) ); } } } } } cIndex = Y.Array.indexOf(seriesKeys, catKey); if(cIndex > -1) { seriesKeys.splice(cIndex, 1); } l = seriesKeys.length; for(i = 0; i < l; ++i) { cIndex = Y.Array.indexOf(claimedKeys, seriesKeys[i]); if(cIndex > -1) { newKeys = newKeys.concat(claimedKeys.splice(cIndex, 1)); } } claimedKeys = newKeys.concat(claimedKeys); l = claimedKeys.length; for(i = 0; i < l; i = i + 1) { cIndex = Y.Array.indexOf(seriesKeys, claimedKeys[i]); if(cIndex > -1) { seriesKeys.splice(cIndex, 1); } } if(!newAxes.hasOwnProperty(categoryAxisName)) { newAxes[categoryAxisName] = {}; } if(!(this._getBaseAttribute(newAxes[categoryAxisName], "keys"))) { this._setBaseAttribute(newAxes[categoryAxisName], "keys", [catKey]); } if(!(this._getBaseAttribute(newAxes[categoryAxisName], "position"))) { this._setBaseAttribute(newAxes[categoryAxisName], "position", categoryPosition); } if(!(this._getBaseAttribute(newAxes[categoryAxisName], "type"))) { this._setBaseAttribute(newAxes[categoryAxisName], "type", this.get("categoryType")); } if(!newAxes.hasOwnProperty(valueAxisName) && seriesKeys && seriesKeys.length > 0) { newAxes[valueAxisName] = {keys:seriesKeys}; valueAxes.push(newAxes[valueAxisName]); } if(claimedKeys.length > 0) { if(seriesKeys.length > 0) { seriesKeys = claimedKeys.concat(seriesKeys); } else { seriesKeys = claimedKeys; } } if(newAxes.hasOwnProperty(valueAxisName)) { if(!(this._getBaseAttribute(newAxes[valueAxisName], "position"))) { this._setBaseAttribute( newAxes[valueAxisName], "position", this._getDefaultAxisPosition(newAxes[valueAxisName], valueAxes, seriesPosition) ); } this._setBaseAttribute(newAxes[valueAxisName], "type", seriesAxis); this._setBaseAttribute(newAxes[valueAxisName], "keys", seriesKeys); } if(!this._wereSeriesKeysExplicitlySet()) { this.set("seriesKeys", seriesKeys, {src: "internal"}); } return newAxes; }, /** * Determines the position of an axis when one is not specified. * * @method _getDefaultAxisPosition * @param {Axis} axis `Axis` instance. * @param {Array} valueAxes Array of `Axis` instances. * @param {String} position Default position depending on the direction of the chart and type of axis. * @return String * @private */ _getDefaultAxisPosition: function(axis, valueAxes, position) { var direction = this.get("direction"), i = Y.Array.indexOf(valueAxes, axis); if(valueAxes[i - 1] && valueAxes[i - 1].position) { if(direction === "horizontal") { if(valueAxes[i - 1].position === "left") { position = "right"; } else if(valueAxes[i - 1].position === "right") { position = "left"; } } else { if (valueAxes[i -1].position === "bottom") { position = "top"; } else { position = "bottom"; } } } return position; }, /** * Returns an object literal containing a categoryItem and a valueItem for a given series index. Below is the structure of each: * * @method getSeriesItems * @param {CartesianSeries} series Reference to a series. * @param {Number} index Index of the specified item within a series. * @return Object An object literal containing the following: * * <dl> * <dt>categoryItem</dt><dd>Object containing the following data related to the category axis of the series. * <dl> * <dt>axis</dt><dd>Reference to the category axis of the series.</dd> * <dt>key</dt><dd>Category key for the series.</dd> * <dt>value</dt><dd>Value on the axis corresponding to the series index.</dd> * </dl> * </dd> * <dt>valueItem</dt><dd>Object containing the following data related to the category axis of the series. * <dl> * <dt>axis</dt><dd>Reference to the value axis of the series.</dd> * <dt>key</dt><dd>Value key for the series.</dd> * <dt>value</dt><dd>Value on the axis corresponding to the series index.</dd> * </dl> * </dd> * </dl> */ getSeriesItems: function(series, index) { var xAxis = series.get("xAxis"), yAxis = series.get("yAxis"), xKey = series.get("xKey"), yKey = series.get("yKey"), categoryItem, valueItem; if(this.get("direction") === "vertical") { categoryItem = { axis:yAxis, key:yKey, value:yAxis.getKeyValueAt(yKey, index) }; valueItem = { axis:xAxis, key:xKey, value: xAxis.getKeyValueAt(xKey, index) }; } else { valueItem = { axis:yAxis, key:yKey, value:yAxis.getKeyValueAt(yKey, index) }; categoryItem = { axis:xAxis, key:xKey, value: xAxis.getKeyValueAt(xKey, index) }; } categoryItem.displayName = series.get("categoryDisplayName"); valueItem.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() { if(this._axesCollection) { var ac = this._axesCollection, i = 0, l = ac.length; for(; i < l; ++i) { this._addToAxesRenderQueue(ac[i]); } this._redraw(); } }, /** * Returns the maximum distance in pixels that the extends outside the top bounds of all vertical axes. * * @method _getTopOverflow * @param {Array} set1 Collection of axes to check. * @param {Array} set2 Seconf collection of axes to check. * @param {Number} width Width of the axes * @return Number * @private */ _getTopOverflow: function(set1, set2, height) { var i = 0, len, overflow = 0, axis; if(set1) { len = set1.length; for(; i < len; ++i) { axis = set1[i]; overflow = Math.max( overflow, Math.abs(axis.getMaxLabelBounds().top) - axis.getEdgeOffset(axis.get("styles").majorTicks.count, height) ); } } if(set2) { i = 0; len = set2.length; for(; i < len; ++i) { axis = set2[i]; overflow = Math.max( overflow, Math.abs(axis.getMaxLabelBounds().top) - axis.getEdgeOffset(axis.get("styles").majorTicks.count, height) ); } } return overflow; }, /** * Returns the maximum distance in pixels that the extends outside the right bounds of all horizontal axes. * * @method _getRightOverflow * @param {Array} set1 Collection of axes to check. * @param {Array} set2 Seconf collection of axes to check. * @param {Number} width Width of the axes * @return Number * @private */ _getRightOverflow: function(set1, set2, width) { var i = 0, len, overflow = 0, axis; if(set1) { len = set1.length; for(; i < len; ++i) { axis = set1[i]; overflow = Math.max( overflow, axis.getMaxLabelBounds().right - axis.getEdgeOffset(axis.get("styles").majorTicks.count, width) ); } } if(set2) { i = 0; len = set2.length; for(; i < len; ++i) { axis = set2[i]; overflow = Math.max( overflow, axis.getMaxLabelBounds().right - axis.getEdgeOffset(axis.get("styles").majorTicks.count, width) ); } } return overflow; }, /** * Returns the maximum distance in pixels that the extends outside the left bounds of all horizontal axes. * * @method _getLeftOverflow * @param {Array} set1 Collection of axes to check. * @param {Array} set2 Seconf collection of axes to check. * @param {Number} width Width of the axes * @return Number * @private */ _getLeftOverflow: function(set1, set2, width) { var i = 0, len, overflow = 0, axis; if(set1) { len = set1.length; for(; i < len; ++i) { axis = set1[i]; overflow = Math.max( overflow, Math.abs(axis.getMinLabelBounds().left) - axis.getEdgeOffset(axis.get("styles").majorTicks.count, width) ); } } if(set2) { i = 0; len = set2.length; for(; i < len; ++i) { axis = set2[i]; overflow = Math.max( overflow, Math.abs(axis.getMinLabelBounds().left) - axis.getEdgeOffset(axis.get("styles").majorTicks.count, width) ); } } return overflow; }, /** * Returns the maximum distance in pixels that the extends outside the bottom bounds of all vertical axes. * * @method _getBottomOverflow * @param {Array} set1 Collection of axes to check. * @param {Array} set2 Seconf collection of axes to check. * @param {Number} height Height of the axes * @return Number * @private */ _getBottomOverflow: function(set1, set2, height) { var i = 0, len, overflow = 0, axis; if(set1) { len = set1.length; for(; i < len; ++i) { axis = set1[i]; overflow = Math.max( overflow, axis.getMinLabelBounds().bottom - axis.getEdgeOffset(axis.get("styles").majorTicks.count, height) ); } } if(set2) { i = 0; len = set2.length; for(; i < len; ++i) { axis = set2[i]; overflow = Math.max( overflow, axis.getMinLabelBounds().bottom - axis.getEdgeOffset(axis.get("styles").majorTicks.count, height) ); } } return overflow; }, /** * Redraws and position all the components of the chart instance. * * @method _redraw * @private */ _redraw: function() { if(this._drawing) { this._callLater = true; return; } this._drawing = true; this._callLater = false; var w = this.get("width"), h = this.get("height"), leftPaneWidth = 0, rightPaneWidth = 0, topPaneHeight = 0, bottomPaneHeight = 0, leftAxesCollection = this.get("leftAxesCollection"), rightAxesCollection = this.get("rightAxesCollection"), topAxesCollection = this.get("topAxesCollection"), bottomAxesCollection = this.get("bottomAxesCollection"), i = 0, l, axis, graphOverflow = "visible", graph = this.get("graph"), topOverflow, bottomOverflow, leftOverflow, rightOverflow, graphWidth, graphHeight, graphX, graphY, allowContentOverflow = this.get("allowContentOverflow"), diff, rightAxesXCoords, leftAxesXCoords, topAxesYCoords, bottomAxesYCoords, graphRect = {}; if(leftAxesCollection) { leftAxesXCoords = []; l = leftAxesCollection.length; for(i = l - 1; i > -1; --i) { leftAxesXCoords.unshift(leftPaneWidth); leftPaneWidth += leftAxesCollection[i].get("width"); } } if(rightAxesCollection) { rightAxesXCoords = []; l = rightAxesCollection.length; i = 0; for(i = l - 1; i > -1; --i) { rightPaneWidth += rightAxesCollection[i].get("width"); rightAxesXCoords.unshift(w - rightPaneWidth); } } if(topAxesCollection) { topAxesYCoords = []; l = topAxesCollection.length; for(i = l - 1; i > -1; --i) { topAxesYCoords.unshift(topPaneHeight); topPaneHeight += topAxesCollection[i].get("height"); } } if(bottomAxesCollection) { bottomAxesYCoords = []; l = bottomAxesCollection.length; for(i = l - 1; i > -1; --i) { bottomPaneHeight += bottomAxesCollection[i].get("height"); bottomAxesYCoords.unshift(h - bottomPaneHeight); } } graphWidth = w - (leftPaneWidth + rightPaneWidth); graphHeight = h - (bottomPaneHeight + topPaneHeight); graphRect.left = leftPaneWidth; graphRect.top = topPaneHeight; graphRect.bottom = h - bottomPaneHeight; graphRect.right = w - rightPaneWidth; if(!allowContentOverflow) { topOverflow = this._getTopOverflow(leftAxesCollection, rightAxesCollection); bottomOverflow = this._getBottomOverflow(leftAxesCollection, rightAxesCollection); leftOverflow = this._getLeftOverflow(bottomAxesCollection, topAxesCollection); rightOverflow = this._getRightOverflow(bottomAxesCollection, topAxesCollection); diff = topOverflow - topPaneHeight; if(diff > 0) { graphRect.top = topOverflow; if(topAxesYCoords) { i = 0; l = topAxesYCoords.length; for(; i < l; ++i) { topAxesYCoords[i] += diff; } } } diff = bottomOverflow - bottomPaneHeight; if(diff > 0) { graphRect.bottom = h - bottomOverflow; if(bottomAxesYCoords) { i = 0; l = bottomAxesYCoords.length; for(; i < l; ++i) { bottomAxesYCoords[i] -= diff; } } } diff = leftOverflow - leftPaneWidth; if(diff > 0) { graphRect.left = leftOverflow; if(leftAxesXCoords) { i = 0; l = leftAxesXCoords.length; for(; i < l; ++i) { leftAxesXCoords[i] += diff; } } } diff = rightOverflow - rightPaneWidth; if(diff > 0) { graphRect.right = w - rightOverflow; if(rightAxesXCoords) { i = 0; l = rightAxesXCoords.length; for(; i < l; ++i) { rightAxesXCoords[i] -= diff; } } } } graphWidth = graphRect.right - graphRect.left; graphHeight = graphRect.bottom - graphRect.top; graphX = graphRect.left; graphY = graphRect.top; if(topAxesCollection) { l = topAxesCollection.length; i = 0; for(; i < l; i++) { axis = topAxesCollection[i]; if(axis.get("width") !== graphWidth) { axis.set("width", graphWidth); } axis.get("boundingBox").setStyle("left", graphX + "px"); axis.get("boundingBox").setStyle("top", topAxesYCoords[i] + "px"); } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } if(bottomAxesCollection) { l = bottomAxesCollection.length; i = 0; for(; i < l; i++) { axis = bottomAxesCollection[i]; if(axis.get("width") !== graphWidth) { axis.set("width", graphWidth); } axis.get("boundingBox").setStyle("left", graphX + "px"); axis.get("boundingBox").setStyle("top", bottomAxesYCoords[i] + "px"); } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } if(leftAxesCollection) { l = leftAxesCollection.length; i = 0; for(; i < l; ++i) { axis = leftAxesCollection[i]; axis.get("boundingBox").setStyle("top", graphY + "px"); axis.get("boundingBox").setStyle("left", leftAxesXCoords[i] + "px"); if(axis.get("height") !== graphHeight) { axis.set("height", graphHeight); } } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } if(rightAxesCollection) { l = rightAxesCollection.length; i = 0; for(; i < l; ++i) { axis = rightAxesCollection[i]; axis.get("boundingBox").setStyle("top", graphY + "px"); axis.get("boundingBox").setStyle("left", rightAxesXCoords[i] + "px"); if(axis.get("height") !== graphHeight) { axis.set("height", graphHeight); } } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } this._drawing = false; if(this._callLater) { this._redraw(); return; } if(graph) { graph.get("boundingBox").setStyle("left", graphX + "px"); graph.get("boundingBox").setStyle("top", graphY + "px"); graph.set("width", graphWidth); graph.set("height", graphHeight); graph.get("boundingBox").setStyle("overflow", graphOverflow); } if(this._overlay) { this._overlay.setStyle("left", graphX + "px"); this._overlay.setStyle("top", graphY + "px"); this._overlay.setStyle("width", graphWidth + "px"); this._overlay.setStyle("height", graphHeight + "px"); } }, /** * Destructor implementation for the CartesianChart class. Calls destroy on all axes, series and the Graph instance. * Removes the tooltip and overlay HTML elements. * * @method destructor * @protected */ destructor: function() { var graph = this.get("graph"), i = 0, len, seriesCollection = this.get("seriesCollection"), axesCollection = this._axesCollection, tooltip = this.get("tooltip").node; if(this._description) { this._description.empty(); this._description.remove(true); } if(this._liveRegion) { this._liveRegion.empty(); this._liveRegion.remove(true); } len = seriesCollection ? seriesCollection.length : 0; for(; i < len; ++i) { if(seriesCollection[i] instanceof Y.CartesianSeries) { seriesCollection[i].destroy(true); } } len = axesCollection ? axesCollection.length : 0; for(i = 0; i < len; ++i) { if(axesCollection[i] instanceof Y.Axis) { axesCollection[i].destroy(true); } } if(graph) { graph.destroy(true); } if(tooltip) { tooltip.empty(); tooltip.remove(true); } if(this._overlay) { this._overlay.empty(); this._overlay.remove(true); } }, /** * 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 = "", series, items, categoryItem, valueItem, seriesIndex = this._seriesIndex, itemIndex = this._itemIndex, seriesCollection = this.get("seriesCollection"), len = seriesCollection.length, dataLength; if(key % 2 === 0) { if(len > 1) { if(key === 38) { seriesIndex = seriesIndex < 1 ? len - 1 : seriesIndex - 1; } else if(key === 40) { seriesIndex = seriesIndex >= len - 1 ? 0 : seriesIndex + 1; } this._itemIndex = -1; } else { seriesIndex = 0; } this._seriesIndex = seriesIndex; series = this.getSeries(parseInt(seriesIndex, 10)); msg = series.get("valueDisplayName") + " series."; } else { if(seriesIndex > -1) { msg = ""; series = this.getSeries(parseInt(seriesIndex, 10)); } else { seriesIndex = 0; this._seriesIndex = seriesIndex; series = this.getSeries(parseInt(seriesIndex, 10)); msg = series.get("valueDisplayName") + " series."; } dataLength = series._dataLength ? series._dataLength : 0; if(key === 37) { itemIndex = itemIndex > 0 ? itemIndex - 1 : dataLength - 1; } else if(key === 39) { itemIndex = itemIndex >= dataLength - 1 ? 0 : itemIndex + 1; } this._itemIndex = itemIndex; items = this.getSeriesItems(series, itemIndex); categoryItem = items.category; valueItem = items.value; if(categoryItem && valueItem && categoryItem.value && valueItem.value) { 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")]) + ", "; } else { msg += "No data available."; } msg += (itemIndex + 1) + " of " + dataLength + ". "; } return msg; } }, { ATTRS: { /** * Indicates whether axis labels are allowed to overflow beyond the bounds of the chart's content box. * * @attribute allowContentOverflow * @type Boolean */ allowContentOverflow: { value: false }, /** * Style object for the axes. * * @attribute axesStyles * @type Object * @private */ axesStyles: { lazyAdd: false, getter: function() { var axes = this.get("axes"), i, styles = this._axesStyles; if(axes) { for(i in axes) { if(axes.hasOwnProperty(i) && axes[i] instanceof Y.Axis) { if(!styles) { styles = {}; } styles[i] = axes[i].get("styles"); } } } return styles; }, setter: function(val) { var axes = this.get("axes"), i; for(i in val) { if(val.hasOwnProperty(i) && axes.hasOwnProperty(i)) { this._setBaseAttribute(axes[i], "styles", val[i]); } } return val; } }, /** * Style object for the series * * @attribute seriesStyles * @type Object * @private */ seriesStyles: { lazyAdd: false, getter: function() { var styles = this._seriesStyles, graph = this.get("graph"), dict, i; if(graph) { dict = graph.get("seriesDictionary"); if(dict) { styles = {}; for(i in dict) { if(dict.hasOwnProperty(i)) { styles[i] = dict[i].get("styles"); } } } } return styles; }, setter: function(val) { var i, l, s; if(Y_Lang.isArray(val)) { s = this.get("seriesCollection"); i = 0; l = val.length; for(; i < l; ++i) { this._setBaseAttribute(s[i], "styles", val[i]); } } else { for(i in val) { if(val.hasOwnProperty(i)) { s = this.getSeries(i); this._setBaseAttribute(s, "styles", val[i]); } } } return val; } }, /** * Styles for the graph. * * @attribute graphStyles * @type Object * @private */ graphStyles: { lazyAdd: false, getter: function() { var graph = this.get("graph"); if(graph) { return(graph.get("styles")); } return this._graphStyles; }, setter: function(val) { var graph = this.get("graph"); this._setBaseAttribute(graph, "styles", val); return val; } }, /** * Style properties for the chart. Contains a key indexed hash of the following: * <dl> * <dt>series</dt><dd>A key indexed hash containing references to the `styles` attribute for each series in the chart. * Specific style attributes vary depending on the series: * <ul> * <li><a href="AreaSeries.html#attr_styles">AreaSeries</a></li> * <li><a href="BarSeries.html#attr_styles">BarSeries</a></li> * <li><a href="ColumnSeries.html#attr_styles">ColumnSeries</a></li> * <li><a href="ComboSeries.html#attr_styles">ComboSeries</a></li> * <li><a href="LineSeries.html#attr_styles">LineSeries</a></li> * <li><a href="MarkerSeries.html#attr_styles">MarkerSeries</a></li> * <li><a href="SplineSeries.html#attr_styles">SplineSeries</a></li> * </ul> * </dd> * <dt>axes</dt><dd>A key indexed hash containing references to the `styles` attribute for each axes in the chart. Specific * style attributes can be found in the <a href="Axis.html#attr_styles">Axis</a> class.</dd> * <dt>graph</dt><dd>A reference to the `styles` attribute in the chart. Specific style attributes can be found in the * <a href="Graph.html#attr_styles">Graph</a> class.</dd> * </dl> * * @attribute styles * @type Object */ styles: { lazyAdd: false, getter: function() { var styles = { axes: this.get("axesStyles"), series: this.get("seriesStyles"), graph: this.get("graphStyles") }; return styles; }, setter: function(val) { if(val.hasOwnProperty("axes")) { if(this.get("axesStyles")) { this.set("axesStyles", val.axes); } else { this._axesStyles = val.axes; } } if(val.hasOwnProperty("series")) { if(this.get("seriesStyles")) { this.set("seriesStyles", val.series); } else { this._seriesStyles = val.series; } } if(val.hasOwnProperty("graph")) { this.set("graphStyles", val.graph); } } }, /** * Axes to appear in the chart. This can be a key indexed hash of axis instances or object literals * used to construct the appropriate axes. * * @attribute axes * @type Object */ axes: { lazyAdd: false, valueFn: "_getDefaultAxes", setter: function(val) { if(this.get("dataProvider")) { val = this._setAxes(val); } return val; } }, /** * Collection of series to appear on the chart. This can be an array of Series instances or object literals * used to construct the appropriate series. * * @attribute seriesCollection * @type Array */ seriesCollection: { lazyAdd: false, valueFn: "_getDefaultSeriesCollection", setter: function(val) { if(this.get("dataProvider")) { return this._parseSeriesCollection(val); } return val; } }, /** * Reference to the left-aligned axes for the chart. * * @attribute leftAxesCollection * @type Array * @private */ leftAxesCollection: {}, /** * Reference to the bottom-aligned axes for the chart. * * @attribute bottomAxesCollection * @type Array * @private */ bottomAxesCollection: {}, /** * Reference to the right-aligned axes for the chart. * * @attribute rightAxesCollection * @type Array * @private */ rightAxesCollection: {}, /** * Reference to the top-aligned axes for the chart. * * @attribute topAxesCollection * @type Array * @private */ topAxesCollection: {}, /** * Indicates whether or not the chart is stacked. * * @attribute stacked * @type Boolean */ stacked: { value: false }, /** * Direction of chart's category axis when there is no series collection specified. Charts can * be horizontal or vertical. When the chart type is column, the chart is horizontal. * When the chart type is bar, the chart is vertical. * * @attribute direction * @type String */ direction: { getter: function() { var type = this.get("type"); if(type === "bar") { return "vertical"; } else if(type === "column") { return "horizontal"; } return this._direction; }, setter: function(val) { this._direction = val; return this._direction; } }, /** * Indicates whether or not an area is filled in a combo chart. * * @attribute showAreaFill * @type Boolean */ showAreaFill: {}, /** * Indicates whether to display markers in a combo chart. * * @attribute showMarkers * @type Boolean */ showMarkers:{}, /** * Indicates whether to display lines in a combo chart. * * @attribute showLines * @type Boolean */ showLines:{}, /** * Indicates the key value used to identify a category axis in the `axes` hash. If * not specified, the categoryKey attribute value will be used. * * @attribute categoryAxisName * @type String */ categoryAxisName: { }, /** * Indicates the key value used to identify a the series axis when an axis not generated. * * @attribute valueAxisName * @type String */ valueAxisName: { value: "values" }, /** * Reference to the horizontalGridlines for the chart. * * @attribute horizontalGridlines * @type Gridlines */ horizontalGridlines: { getter: function() { var graph = this.get("graph"); if(graph) { return graph.get("horizontalGridlines"); } return this._horizontalGridlines; }, setter: function(val) { var graph = this.get("graph"); if(val && !Y_Lang.isObject(val)) { val = {}; } if(graph) { graph.set("horizontalGridlines", val); } else { this._horizontalGridlines = val; } } }, /** * Reference to the verticalGridlines for the chart. * * @attribute verticalGridlines * @type Gridlines */ verticalGridlines: { getter: function() { var graph = this.get("graph"); if(graph) { return graph.get("verticalGridlines"); } return this._verticalGridlines; }, setter: function(val) { var graph = this.get("graph"); if(val && !Y_Lang.isObject(val)) { val = {}; } if(graph) { graph.set("verticalGridlines", val); } else { this._verticalGridlines = val; } } }, /** * Type of chart when there is no series collection specified. * * @attribute type * @type String */ type: { getter: function() { if(this.get("stacked")) { return "stacked" + this._type; } return this._type; }, setter: function(val) { if(this._type === "bar") { if(val !== "bar") { this.set("direction", "horizontal"); } } else { if(val === "bar") { this.set("direction", "vertical"); } } this._type = val; return this._type; } }, /** * Reference to the category axis used by the chart. * * @attribute categoryAxis * @type Axis */ categoryAxis:{} } });