Current Path : C:/xampp/htdocs/moodle/lib/yuilib/3.17.2/charts-base/ |
Current File : C:/xampp/htdocs/moodle/lib/yuilib/3.17.2/charts-base/charts-base.js |
/* YUI 3.17.2 (build 9c3c78e) Copyright 2014 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: * * <table> * <tr><th>Key Value</th><th>Class</th></tr> * <tr><td>line</td><td>Y.LineSeries</td></tr> * <tr><td>column</td><td>Y.ColumnSeries</td></tr> * <tr><td>bar</td><td>Y.BarSeries</td></tr> * <tr><td>area</td><td>Y.AreaSeries</td></tr> * <tr><td>stackedarea</td><td>Y.StackedAreaSeries</td></tr> * <tr><td>stackedline</td><td>Y.StackedLineSeries</td></tr> * <tr><td>stackedcolumn</td><td>Y.StackedColumnSeries</td></tr> * <tr><td>stackedbar</td><td>Y.StackedBarSeries</td></tr> * <tr><td>markerseries</td><td>Y.MarkerSeries</td></tr> * <tr><td>spline</td><td>Y.SplineSeries</td></tr> * <tr><td>areaspline</td><td>Y.AreaSplineSeries</td></tr> * <tr><td>stackedspline</td><td>Y.StackedSplineSeries</td></tr> * <tr><td>stackedareaspline</td><td>Y.StackedAreaSplineSeries</td></tr> * <tr><td>stackedmarkerseries</td><td>Y.StackedMarkerSeries</td></tr> * <tr><td>pie</td><td>Y.PieSeries</td></tr> * <tr><td>combo</td><td>Y.ComboSeries</td></tr> * <tr><td>stackedcombo</td><td>Y.StackedComboSeries</td></tr> * <tr><td>combospline</td><td>Y.ComboSplineSeries</td></tr> * <tr><td>stackedcombospline</td><td>Y.StackedComboSplineSeries</td></tr> * </table> * * 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: * <dl> * <dt>background</dt><dd>An object containing the following values: * <dl> * <dt>fill</dt><dd>Defines the style properties for the fill. Contains the following values: * <dl> * <dt>color</dt><dd>Color of the fill. The default value is #faf9f2.</dd> * <dt>alpha</dt><dd>Number from 0 to 1 indicating the opacity of the background fill. * The default value is 1.</dd> * </dl> * </dd> * <dt>border</dt><dd>Defines the style properties for the border. Contains the following values: * <dl> * <dt>color</dt><dd>Color of the border. The default value is #dad8c9.</dd> * <dt>alpha</dt><dd>Number from 0 to 1 indicating the opacity of the background border. * The default value is 1.</dd> * <dt>weight</dt><dd>Number indicating the width of the border. The default value is 1.</dd> * </dl> * </dd> * </dl> * </dd> * </dl> * * @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._wereSeriesKeysExplicitlySet()) { this.set("seriesKeys", this._buildSeriesKeys(defDataProvider), {src: "internal"}); } return defDataProvider; }, setter: function(val) { var dataProvider = this._setDataValues(val); if(!this._wereSeriesKeysExplicitlySet()) { 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.set("text", val); } return val; } }, /** * Reference to the default tooltip available for the chart. * <p>Contains the following properties:</p> * <dl> * <dt>node</dt><dd>Reference to the actual dom node</dd> * <dt>showEvent</dt><dd>Event that should trigger the tooltip</dd> * <dt>hideEvent</dt><dd>Event that should trigger the removal of a tooltip (can be an event or an array of events)</dd> * <dt>styles</dt><dd>A hash of style properties that will be applied to the tooltip node</dd> * <dt>show</dt><dd>Indicates whether or not to show the tooltip</dd> * <dt>markerEventHandler</dt><dd>Displays and hides tooltip based on marker events</dd> * <dt>planarEventHandler</dt><dd>Displays and hides tooltip based on planar events</dd> * <dt>markerLabelFunction</dt><dd>Reference to the function used to format a marker event triggered tooltip's text. * The method contains the following arguments: * <dl> * <dt>categoryItem</dt><dd>An object containing the following: * <dl> * <dt>axis</dt><dd>The axis to which the category is bound.</dd> * <dt>displayName</dt><dd>The display name set to the category (defaults to key if not provided).</dd> * <dt>key</dt><dd>The key of the category.</dd> * <dt>value</dt><dd>The value of the category.</dd> * </dl> * </dd> * <dt>valueItem</dt><dd>An object containing the following: * <dl> * <dt>axis</dt><dd>The axis to which the item's series is bound.</dd> * <dt>displayName</dt><dd>The display name of the series. (defaults to key if not provided)</dd> * <dt>key</dt><dd>The key for the series.</dd> * <dt>value</dt><dd>The value for the series item.</dd> * </dl> * </dd> * <dt>itemIndex</dt><dd>The index of the item within the series.</dd> * <dt>series</dt><dd> The `CartesianSeries` instance of the item.</dd> * <dt>seriesIndex</dt><dd>The index of the series in the `seriesCollection`.</dd> * </dl> * 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. * </dd> * <dt>planarLabelFunction</dt><dd>Reference to the function used to format a planar event triggered tooltip's text * <dl> * <dt>categoryAxis</dt><dd> `CategoryAxis` Reference to the categoryAxis of the chart. * <dt>valueItems</dt><dd>Array of objects for each series that has a data point in the coordinate plane of the event. Each * object contains the following data: * <dl> * <dt>axis</dt><dd>The value axis of the series.</dd> * <dt>key</dt><dd>The key for the series.</dd> * <dt>value</dt><dd>The value for the series item.</dd> * <dt>displayName</dt><dd>The display name of the series. (defaults to key if not provided)</dd> * </dl> * </dd> * <dt>index</dt><dd>The index of the item within its series.</dd> * <dt>seriesArray</dt><dd>Array of series instances for each value item.</dd> * <dt>seriesIndex</dt><dd>The index of the series in the `seriesCollection`.</dd> * </dl> * </dd> * </dl> * 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. * </dd> * <dt>setTextFunction</dt><dd>Method that writes content returned from `planarLabelFunction` or `markerLabelFunction` into the * the tooltip node. Has the following signature: * <dl> * <dt>label</dt><dd>The `HTMLElement` that the content is to be added.</dd> * <dt>val</dt><dd>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.</dd> * </dl> * </dd> * </dl> * @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. * * <dl> * <dt>category</dt><dd>Specifies a `CategoryAxis`.</dd> * <dt>time</dt><dd>Specifies a `TimeAxis</dd> * </dl> * * @attribute categoryType * @type String * @default category */ categoryType:{ value:"category" }, /** * Indicates the the type of interactions that will fire events. * * <dl> * <dt>marker</dt><dd>Events will be broadcasted when the mouse interacts with individual markers.</dd> * <dt>planar</dt><dd>Events will be broadcasted when the mouse intersects the plane of any markers on the chart.</dd> * <dt>none</dt><dd>No events will be broadcasted.</dd> * </dl> * * @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 = { /** * Utility method to determine if `seriesKeys` was explicitly provided * (for example during construction, or set by the user), as opposed to * being derived from the dataProvider for example. * * @method _wereSeriesKeysExplicitlySet * @private * @return boolean true if the `seriesKeys` attribute was explicitly set. */ _wereSeriesKeysExplicitlySet : function() { var seriesKeys = this.get("seriesKeys"); return seriesKeys && this._seriesKeysExplicitlySet; }, /** * 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.set("text", 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("<div></div>"), 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.set("text", 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: * <dl> * <dt>categoryItem</dt><dd>Hash containing information about the category `Axis`.</dd> * <dt>valueItem</dt><dd>Hash containing information about the value `Axis`.</dd> * <dt>node</dt><dd>The dom node of the marker.</dd> * <dt>x</dt><dd>The x-coordinate of the mouse in relation to the Chart.</dd> * <dt>y</dt><dd>The y-coordinate of the mouse in relation to the Chart.</dd> * <dt>series</dt><dd>Reference to the series of the marker.</dd> * <dt>index</dt><dd>Index of the marker in the series.</dd> * <dt>seriesIndex</dt><dd>The `order` of the marker's series.</dd> * </dl> */ /** * 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: * <dl> * <dt>categoryItem</dt><dd>Hash containing information about the category `Axis`.</dd> * <dt>valueItem</dt><dd>Hash containing information about the value `Axis`.</dd> * <dt>node</dt><dd>The dom node of the marker.</dd> * <dt>x</dt><dd>The x-coordinate of the mouse in relation to the Chart.</dd> * <dt>y</dt><dd>The y-coordinate of the mouse in relation to the Chart.</dd> * <dt>series</dt><dd>Reference to the series of the marker.</dd> * <dt>index</dt><dd>Index of the marker in the series.</dd> * <dt>seriesIndex</dt><dd>The `order` of the marker's series.</dd> * </dl> */ /** * 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: * <dl> * <dt>categoryItem</dt><dd>Hash containing information about the category `Axis`.</dd> * <dt>valueItem</dt><dd>Hash containing information about the value `Axis`.</dd> * <dt>node</dt><dd>The dom node of the marker.</dd> * <dt>x</dt><dd>The x-coordinate of the mouse in relation to the Chart.</dd> * <dt>y</dt><dd>The y-coordinate of the mouse in relation to the Chart.</dd> * <dt>series</dt><dd>Reference to the series of the marker.</dd> * <dt>index</dt><dd>Index of the marker in the series.</dd> * <dt>seriesIndex</dt><dd>The `order` of the marker's series.</dd> * </dl> */ /** * 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: * <dl> * <dt>categoryItem</dt><dd>Hash containing information about the category `Axis`.</dd> * <dt>valueItem</dt><dd>Hash containing information about the value `Axis`.</dd> * <dt>node</dt><dd>The dom node of the marker.</dd> * <dt>x</dt><dd>The x-coordinate of the mouse in relation to the Chart.</dd> * <dt>y</dt><dd>The y-coordinate of the mouse in relation to the Chart.</dd> * <dt>series</dt><dd>Reference to the series of the marker.</dd> * <dt>index</dt><dd>Index of the marker in the series.</dd> * <dt>seriesIndex</dt><dd>The `order` of the marker's series.</dd> * </dl> */ /** * 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: * <dl> * <dt>categoryItem</dt><dd>Hash containing information about the category `Axis`.</dd> * <dt>valueItem</dt><dd>Hash containing information about the value `Axis`.</dd> * <dt>node</dt><dd>The dom node of the marker.</dd> * <dt>x</dt><dd>The x-coordinate of the mouse in relation to the Chart.</dd> * <dt>y</dt><dd>The y-coordinate of the mouse in relation to the Chart.</dd> * <dt>pageX</dt><dd>The x location of the event on the page (including scroll)</dd> * <dt>pageY</dt><dd>The y location of the event on the page (including scroll)</dd> * <dt>series</dt><dd>Reference to the series of the marker.</dd> * <dt>index</dt><dd>Index of the marker in the series.</dd> * <dt>seriesIndex</dt><dd>The `order` of the marker's series.</dd> * <dt>originEvent</dt><dd>Underlying dom event.</dd> * </dl> */ 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; if(val.node && tt.node) { tt.node.destroy(true); node = Y.one(val.node); } else { 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: * <dl> * <dt>axis</dt><dd>The value axis of the series.</dd> * <dt>key</dt><dd>The key for the series.</dd> * <dt>value</dt><dd>The value for the series item.</dd> * <dt>displayName</dt><dd>The display name of the series. (defaults to key if not provided)</dd> * </dl> * @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 {HTMLElement} * @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: * <dl> * <dt>axis</dt><dd>The axis to which the category is bound.</dd> * <dt>displayName</dt><dd>The display name set to the category (defaults to key if not provided)</dd> * <dt>key</dt><dd>The key of the category.</dd> * <dt>value</dt><dd>The value of the category</dd> * </dl> * @param {Object} valueItem An object containing the following: * <dl> * <dt>axis</dt><dd>The axis to which the item's series is bound.</dd> * <dt>displayName</dt><dd>The display name of the series. (defaults to key if not provided)</dd> * <dt>key</dt><dd>The key for the series.</dd> * <dt>value</dt><dd>The value for the series item.</dd> * </dl> * @return {HTMLElement} * @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.empty(); 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"), overlayClass = _getClassName("overlay"); //move the position = absolute logic to a class file bb.setStyle("position", "absolute"); cb.setStyle("position", "absolute"); this._addAxes(); this._addGridlines(); this._addSeries(); if(tt && tt.show) { this._addTooltip(); } if(this.get("interactionType") === "planar") { this._overlay = Y.Node.create("<div></div>"); this._overlay.set("id", this.get("id") + "_overlay"); this._overlay.setStyle("position", "absolute"); this._overlay.setStyle("background", "#fff"); this._overlay.setStyle("opacity", 0); this._overlay.addClass(overlayClass); this._overlay.setStyle("zIndex", 4); cb.append(this._overlay); } this._setAriaElements(bb, cb); this._redraw(); }, /** * When `interactionType` is set to `planar`, listens for mouse move events and fires `planarEvent:mouseover` or `planarEvent:mouseout` * depending on the position of the mouse in relation to data points on the `Chart`. * * @method _planarEventDispatcher * @param {Object} e Event object. * @private */ _planarEventDispatcher: function(e) { var graph = this.get("graph"), bb = this.get("boundingBox"), cb = graph.get("contentBox"), isTouch = e && e.hasOwnProperty("changedTouches"), pageX = isTouch ? e.changedTouches[0].pageX : e.pageX, pageY = isTouch ? e.changedTouches[0].pageY : e.pageY, posX = pageX - bb.getX(), posY = pageY - bb.getY(), offset = { x: pageX - cb.getX(), y: pageY - cb.getY() }, sc = graph.get("seriesCollection"), series, i = 0, index, oldIndex = this._selectedIndex, item, items = [], categoryItems = [], valueItems = [], direction = this.get("direction"), hasMarkers, catAxis, valAxis, coord, //data columns and area data could be created on a graph level markerPlane, len, coords; e.halt(true); if(direction === "horizontal") { catAxis = "x"; valAxis = "y"; } else { valAxis = "x"; catAxis = "y"; } coord = offset[catAxis]; if(sc) { len = sc.length; while(i < len && !markerPlane) { if(sc[i]) { markerPlane = sc[i].get(catAxis + "MarkerPlane"); } i++; } } if(markerPlane) { len = markerPlane.length; for(i = 0; i < len; ++i) { if(coord <= markerPlane[i].end && coord >= markerPlane[i].start) { index = i; break; } } len = sc.length; for(i = 0; i < len; ++i) { series = sc[i]; coords = series.get(valAxis + "coords"); hasMarkers = series.get("markers"); if(hasMarkers && !isNaN(oldIndex) && oldIndex > -1) { series.updateMarkerState("mouseout", oldIndex); } if(coords && coords[index] > -1) { if(hasMarkers && !isNaN(index) && index > -1) { series.updateMarkerState("mouseover", index); } item = this.getSeriesItems(series, index); categoryItems.push(item.category); valueItems.push(item.value); items.push(series); } } this._selectedIndex = index; /** * Broadcasts when `interactionType` is set to `planar` and a series' marker plane has received a mouseover event. * * * @event planarEvent:mouseover * @preventable false * @param {EventFacade} e Event facade with the following additional * properties: * <dl> * <dt>categoryItem</dt><dd>An array of hashes, each containing information about the category `Axis` of each marker * whose plane has been intersected.</dd> * <dt>valueItem</dt><dd>An array of hashes, each containing information about the value `Axis` of each marker whose * plane has been intersected.</dd> * <dt>x</dt><dd>The x-coordinate of the mouse in relation to the Chart.</dd> * <dt>y</dt><dd>The y-coordinate of the mouse in relation to the Chart.</dd> * <dt>pageX</dt><dd>The x location of the event on the page (including scroll)</dd> * <dt>pageY</dt><dd>The y location of the event on the page (including scroll)</dd> * <dt>items</dt><dd>An array including all the series which contain a marker whose plane has been intersected.</dd> * <dt>index</dt><dd>Index of the markers in their respective series.</dd> * <dt>originEvent</dt><dd>Underlying dom event.</dd> * </dl> */ /** * Broadcasts when `interactionType` is set to `planar` and a series' marker plane has received a mouseout event. * * @event planarEvent:mouseout * @preventable false * @param {EventFacade} e */ if(index > -1) { this.fire("planarEvent:mouseover", { categoryItem:categoryItems, valueItem:valueItems, x:posX, y:posY, pageX:pageX, pageY:pageY, items:items, index:index, originEvent:e }); } else { this.fire("planarEvent:mouseout"); } } }, /** * Indicates the default series type for the chart. * * @property _type * @type {String} * @private */ _type: "combo", /** * Queue of axes instances that will be updated. This method is used internally to determine when all axes have been updated. * * @property _itemRenderQueue * @type Array * @private */ _itemRenderQueue: null, /** * Adds an `Axis` instance to the `_itemRenderQueue`. * * @method _addToAxesRenderQueue * @param {Axis} axis An `Axis` instance. * @private */ _addToAxesRenderQueue: function(axis) { if(!this._itemRenderQueue) { this._itemRenderQueue = []; } if(Y.Array.indexOf(this._itemRenderQueue, axis) < 0) { this._itemRenderQueue.push(axis); } }, /** * Adds axis instance to the appropriate array based on position * * @method _addToAxesCollection * @param {String} position The position of the axis * @param {Axis} axis The `Axis` instance */ _addToAxesCollection: function(position, axis) { var axesCollection = this.get(position + "AxesCollection"); if(!axesCollection) { axesCollection = []; this.set(position + "AxesCollection", axesCollection); } axesCollection.push(axis); }, /** * Returns the default value for the `seriesCollection` attribute. * * @method _getDefaultSeriesCollection * @param {Array} val Array containing either `CartesianSeries` instances or objects containing data to construct series instances. * @return Array * @private */ _getDefaultSeriesCollection: function() { var seriesCollection, dataProvider = this.get("dataProvider"); if(dataProvider) { seriesCollection = this._parseSeriesCollection(); } return seriesCollection; }, /** * Parses and returns a series collection from an object and default properties. * * @method _parseSeriesCollection * @param {Object} val Object contain properties for series being set. * @return Object * @private */ _parseSeriesCollection: function(val) { var dir = this.get("direction"), seriesStyles = this.get("styles").series, stylesAreArray = seriesStyles && Y_Lang.isArray(seriesStyles), stylesIndex, setStyles, globalStyles, sc = [], catAxis, valAxis, tempKeys = [], series, seriesKeys = this.get("seriesKeys").concat(), i, index, l, type = this.get("type"), key, catKey, seriesKey, graph, orphans = [], categoryKey = this.get("categoryKey"), showMarkers = this.get("showMarkers"), showAreaFill = this.get("showAreaFill"), showLines = this.get("showLines"); val = val ? val.concat() : []; if(dir === "vertical") { catAxis = "yAxis"; catKey = "yKey"; valAxis = "xAxis"; seriesKey = "xKey"; } else { catAxis = "xAxis"; catKey = "xKey"; valAxis = "yAxis"; seriesKey = "yKey"; } l = val.length; while(val && val.length > 0) { series = val.shift(); key = this._getBaseAttribute(series, seriesKey); if(key) { index = Y.Array.indexOf(seriesKeys, key); if(index > -1) { seriesKeys.splice(index, 1); tempKeys.push(key); sc.push(series); } else { orphans.push(series); } } else { orphans.push(series); } } while(orphans.length > 0) { series = orphans.shift(); if(seriesKeys.length > 0) { key = seriesKeys.shift(); this._setBaseAttribute(series, seriesKey, key); tempKeys.push(key); sc.push(series); } else if(series instanceof Y.CartesianSeries) { series.destroy(true); } } if(seriesKeys.length > 0) { tempKeys = tempKeys.concat(seriesKeys); } l = tempKeys.length; for(i = 0; i < l; ++i) { series = sc[i] || {type:type}; if(series instanceof Y.CartesianSeries) { this._parseSeriesAxes(series); } else { series[catKey] = series[catKey] || categoryKey; series[seriesKey] = series[seriesKey] || seriesKeys.shift(); series[catAxis] = this._getCategoryAxis(); series[valAxis] = this._getSeriesAxis(series[seriesKey]); series.type = series.type || type; series.direction = series.direction || dir; if(series.type === "combo" || series.type === "stackedcombo" || series.type === "combospline" || series.type === "stackedcombospline") { if(showAreaFill !== null) { series.showAreaFill = (series.showAreaFill !== null && series.showAreaFill !== undefined) ? series.showAreaFill : showAreaFill; } if(showMarkers !== null) { series.showMarkers = (series.showMarkers !== null && series.showMarkers !== undefined) ? series.showMarkers : showMarkers; } if(showLines !== null) { series.showLines = (series.showLines !== null && series.showLines !== undefined) ? series.showLines : showLines; } } if(seriesStyles) { stylesIndex = stylesAreArray ? i : series[seriesKey]; globalStyles = seriesStyles[stylesIndex]; if(globalStyles) { setStyles = series.styles; if(setStyles) { series.styles = this._mergeStyles(setStyles, globalStyles); } else { series.styles = globalStyles; } } } sc[i] = series; } } if(sc) { graph = this.get("graph"); graph.set("seriesCollection", sc); sc = graph.get("seriesCollection"); } return sc; }, /** * Parse and sets the axes for a series instance. * * @method _parseSeriesAxes * @param {CartesianSeries} series A `CartesianSeries` instance. * @private */ _parseSeriesAxes: function(series) { var axes = this.get("axes"), xAxis = series.get("xAxis"), yAxis = series.get("yAxis"), YAxis = Y.Axis, axis; if(xAxis && !(xAxis instanceof YAxis) && Y_Lang.isString(xAxis) && axes.hasOwnProperty(xAxis)) { axis = axes[xAxis]; if(axis instanceof YAxis) { series.set("xAxis", axis); } } if(yAxis && !(yAxis instanceof YAxis) && Y_Lang.isString(yAxis) && axes.hasOwnProperty(yAxis)) { axis = axes[yAxis]; if(axis instanceof YAxis) { series.set("yAxis", axis); } } }, /** * Returns the category axis instance for the chart. * * @method _getCategoryAxis * @return Axis * @private */ _getCategoryAxis: function() { var axis, axes = this.get("axes"), categoryAxisName = this.get("categoryAxisName") || this.get("categoryKey"); axis = axes[categoryAxisName]; return axis; }, /** * Returns the value axis for a series. * * @method _getSeriesAxis * @param {String} key The key value used to determine the axis instance. * @return Axis * @private */ _getSeriesAxis:function(key, axisName) { var axes = this.get("axes"), i, keys, axis; if(axes) { if(axisName && axes.hasOwnProperty(axisName)) { axis = axes[axisName]; } else { for(i in axes) { if(axes.hasOwnProperty(i)) { keys = axes[i].get("keys"); if(keys && keys.hasOwnProperty(key)) { axis = axes[i]; break; } } } } } return axis; }, /** * Gets an attribute from an object, using a getter for Base objects and a property for object * literals. Used for determining attributes from series/axis references which can be an actual class instance * or a hash of properties that will be used to create a class instance. * * @method _getBaseAttribute * @param {Object} item Object or instance in which the attribute resides. * @param {String} key Attribute whose value will be returned. * @return Object * @private */ _getBaseAttribute: function(item, key) { if(item instanceof Y.Base) { return item.get(key); } if(item.hasOwnProperty(key)) { return item[key]; } return null; }, /** * Sets an attribute on an object, using a setter of Base objects and a property for object * literals. Used for setting attributes on a Base class, either directly or to be stored in an object literal * for use at instantiation. * * @method _setBaseAttribute * @param {Object} item Object or instance in which the attribute resides. * @param {String} key Attribute whose value will be assigned. * @param {Object} value Value to be assigned to the attribute. * @private */ _setBaseAttribute: function(item, key, value) { if(item instanceof Y.Base) { item.set(key, value); } else { item[key] = value; } }, /** * Creates `Axis` instances. * * @method _setAxes * @param {Object} val Object containing `Axis` instances or objects in which to construct `Axis` instances. * @return Object * @private */ _setAxes: function(val) { var hash = this._parseAxes(val), axes = {}, axesAttrs = { edgeOffset: "edgeOffset", calculateEdgeOffset: "calculateEdgeOffset", position: "position", overlapGraph:"overlapGraph", labelValues: "labelValues", hideFirstMajorUnit: "hideFirstMajorUnit", hideLastMajorUnit: "hideLastMajorUnit", labelFunction:"labelFunction", labelFunctionScope:"labelFunctionScope", labelFormat:"labelFormat", appendLabelFunction: "appendLabelFunction", appendTitleFunction: "appendTitleFunction", maximum:"maximum", minimum:"minimum", roundingMethod:"roundingMethod", alwaysShowZero:"alwaysShowZero", scaleType: "scaleType", title:"title", width:"width", height:"height" }, dp = this.get("dataProvider"), ai, i, pos, axis, axisPosition, dh, AxisClass, config, axesCollection; for(i in hash) { if(hash.hasOwnProperty(i)) { dh = hash[i]; if(dh instanceof Y.Axis) { axis = dh; } else { axis = null; config = {}; config.dataProvider = dh.dataProvider || dp; config.keys = dh.keys; if(dh.hasOwnProperty("roundingUnit")) { config.roundingUnit = dh.roundingUnit; } pos = dh.position; if(dh.styles) { config.styles = dh.styles; } config.position = dh.position; for(ai in axesAttrs) { if(axesAttrs.hasOwnProperty(ai) && dh.hasOwnProperty(ai)) { config[ai] = dh[ai]; } } //only check for existing axis if we constructed the default axes already if(val) { axis = this.getAxisByKey(i); } if(axis && axis instanceof Y.Axis) { axisPosition = axis.get("position"); if(pos !== axisPosition) { if(axisPosition !== "none") { axesCollection = this.get(axisPosition + "AxesCollection"); axesCollection.splice(Y.Array.indexOf(axesCollection, axis), 1); } if(pos !== "none") { this._addToAxesCollection(pos, axis); } } axis.setAttrs(config); } else { AxisClass = this._getAxisClass(dh.type); axis = new AxisClass(config); axis.after("axisRendered", Y.bind(this._itemRendered, this)); } } if(axis) { axesCollection = this.get(pos + "AxesCollection"); if(axesCollection && Y.Array.indexOf(axesCollection, axis) > 0) { axis.set("overlapGraph", false); } axes[i] = axis; } } } return axes; }, /** * Adds axes to the chart. * * @method _addAxes * @private */ _addAxes: function() { var axes = this.get("axes"), i, axis, pos, w = this.get("width"), h = this.get("height"), node = Y.Node.one(this._parentNode); if(!this._axesCollection) { this._axesCollection = []; } for(i in axes) { if(axes.hasOwnProperty(i)) { axis = axes[i]; if(axis instanceof Y.Axis) { if(!w) { this.set("width", node.get("offsetWidth")); w = this.get("width"); } if(!h) { this.set("height", node.get("offsetHeight")); h = this.get("height"); } this._addToAxesRenderQueue(axis); pos = axis.get("position"); if(!this.get(pos + "AxesCollection")) { this.set(pos + "AxesCollection", [axis]); } else { this.get(pos + "AxesCollection").push(axis); } this._axesCollection.push(axis); if(axis.get("keys").hasOwnProperty(this.get("categoryKey"))) { this.set("categoryAxis", axis); } axis.render(this.get("contentBox")); } } } }, /** * Renders the Graph. * * @method _addSeries * @private */ _addSeries: function() { var graph = this.get("graph"); graph.render(this.get("contentBox")); }, /** * Adds gridlines to the chart. * * @method _addGridlines * @private */ _addGridlines: function() { var graph = this.get("graph"), hgl = this.get("horizontalGridlines"), vgl = this.get("verticalGridlines"), direction = this.get("direction"), leftAxesCollection = this.get("leftAxesCollection"), rightAxesCollection = this.get("rightAxesCollection"), bottomAxesCollection = this.get("bottomAxesCollection"), topAxesCollection = this.get("topAxesCollection"), seriesAxesCollection, catAxis = this.get("categoryAxis"), hAxis, vAxis; if(this._axesCollection) { seriesAxesCollection = this._axesCollection.concat(); seriesAxesCollection.splice(Y.Array.indexOf(seriesAxesCollection, catAxis), 1); } if(hgl) { if(leftAxesCollection && leftAxesCollection[0]) { hAxis = leftAxesCollection[0]; } else if(rightAxesCollection && rightAxesCollection[0]) { hAxis = rightAxesCollection[0]; } else { hAxis = direction === "horizontal" ? catAxis : seriesAxesCollection[0]; } if(!this._getBaseAttribute(hgl, "axis") && hAxis) { this._setBaseAttribute(hgl, "axis", hAxis); } if(this._getBaseAttribute(hgl, "axis")) { graph.set("horizontalGridlines", hgl); } } if(vgl) { if(bottomAxesCollection && bottomAxesCollection[0]) { vAxis = bottomAxesCollection[0]; } else if (topAxesCollection && topAxesCollection[0]) { vAxis = topAxesCollection[0]; } else { vAxis = direction === "vertical" ? catAxis : seriesAxesCollection[0]; } if(!this._getBaseAttribute(vgl, "axis") && vAxis) { this._setBaseAttribute(vgl, "axis", vAxis); } if(this._getBaseAttribute(vgl, "axis")) { graph.set("verticalGridlines", vgl); } } }, /** * Default Function for the axes attribute. * * @method _getDefaultAxes * @return Object * @private */ _getDefaultAxes: function() { var axes; if(this.get("dataProvider")) { axes = this._parseAxes(); } return axes; }, /** * Generates and returns a key-indexed object containing `Axis` instances or objects used to create `Axis` instances. * * @method _parseAxes * @param {Object} axes Object containing `Axis` instances or `Axis` attributes. * @return Object * @private */ _parseAxes: function(axes) { var catKey = this.get("categoryKey"), axis, attr, keys, newAxes = {}, claimedKeys = [], newKeys = [], categoryAxisName = this.get("categoryAxisName") || this.get("categoryKey"), valueAxisName = this.get("valueAxisName"), seriesKeys = this.get("seriesKeys").concat(), i, l, ii, ll, cIndex, direction = this.get("direction"), seriesPosition, categoryPosition, valueAxes = [], seriesAxis = this.get("stacked") ? "stacked" : "numeric"; if(direction === "vertical") { seriesPosition = "bottom"; categoryPosition = "left"; } else { seriesPosition = "left"; categoryPosition = "bottom"; } if(axes) { for(i in axes) { if(axes.hasOwnProperty(i)) { axis = axes[i]; keys = this._getBaseAttribute(axis, "keys"); attr = this._getBaseAttribute(axis, "type"); if(attr === "time" || attr === "category") { categoryAxisName = i; this.set("categoryAxisName", i); if(Y_Lang.isArray(keys) && keys.length > 0) { catKey = keys[0]; this.set("categoryKey", catKey); } newAxes[i] = axis; } else if(i === categoryAxisName) { newAxes[i] = axis; } else { newAxes[i] = axis; if(i !== valueAxisName && keys && Y_Lang.isArray(keys)) { ll = keys.length; for(ii = 0; ii < ll; ++ii) { claimedKeys.push(keys[ii]); } valueAxes.push(newAxes[i]); } if(!(this._getBaseAttribute(newAxes[i], "type"))) { this._setBaseAttribute(newAxes[i], "type", seriesAxis); } if(!(this._getBaseAttribute(newAxes[i], "position"))) { this._setBaseAttribute( newAxes[i], "position", this._getDefaultAxisPosition(newAxes[i], valueAxes, seriesPosition) ); } } } } } cIndex = Y.Array.indexOf(seriesKeys, catKey); if(cIndex > -1) { seriesKeys.splice(cIndex, 1); } l = seriesKeys.length; for(i = 0; i < l; ++i) { cIndex = Y.Array.indexOf(claimedKeys, seriesKeys[i]); if(cIndex > -1) { newKeys = newKeys.concat(claimedKeys.splice(cIndex, 1)); } } claimedKeys = newKeys.concat(claimedKeys); l = claimedKeys.length; for(i = 0; i < l; i = i + 1) { cIndex = Y.Array.indexOf(seriesKeys, claimedKeys[i]); if(cIndex > -1) { seriesKeys.splice(cIndex, 1); } } if(!newAxes.hasOwnProperty(categoryAxisName)) { newAxes[categoryAxisName] = {}; } if(!(this._getBaseAttribute(newAxes[categoryAxisName], "keys"))) { this._setBaseAttribute(newAxes[categoryAxisName], "keys", [catKey]); } if(!(this._getBaseAttribute(newAxes[categoryAxisName], "position"))) { this._setBaseAttribute(newAxes[categoryAxisName], "position", categoryPosition); } if(!(this._getBaseAttribute(newAxes[categoryAxisName], "type"))) { this._setBaseAttribute(newAxes[categoryAxisName], "type", this.get("categoryType")); } if(!newAxes.hasOwnProperty(valueAxisName) && seriesKeys && seriesKeys.length > 0) { newAxes[valueAxisName] = {keys:seriesKeys}; valueAxes.push(newAxes[valueAxisName]); } if(claimedKeys.length > 0) { if(seriesKeys.length > 0) { seriesKeys = claimedKeys.concat(seriesKeys); } else { seriesKeys = claimedKeys; } } if(newAxes.hasOwnProperty(valueAxisName)) { if(!(this._getBaseAttribute(newAxes[valueAxisName], "position"))) { this._setBaseAttribute( newAxes[valueAxisName], "position", this._getDefaultAxisPosition(newAxes[valueAxisName], valueAxes, seriesPosition) ); } this._setBaseAttribute(newAxes[valueAxisName], "type", seriesAxis); this._setBaseAttribute(newAxes[valueAxisName], "keys", seriesKeys); } if(!this._wereSeriesKeysExplicitlySet()) { this.set("seriesKeys", seriesKeys, {src: "internal"}); } return newAxes; }, /** * Determines the position of an axis when one is not specified. * * @method _getDefaultAxisPosition * @param {Axis} axis `Axis` instance. * @param {Array} valueAxes Array of `Axis` instances. * @param {String} position Default position depending on the direction of the chart and type of axis. * @return String * @private */ _getDefaultAxisPosition: function(axis, valueAxes, position) { var direction = this.get("direction"), i = Y.Array.indexOf(valueAxes, axis); if(valueAxes[i - 1] && valueAxes[i - 1].position) { if(direction === "horizontal") { if(valueAxes[i - 1].position === "left") { position = "right"; } else if(valueAxes[i - 1].position === "right") { position = "left"; } } else { if (valueAxes[i -1].position === "bottom") { position = "top"; } else { position = "bottom"; } } } return position; }, /** * Returns an object literal containing a categoryItem and a valueItem for a given series index. Below is the structure of each: * * @method getSeriesItems * @param {CartesianSeries} series Reference to a series. * @param {Number} index Index of the specified item within a series. * @return Object An object literal containing the following: * * <dl> * <dt>categoryItem</dt><dd>Object containing the following data related to the category axis of the series. * <dl> * <dt>axis</dt><dd>Reference to the category axis of the series.</dd> * <dt>key</dt><dd>Category key for the series.</dd> * <dt>value</dt><dd>Value on the axis corresponding to the series index.</dd> * </dl> * </dd> * <dt>valueItem</dt><dd>Object containing the following data related to the category axis of the series. * <dl> * <dt>axis</dt><dd>Reference to the value axis of the series.</dd> * <dt>key</dt><dd>Value key for the series.</dd> * <dt>value</dt><dd>Value on the axis corresponding to the series index.</dd> * </dl> * </dd> * </dl> */ getSeriesItems: function(series, index) { var xAxis = series.get("xAxis"), yAxis = series.get("yAxis"), xKey = series.get("xKey"), yKey = series.get("yKey"), categoryItem, valueItem; if(this.get("direction") === "vertical") { categoryItem = { axis:yAxis, key:yKey, value:yAxis.getKeyValueAt(yKey, index) }; valueItem = { axis:xAxis, key:xKey, value: xAxis.getKeyValueAt(xKey, index) }; } else { valueItem = { axis:yAxis, key:yKey, value:yAxis.getKeyValueAt(yKey, index) }; categoryItem = { axis:xAxis, key:xKey, value: xAxis.getKeyValueAt(xKey, index) }; } categoryItem.displayName = series.get("categoryDisplayName"); valueItem.displayName = series.get("valueDisplayName"); categoryItem.value = categoryItem.axis.getKeyValueAt(categoryItem.key, index); valueItem.value = valueItem.axis.getKeyValueAt(valueItem.key, index); return {category:categoryItem, value:valueItem}; }, /** * Handler for sizeChanged event. * * @method _sizeChanged * @param {Object} e Event object. * @private */ _sizeChanged: function() { if(this._axesCollection) { var ac = this._axesCollection, i = 0, l = ac.length; for(; i < l; ++i) { this._addToAxesRenderQueue(ac[i]); } this._redraw(); } }, /** * Returns the maximum distance in pixels that the extends outside the top bounds of all vertical axes. * * @method _getTopOverflow * @param {Array} set1 Collection of axes to check. * @param {Array} set2 Seconf collection of axes to check. * @param {Number} width Width of the axes * @return Number * @private */ _getTopOverflow: function(set1, set2, height) { var i = 0, len, overflow = 0, axis; if(set1) { len = set1.length; for(; i < len; ++i) { axis = set1[i]; overflow = Math.max( overflow, Math.abs(axis.getMaxLabelBounds().top) - axis.getEdgeOffset(axis.get("styles").majorTicks.count, height) ); } } if(set2) { i = 0; len = set2.length; for(; i < len; ++i) { axis = set2[i]; overflow = Math.max( overflow, Math.abs(axis.getMaxLabelBounds().top) - axis.getEdgeOffset(axis.get("styles").majorTicks.count, height) ); } } return overflow; }, /** * Returns the maximum distance in pixels that the extends outside the right bounds of all horizontal axes. * * @method _getRightOverflow * @param {Array} set1 Collection of axes to check. * @param {Array} set2 Seconf collection of axes to check. * @param {Number} width Width of the axes * @return Number * @private */ _getRightOverflow: function(set1, set2, width) { var i = 0, len, overflow = 0, axis; if(set1) { len = set1.length; for(; i < len; ++i) { axis = set1[i]; overflow = Math.max( overflow, axis.getMaxLabelBounds().right - axis.getEdgeOffset(axis.get("styles").majorTicks.count, width) ); } } if(set2) { i = 0; len = set2.length; for(; i < len; ++i) { axis = set2[i]; overflow = Math.max( overflow, axis.getMaxLabelBounds().right - axis.getEdgeOffset(axis.get("styles").majorTicks.count, width) ); } } return overflow; }, /** * Returns the maximum distance in pixels that the extends outside the left bounds of all horizontal axes. * * @method _getLeftOverflow * @param {Array} set1 Collection of axes to check. * @param {Array} set2 Seconf collection of axes to check. * @param {Number} width Width of the axes * @return Number * @private */ _getLeftOverflow: function(set1, set2, width) { var i = 0, len, overflow = 0, axis; if(set1) { len = set1.length; for(; i < len; ++i) { axis = set1[i]; overflow = Math.max( overflow, Math.abs(axis.getMinLabelBounds().left) - axis.getEdgeOffset(axis.get("styles").majorTicks.count, width) ); } } if(set2) { i = 0; len = set2.length; for(; i < len; ++i) { axis = set2[i]; overflow = Math.max( overflow, Math.abs(axis.getMinLabelBounds().left) - axis.getEdgeOffset(axis.get("styles").majorTicks.count, width) ); } } return overflow; }, /** * Returns the maximum distance in pixels that the extends outside the bottom bounds of all vertical axes. * * @method _getBottomOverflow * @param {Array} set1 Collection of axes to check. * @param {Array} set2 Seconf collection of axes to check. * @param {Number} height Height of the axes * @return Number * @private */ _getBottomOverflow: function(set1, set2, height) { var i = 0, len, overflow = 0, axis; if(set1) { len = set1.length; for(; i < len; ++i) { axis = set1[i]; overflow = Math.max( overflow, axis.getMinLabelBounds().bottom - axis.getEdgeOffset(axis.get("styles").majorTicks.count, height) ); } } if(set2) { i = 0; len = set2.length; for(; i < len; ++i) { axis = set2[i]; overflow = Math.max( overflow, axis.getMinLabelBounds().bottom - axis.getEdgeOffset(axis.get("styles").majorTicks.count, height) ); } } return overflow; }, /** * Redraws and position all the components of the chart instance. * * @method _redraw * @private */ _redraw: function() { if(this._drawing) { this._callLater = true; return; } this._drawing = true; this._callLater = false; var w = this.get("width"), h = this.get("height"), leftPaneWidth = 0, rightPaneWidth = 0, topPaneHeight = 0, bottomPaneHeight = 0, leftAxesCollection = this.get("leftAxesCollection"), rightAxesCollection = this.get("rightAxesCollection"), topAxesCollection = this.get("topAxesCollection"), bottomAxesCollection = this.get("bottomAxesCollection"), i = 0, l, axis, graphOverflow = "visible", graph = this.get("graph"), topOverflow, bottomOverflow, leftOverflow, rightOverflow, graphWidth, graphHeight, graphX, graphY, allowContentOverflow = this.get("allowContentOverflow"), diff, rightAxesXCoords, leftAxesXCoords, topAxesYCoords, bottomAxesYCoords, graphRect = {}; if(leftAxesCollection) { leftAxesXCoords = []; l = leftAxesCollection.length; for(i = l - 1; i > -1; --i) { leftAxesXCoords.unshift(leftPaneWidth); leftPaneWidth += leftAxesCollection[i].get("width"); } } if(rightAxesCollection) { rightAxesXCoords = []; l = rightAxesCollection.length; i = 0; for(i = l - 1; i > -1; --i) { rightPaneWidth += rightAxesCollection[i].get("width"); rightAxesXCoords.unshift(w - rightPaneWidth); } } if(topAxesCollection) { topAxesYCoords = []; l = topAxesCollection.length; for(i = l - 1; i > -1; --i) { topAxesYCoords.unshift(topPaneHeight); topPaneHeight += topAxesCollection[i].get("height"); } } if(bottomAxesCollection) { bottomAxesYCoords = []; l = bottomAxesCollection.length; for(i = l - 1; i > -1; --i) { bottomPaneHeight += bottomAxesCollection[i].get("height"); bottomAxesYCoords.unshift(h - bottomPaneHeight); } } graphWidth = w - (leftPaneWidth + rightPaneWidth); graphHeight = h - (bottomPaneHeight + topPaneHeight); graphRect.left = leftPaneWidth; graphRect.top = topPaneHeight; graphRect.bottom = h - bottomPaneHeight; graphRect.right = w - rightPaneWidth; if(!allowContentOverflow) { topOverflow = this._getTopOverflow(leftAxesCollection, rightAxesCollection); bottomOverflow = this._getBottomOverflow(leftAxesCollection, rightAxesCollection); leftOverflow = this._getLeftOverflow(bottomAxesCollection, topAxesCollection); rightOverflow = this._getRightOverflow(bottomAxesCollection, topAxesCollection); diff = topOverflow - topPaneHeight; if(diff > 0) { graphRect.top = topOverflow; if(topAxesYCoords) { i = 0; l = topAxesYCoords.length; for(; i < l; ++i) { topAxesYCoords[i] += diff; } } } diff = bottomOverflow - bottomPaneHeight; if(diff > 0) { graphRect.bottom = h - bottomOverflow; if(bottomAxesYCoords) { i = 0; l = bottomAxesYCoords.length; for(; i < l; ++i) { bottomAxesYCoords[i] -= diff; } } } diff = leftOverflow - leftPaneWidth; if(diff > 0) { graphRect.left = leftOverflow; if(leftAxesXCoords) { i = 0; l = leftAxesXCoords.length; for(; i < l; ++i) { leftAxesXCoords[i] += diff; } } } diff = rightOverflow - rightPaneWidth; if(diff > 0) { graphRect.right = w - rightOverflow; if(rightAxesXCoords) { i = 0; l = rightAxesXCoords.length; for(; i < l; ++i) { rightAxesXCoords[i] -= diff; } } } } graphWidth = graphRect.right - graphRect.left; graphHeight = graphRect.bottom - graphRect.top; graphX = graphRect.left; graphY = graphRect.top; if(topAxesCollection) { l = topAxesCollection.length; i = 0; for(; i < l; i++) { axis = topAxesCollection[i]; if(axis.get("width") !== graphWidth) { axis.set("width", graphWidth); } axis.get("boundingBox").setStyle("left", graphX + "px"); axis.get("boundingBox").setStyle("top", topAxesYCoords[i] + "px"); } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } if(bottomAxesCollection) { l = bottomAxesCollection.length; i = 0; for(; i < l; i++) { axis = bottomAxesCollection[i]; if(axis.get("width") !== graphWidth) { axis.set("width", graphWidth); } axis.get("boundingBox").setStyle("left", graphX + "px"); axis.get("boundingBox").setStyle("top", bottomAxesYCoords[i] + "px"); } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } if(leftAxesCollection) { l = leftAxesCollection.length; i = 0; for(; i < l; ++i) { axis = leftAxesCollection[i]; axis.get("boundingBox").setStyle("top", graphY + "px"); axis.get("boundingBox").setStyle("left", leftAxesXCoords[i] + "px"); if(axis.get("height") !== graphHeight) { axis.set("height", graphHeight); } } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } if(rightAxesCollection) { l = rightAxesCollection.length; i = 0; for(; i < l; ++i) { axis = rightAxesCollection[i]; axis.get("boundingBox").setStyle("top", graphY + "px"); axis.get("boundingBox").setStyle("left", rightAxesXCoords[i] + "px"); if(axis.get("height") !== graphHeight) { axis.set("height", graphHeight); } } if(axis._hasDataOverflow()) { graphOverflow = "hidden"; } } this._drawing = false; if(this._callLater) { this._redraw(); return; } if(graph) { graph.get("boundingBox").setStyle("left", graphX + "px"); graph.get("boundingBox").setStyle("top", graphY + "px"); graph.set("width", graphWidth); graph.set("height", graphHeight); graph.get("boundingBox").setStyle("overflow", graphOverflow); } if(this._overlay) { this._overlay.setStyle("left", graphX + "px"); this._overlay.setStyle("top", graphY + "px"); this._overlay.setStyle("width", graphWidth + "px"); this._overlay.setStyle("height", graphHeight + "px"); } }, /** * Destructor implementation for the CartesianChart class. Calls destroy on all axes, series and the Graph instance. * Removes the tooltip and overlay HTML elements. * * @method destructor * @protected */ destructor: function() { var graph = this.get("graph"), i = 0, len, seriesCollection = this.get("seriesCollection"), axesCollection = this._axesCollection, tooltip = this.get("tooltip").node; if(this._description) { this._description.empty(); this._description.remove(true); } if(this._liveRegion) { this._liveRegion.empty(); this._liveRegion.remove(true); } len = seriesCollection ? seriesCollection.length : 0; for(; i < len; ++i) { if(seriesCollection[i] instanceof Y.CartesianSeries) { seriesCollection[i].destroy(true); } } len = axesCollection ? axesCollection.length : 0; for(i = 0; i < len; ++i) { if(axesCollection[i] instanceof Y.Axis) { axesCollection[i].destroy(true); } } if(graph) { graph.destroy(true); } if(tooltip) { tooltip.empty(); tooltip.remove(true); } if(this._overlay) { this._overlay.empty(); this._overlay.remove(true); } }, /** * Returns the appropriate message based on the key press. * * @method _getAriaMessage * @param {Number} key The keycode that was pressed. * @return String */ _getAriaMessage: function(key) { var msg = "", series, items, categoryItem, valueItem, seriesIndex = this._seriesIndex, itemIndex = this._itemIndex, seriesCollection = this.get("seriesCollection"), len = seriesCollection.length, dataLength; if(key % 2 === 0) { if(len > 1) { if(key === 38) { seriesIndex = seriesIndex < 1 ? len - 1 : seriesIndex - 1; } else if(key === 40) { seriesIndex = seriesIndex >= len - 1 ? 0 : seriesIndex + 1; } this._itemIndex = -1; } else { seriesIndex = 0; } this._seriesIndex = seriesIndex; series = this.getSeries(parseInt(seriesIndex, 10)); msg = series.get("valueDisplayName") + " series."; } else { if(seriesIndex > -1) { msg = ""; series = this.getSeries(parseInt(seriesIndex, 10)); } else { seriesIndex = 0; this._seriesIndex = seriesIndex; series = this.getSeries(parseInt(seriesIndex, 10)); msg = series.get("valueDisplayName") + " series."; } dataLength = series._dataLength ? series._dataLength : 0; if(key === 37) { itemIndex = itemIndex > 0 ? itemIndex - 1 : dataLength - 1; } else if(key === 39) { itemIndex = itemIndex >= dataLength - 1 ? 0 : itemIndex + 1; } this._itemIndex = itemIndex; items = this.getSeriesItems(series, itemIndex); categoryItem = items.category; valueItem = items.value; if(categoryItem && valueItem && categoryItem.value && valueItem.value) { msg += categoryItem.displayName + ": " + categoryItem.axis.formatLabel.apply(this, [categoryItem.value, categoryItem.axis.get("labelFormat")]) + ", "; msg += valueItem.displayName + ": " + valueItem.axis.formatLabel.apply(this, [valueItem.value, valueItem.axis.get("labelFormat")]) + ", "; } else { msg += "No data available."; } msg += (itemIndex + 1) + " of " + dataLength + ". "; } return msg; } }, { ATTRS: { /** * Indicates whether axis labels are allowed to overflow beyond the bounds of the chart's content box. * * @attribute allowContentOverflow * @type Boolean */ allowContentOverflow: { value: false }, /** * Style object for the axes. * * @attribute axesStyles * @type Object * @private */ axesStyles: { lazyAdd: false, getter: function() { var axes = this.get("axes"), i, styles = this._axesStyles; if(axes) { for(i in axes) { if(axes.hasOwnProperty(i) && axes[i] instanceof Y.Axis) { if(!styles) { styles = {}; } styles[i] = axes[i].get("styles"); } } } return styles; }, setter: function(val) { var axes = this.get("axes"), i; for(i in val) { if(val.hasOwnProperty(i) && axes.hasOwnProperty(i)) { this._setBaseAttribute(axes[i], "styles", val[i]); } } return val; } }, /** * Style object for the series * * @attribute seriesStyles * @type Object * @private */ seriesStyles: { lazyAdd: false, getter: function() { var styles = this._seriesStyles, graph = this.get("graph"), dict, i; if(graph) { dict = graph.get("seriesDictionary"); if(dict) { styles = {}; for(i in dict) { if(dict.hasOwnProperty(i)) { styles[i] = dict[i].get("styles"); } } } } return styles; }, setter: function(val) { var i, l, s; if(Y_Lang.isArray(val)) { s = this.get("seriesCollection"); i = 0; l = val.length; for(; i < l; ++i) { this._setBaseAttribute(s[i], "styles", val[i]); } } else { for(i in val) { if(val.hasOwnProperty(i)) { s = this.getSeries(i); this._setBaseAttribute(s, "styles", val[i]); } } } return val; } }, /** * Styles for the graph. * * @attribute graphStyles * @type Object * @private */ graphStyles: { lazyAdd: false, getter: function() { var graph = this.get("graph"); if(graph) { return(graph.get("styles")); } return this._graphStyles; }, setter: function(val) { var graph = this.get("graph"); this._setBaseAttribute(graph, "styles", val); return val; } }, /** * Style properties for the chart. Contains a key indexed hash of the following: * <dl> * <dt>series</dt><dd>A key indexed hash containing references to the `styles` attribute for each series in the chart. * Specific style attributes vary depending on the series: * <ul> * <li><a href="AreaSeries.html#attr_styles">AreaSeries</a></li> * <li><a href="BarSeries.html#attr_styles">BarSeries</a></li> * <li><a href="ColumnSeries.html#attr_styles">ColumnSeries</a></li> * <li><a href="ComboSeries.html#attr_styles">ComboSeries</a></li> * <li><a href="LineSeries.html#attr_styles">LineSeries</a></li> * <li><a href="MarkerSeries.html#attr_styles">MarkerSeries</a></li> * <li><a href="SplineSeries.html#attr_styles">SplineSeries</a></li> * </ul> * </dd> * <dt>axes</dt><dd>A key indexed hash containing references to the `styles` attribute for each axes in the chart. Specific * style attributes can be found in the <a href="Axis.html#attr_styles">Axis</a> class.</dd> * <dt>graph</dt><dd>A reference to the `styles` attribute in the chart. Specific style attributes can be found in the * <a href="Graph.html#attr_styles">Graph</a> class.</dd> * </dl> * * @attribute styles * @type Object */ styles: { lazyAdd: false, getter: function() { var styles = { axes: this.get("axesStyles"), series: this.get("seriesStyles"), graph: this.get("graphStyles") }; return styles; }, setter: function(val) { if(val.hasOwnProperty("axes")) { if(this.get("axesStyles")) { this.set("axesStyles", val.axes); } else { this._axesStyles = val.axes; } } if(val.hasOwnProperty("series")) { if(this.get("seriesStyles")) { this.set("seriesStyles", val.series); } else { this._seriesStyles = val.series; } } if(val.hasOwnProperty("graph")) { this.set("graphStyles", val.graph); } } }, /** * Axes to appear in the chart. This can be a key indexed hash of axis instances or object literals * used to construct the appropriate axes. * * @attribute axes * @type Object */ axes: { lazyAdd: false, valueFn: "_getDefaultAxes", setter: function(val) { if(this.get("dataProvider")) { val = this._setAxes(val); } return val; } }, /** * Collection of series to appear on the chart. This can be an array of Series instances or object literals * used to construct the appropriate series. * * @attribute seriesCollection * @type Array */ seriesCollection: { lazyAdd: false, valueFn: "_getDefaultSeriesCollection", setter: function(val) { if(this.get("dataProvider")) { return this._parseSeriesCollection(val); } return val; } }, /** * Reference to the left-aligned axes for the chart. * * @attribute leftAxesCollection * @type Array * @private */ leftAxesCollection: {}, /** * Reference to the bottom-aligned axes for the chart. * * @attribute bottomAxesCollection * @type Array * @private */ bottomAxesCollection: {}, /** * Reference to the right-aligned axes for the chart. * * @attribute rightAxesCollection * @type Array * @private */ rightAxesCollection: {}, /** * Reference to the top-aligned axes for the chart. * * @attribute topAxesCollection * @type Array * @private */ topAxesCollection: {}, /** * Indicates whether or not the chart is stacked. * * @attribute stacked * @type Boolean */ stacked: { value: false }, /** * Direction of chart's category axis when there is no series collection specified. Charts can * be horizontal or vertical. When the chart type is column, the chart is horizontal. * When the chart type is bar, the chart is vertical. * * @attribute direction * @type String */ direction: { getter: function() { var type = this.get("type"); if(type === "bar") { return "vertical"; } else if(type === "column") { return "horizontal"; } return this._direction; }, setter: function(val) { this._direction = val; return this._direction; } }, /** * Indicates whether or not an area is filled in a combo chart. * * @attribute showAreaFill * @type Boolean */ showAreaFill: {}, /** * Indicates whether to display markers in a combo chart. * * @attribute showMarkers * @type Boolean */ showMarkers:{}, /** * Indicates whether to display lines in a combo chart. * * @attribute showLines * @type Boolean */ showLines:{}, /** * Indicates the key value used to identify a category axis in the `axes` hash. If * not specified, the categoryKey attribute value will be used. * * @attribute categoryAxisName * @type String */ categoryAxisName: { }, /** * Indicates the key value used to identify a the series axis when an axis not generated. * * @attribute valueAxisName * @type String */ valueAxisName: { value: "values" }, /** * Reference to the horizontalGridlines for the chart. * * @attribute horizontalGridlines * @type Gridlines */ horizontalGridlines: { getter: function() { var graph = this.get("graph"); if(graph) { return graph.get("horizontalGridlines"); } return this._horizontalGridlines; }, setter: function(val) { var graph = this.get("graph"); if(val && !Y_Lang.isObject(val)) { val = {}; } if(graph) { graph.set("horizontalGridlines", val); } else { this._horizontalGridlines = val; } } }, /** * Reference to the verticalGridlines for the chart. * * @attribute verticalGridlines * @type Gridlines */ verticalGridlines: { getter: function() { var graph = this.get("graph"); if(graph) { return graph.get("verticalGridlines"); } return this._verticalGridlines; }, setter: function(val) { var graph = this.get("graph"); if(val && !Y_Lang.isObject(val)) { val = {}; } if(graph) { graph.set("verticalGridlines", val); } else { this._verticalGridlines = val; } } }, /** * Type of chart when there is no series collection specified. * * @attribute type * @type String */ type: { getter: function() { if(this.get("stacked")) { return "stacked" + this._type; } return this._type; }, setter: function(val) { if(this._type === "bar") { if(val !== "bar") { this.set("direction", "horizontal"); } } else { if(val === "bar") { this.set("direction", "vertical"); } } this._type = val; return this._type; } }, /** * Reference to the category axis used by the chart. * * @attribute categoryAxis * @type Axis */ categoryAxis:{} } }); /** * The PieChart class creates a pie chart * * @class PieChart * @extends ChartBase * @constructor * @submodule charts-base */ Y.PieChart = Y.Base.create("pieChart", Y.Widget, [Y.ChartBase], { /** * Calculates and returns a `seriesCollection`. * * @method _getSeriesCollection * @return Array * @private */ _getSeriesCollection: function() { if(this._seriesCollection) { return this._seriesCollection; } var axes = this.get("axes"), sc = [], seriesKeys, i = 0, l, type = this.get("type"), key, catAxis = "categoryAxis", catKey = "categoryKey", valAxis = "valueAxis", seriesKey = "valueKey"; if(axes) { seriesKeys = axes.values.get("keyCollection"); key = axes.category.get("keyCollection")[0]; l = seriesKeys.length; for(; i < l; ++i) { sc[i] = {type:type}; sc[i][catAxis] = "category"; sc[i][valAxis] = "values"; sc[i][catKey] = key; sc[i][seriesKey] = seriesKeys[i]; } } this._seriesCollection = sc; return sc; }, /** * Creates `Axis` instances. * * @method _parseAxes * @param {Object} val Object containing `Axis` instances or objects in which to construct `Axis` instances. * @return Object * @private */ _parseAxes: function(hash) { if(!this._axes) { this._axes = {}; } var i, pos, axis, dh, config, AxisClass, type = this.get("type"), w = this.get("width"), h = this.get("height"), node = Y.Node.one(this._parentNode); if(!w) { this.set("width", node.get("offsetWidth")); w = this.get("width"); } if(!h) { this.set("height", node.get("offsetHeight")); h = this.get("height"); } for(i in hash) { if(hash.hasOwnProperty(i)) { dh = hash[i]; pos = type === "pie" ? "none" : dh.position; AxisClass = this._getAxisClass(dh.type); config = {dataProvider:this.get("dataProvider")}; if(dh.hasOwnProperty("roundingUnit")) { config.roundingUnit = dh.roundingUnit; } config.keys = dh.keys; config.width = w; config.height = h; config.position = pos; config.styles = dh.styles; axis = new AxisClass(config); axis.on("axisRendered", Y.bind(this._itemRendered, this)); this._axes[i] = axis; } } }, /** * Adds axes to the chart. * * @method _addAxes * @private */ _addAxes: function() { var axes = this.get("axes"), i, axis, p; if(!axes) { this.set("axes", this._getDefaultAxes()); axes = this.get("axes"); } if(!this._axesCollection) { this._axesCollection = []; } for(i in axes) { if(axes.hasOwnProperty(i)) { axis = axes[i]; p = axis.get("position"); if(!this.get(p + "AxesCollection")) { this.set(p + "AxesCollection", [axis]); } else { this.get(p + "AxesCollection").push(axis); } this._axesCollection.push(axis); } } }, /** * Renders the Graph. * * @method _addSeries * @private */ _addSeries: function() { var graph = this.get("graph"), seriesCollection = this.get("seriesCollection"); this._parseSeriesAxes(seriesCollection); graph.set("showBackground", false); graph.set("width", this.get("width")); graph.set("height", this.get("height")); graph.set("seriesCollection", seriesCollection); this._seriesCollection = graph.get("seriesCollection"); graph.render(this.get("contentBox")); }, /** * Parse and sets the axes for the chart. * * @method _parseSeriesAxes * @param {Array} c A collection `PieSeries` instance. * @private */ _parseSeriesAxes: function(c) { var i = 0, len = c.length, s, axes = this.get("axes"), axis; for(; i < len; ++i) { s = c[i]; if(s) { //If series is an actual series instance, //replace axes attribute string ids with axes if(s instanceof Y.PieSeries) { axis = s.get("categoryAxis"); if(axis && !(axis instanceof Y.Axis)) { s.set("categoryAxis", axes[axis]); } axis = s.get("valueAxis"); if(axis && !(axis instanceof Y.Axis)) { s.set("valueAxis", axes[axis]); } continue; } s.categoryAxis = axes.category; s.valueAxis = axes.values; if(!s.type) { s.type = this.get("type"); } } } }, /** * Generates and returns a key-indexed object containing `Axis` instances or objects used to create `Axis` instances. * * @method _getDefaultAxes * @return Object * @private */ _getDefaultAxes: function() { var catKey = this.get("categoryKey"), seriesKeys = this.get("seriesKeys").concat(), seriesAxis = "numeric"; return { values:{ keys:seriesKeys, type:seriesAxis }, category:{ keys:[catKey], type:this.get("categoryType") } }; }, /** * Returns an object literal containing a categoryItem and a valueItem for a given series index. * * @method getSeriesItem * @param series Reference to a series. * @param index Index of the specified item within a series. * @return Object */ getSeriesItems: function(series, index) { var categoryItem = { axis: series.get("categoryAxis"), key: series.get("categoryKey"), displayName: series.get("categoryDisplayName") }, valueItem = { axis: series.get("valueAxis"), key: series.get("valueKey"), displayName: series.get("valueDisplayName") }; categoryItem.value = categoryItem.axis.getKeyValueAt(categoryItem.key, index); valueItem.value = valueItem.axis.getKeyValueAt(valueItem.key, index); return {category:categoryItem, value:valueItem}; }, /** * Handler for sizeChanged event. * * @method _sizeChanged * @param {Object} e Event object. * @private */ _sizeChanged: function() { this._redraw(); }, /** * Redraws the chart instance. * * @method _redraw * @private */ _redraw: function() { var graph = this.get("graph"), w = this.get("width"), h = this.get("height"), dimension; if(graph) { dimension = Math.min(w, h); graph.set("width", dimension); graph.set("height", dimension); } }, /** * Formats tooltip text for a pie chart. * * @method _tooltipLabelFunction * @param {Object} categoryItem An object containing the following: * <dl> * <dt>axis</dt><dd>The axis to which the category is bound.</dd> * <dt>displayName</dt><dd>The display name set to the category (defaults to key if not provided)</dd> * <dt>key</dt><dd>The key of the category.</dd> * <dt>value</dt><dd>The value of the category</dd> * </dl> * @param {Object} valueItem An object containing the following: * <dl> * <dt>axis</dt><dd>The axis to which the item's series is bound.</dd> * <dt>displayName</dt><dd>The display name of the series. (defaults to key if not provided)</dd> * <dt>key</dt><dd>The key for the series.</dd> * <dt>value</dt><dd>The value for the series item.</dd> * </dl> * @param {Number} itemIndex The index of the item within the series. * @param {CartesianSeries} series The `PieSeries` instance of the item. * @return {HTMLElement} * @private */ _tooltipLabelFunction: function(categoryItem, valueItem, itemIndex, series) { var msg = DOCUMENT.createElement("div"), total = series.getTotalValues(), pct = Math.round((valueItem.value / total) * 10000)/100; msg.appendChild(DOCUMENT.createTextNode(categoryItem.displayName + ": " + categoryItem.axis.get("labelFunction").apply(this, [categoryItem.value, categoryItem.axis.get("labelFormat")]))); msg.appendChild(DOCUMENT.createElement("br")); msg.appendChild(DOCUMENT.createTextNode(valueItem.displayName + ": " + valueItem.axis.get("labelFunction").apply(this, [valueItem.value, valueItem.axis.get("labelFormat")]))); msg.appendChild(DOCUMENT.createElement("br")); msg.appendChild(DOCUMENT.createTextNode(pct + "%")); return msg; }, /** * Returns the appropriate message based on the key press. * * @method _getAriaMessage * @param {Number} key The keycode that was pressed. * @return String */ _getAriaMessage: function(key) { var msg = "", categoryItem, items, series, valueItem, seriesIndex = 0, itemIndex = this._itemIndex, len, total, pct, markers; series = this.getSeries(parseInt(seriesIndex, 10)); markers = series.get("markers"); len = markers && markers.length ? markers.length : 0; if(key === 37) { itemIndex = itemIndex > 0 ? itemIndex - 1 : len - 1; } else if(key === 39) { itemIndex = itemIndex >= len - 1 ? 0 : itemIndex + 1; } this._itemIndex = itemIndex; items = this.getSeriesItems(series, itemIndex); categoryItem = items.category; valueItem = items.value; total = series.getTotalValues(); pct = Math.round((valueItem.value / total) * 10000)/100; if(categoryItem && valueItem) { msg += categoryItem.displayName + ": " + categoryItem.axis.formatLabel.apply(this, [categoryItem.value, categoryItem.axis.get("labelFormat")]) + ", "; msg += valueItem.displayName + ": " + valueItem.axis.formatLabel.apply(this, [valueItem.value, valueItem.axis.get("labelFormat")]) + ", "; msg += "Percent of total " + valueItem.displayName + ": " + pct + "%,"; } else { msg += "No data available,"; } msg += (itemIndex + 1) + " of " + len + ". "; return msg; }, /** * Destructor implementation for the PieChart class. * * @method destructor * @protected */ destructor: function() { var series, axis, tooltip = this.get("tooltip"), tooltipNode = tooltip.node, graph = this.get("graph"), axesCollection = this._axesCollection, seriesCollection = this.get("seriesCollection"); while(seriesCollection.length > 0) { series = seriesCollection.shift(); series.destroy(true); } while(axesCollection.length > 0) { axis = axesCollection.shift(); if(axis instanceof Y.Axis) { axis.destroy(true); } } if(this._description) { this._description.empty(); this._description.remove(true); } if(this._liveRegion) { this._liveRegion.empty(); this._liveRegion.remove(true); } if(graph) { graph.destroy(true); } if(tooltipNode) { tooltipNode.empty(); tooltipNode.remove(true); } } }, { ATTRS: { /** * Sets the aria description for the chart. * * @attribute ariaDescription * @type String */ ariaDescription: { value: "Use the left and right keys to navigate through items.", setter: function(val) { if(this._description) { this._description.set("text", val); } return val; } }, /** * Axes to appear in the chart. * * @attribute axes * @type Object */ axes: { getter: function() { return this._axes; }, setter: function(val) { this._parseAxes(val); } }, /** * Collection of series to appear on the chart. This can be an array of Series instances or object literals * used to describe a Series instance. * * @attribute seriesCollection * @type Array */ seriesCollection: { lazyAdd: false, getter: function() { return this._getSeriesCollection(); }, setter: function(val) { return this._setSeriesCollection(val); } }, /** * Type of chart when there is no series collection specified. * * @attribute type * @type String */ type: { value: "pie" } } }); /** * 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.17.2', { "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" ] });