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