/**
* Graph manages and contains series instances for a `CartesianChart`
* instance.
*
* @module charts
* @submodule charts-base
* @class Graph
* @constructor
* @extends Widget
* @uses Renderer
*/
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.CartesianSeries)
{
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);
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,
seriesType,
series;
seriesData.graph = this;
if(!seriesTypes.hasOwnProperty(type))
{
seriesTypes[type] = [];
}
typeSeriesCollection = seriesTypes[type];
seriesData.graph = this;
seriesData.order = typeSeriesCollection.length;
seriesData.graphOrder = seriesCollection.length;
seriesType = this._getSeries(seriesData.type);
series = new seriesType(seriesData);
this.addDispatcher(series);
series.after("drawingComplete", Y.bind(this._drawingCompleteHandler, this));
typeSeriesCollection.push(series);
seriesCollection.push(series);
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(e)
{
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);
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: {},
/**
* 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 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 && val.axis)
{
gl = new Y.Gridlines({direction:"horizontal", axis:val.axis, graph:this, styles:val.styles});
return gl;
}
}
},
/**
* Reference to the vertical `Gridlines` instance.
*
* @attribute verticalGridlines
* @type Gridlines
* @default null
*/
verticalGridlines: {
value: null,
setter: function(val)
{
var 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 && val.axis)
{
gl = new Y.Gridlines({direction:"vertical", axis:val.axis, graph:this, styles:val.styles});
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
*/
}
});