/**
* @class Ext.chart.series.Series
*
* Series is the abstract class containing the common logic to all chart series. Series includes
* methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
* mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
*
* ## Listeners
*
* The series class supports listeners via the Observable syntax. Some of these listeners are:
*
* - `itemmouseup` When the user interacts with a marker.
* - `itemmousedown` When the user interacts with a marker.
* - `itemmousemove` When the user iteracts with a marker.
* - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
*
* For example:
*
* series: [{
* type: 'column',
* axis: 'left',
* listeners: {
* 'afterrender': function() {
* console('afterrender');
* }
* },
* xField: 'category',
* yField: 'data1'
* }]
*
*/
Ext.define('Ext.chart.series.Series', {
/* Begin Definitions */
mixins: {
observable: 'Ext.util.Observable',
labels: 'Ext.chart.Label',
highlights: 'Ext.chart.Highlight',
tips: 'Ext.chart.Tip',
callouts: 'Ext.chart.Callout'
},
/* End Definitions */
/**
* @cfg {Boolean|Object} highlight
* If set to `true` it will highlight the markers or the series when hovering
* with the mouse. This parameter can also be an object with the same style
* properties you would apply to a {@link Ext.draw.Sprite} to apply custom
* styles to markers and series.
*/
/**
* @cfg {Object} tips
* Add tooltips to the visualization's markers. The options for the tips are the
* same configuration used with {@link Ext.tip.ToolTip}. For example:
*
* tips: {
* trackMouse: true,
* width: 140,
* height: 28,
* renderer: function(storeItem, item) {
* this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
* }
* },
*/
/**
* @cfg {String} type
* The type of series. Set in subclasses.
*/
type: null,
/**
* @cfg {String} title
* The human-readable name of the series.
*/
title: null,
/**
* @cfg {Boolean} showInLegend
* Whether to show this series in the legend.
*/
showInLegend: true,
/**
* @cfg {Function} renderer
* A function that can be overridden to set custom styling properties to each rendered element.
* Passes in (sprite, record, attributes, index, store) to the function.
*/
renderer: function(sprite, record, attributes, index, store) {
return attributes;
},
/**
* @cfg {Array} shadowAttributes
* An array with shadow attributes
*/
shadowAttributes: null,
//@private triggerdrawlistener flag
triggerAfterDraw: false,
/**
* @cfg {Object} listeners
* An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
*
* <ul>
* <li>itemmouseover</li>
* <li>itemmouseout</li>
* <li>itemmousedown</li>
* <li>itemmouseup</li>
* </ul>
*/
constructor: function(config) {
var me = this;
if (config) {
Ext.apply(me, config);
}
me.shadowGroups = [];
me.mixins.labels.constructor.call(me, config);
me.mixins.highlights.constructor.call(me, config);
me.mixins.tips.constructor.call(me, config);
me.mixins.callouts.constructor.call(me, config);
me.addEvents({
scope: me,
itemmouseover: true,
itemmouseout: true,
itemmousedown: true,
itemmouseup: true,
mouseleave: true,
afterdraw: true,
/**
* @event titlechange
* Fires when the series title is changed via {@link #setTitle}.
* @param {String} title The new title value
* @param {Number} index The index in the collection of titles
*/
titlechange: true
});
me.mixins.observable.constructor.call(me, config);
me.on({
scope: me,
itemmouseover: me.onItemMouseOver,
itemmouseout: me.onItemMouseOut,
mouseleave: me.onMouseLeave
});
},
// @private set the bbox and clipBox for the series
setBBox: function(noGutter) {
var me = this,
chart = me.chart,
chartBBox = chart.chartBBox,
gutterX = noGutter ? 0 : chart.maxGutter[0],
gutterY = noGutter ? 0 : chart.maxGutter[1],
clipBox, bbox;
clipBox = {
x: chartBBox.x,
y: chartBBox.y,
width: chartBBox.width,
height: chartBBox.height
};
me.clipBox = clipBox;
bbox = {
x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
};
me.bbox = bbox;
},
// @private set the animation for the sprite
onAnimate: function(sprite, attr) {
var me = this;
sprite.stopAnimation();
if (me.triggerAfterDraw) {
return sprite.animate(Ext.applyIf(attr, me.chart.animate));
} else {
me.triggerAfterDraw = true;
return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
listeners: {
'afteranimate': function() {
me.triggerAfterDraw = false;
me.fireEvent('afterrender');
}
}
}));
}
},
// @private return the gutter.
getGutters: function() {
return [0, 0];
},
// @private wrapper for the itemmouseover event.
onItemMouseOver: function(item) {
var me = this;
if (item.series === me) {
if (me.highlight) {
me.highlightItem(item);
}
if (me.tooltip) {
me.showTip(item);
}
}
},
// @private wrapper for the itemmouseout event.
onItemMouseOut: function(item) {
var me = this;
if (item.series === me) {
me.unHighlightItem();
if (me.tooltip) {
me.hideTip(item);
}
}
},
// @private wrapper for the mouseleave event.
onMouseLeave: function() {
var me = this;
me.unHighlightItem();
if (me.tooltip) {
me.hideTip();
}
},
/**
* For a given x/y point relative to the Surface, find a corresponding item from this
* series, if any.
* @param {Number} x
* @param {Number} y
* @return {Object} An object describing the item, or null if there is no matching item. The exact contents of
* this object will vary by series type, but should always contain at least the following:
* <ul>
* <li>{Ext.chart.series.Series} series - the Series object to which the item belongs</li>
* <li>{Object} value - the value(s) of the item's data point</li>
* <li>{Array} point - the x/y coordinates relative to the chart box of a single point
* for this data item, which can be used as e.g. a tooltip anchor point.</li>
* <li>{Ext.draw.Sprite} sprite - the item's rendering Sprite.
* </ul>
*/
getItemForPoint: function(x, y) {
//if there are no items to query just return null.
if (!this.items || !this.items.length || this.seriesIsHidden) {
return null;
}
var me = this,
items = me.items,
bbox = me.bbox,
item, i, ln;
// Check bounds
if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
return null;
}
for (i = 0, ln = items.length; i < ln; i++) {
if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
return items[i];
}
}
return null;
},
isItemInPoint: function(x, y, item, i) {
return false;
},
/**
* Hides all the elements in the series.
*/
hideAll: function() {
var me = this,
items = me.items,
item, len, i, sprite;
me.seriesIsHidden = true;
me._prevShowMarkers = me.showMarkers;
me.showMarkers = false;
//hide all labels
me.hideLabels(0);
//hide all sprites
for (i = 0, len = items.length; i < len; i++) {
item = items[i];
sprite = item.sprite;
if (sprite) {
sprite.setAttributes({
hidden: true
}, true);
}
}
},
/**
* Shows all the elements in the series.
*/
showAll: function() {
var me = this,
prevAnimate = me.chart.animate;
me.chart.animate = false;
me.seriesIsHidden = false;
me.showMarkers = me._prevShowMarkers;
me.drawSeries();
me.chart.animate = prevAnimate;
},
/**
* Returns a string with the color to be used for the series legend item.
*/
getLegendColor: function(index) {
var me = this, fill, stroke;
if (me.seriesStyle) {
fill = me.seriesStyle.fill;
stroke = me.seriesStyle.stroke;
if (fill && fill != 'none') {
return fill;
}
return stroke;
}
return '#000';
},
/**
* Checks whether the data field should be visible in the legend
* @private
* @param {Number} index The index of the current item
*/
visibleInLegend: function(index){
var excludes = this.__excludes;
if (excludes) {
return !excludes[index];
}
return !this.seriesIsHidden;
},
/**
* Changes the value of the {@link #title} for the series.
* Arguments can take two forms:
* <ul>
* <li>A single String value: this will be used as the new single title for the series (applies
* to series with only one yField)</li>
* <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
* </ul>
* @param {Number} index
* @param {String} title
*/
setTitle: function(index, title) {
var me = this,
oldTitle = me.title;
if (Ext.isString(index)) {
title = index;
index = 0;
}
if (Ext.isArray(oldTitle)) {
oldTitle[index] = title;
} else {
me.title = title;
}
me.fireEvent('titlechange', title, index);
}
});