    /**
     * @class Ext.chart.series.Gauge
     *
     * Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
     * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instantiating the
     * visualization and using the `setValue` method to adjust the value you want.
     *
     * An example of Gauge visualization:
     *
     *     @example
     *     var store = Ext.create('Ext.data.JsonStore', {
     *         fields: ['value'],
     *         data: [
     *             { 'value':80 }
     *         ]
     *     });
     *
     *     Ext.create('Ext.chart.Chart', {
     *         renderTo: Ext.getBody(),
     *         store: store,
     *         width: 400,
     *         height: 250,
     *         animate: true,
     *         insetPadding: 30,
     *         axes: [{
     *             type: 'gauge',
     *             position: 'gauge',
     *             minimum: 0,
     *             maximum: 100,
     *             steps: 10,
     *             margin: 10
     *         }],
     *         series: [{
     *             type: 'gauge',
     *             field: 'value',
     *             donut: 30,
     *             colorSet: ['#F49D10', '#ddd']
     *         }]
     *     });
     *
     *     Ext.widget("button", {
     *         renderTo: Ext.getBody(),
     *         text: "Refresh",
     *         handler: function() {
     *             store.getAt(0).set('value', Math.round(Math.random()*100));
     *         }
     *     });
     *
     * In this example we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
     * setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
     * the visual display and the color set to be used with the visualization.
     */
    Ext.define('Ext.chart.series.Gauge', {

        /* Begin Definitions */

        extend: 'Ext.chart.series.Series',

        /* End Definitions */

        type: "gauge",
        alias: 'series.gauge',

        rad: Math.PI / 180,

        /**
         * @cfg {Number} highlightDuration
         * The duration for the pie slice highlight effect.
         */
        highlightDuration: 150,

        /**
         * @cfg {String} angleField (required)
         * The store record field name to be used for the pie angles.
         * The values bound to this field name must be positive real numbers.
         */
        angleField: false,

        /**
         * @cfg {Boolean} needle
         * Use the Gauge Series as an area series or add a needle to it. Default's false.
         */
        needle: false,

        /**
         * @cfg {Boolean/Number} donut
         * Use the entire disk or just a fraction of it for the gauge. Default's false.
         */
        donut: false,

        /**
         * @cfg {Boolean} showInLegend
         * Whether to add the pie chart elements as legend items. Default's false.
         */
        showInLegend: false,

        /**
         * @cfg {Object} style
         * An object containing styles for overriding series styles from Theming.
         */
        style: {},

        constructor: function(config) {
            this.callParent(arguments);
            var me = this,
                chart = me.chart,
                surface = chart.surface,
                store = chart.store,
                shadow = chart.shadow, i, l, cfg;
            Ext.apply(me, config, {
                shadowAttributes: [{
                    "stroke-width": 6,
                    "stroke-opacity": 1,
                    stroke: 'rgb(200, 200, 200)',
                    translate: {
                        x: 1.2,
                        y: 2
                    }
                },
                {
                    "stroke-width": 4,
                    "stroke-opacity": 1,
                    stroke: 'rgb(150, 150, 150)',
                    translate: {
                        x: 0.9,
                        y: 1.5
                    }
                },
                {
                    "stroke-width": 2,
                    "stroke-opacity": 1,
                    stroke: 'rgb(100, 100, 100)',
                    translate: {
                        x: 0.6,
                        y: 1
                    }
                }]
            });
            me.group = surface.getGroup(me.seriesId);
            if (shadow) {
                for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
                    me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
                }
            }
            surface.customAttributes.segment = function(opt) {
                return me.getSegment(opt);
            };
        },

        // @private updates some onbefore render parameters.
        initialize: function() {
            var me = this,
                store = me.chart.getChartStore(),
                data = store.data.items,
                label = me.label,
                ln = data.length;

            me.yField = [];
            if (label && label.field && ln > 0) {
                me.yField.push(data[0].get(label.field));
            }
        },

        // @private returns an object with properties for a Slice
        getSegment: function(opt) {
            var me = this,
                rad = me.rad,
                cos = Math.cos,
                sin = Math.sin,
                abs = Math.abs,
                x = me.centerX,
                y = me.centerY,
                x1 = 0, x2 = 0, x3 = 0, x4 = 0,
                y1 = 0, y2 = 0, y3 = 0, y4 = 0,
                delta = 1e-2,
                r = opt.endRho - opt.startRho,
                startAngle = opt.startAngle,
                endAngle = opt.endAngle,
                midAngle = (startAngle + endAngle) / 2 * rad,
                margin = opt.margin || 0,
                flag = abs(endAngle - startAngle) > 180,
                a1 = Math.min(startAngle, endAngle) * rad,
                a2 = Math.max(startAngle, endAngle) * rad,
                singleSlice = false;

            x += margin * cos(midAngle);
            y += margin * sin(midAngle);

            x1 = x + opt.startRho * cos(a1);
            y1 = y + opt.startRho * sin(a1);

            x2 = x + opt.endRho * cos(a1);
            y2 = y + opt.endRho * sin(a1);

            x3 = x + opt.startRho * cos(a2);
            y3 = y + opt.startRho * sin(a2);

            x4 = x + opt.endRho * cos(a2);
            y4 = y + opt.endRho * sin(a2);

            if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
                singleSlice = true;
            }
            //Solves mysterious clipping bug with IE
            if (singleSlice) {
                return {
                    path: [
                    ["M", x1, y1],
                    ["L", x2, y2],
                    ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
                    ["Z"]]
                };
            } else {
                return {
                    path: [
                    ["M", x1, y1],
                    ["L", x2, y2],
                    ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
                    ["L", x3, y3],
                    ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
                    ["Z"]]
                };
            }
        },

        // @private utility function to calculate the middle point of a pie slice.
        calcMiddle: function(item) {
            var me = this,
                rad = me.rad,
                slice = item.slice,
                x = me.centerX,
                y = me.centerY,
                startAngle = slice.startAngle,
                endAngle = slice.endAngle,
                radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
                donut = +me.donut,
                a1 = Math.min(startAngle, endAngle) * rad,
                a2 = Math.max(startAngle, endAngle) * rad,
                midAngle = -(a1 + (a2 - a1) / 2),
                xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
                ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);

            item.middle = {
                x: xm,
                y: ym
            };
        },

        /**
         * Draws the series for the current chart.
         */
        drawSeries: function() {
            var me = this,
                chart = me.chart,
                store = chart.getChartStore(),
                group = me.group,
                animate = me.chart.animate,
                axis = me.chart.axes.get(0),
                minimum = axis && axis.minimum || me.minimum || 0,
                maximum = axis && axis.maximum || me.maximum || 0,
                field = me.angleField || me.field || me.xField,
                surface = chart.surface,
                chartBBox = chart.chartBBox,
                rad = me.rad,
                donut = +me.donut,
                values = {},
                items = [],
                seriesStyle = me.seriesStyle,
                seriesLabelStyle = me.seriesLabelStyle,
                colorArrayStyle = me.colorArrayStyle,
                colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
                cos = Math.cos,
                sin = Math.sin,
                defaultStart = -180,
                reverse = me.reverse,
                rendererAttributes, centerX, centerY, slice, slices, sprite, value,
                item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
                p, spriteOptions, bbox, splitAngle, sliceA, sliceB;

            Ext.apply(seriesStyle, me.style || {});

            me.setBBox();
            bbox = me.bbox;

            //override theme colors
            if (me.colorSet) {
                colorArrayStyle = me.colorSet;
                colorArrayLength = colorArrayStyle.length;
            }

            //if not store or store is empty then there's nothing to draw
            if (!store || !store.getCount() || me.seriesIsHidden) {
                me.hide();
                me.items = [];
                return;
            }

            centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
            centerY = me.centerY = chartBBox.y + chartBBox.height;
            me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
            me.slices = slices = [];
            me.items = items = [];

            if (!me.value) {
                record = store.getAt(0);
                me.value = record.get(field);
            }

            value = reverse ? maximum - me.value : me.value;
            if (me.needle) {
                sliceA = {
                    series: me,
                    value: value,
                    startAngle: defaultStart,
                    endAngle: 0,
                    rho: me.radius
                };
                splitAngle = defaultStart * (1 - (value - minimum) / (maximum - minimum));
                slices.push(sliceA);
            } else {
                splitAngle = defaultStart * (1 - (value - minimum) / (maximum - minimum));
                sliceA = {
                    series: me,
                    value: value,
                    startAngle: defaultStart,
                    endAngle: splitAngle,
                    rho: me.radius
                };
                sliceB = {
                    series: me,
                    value: maximum - value,
                    startAngle: splitAngle,
                    endAngle: 0,
                    rho: me.radius
                };

                if (reverse) {
                    slices.push(sliceB, sliceA);
                } else {
                    slices.push(sliceA, sliceB);
                }
            }

            //do pie slices after.
            for (i = 0, ln = slices.length; i < ln; i++) {
                slice = slices[i];
                sprite = group.getAt(i);
                //set pie slice properties
                rendererAttributes = Ext.apply({
                    segment: {
                        startAngle: slice.startAngle,
                        endAngle: slice.endAngle,
                        margin: 0,
                        rho: slice.rho,
                        startRho: slice.rho * +donut / 100,
                        endRho: slice.rho
                    }
                }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));

                item = Ext.apply({},
                rendererAttributes.segment, {
                    slice: slice,
                    series: me,
                    storeItem: record,
                    index: i
                });
                items[i] = item;
                // Create a new sprite if needed (no height)
                if (!sprite) {
                    spriteOptions = Ext.apply({
                        type: "path",
                        group: group
                    }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
                    sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
                }
                slice.sprite = slice.sprite || [];
                item.sprite = sprite;
                slice.sprite.push(sprite);
                if (animate) {
                    rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
                    sprite._to = rendererAttributes;
                    me.onAnimate(sprite, {
                        to: rendererAttributes
                    });
                } else {
                    rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
                        hidden: false
                    }), i, store);
                    sprite.setAttributes(rendererAttributes, true);
                }
            }

            if (me.needle) {
                splitAngle = splitAngle * Math.PI / 180;

                if (!me.needleSprite) {
                    me.needleSprite = me.chart.surface.add({
                        type: 'path',
                        path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
                                    centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
                               'L', centerX + me.radius * cos(splitAngle),
                                    centerY + -Math.abs(me.radius * sin(splitAngle))],
                        'stroke-width': 4,
                        'stroke': '#222'
                    });
                } else {
                    if (animate) {
                        me.onAnimate(me.needleSprite, {
                            to: {
                            path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
                                        centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
                                   'L', centerX + me.radius * cos(splitAngle),
                                        centerY + -Math.abs(me.radius * sin(splitAngle))]
                            }
                        });
                    } else {
                        me.needleSprite.setAttributes({
                            type: 'path',
                            path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
                                        centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
                                   'L', centerX + me.radius * cos(splitAngle),
                                        centerY + -Math.abs(me.radius * sin(splitAngle))]
                        });
                    }
                }
                me.needleSprite.setAttributes({
                    hidden: false
                }, true);
            }

            delete me.value;
        },

        /**
         * Sets the Gauge chart to the current specified value.
        */
        setValue: function (value) {
            this.value = value;
            this.drawSeries();
        },

        // @private callback for when creating a label sprite.
        onCreateLabel: function(storeItem, item, i, display) {},

        // @private callback for when placing a label sprite.
        onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},

        // @private callback for when placing a callout.
        onPlaceCallout: function() {},

        // @private handles sprite animation for the series.
        onAnimate: function(sprite, attr) {
            sprite.show();
            return this.callParent(arguments);
        },

        isItemInPoint: function(x, y, item, i) {
            var me = this,
                cx = me.centerX,
                cy = me.centerY,
                abs = Math.abs,
                dx = abs(x - cx),
                dy = abs(y - cy),
                startAngle = item.startAngle,
                endAngle = item.endAngle,
                rho = Math.sqrt(dx * dx + dy * dy),
                angle = Math.atan2(y - cy, x - cx) / me.rad;

            //Only trigger events for the filled portion of the Gauge.
            return (i === 0) && (angle >= startAngle && angle < endAngle &&
                                 rho >= item.startRho && rho <= item.endRho);
        },

        /**
         * Returns the color of the series (to be displayed as color for the series legend item).
         * @param item {Object} Info about the item; same format as returned by #getItemForPoint
         */
        getLegendColor: function(index) {
            var colors = this.colorSet || this.colorArrayStyle;
            return colors[index % colors.length];
        }
    });