diff -r 322d0feea350 -r 89ef5ed3c48b src/cm/media/js/lib/yui/yui_3.10.3/build/charts-base/charts-base.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/yui/yui_3.10.3/build/charts-base/charts-base.js Tue Jul 16 14:29:46 2013 +0200 @@ -0,0 +1,5119 @@ +/* +YUI 3.10.3 (build 2fb5187) +Copyright 2013 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +http://yuilibrary.com/license/ +*/ + +YUI.add('charts-base', function (Y, NAME) { + +/** + * Provides functionality for creating charts. + * + * @module charts + * @submodule charts-base + */ +var CONFIG = Y.config, + WINDOW = CONFIG.win, + DOCUMENT = CONFIG.doc, + Y_Lang = Y.Lang, + IS_STRING = Y_Lang.isString, + _getClassName = Y.ClassNameManager.getClassName, + SERIES_MARKER = _getClassName("seriesmarker"); + +/** + * Gridlines draws gridlines on a Graph. + * + * @class Gridlines + * @constructor + * @extends Base + * @uses Renderer + * @param {Object} config (optional) Configuration parameters. + * @submodule charts-base + */ +Y.Gridlines = Y.Base.create("gridlines", Y.Base, [Y.Renderer], { + /** + * Reference to the `Path` element used for drawing Gridlines. + * + * @property _path + * @type Path + * @private + */ + _path: null, + + /** + * Removes the Gridlines. + * + * @method remove + * @private + */ + remove: function() + { + var path = this._path; + if(path) + { + path.destroy(); + } + }, + + /** + * Draws the gridlines + * + * @method draw + * @protected + */ + draw: function() + { + if(this.get("axis") && this.get("graph")) + { + this._drawGridlines(); + } + }, + + /** + * Algorithm for drawing gridlines + * + * @method _drawGridlines + * @private + */ + _drawGridlines: function() + { + var path, + axis = this.get("axis"), + axisPosition = axis.get("position"), + points, + i = 0, + l, + direction = this.get("direction"), + graph = this.get("graph"), + w = graph.get("width"), + h = graph.get("height"), + line = this.get("styles").line, + color = line.color, + weight = line.weight, + alpha = line.alpha, + count = this.get("count"), + length, + lineFunction; + if(isFinite(w) && isFinite(h) && w > 0 && h > 0) + { + if(count && Y.Lang.isNumber(count)) + { + points = this._getPoints(count, w, h); + } + else if(axisPosition !== "none" && axis && axis.get("tickPoints")) + { + points = axis.get("tickPoints"); + } + else + { + points = this._getPoints(axis.get("styles").majorUnit.count, w, h); + } + l = points.length; + path = graph.get("gridlines"); + path.set("width", w); + path.set("height", h); + path.set("stroke", { + weight: weight, + color: color, + opacity: alpha + }); + if(direction === "vertical") + { + lineFunction = this._verticalLine; + length = h; + } + else + { + lineFunction = this._horizontalLine; + length = w; + } + for(i = 0; i < l; i = i + 1) + { + lineFunction(path, points[i], length); + } + path.end(); + } + }, + + /** + * Calculates the coordinates for the gridlines based on a count. + * + * @method _getPoints + * @param {Number} count Number of gridlines + * @return Array + * @private + */ + _getPoints: function(count, w, h) + { + var i, + points = [], + multiplier, + divisor = count - 1; + for(i = 0; i < count; i = i + 1) + { + multiplier = i/divisor; + points[i] = { + x: w * multiplier, + y: h * multiplier + }; + } + return points; + }, + + /** + * Algorithm for horizontal lines. + * + * @method _horizontalLine + * @param {Path} path Reference to path element + * @param {Object} pt Coordinates corresponding to a major unit of an axis. + * @param {Number} w Width of the Graph + * @private + */ + _horizontalLine: function(path, pt, w) + { + path.moveTo(0, pt.y); + path.lineTo(w, pt.y); + }, + + /** + * Algorithm for vertical lines. + * + * @method _verticalLine + * @param {Path} path Reference to path element + * @param {Object} pt Coordinates corresponding to a major unit of an axis. + * @param {Number} h Height of the Graph + * @private + */ + _verticalLine: function(path, pt, h) + { + path.moveTo(pt.x, 0); + path.lineTo(pt.x, h); + }, + + /** + * Gets the default value for the `styles` attribute. Overrides + * base implementation. + * + * @method _getDefaultStyles + * @return Object + * @protected + */ + _getDefaultStyles: function() + { + var defs = { + line: { + color:"#f0efe9", + weight: 1, + alpha: 1 + } + }; + return defs; + } + +}, +{ + ATTRS: { + /** + * Indicates the direction of the gridline. + * + * @attribute direction + * @type String + */ + direction: {}, + + /** + * Indicate the `Axis` in which to bind + * the gridlines. + * + * @attribute axis + * @type Axis + */ + axis: {}, + + /** + * Indicates the `Graph` in which the gridlines + * are drawn. + * + * @attribute graph + * @type Graph + */ + graph: {}, + + /** + * Indicates the number of gridlines to display. If no value is set, gridlines will equal the number of ticks in + * the corresponding axis. + * + * @attribute count + * @type Number + */ + count: {} + } +}); +/** + * Graph manages and contains series instances for a `CartesianChart` + * instance. + * + * @class Graph + * @constructor + * @extends Widget + * @uses Renderer + * @submodule charts-base + */ +Y.Graph = Y.Base.create("graph", Y.Widget, [Y.Renderer], { + /** + * @method bindUI + * @private + */ + bindUI: function() + { + var bb = this.get("boundingBox"); + bb.setStyle("position", "absolute"); + this.after("widthChange", this._sizeChangeHandler); + this.after("heightChange", this._sizeChangeHandler); + this.after("stylesChange", this._updateStyles); + this.after("groupMarkersChange", this._drawSeries); + }, + + /** + * @method syncUI + * @private + */ + syncUI: function() + { + var background, + cb, + bg, + sc = this.get("seriesCollection"), + series, + i = 0, + len = sc ? sc.length : 0, + hgl = this.get("horizontalGridlines"), + vgl = this.get("verticalGridlines"); + if(this.get("showBackground")) + { + background = this.get("background"); + cb = this.get("contentBox"); + bg = this.get("styles").background; + bg.stroke = bg.border; + bg.stroke.opacity = bg.stroke.alpha; + bg.fill.opacity = bg.fill.alpha; + bg.width = this.get("width"); + bg.height = this.get("height"); + bg.type = bg.shape; + background.set(bg); + } + for(; i < len; ++i) + { + series = sc[i]; + if(series instanceof Y.SeriesBase) + { + series.render(); + } + } + if(hgl && hgl instanceof Y.Gridlines) + { + hgl.draw(); + } + if(vgl && vgl instanceof Y.Gridlines) + { + vgl.draw(); + } + }, + + /** + * Object of arrays containing series mapped to a series type. + * + * @property seriesTypes + * @type Object + * @private + */ + seriesTypes: null, + + /** + * Returns a series instance based on an index. + * + * @method getSeriesByIndex + * @param {Number} val index of the series + * @return CartesianSeries + */ + getSeriesByIndex: function(val) + { + var col = this.get("seriesCollection"), + series; + if(col && col.length > val) + { + series = col[val]; + } + return series; + }, + + /** + * Returns a series instance based on a key value. + * + * @method getSeriesByKey + * @param {String} val key value of the series + * @return CartesianSeries + */ + getSeriesByKey: function(val) + { + var obj = this._seriesDictionary, + series; + if(obj && obj.hasOwnProperty(val)) + { + series = obj[val]; + } + return series; + }, + + /** + * Adds dispatcher to a `_dispatcher` used to + * to ensure all series have redrawn before for firing event. + * + * @method addDispatcher + * @param {CartesianSeries} val series instance to add + * @protected + */ + addDispatcher: function(val) + { + if(!this._dispatchers) + { + this._dispatchers = []; + } + this._dispatchers.push(val); + }, + + /** + * Collection of series to be displayed in the graph. + * + * @property _seriesCollection + * @type Array + * @private + */ + _seriesCollection: null, + + /** + * Object containing key value pairs of `CartesianSeries` instances. + * + * @property _seriesDictionary + * @type Object + * @private + */ + _seriesDictionary: null, + + /** + * Parses series instances to be displayed in the graph. + * + * @method _parseSeriesCollection + * @param {Array} Collection of `CartesianSeries` instances or objects container `CartesianSeries` attributes values. + * @private + */ + _parseSeriesCollection: function(val) + { + if(!val) + { + return; + } + var len = val.length, + i = 0, + series, + seriesKey; + this._seriesCollection = []; + this._seriesDictionary = {}; + this.seriesTypes = []; + for(; i < len; ++i) + { + series = val[i]; + if(!(series instanceof Y.CartesianSeries) && !(series instanceof Y.PieSeries)) + { + this._createSeries(series); + continue; + } + this._addSeries(series); + } + len = this._seriesCollection.length; + for(i = 0; i < len; ++i) + { + series = this.get("seriesCollection")[i]; + seriesKey = series.get("direction") === "horizontal" ? "yKey" : "xKey"; + this._seriesDictionary[series.get(seriesKey)] = series; + } + }, + + /** + * Adds a series to the graph. + * + * @method _addSeries + * @param {CartesianSeries} series Series to add to the graph. + * @private + */ + _addSeries: function(series) + { + var type = series.get("type"), + seriesCollection = this.get("seriesCollection"), + graphSeriesLength = seriesCollection.length, + seriesTypes = this.seriesTypes, + typeSeriesCollection; + if(!series.get("graph")) + { + series.set("graph", this); + } + seriesCollection.push(series); + if(!seriesTypes.hasOwnProperty(type)) + { + this.seriesTypes[type] = []; + } + typeSeriesCollection = this.seriesTypes[type]; + series.set("graphOrder", graphSeriesLength); + series.set("order", typeSeriesCollection.length); + typeSeriesCollection.push(series); + series.set("seriesTypeCollection", typeSeriesCollection); + this.addDispatcher(series); + series.after("drawingComplete", Y.bind(this._drawingCompleteHandler, this)); + this.fire("seriesAdded", series); + }, + + /** + * Creates a `CartesianSeries` instance from an object containing attribute key value pairs. The key value pairs include + * attributes for the specific series and a type value which defines the type of series to be used. + * + * @method createSeries + * @param {Object} seriesData Series attribute key value pairs. + * @private + */ + _createSeries: function(seriesData) + { + var type = seriesData.type, + seriesCollection = this.get("seriesCollection"), + seriesTypes = this.seriesTypes, + typeSeriesCollection, + SeriesClass, + series; + seriesData.graph = this; + if(!seriesTypes.hasOwnProperty(type)) + { + seriesTypes[type] = []; + } + typeSeriesCollection = seriesTypes[type]; + seriesData.graph = this; + seriesData.order = typeSeriesCollection.length; + seriesData.graphOrder = seriesCollection.length; + SeriesClass = this._getSeries(seriesData.type); + series = new SeriesClass(seriesData); + this.addDispatcher(series); + series.after("drawingComplete", Y.bind(this._drawingCompleteHandler, this)); + typeSeriesCollection.push(series); + seriesCollection.push(series); + series.set("seriesTypeCollection", typeSeriesCollection); + if(this.get("rendered")) + { + series.render(); + } + }, + + /** + * String reference for pre-defined `Series` classes. + * + * @property _seriesMap + * @type Object + * @private + */ + _seriesMap: { + line : Y.LineSeries, + column : Y.ColumnSeries, + bar : Y.BarSeries, + area : Y.AreaSeries, + candlestick : Y.CandlestickSeries, + ohlc : Y.OHLCSeries, + stackedarea : Y.StackedAreaSeries, + stackedline : Y.StackedLineSeries, + stackedcolumn : Y.StackedColumnSeries, + stackedbar : Y.StackedBarSeries, + markerseries : Y.MarkerSeries, + spline : Y.SplineSeries, + areaspline : Y.AreaSplineSeries, + stackedspline : Y.StackedSplineSeries, + stackedareaspline : Y.StackedAreaSplineSeries, + stackedmarkerseries : Y.StackedMarkerSeries, + pie : Y.PieSeries, + combo : Y.ComboSeries, + stackedcombo : Y.StackedComboSeries, + combospline : Y.ComboSplineSeries, + stackedcombospline : Y.StackedComboSplineSeries + }, + + /** + * Returns a specific `CartesianSeries` class based on key value from a look up table of a direct reference to a + * class. When specifying a key value, the following options are available: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Key ValueClass
lineY.LineSeries
columnY.ColumnSeries
barY.BarSeries
areaY.AreaSeries
stackedareaY.StackedAreaSeries
stackedlineY.StackedLineSeries
stackedcolumnY.StackedColumnSeries
stackedbarY.StackedBarSeries
markerseriesY.MarkerSeries
splineY.SplineSeries
areasplineY.AreaSplineSeries
stackedsplineY.StackedSplineSeries
stackedareasplineY.StackedAreaSplineSeries
stackedmarkerseriesY.StackedMarkerSeries
pieY.PieSeries
comboY.ComboSeries
stackedcomboY.StackedComboSeries
combosplineY.ComboSplineSeries
stackedcombosplineY.StackedComboSplineSeries
+ * + * When referencing a class directly, you can specify any of the above classes or any custom class that extends + * `CartesianSeries` or `PieSeries`. + * + * @method _getSeries + * @param {String | Object} type Series type. + * @return CartesianSeries + * @private + */ + _getSeries: function(type) + { + var seriesClass; + if(Y_Lang.isString(type)) + { + seriesClass = this._seriesMap[type]; + } + else + { + seriesClass = type; + } + return seriesClass; + }, + + /** + * Event handler for marker events. + * + * @method _markerEventHandler + * @param {Object} e Event object. + * @private + */ + _markerEventHandler: function(e) + { + var type = e.type, + markerNode = e.currentTarget, + strArr = markerNode.getAttribute("id").split("_"), + series = this.getSeriesByIndex(strArr[1]), + index = strArr[2]; + series.updateMarkerState(type, index); + }, + + /** + * Collection of `CartesianSeries` instances to be redrawn. + * + * @property _dispatchers + * @type Array + * @private + */ + _dispatchers: null, + + /** + * Updates the `Graph` styles. + * + * @method _updateStyles + * @private + */ + _updateStyles: function() + { + var styles = this.get("styles").background, + border = styles.border; + border.opacity = border.alpha; + styles.stroke = border; + styles.fill.opacity = styles.fill.alpha; + this.get("background").set(styles); + this._sizeChangeHandler(); + }, + + /** + * Event handler for size changes. + * + * @method _sizeChangeHandler + * @param {Object} e Event object. + * @private + */ + _sizeChangeHandler: function() + { + var hgl = this.get("horizontalGridlines"), + vgl = this.get("verticalGridlines"), + w = this.get("width"), + h = this.get("height"), + bg = this.get("styles").background, + weight, + background; + if(bg && bg.border) + { + weight = bg.border.weight || 0; + } + if(this.get("showBackground")) + { + background = this.get("background"); + if(w && h) + { + background.set("width", w); + background.set("height", h); + } + } + if(this._gridlines) + { + this._gridlines.clear(); + } + if(hgl && hgl instanceof Y.Gridlines) + { + hgl.draw(); + } + if(vgl && vgl instanceof Y.Gridlines) + { + vgl.draw(); + } + this._drawSeries(); + }, + + /** + * Draws each series. + * + * @method _drawSeries + * @private + */ + _drawSeries: function() + { + if(this._drawing) + { + this._callLater = true; + return; + } + var sc, + i, + len, + graphic = this.get("graphic"); + graphic.set("autoDraw", false); + graphic.set("width", this.get("width")); + graphic.set("height", this.get("height")); + this._callLater = false; + this._drawing = true; + sc = this.get("seriesCollection"); + i = 0; + len = sc ? sc.length : 0; + for(; i < len; ++i) + { + sc[i].draw(); + if((!sc[i].get("xcoords") || !sc[i].get("ycoords")) && !sc[i] instanceof Y.PieSeries) + { + this._callLater = true; + break; + } + } + this._drawing = false; + if(this._callLater) + { + this._drawSeries(); + } + }, + + /** + * Event handler for series drawingComplete event. + * + * @method _drawingCompleteHandler + * @param {Object} e Event object. + * @private + */ + _drawingCompleteHandler: function(e) + { + var series = e.currentTarget, + graphic, + index = Y.Array.indexOf(this._dispatchers, series); + if(index > -1) + { + this._dispatchers.splice(index, 1); + } + if(this._dispatchers.length < 1) + { + graphic = this.get("graphic"); + if(!graphic.get("autoDraw")) + { + graphic._redraw(); + } + this.fire("chartRendered"); + } + }, + + /** + * Gets the default value for the `styles` attribute. Overrides + * base implementation. + * + * @method _getDefaultStyles + * @return Object + * @protected + */ + _getDefaultStyles: function() + { + var defs = { + background: { + shape: "rect", + fill:{ + color:"#faf9f2" + }, + border: { + color:"#dad8c9", + weight: 1 + } + } + }; + return defs; + }, + + /** + * Destructor implementation Graph class. Removes all Graphic instances from the widget. + * + * @method destructor + * @protected + */ + destructor: function() + { + if(this._graphic) + { + this._graphic.destroy(); + this._graphic = null; + } + if(this._background) + { + this._background.get("graphic").destroy(); + this._background = null; + } + if(this._gridlines) + { + this._gridlines.get("graphic").destroy(); + this._gridlines = null; + } + } +}, { + ATTRS: { + /** + * The x-coordinate for the graph. + * + * @attribute x + * @type Number + * @protected + */ + x: { + setter: function(val) + { + this.get("boundingBox").setStyle("left", val + "px"); + return val; + } + }, + + /** + * The y-coordinate for the graph. + * + * @attribute y + * @type Number + * @protected + */ + y: { + setter: function(val) + { + this.get("boundingBox").setStyle("top", val + "px"); + return val; + } + }, + + /** + * Reference to the chart instance using the graph. + * + * @attribute chart + * @type ChartBase + * @readOnly + */ + chart: { + getter: function() { + var chart = this._state.chart || this; + return chart; + } + }, + + /** + * Collection of series. When setting the `seriesCollection` the array can contain a combination of either + * `CartesianSeries` instances or object literals with properties that will define a series. + * + * @attribute seriesCollection + * @type CartesianSeries + */ + seriesCollection: { + getter: function() + { + return this._seriesCollection; + }, + + setter: function(val) + { + this._parseSeriesCollection(val); + return this._seriesCollection; + } + }, + + /** + * Indicates whether the `Graph` has a background. + * + * @attribute showBackground + * @type Boolean + * @default true + */ + showBackground: { + value: true + }, + + /** + * Read-only hash lookup for all series on in the `Graph`. + * + * @attribute seriesDictionary + * @type Object + * @readOnly + */ + seriesDictionary: { + readOnly: true, + + getter: function() + { + return this._seriesDictionary; + } + }, + + /** + * Reference to the horizontal `Gridlines` instance. + * + * @attribute horizontalGridlines + * @type Gridlines + * @default null + */ + horizontalGridlines: { + value: null, + + setter: function(val) + { + var cfg, + key, + gl = this.get("horizontalGridlines"); + if(gl && gl instanceof Y.Gridlines) + { + gl.remove(); + } + if(val instanceof Y.Gridlines) + { + gl = val; + val.set("graph", this); + return val; + } + else if(val) + { + cfg = { + direction: "horizonal", + graph: this + }; + for(key in val) + { + if(val.hasOwnProperty(key)) + { + cfg[key] = val[key]; + } + } + gl = new Y.Gridlines(cfg); + return gl; + } + } + }, + + /** + * Reference to the vertical `Gridlines` instance. + * + * @attribute verticalGridlines + * @type Gridlines + * @default null + */ + verticalGridlines: { + value: null, + + setter: function(val) + { + var cfg, + key, + gl = this.get("verticalGridlines"); + if(gl && gl instanceof Y.Gridlines) + { + gl.remove(); + } + if(val instanceof Y.Gridlines) + { + gl = val; + val.set("graph", this); + return val; + } + else if(val) + { + cfg = { + direction: "vertical", + graph: this + }; + for(key in val) + { + if(val.hasOwnProperty(key)) + { + cfg[key] = val[key]; + } + } + gl = new Y.Gridlines(cfg); + return gl; + } + } + }, + + /** + * Reference to graphic instance used for the background. + * + * @attribute background + * @type Graphic + * @readOnly + */ + background: { + getter: function() + { + if(!this._background) + { + this._backgroundGraphic = new Y.Graphic({render:this.get("contentBox")}); + this._backgroundGraphic.get("node").style.zIndex = 0; + this._background = this._backgroundGraphic.addShape({type: "rect"}); + } + return this._background; + } + }, + + /** + * Reference to graphic instance used for gridlines. + * + * @attribute gridlines + * @type Graphic + * @readOnly + */ + gridlines: { + readOnly: true, + + getter: function() + { + if(!this._gridlines) + { + this._gridlinesGraphic = new Y.Graphic({render:this.get("contentBox")}); + this._gridlinesGraphic.get("node").style.zIndex = 1; + this._gridlines = this._gridlinesGraphic.addShape({type: "path"}); + } + return this._gridlines; + } + }, + + /** + * Reference to graphic instance used for series. + * + * @attribute graphic + * @type Graphic + * @readOnly + */ + graphic: { + readOnly: true, + + getter: function() + { + if(!this._graphic) + { + this._graphic = new Y.Graphic({render:this.get("contentBox")}); + this._graphic.get("node").style.zIndex = 2; + this._graphic.set("autoDraw", false); + } + return this._graphic; + } + }, + + /** + * Indicates whether or not markers for a series will be grouped and rendered in a single complex shape instance. + * + * @attribute groupMarkers + * @type Boolean + */ + groupMarkers: { + value: false + } + + /** + * Style properties used for drawing a background. Below are the default values: + *
+ *
background
An object containing the following values: + *
+ *
fill
Defines the style properties for the fill. Contains the following values: + *
+ *
color
Color of the fill. The default value is #faf9f2.
+ *
alpha
Number from 0 to 1 indicating the opacity of the background fill. + * The default value is 1.
+ *
+ *
+ *
border
Defines the style properties for the border. Contains the following values: + *
+ *
color
Color of the border. The default value is #dad8c9.
+ *
alpha
Number from 0 to 1 indicating the opacity of the background border. + * The default value is 1.
+ *
weight
Number indicating the width of the border. The default value is 1.
+ *
+ *
+ *
+ *
+ *
+ * + * @attribute styles + * @type Object + */ + } +}); +/** + * The ChartBase class is an abstract class used to create charts. + * + * @class ChartBase + * @constructor + * @submodule charts-base + */ +function ChartBase() {} + +ChartBase.ATTRS = { + /** + * Data used to generate the chart. + * + * @attribute dataProvider + * @type Array + */ + dataProvider: { + lazyAdd: false, + + valueFn: function() + { + var defDataProvider = []; + if(!this._seriesKeysExplicitlySet) + { + this.set("seriesKeys", this._buildSeriesKeys(defDataProvider), {src: "internal"}); + } + return defDataProvider; + }, + + setter: function(val) + { + var dataProvider = this._setDataValues(val); + if(!this._seriesKeysExplicitlySet) + { + this.set("seriesKeys", this._buildSeriesKeys(dataProvider), {src: "internal"}); + } + return dataProvider; + } + }, + + /** + * A collection of keys that map to the series axes. If no keys are set, + * they will be generated automatically depending on the data structure passed into + * the chart. + * + * @attribute seriesKeys + * @type Array + */ + seriesKeys: { + lazyAdd: false, + + setter: function(val) + { + var opts = arguments[2]; + if(!val || (opts && opts.src && opts.src === "internal")) + { + this._seriesKeysExplicitlySet = false; + } + else + { + this._seriesKeysExplicitlySet = true; + } + return val; + } + }, + + /** + * Sets the `aria-label` for the chart. + * + * @attribute ariaLabel + * @type String + */ + ariaLabel: { + value: "Chart Application", + + setter: function(val) + { + var cb = this.get("contentBox"); + if(cb) + { + cb.setAttribute("aria-label", val); + } + return val; + } + }, + + /** + * Sets the aria description for the chart. + * + * @attribute ariaDescription + * @type String + */ + ariaDescription: { + value: "Use the up and down keys to navigate between series. Use the left and right keys to navigate through items in a series.", + + setter: function(val) + { + if(this._description) + { + this._description.setContent(""); + this._description.appendChild(DOCUMENT.createTextNode(val)); + } + return val; + } + }, + + /** + * Reference to the default tooltip available for the chart. + *

Contains the following properties:

+ *
+ *
node
Reference to the actual dom node
+ *
showEvent
Event that should trigger the tooltip
+ *
hideEvent
Event that should trigger the removal of a tooltip (can be an event or an array of events)
+ *
styles
A hash of style properties that will be applied to the tooltip node
+ *
show
Indicates whether or not to show the tooltip
+ *
markerEventHandler
Displays and hides tooltip based on marker events
+ *
planarEventHandler
Displays and hides tooltip based on planar events
+ *
markerLabelFunction
Reference to the function used to format a marker event triggered tooltip's text. + * The method contains the following arguments: + *
+ *
categoryItem
An object containing the following: + *
+ *
axis
The axis to which the category is bound.
+ *
displayName
The display name set to the category (defaults to key if not provided).
+ *
key
The key of the category.
+ *
value
The value of the category.
+ *
+ *
+ *
valueItem
An object containing the following: + *
+ *
axis
The axis to which the item's series is bound.
+ *
displayName
The display name of the series. (defaults to key if not provided)
+ *
key
The key for the series.
+ *
value
The value for the series item.
+ *
+ *
+ *
itemIndex
The index of the item within the series.
+ *
series
The `CartesianSeries` instance of the item.
+ *
seriesIndex
The index of the series in the `seriesCollection`.
+ *
+ * The method returns an `HTMLElement` which is written into the DOM using `appendChild`. If you override this method and choose + * to return an html string, you will also need to override the tooltip's `setTextFunction` method to accept an html string. + *
+ *
planarLabelFunction
Reference to the function used to format a planar event triggered tooltip's text + *
+ *
categoryAxis
`CategoryAxis` Reference to the categoryAxis of the chart. + *
valueItems
Array of objects for each series that has a data point in the coordinate plane of the event. Each + * object contains the following data: + *
+ *
axis
The value axis of the series.
+ *
key
The key for the series.
+ *
value
The value for the series item.
+ *
displayName
The display name of the series. (defaults to key if not provided)
+ *
+ *
+ *
index
The index of the item within its series.
+ *
seriesArray
Array of series instances for each value item.
+ *
seriesIndex
The index of the series in the `seriesCollection`.
+ *
+ *
+ *
+ * The method returns an `HTMLElement` which is written into the DOM using `appendChild`. If you override this method and choose + * to return an html string, you will also need to override the tooltip's `setTextFunction` method to accept an html string. + * + *
setTextFunction
Method that writes content returned from `planarLabelFunction` or `markerLabelFunction` into the + * the tooltip node. Has the following signature: + *
+ *
label
The `HTMLElement` that the content is to be added.
+ *
val
The content to be rendered into tooltip. This can be a `String` or `HTMLElement`. If an HTML string is used, + * it will be rendered as a string.
+ *
+ *
+ * + * @attribute tooltip + * @type Object + */ + tooltip: { + valueFn: "_getTooltip", + + setter: function(val) + { + return this._updateTooltip(val); + } + }, + + /** + * The key value used for the chart's category axis. + * + * @attribute categoryKey + * @type String + * @default category + */ + categoryKey: { + value: "category" + }, + + /** + * Indicates the type of axis to use for the category axis. + * + *
+ *
category
Specifies a `CategoryAxis`.
+ *
time
Specifies a `TimeAxis
+ *
+ * + * @attribute categoryType + * @type String + * @default category + */ + categoryType:{ + value:"category" + }, + + /** + * Indicates the the type of interactions that will fire events. + * + *
+ *
marker
Events will be broadcasted when the mouse interacts with individual markers.
+ *
planar
Events will be broadcasted when the mouse intersects the plane of any markers on the chart.
+ *
none
No events will be broadcasted.
+ *
+ * + * @attribute interactionType + * @type String + * @default marker + */ + interactionType: { + value: "marker" + }, + + /** + * Reference to all the axes in the chart. + * + * @attribute axesCollection + * @type Array + */ + axesCollection: {}, + + /** + * Reference to graph instance. + * + * @attribute graph + * @type Graph + */ + graph: { + valueFn: "_getGraph" + }, + + /** + * Indicates whether or not markers for a series will be grouped and rendered in a single complex shape instance. + * + * @attribute groupMarkers + * @type Boolean + */ + groupMarkers: { + value: false + } +}; + +ChartBase.prototype = { + /** + * Handles groupMarkers change event. + * + * @method _groupMarkersChangeHandler + * @param {Object} e Event object. + * @private + */ + _groupMarkersChangeHandler: function(e) + { + var graph = this.get("graph"), + useGroupMarkers = e.newVal; + if(graph) + { + graph.set("groupMarkers", useGroupMarkers); + } + }, + + /** + * Handler for itemRendered event. + * + * @method _itemRendered + * @param {Object} e Event object. + * @private + */ + _itemRendered: function(e) + { + this._itemRenderQueue = this._itemRenderQueue.splice(1 + Y.Array.indexOf(this._itemRenderQueue, e.currentTarget), 1); + if(this._itemRenderQueue.length < 1) + { + this._redraw(); + } + }, + + /** + * Default value function for the `Graph` attribute. + * + * @method _getGraph + * @return Graph + * @private + */ + _getGraph: function() + { + var graph = new Y.Graph({ + chart:this, + groupMarkers: this.get("groupMarkers") + }); + graph.after("chartRendered", Y.bind(function() { + this.fire("chartRendered"); + }, this)); + return graph; + }, + + /** + * Returns a series instance by index or key value. + * + * @method getSeries + * @param val + * @return CartesianSeries + */ + getSeries: function(val) + { + var series = null, + graph = this.get("graph"); + if(graph) + { + if(Y_Lang.isNumber(val)) + { + series = graph.getSeriesByIndex(val); + } + else + { + series = graph.getSeriesByKey(val); + } + } + return series; + }, + + /** + * Returns an `Axis` instance by key reference. If the axis was explicitly set through the `axes` attribute, + * the key will be the same as the key used in the `axes` object. For default axes, the key for + * the category axis is the value of the `categoryKey` (`category`). For the value axis, the default + * key is `values`. + * + * @method getAxisByKey + * @param {String} val Key reference used to look up the axis. + * @return Axis + */ + getAxisByKey: function(val) + { + var axis, + axes = this.get("axes"); + if(axes && axes.hasOwnProperty(val)) + { + axis = axes[val]; + } + return axis; + }, + + /** + * Returns the category axis for the chart. + * + * @method getCategoryAxis + * @return Axis + */ + getCategoryAxis: function() + { + var axis, + key = this.get("categoryKey"), + axes = this.get("axes"); + if(axes.hasOwnProperty(key)) + { + axis = axes[key]; + } + return axis; + }, + + /** + * Default direction of the chart. + * + * @property _direction + * @type String + * @default horizontal + * @private + */ + _direction: "horizontal", + + /** + * Storage for the `dataProvider` attribute. + * + * @property _dataProvider + * @type Array + * @private + */ + _dataProvider: null, + + /** + * Setter method for `dataProvider` attribute. + * + * @method _setDataValues + * @param {Array} val Array to be set as `dataProvider`. + * @return Array + * @private + */ + _setDataValues: function(val) + { + if(Y_Lang.isArray(val[0])) + { + var hash, + dp = [], + cats = val[0], + i = 0, + l = cats.length, + n, + sl = val.length; + for(; i < l; ++i) + { + hash = {category:cats[i]}; + for(n = 1; n < sl; ++n) + { + hash["series" + n] = val[n][i]; + } + dp[i] = hash; + } + return dp; + } + return val; + }, + + /** + * Storage for `seriesCollection` attribute. + * + * @property _seriesCollection + * @type Array + * @private + */ + _seriesCollection: null, + + /** + * Setter method for `seriesCollection` attribute. + * + * @property _setSeriesCollection + * @param {Array} val Array of either `CartesianSeries` instances or objects containing series attribute key value pairs. + * @private + */ + _setSeriesCollection: function(val) + { + this._seriesCollection = val; + }, + /** + * Helper method that returns the axis class that a key references. + * + * @method _getAxisClass + * @param {String} t The type of axis. + * @return Axis + * @private + */ + _getAxisClass: function(t) + { + return this._axisClass[t]; + }, + + /** + * Key value pairs of axis types. + * + * @property _axisClass + * @type Object + * @private + */ + _axisClass: { + stacked: Y.StackedAxis, + numeric: Y.NumericAxis, + category: Y.CategoryAxis, + time: Y.TimeAxis + }, + + /** + * Collection of axes. + * + * @property _axes + * @type Array + * @private + */ + _axes: null, + + /** + * @method initializer + * @private + */ + initializer: function() + { + this._itemRenderQueue = []; + this._seriesIndex = -1; + this._itemIndex = -1; + this.after("dataProviderChange", this._dataProviderChangeHandler); + }, + + /** + * @method renderUI + * @private + */ + renderUI: function() + { + var tt = this.get("tooltip"), + bb = this.get("boundingBox"), + cb = this.get("contentBox"); + //move the position = absolute logic to a class file + bb.setStyle("position", "absolute"); + cb.setStyle("position", "absolute"); + this._addAxes(); + this._addSeries(); + if(tt && tt.show) + { + this._addTooltip(); + } + this._setAriaElements(bb, cb); + }, + + /** + * Creates an aria `live-region`, `aria-label` and `aria-describedby` for the Chart. + * + * @method _setAriaElements + * @param {Node} cb Reference to the Chart's `contentBox` attribute. + * @private + */ + _setAriaElements: function(bb, cb) + { + var description = this._getAriaOffscreenNode(), + id = this.get("id") + "_description", + liveRegion = this._getAriaOffscreenNode(); + cb.set("tabIndex", 0); + cb.set("role", "img"); + cb.setAttribute("aria-label", this.get("ariaLabel")); + cb.setAttribute("aria-describedby", id); + description.set("id", id); + description.set("tabIndex", -1); + description.appendChild(DOCUMENT.createTextNode(this.get("ariaDescription"))); + liveRegion.set("id", "live-region"); + liveRegion.set("aria-live", "polite"); + liveRegion.set("aria-atomic", "true"); + liveRegion.set("role", "status"); + bb.setAttribute("role", "application"); + bb.appendChild(description); + bb.appendChild(liveRegion); + this._description = description; + this._liveRegion = liveRegion; + }, + + /** + * Sets a node offscreen for use as aria-description or aria-live-regin. + * + * @method _setOffscreen + * @return Node + * @private + */ + _getAriaOffscreenNode: function() + { + var node = Y.Node.create("
"), + ie = Y.UA.ie, + clipRect = (ie && ie < 8) ? "rect(1px 1px 1px 1px)" : "rect(1px, 1px, 1px, 1px)"; + node.setStyle("position", "absolute"); + node.setStyle("height", "1px"); + node.setStyle("width", "1px"); + node.setStyle("overflow", "hidden"); + node.setStyle("clip", clipRect); + return node; + }, + + /** + * @method syncUI + * @private + */ + syncUI: function() + { + this._redraw(); + }, + + /** + * @method bindUI + * @private + */ + bindUI: function() + { + this.after("tooltipChange", Y.bind(this._tooltipChangeHandler, this)); + this.after("widthChange", this._sizeChanged); + this.after("heightChange", this._sizeChanged); + this.after("groupMarkersChange", this._groupMarkersChangeHandler); + var tt = this.get("tooltip"), + hideEvent = "mouseout", + showEvent = "mouseover", + cb = this.get("contentBox"), + interactionType = this.get("interactionType"), + i = 0, + len, + markerClassName = "." + SERIES_MARKER, + isTouch = ((WINDOW && ("ontouchstart" in WINDOW)) && !(Y.UA.chrome && Y.UA.chrome < 6)); + Y.on("keydown", Y.bind(function(e) { + var key = e.keyCode, + numKey = parseFloat(key), + msg; + if(numKey > 36 && numKey < 41) + { + e.halt(); + msg = this._getAriaMessage(numKey); + this._liveRegion.setContent(""); + this._liveRegion.appendChild(DOCUMENT.createTextNode(msg)); + } + }, this), this.get("contentBox")); + if(interactionType === "marker") + { + //if touch capabilities, toggle tooltip on touchend. otherwise, the tooltip attribute's hideEvent/showEvent types. + hideEvent = tt.hideEvent; + showEvent = tt.showEvent; + if(isTouch) + { + Y.delegate("touchend", Y.bind(this._markerEventDispatcher, this), cb, markerClassName); + //hide active tooltip if the chart is touched + Y.on("touchend", Y.bind(function(e) { + //only halt the event if it originated from the chart + if(cb.contains(e.target)) + { + e.halt(true); + } + if(this._activeMarker) + { + this._activeMarker = null; + this.hideTooltip(e); + } + }, this)); + } + else + { + Y.delegate("mouseenter", Y.bind(this._markerEventDispatcher, this), cb, markerClassName); + Y.delegate("mousedown", Y.bind(this._markerEventDispatcher, this), cb, markerClassName); + Y.delegate("mouseup", Y.bind(this._markerEventDispatcher, this), cb, markerClassName); + Y.delegate("mouseleave", Y.bind(this._markerEventDispatcher, this), cb, markerClassName); + Y.delegate("click", Y.bind(this._markerEventDispatcher, this), cb, markerClassName); + Y.delegate("mousemove", Y.bind(this._positionTooltip, this), cb, markerClassName); + } + } + else if(interactionType === "planar") + { + if(isTouch) + { + this._overlay.on("touchend", Y.bind(this._planarEventDispatcher, this)); + } + else + { + this._overlay.on("mousemove", Y.bind(this._planarEventDispatcher, this)); + this.on("mouseout", this.hideTooltip); + } + } + if(tt) + { + this.on("markerEvent:touchend", Y.bind(function(e) { + var marker = e.series.get("markers")[e.index]; + if(this._activeMarker && marker === this._activeMarker) + { + this._activeMarker = null; + this.hideTooltip(e); + } + else + { + + this._activeMarker = marker; + tt.markerEventHandler.apply(this, [e]); + } + }, this)); + if(hideEvent && showEvent && hideEvent === showEvent) + { + this.on(interactionType + "Event:" + hideEvent, this.toggleTooltip); + } + else + { + if(showEvent) + { + this.on(interactionType + "Event:" + showEvent, tt[interactionType + "EventHandler"]); + } + if(hideEvent) + { + if(Y_Lang.isArray(hideEvent)) + { + len = hideEvent.length; + for(; i < len; ++i) + { + this.on(interactionType + "Event:" + hideEvent[i], this.hideTooltip); + } + } + this.on(interactionType + "Event:" + hideEvent, this.hideTooltip); + } + } + } + }, + + /** + * Event handler for marker events. + * + * @method _markerEventDispatcher + * @param {Object} e Event object. + * @private + */ + _markerEventDispatcher: function(e) + { + var type = e.type, + cb = this.get("contentBox"), + markerNode = e.currentTarget, + strArr = markerNode.getAttribute("id").split("_"), + index = strArr.pop(), + seriesIndex = strArr.pop(), + series = this.getSeries(parseInt(seriesIndex, 10)), + items = this.getSeriesItems(series, index), + isTouch = e && e.hasOwnProperty("changedTouches"), + pageX = isTouch ? e.changedTouches[0].pageX : e.pageX, + pageY = isTouch ? e.changedTouches[0].pageY : e.pageY, + x = pageX - cb.getX(), + y = pageY - cb.getY(); + if(type === "mouseenter") + { + type = "mouseover"; + } + else if(type === "mouseleave") + { + type = "mouseout"; + } + series.updateMarkerState(type, index); + e.halt(); + /** + * Broadcasts when `interactionType` is set to `marker` and a series marker has received a mouseover event. + * + * + * @event markerEvent:mouseover + * @preventable false + * @param {EventFacade} e Event facade with the following additional + * properties: + *
+ *
categoryItem
Hash containing information about the category `Axis`.
+ *
valueItem
Hash containing information about the value `Axis`.
+ *
node
The dom node of the marker.
+ *
x
The x-coordinate of the mouse in relation to the Chart.
+ *
y
The y-coordinate of the mouse in relation to the Chart.
+ *
series
Reference to the series of the marker.
+ *
index
Index of the marker in the series.
+ *
seriesIndex
The `order` of the marker's series.
+ *
+ */ + /** + * Broadcasts when `interactionType` is set to `marker` and a series marker has received a mouseout event. + * + * @event markerEvent:mouseout + * @preventable false + * @param {EventFacade} e Event facade with the following additional + * properties: + *
+ *
categoryItem
Hash containing information about the category `Axis`.
+ *
valueItem
Hash containing information about the value `Axis`.
+ *
node
The dom node of the marker.
+ *
x
The x-coordinate of the mouse in relation to the Chart.
+ *
y
The y-coordinate of the mouse in relation to the Chart.
+ *
series
Reference to the series of the marker.
+ *
index
Index of the marker in the series.
+ *
seriesIndex
The `order` of the marker's series.
+ *
+ */ + /** + * Broadcasts when `interactionType` is set to `marker` and a series marker has received a mousedown event. + * + * @event markerEvent:mousedown + * @preventable false + * @param {EventFacade} e Event facade with the following additional + * properties: + *
+ *
categoryItem
Hash containing information about the category `Axis`.
+ *
valueItem
Hash containing information about the value `Axis`.
+ *
node
The dom node of the marker.
+ *
x
The x-coordinate of the mouse in relation to the Chart.
+ *
y
The y-coordinate of the mouse in relation to the Chart.
+ *
series
Reference to the series of the marker.
+ *
index
Index of the marker in the series.
+ *
seriesIndex
The `order` of the marker's series.
+ *
+ */ + /** + * Broadcasts when `interactionType` is set to `marker` and a series marker has received a mouseup event. + * + * @event markerEvent:mouseup + * @preventable false + * @param {EventFacade} e Event facade with the following additional + * properties: + *
+ *
categoryItem
Hash containing information about the category `Axis`.
+ *
valueItem
Hash containing information about the value `Axis`.
+ *
node
The dom node of the marker.
+ *
x
The x-coordinate of the mouse in relation to the Chart.
+ *
y
The y-coordinate of the mouse in relation to the Chart.
+ *
series
Reference to the series of the marker.
+ *
index
Index of the marker in the series.
+ *
seriesIndex
The `order` of the marker's series.
+ *
+ */ + /** + * Broadcasts when `interactionType` is set to `marker` and a series marker has received a click event. + * + * @event markerEvent:click + * @preventable false + * @param {EventFacade} e Event facade with the following additional + * properties: + *
+ *
categoryItem
Hash containing information about the category `Axis`.
+ *
valueItem
Hash containing information about the value `Axis`.
+ *
node
The dom node of the marker.
+ *
x
The x-coordinate of the mouse in relation to the Chart.
+ *
y
The y-coordinate of the mouse in relation to the Chart.
+ *
pageX
The x location of the event on the page (including scroll)
+ *
pageY
The y location of the event on the page (including scroll)
+ *
series
Reference to the series of the marker.
+ *
index
Index of the marker in the series.
+ *
seriesIndex
The `order` of the marker's series.
+ *
originEvent
Underlying dom event.
+ *
+ */ + this.fire("markerEvent:" + type, { + originEvent: e, + pageX:pageX, + pageY:pageY, + categoryItem:items.category, + valueItem:items.value, + node:markerNode, + x:x, + y:y, + series:series, + index:index, + seriesIndex:seriesIndex + }); + }, + + /** + * Event handler for dataProviderChange. + * + * @method _dataProviderChangeHandler + * @param {Object} e Event object. + * @private + */ + _dataProviderChangeHandler: function(e) + { + var dataProvider = e.newVal, + axes, + i, + axis; + this._seriesIndex = -1; + this._itemIndex = -1; + if(this instanceof Y.CartesianChart) + { + this.set("axes", this.get("axes")); + this.set("seriesCollection", this.get("seriesCollection")); + } + axes = this.get("axes"); + if(axes) + { + for(i in axes) + { + if(axes.hasOwnProperty(i)) + { + axis = axes[i]; + if(axis instanceof Y.Axis) + { + if(axis.get("position") !== "none") + { + this._addToAxesRenderQueue(axis); + } + axis.set("dataProvider", dataProvider); + } + } + } + } + }, + + /** + * Event listener for toggling the tooltip. If a tooltip is visible, hide it. If not, it + * will create and show a tooltip based on the event object. + * + * @method toggleTooltip + * @param {Object} e Event object. + */ + toggleTooltip: function(e) + { + var tt = this.get("tooltip"); + if(tt.visible) + { + this.hideTooltip(); + } + else + { + tt.markerEventHandler.apply(this, [e]); + } + }, + + /** + * Shows a tooltip + * + * @method _showTooltip + * @param {String} msg Message to dispaly in the tooltip. + * @param {Number} x x-coordinate + * @param {Number} y y-coordinate + * @private + */ + _showTooltip: function(msg, x, y) + { + var tt = this.get("tooltip"), + node = tt.node; + if(msg) + { + tt.visible = true; + tt.setTextFunction(node, msg); + node.setStyle("top", y + "px"); + node.setStyle("left", x + "px"); + node.setStyle("visibility", "visible"); + } + }, + + /** + * Positions the tooltip + * + * @method _positionTooltip + * @param {Object} e Event object. + * @private + */ + _positionTooltip: function(e) + { + var tt = this.get("tooltip"), + node = tt.node, + cb = this.get("contentBox"), + x = (e.pageX + 10) - cb.getX(), + y = (e.pageY + 10) - cb.getY(); + if(node) + { + node.setStyle("left", x + "px"); + node.setStyle("top", y + "px"); + } + }, + + /** + * Hides the default tooltip + * + * @method hideTooltip + */ + hideTooltip: function() + { + var tt = this.get("tooltip"), + node = tt.node; + tt.visible = false; + node.set("innerHTML", ""); + node.setStyle("left", -10000); + node.setStyle("top", -10000); + node.setStyle("visibility", "hidden"); + }, + + /** + * Adds a tooltip to the dom. + * + * @method _addTooltip + * @private + */ + _addTooltip: function() + { + var tt = this.get("tooltip"), + id = this.get("id") + "_tooltip", + cb = this.get("contentBox"), + oldNode = DOCUMENT.getElementById(id); + if(oldNode) + { + cb.removeChild(oldNode); + } + tt.node.set("id", id); + tt.node.setStyle("visibility", "hidden"); + cb.appendChild(tt.node); + }, + + /** + * Updates the tooltip attribute. + * + * @method _updateTooltip + * @param {Object} val Object containing properties for the tooltip. + * @return Object + * @private + */ + _updateTooltip: function(val) + { + var tt = this.get("tooltip") || this._getTooltip(), + i, + styles, + node, + props = { + markerLabelFunction:"markerLabelFunction", + planarLabelFunction:"planarLabelFunction", + setTextFunction:"setTextFunction", + showEvent:"showEvent", + hideEvent:"hideEvent", + markerEventHandler:"markerEventHandler", + planarEventHandler:"planarEventHandler", + show:"show" + }; + if(Y_Lang.isObject(val)) + { + styles = val.styles; + node = Y.one(val.node) || tt.node; + if(styles) + { + for(i in styles) + { + if(styles.hasOwnProperty(i)) + { + node.setStyle(i, styles[i]); + } + } + } + for(i in props) + { + if(val.hasOwnProperty(i)) + { + tt[i] = val[i]; + } + } + tt.node = node; + } + return tt; + }, + + /** + * Default getter for `tooltip` attribute. + * + * @method _getTooltip + * @return Object + * @private + */ + _getTooltip: function() + { + var node = DOCUMENT.createElement("div"), + tooltipClass = _getClassName("chart-tooltip"), + tt = { + setTextFunction: this._setText, + markerLabelFunction: this._tooltipLabelFunction, + planarLabelFunction: this._planarLabelFunction, + show: true, + hideEvent: "mouseout", + showEvent: "mouseover", + markerEventHandler: function(e) + { + var tt = this.get("tooltip"), + msg = tt.markerLabelFunction.apply(this, [e.categoryItem, e.valueItem, e.index, e.series, e.seriesIndex]); + this._showTooltip(msg, e.x + 10, e.y + 10); + }, + planarEventHandler: function(e) + { + var tt = this.get("tooltip"), + msg , + categoryAxis = this.get("categoryAxis"); + msg = tt.planarLabelFunction.apply(this, [categoryAxis, e.valueItem, e.index, e.items, e.seriesIndex]); + this._showTooltip(msg, e.x + 10, e.y + 10); + } + }; + node = Y.one(node); + node.set("id", this.get("id") + "_tooltip"); + node.setStyle("fontSize", "85%"); + node.setStyle("opacity", "0.83"); + node.setStyle("position", "absolute"); + node.setStyle("paddingTop", "2px"); + node.setStyle("paddingRight", "5px"); + node.setStyle("paddingBottom", "4px"); + node.setStyle("paddingLeft", "2px"); + node.setStyle("backgroundColor", "#fff"); + node.setStyle("border", "1px solid #dbdccc"); + node.setStyle("pointerEvents", "none"); + node.setStyle("zIndex", 3); + node.setStyle("whiteSpace", "noWrap"); + node.setStyle("visibility", "hidden"); + node.addClass(tooltipClass); + tt.node = Y.one(node); + return tt; + }, + + /** + * Formats tooltip text when `interactionType` is `planar`. + * + * @method _planarLabelFunction + * @param {Axis} categoryAxis Reference to the categoryAxis of the chart. + * @param {Array} valueItems Array of objects for each series that has a data point in the coordinate plane of the event. + * Each object contains the following data: + *
+ *
axis
The value axis of the series.
+ *
key
The key for the series.
+ *
value
The value for the series item.
+ *
displayName
The display name of the series. (defaults to key if not provided)
+ *
+ * @param {Number} index The index of the item within its series. + * @param {Array} seriesArray Array of series instances for each value item. + * @param {Number} seriesIndex The index of the series in the `seriesCollection`. + * @return {String | HTML} + * @private + */ + _planarLabelFunction: function(categoryAxis, valueItems, index, seriesArray) + { + var msg = DOCUMENT.createElement("div"), + valueItem, + i = 0, + len = seriesArray.length, + axis, + categoryValue, + seriesValue, + series; + if(categoryAxis) + { + categoryValue = categoryAxis.get("labelFunction").apply( + this, + [categoryAxis.getKeyValueAt(this.get("categoryKey"), index), categoryAxis.get("labelFormat")] + ); + if(!Y_Lang.isObject(categoryValue)) + { + categoryValue = DOCUMENT.createTextNode(categoryValue); + } + msg.appendChild(categoryValue); + } + + for(; i < len; ++i) + { + series = seriesArray[i]; + if(series.get("visible")) + { + valueItem = valueItems[i]; + axis = valueItem.axis; + seriesValue = axis.get("labelFunction").apply( + this, + [axis.getKeyValueAt(valueItem.key, index), axis.get("labelFormat")] + ); + msg.appendChild(DOCUMENT.createElement("br")); + msg.appendChild(DOCUMENT.createTextNode(valueItem.displayName)); + msg.appendChild(DOCUMENT.createTextNode(": ")); + if(!Y_Lang.isObject(seriesValue)) + { + seriesValue = DOCUMENT.createTextNode(seriesValue); + } + msg.appendChild(seriesValue); + } + } + return msg; + }, + + /** + * Formats tooltip text when `interactionType` is `marker`. + * + * @method _tooltipLabelFunction + * @param {Object} categoryItem An object containing the following: + *
+ *
axis
The axis to which the category is bound.
+ *
displayName
The display name set to the category (defaults to key if not provided)
+ *
key
The key of the category.
+ *
value
The value of the category
+ *
+ * @param {Object} valueItem An object containing the following: + *
+ *
axis
The axis to which the item's series is bound.
+ *
displayName
The display name of the series. (defaults to key if not provided)
+ *
key
The key for the series.
+ *
value
The value for the series item.
+ *
+ * @return {String | HTML} + * @private + */ + _tooltipLabelFunction: function(categoryItem, valueItem) + { + var msg = DOCUMENT.createElement("div"), + categoryValue = categoryItem.axis.get("labelFunction").apply( + this, + [categoryItem.value, categoryItem.axis.get("labelFormat")] + ), + seriesValue = valueItem.axis.get("labelFunction").apply( + this, + [valueItem.value, valueItem.axis.get("labelFormat")] + ); + msg.appendChild(DOCUMENT.createTextNode(categoryItem.displayName)); + msg.appendChild(DOCUMENT.createTextNode(": ")); + if(!Y_Lang.isObject(categoryValue)) + { + categoryValue = DOCUMENT.createTextNode(categoryValue); + } + msg.appendChild(categoryValue); + msg.appendChild(DOCUMENT.createElement("br")); + msg.appendChild(DOCUMENT.createTextNode(valueItem.displayName)); + msg.appendChild(DOCUMENT.createTextNode(": ")); + if(!Y_Lang.isObject(seriesValue)) + { + seriesValue = DOCUMENT.createTextNode(seriesValue); + } + msg.appendChild(seriesValue); + return msg; + }, + + /** + * Event handler for the tooltipChange. + * + * @method _tooltipChangeHandler + * @param {Object} e Event object. + * @private + */ + _tooltipChangeHandler: function() + { + if(this.get("tooltip")) + { + var tt = this.get("tooltip"), + node = tt.node, + show = tt.show, + cb = this.get("contentBox"); + if(node && show) + { + if(!cb.contains(node)) + { + this._addTooltip(); + } + } + } + }, + + /** + * Updates the content of text field. This method writes a value into a text field using + * `appendChild`. If the value is a `String`, it is converted to a `TextNode` first. + * + * @method _setText + * @param label {HTMLElement} label to be updated + * @param val {String} value with which to update the label + * @private + */ + _setText: function(textField, val) + { + textField.setContent(""); + if(Y_Lang.isNumber(val)) + { + val = val + ""; + } + else if(!val) + { + val = ""; + } + if(IS_STRING(val)) + { + val = DOCUMENT.createTextNode(val); + } + textField.appendChild(val); + }, + + /** + * Returns all the keys contained in a `dataProvider`. + * + * @method _getAllKeys + * @param {Array} dp Collection of objects to be parsed. + * @return Object + */ + _getAllKeys: function(dp) + { + var i = 0, + len = dp.length, + item, + key, + keys = {}; + for(; i < len; ++i) + { + item = dp[i]; + for(key in item) + { + if(item.hasOwnProperty(key)) + { + keys[key] = true; + } + } + } + return keys; + }, + + /** + * Constructs seriesKeys if not explicitly specified. + * + * @method _buildSeriesKeys + * @param {Array} dataProvider The dataProvider for the chart. + * @return Array + * @private + */ + _buildSeriesKeys: function(dataProvider) + { + var allKeys, + catKey = this.get("categoryKey"), + keys = [], + i; + if(this._seriesKeysExplicitlySet) + { + return this._seriesKeys; + } + allKeys = this._getAllKeys(dataProvider); + for(i in allKeys) + { + if(allKeys.hasOwnProperty(i) && i !== catKey) + { + keys.push(i); + } + } + return keys; + } +}; +Y.ChartBase = ChartBase; +/** + * 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"), + overlay, + 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") + { + overlay = DOCUMENT.createElement("div"); + this.get("contentBox").appendChild(overlay); + this._overlay = Y.one(overlay); + 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); + } + 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: + *
+ *
categoryItem
An array of hashes, each containing information about the category `Axis` of each marker + * whose plane has been intersected.
+ *
valueItem
An array of hashes, each containing information about the value `Axis` of each marker whose + * plane has been intersected.
+ *
x
The x-coordinate of the mouse in relation to the Chart.
+ *
y
The y-coordinate of the mouse in relation to the Chart.
+ *
pageX
The x location of the event on the page (including scroll)
+ *
pageY
The y location of the event on the page (including scroll)
+ *
items
An array including all the series which contain a marker whose plane has been intersected.
+ *
index
Index of the markers in their respective series.
+ *
originEvent
Underlying dom event.
+ *
+ */ + /** + * 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", + 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._seriesKeysExplicitlySet) + { + 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: + * + *
+ *
categoryItem
Object containing the following data related to the category axis of the series. + *
+ *
axis
Reference to the category axis of the series.
+ *
key
Category key for the series.
+ *
value
Value on the axis corresponding to the series index.
+ *
+ *
+ *
valueItem
Object containing the following data related to the category axis of the series. + *
+ *
axis
Reference to the value axis of the series.
+ *
key
Value key for the series.
+ *
value
Value on the axis corresponding to the series index.
+ *
+ *
+ *
+ */ + 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: + *
+ *
series
A key indexed hash containing references to the `styles` attribute for each series in the chart. + * Specific style attributes vary depending on the series: + * + *
+ *
axes
A key indexed hash containing references to the `styles` attribute for each axes in the chart. Specific + * style attributes can be found in the Axis class.
+ *
graph
A reference to the `styles` attribute in the chart. Specific style attributes can be found in the + * Graph class.
+ *
+ * + * @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:{} + } +}); +/** + * 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: + *
+ *
axis
The axis to which the category is bound.
+ *
displayName
The display name set to the category (defaults to key if not provided)
+ *
key
The key of the category.
+ *
value
The value of the category
+ *
+ * @param {Object} valueItem An object containing the following: + *
+ *
axis
The axis to which the item's series is bound.
+ *
displayName
The display name of the series. (defaults to key if not provided)
+ *
key
The key for the series.
+ *
value
The value for the series item.
+ *
+ * @param {Number} itemIndex The index of the item within the series. + * @param {CartesianSeries} series The `PieSeries` instance of the item. + * @return {HTML} + * @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; + } +}, { + 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.setContent(""); + this._description.appendChild(DOCUMENT.createTextNode(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" + } + } +}); +/** + * The Chart class is the basic application used to create a chart. + * + * @class Chart + * @constructor + * @submodule charts-base + */ +function Chart(cfg) +{ + if(cfg.type !== "pie") + { + return new Y.CartesianChart(cfg); + } + else + { + return new Y.PieChart(cfg); + } +} +Y.Chart = Chart; + + +}, '3.10.3', { + "requires": [ + "dom", + "event-mouseenter", + "event-touch", + "graphics-group", + "axes", + "series-pie", + "series-line", + "series-marker", + "series-area", + "series-spline", + "series-column", + "series-bar", + "series-areaspline", + "series-combo", + "series-combospline", + "series-line-stacked", + "series-marker-stacked", + "series-area-stacked", + "series-spline-stacked", + "series-column-stacked", + "series-bar-stacked", + "series-areaspline-stacked", + "series-combo-stacked", + "series-combospline-stacked" + ] +});