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