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