You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5188 lines
155 KiB
5188 lines
155 KiB
/*
|
|
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"
|
|
]
|
|
});
|
|
|