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