X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/530ef4b6c5b943cfa68b779d11cf7de29aa878bf..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/docs/source/Line.html diff --git a/docs/source/Line.html b/docs/source/Line.html new file mode 100644 index 00000000..f924e614 --- /dev/null +++ b/docs/source/Line.html @@ -0,0 +1,990 @@ +Sencha Documentation Project
/**
+ * @class Ext.chart.series.Line
+ * @extends Ext.chart.series.Cartesian
+ * 
+ * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different 
+ * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
+ * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart 
+ * documentation for more information. A typical configuration object for the line series could be:
+ *
+ * {@img Ext.chart.series.Line/Ext.chart.series.Line.png Ext.chart.series.Line chart series}
+ *
+ *     var store = Ext.create('Ext.data.JsonStore', {
+ *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
+ *         data: [
+ *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
+ *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
+ *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
+ *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
+ *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
+ *         ]
+ *     });
+ *     
+ *     Ext.create('Ext.chart.Chart', {
+ *         renderTo: Ext.getBody(),
+ *         width: 500,
+ *         height: 300,
+ *         animate: true,
+ *         store: store,
+ *         axes: [{
+ *             type: 'Numeric',
+ *             position: 'bottom',
+ *             fields: ['data1'],
+ *             label: {
+ *                 renderer: Ext.util.Format.numberRenderer('0,0')
+ *             },
+ *             title: 'Sample Values',
+ *             grid: true,
+ *             minimum: 0
+ *         }, {
+ *             type: 'Category',
+ *             position: 'left',
+ *             fields: ['name'],
+ *             title: 'Sample Metrics'
+ *         }],
+ *         series: [{
+ *             type: 'line',
+ *             highlight: {
+ *                 size: 7,
+ *                 radius: 7
+ *             },
+ *             axis: 'left',
+ *             xField: 'name',
+ *             yField: 'data1',
+ *             markerCfg: {
+ *                 type: 'cross',
+ *                 size: 4,
+ *                 radius: 4,
+ *                 'stroke-width': 0
+ *             }
+ *         }, {
+ *             type: 'line',
+ *             highlight: {
+ *                 size: 7,
+ *                 radius: 7
+ *             },
+ *             axis: 'left',
+ *             fill: true,
+ *             xField: 'name',
+ *             yField: 'data3',
+ *             markerCfg: {
+ *                 type: 'circle',
+ *                 size: 4,
+ *                 radius: 4,
+ *                 'stroke-width': 0
+ *             }
+ *         }]
+ *     });
+ *  
+ * In this configuration we're adding two series (or lines), one bound to the `data1` property of the store and the other to `data3`. The type for both configurations is 
+ * `line`. The `xField` for both series is the same, the name propert of the store. Both line series share the same axis, the left axis. You can set particular marker 
+ * configuration by adding properties onto the markerConfig object. Both series have an object as highlight so that markers animate smoothly to the properties in highlight 
+ * when hovered. The second series has `fill=true` which means that the line will also have an area below it of the same color.
+ */
+
+Ext.define('Ext.chart.series.Line', {
+
+    /* Begin Definitions */
+
+    extend: 'Ext.chart.series.Cartesian',
+
+    alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
+
+    requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
+
+    /* End Definitions */
+
+    type: 'line',
+    
+    alias: 'series.line',
+
+    /**
+     * @cfg {Number} selectionTolerance
+     * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
+     */
+    selectionTolerance: 20,
+    
+    /**
+     * @cfg {Boolean} showMarkers
+     * Whether markers should be displayed at the data points along the line. If true,
+     * then the {@link #markerConfig} config item will determine the markers' styling.
+     */
+    showMarkers: true,
+
+    /**
+     * @cfg {Object} markerConfig
+     * The display style for the markers. Only used if {@link #showMarkers} is true.
+     * The markerConfig is a configuration object containing the same set of properties defined in
+     * the Sprite class. For example, if we were to set red circles as markers to the line series we could
+     * pass the object:
+     *
+     <pre><code>
+        markerConfig: {
+            type: 'circle',
+            radius: 4,
+            'fill': '#f00'
+        }
+     </code></pre>
+     
+     */
+    markerConfig: {},
+
+    /**
+     * @cfg {Object} style
+     * An object containing styles for the visualization lines. These styles will override the theme styles. 
+     * Some options contained within the style object will are described next.
+     */
+    style: {},
+    
+    /**
+     * @cfg {Boolean} smooth
+     * If true, the line will be smoothed/rounded around its points, otherwise straight line
+     * segments will be drawn. Defaults to false.
+     */
+    smooth: false,
+
+    /**
+     * @cfg {Boolean} fill
+     * If true, the area below the line will be filled in using the {@link #style.eefill} and
+     * {@link #style.opacity} config properties. Defaults to false.
+     */
+    fill: false,
+
+    constructor: function(config) {
+        this.callParent(arguments);
+        var me = this,
+            surface = me.chart.surface,
+            shadow = me.chart.shadow,
+            i, l;
+        Ext.apply(me, config, {
+            highlightCfg: {
+                'stroke-width': 3
+            },
+            shadowAttributes: [{
+                "stroke-width": 6,
+                "stroke-opacity": 0.05,
+                stroke: 'rgb(0, 0, 0)',
+                translate: {
+                    x: 1,
+                    y: 1
+                }
+            }, {
+                "stroke-width": 4,
+                "stroke-opacity": 0.1,
+                stroke: 'rgb(0, 0, 0)',
+                translate: {
+                    x: 1,
+                    y: 1
+                }
+            }, {
+                "stroke-width": 2,
+                "stroke-opacity": 0.15,
+                stroke: 'rgb(0, 0, 0)',
+                translate: {
+                    x: 1,
+                    y: 1
+                }
+            }]
+        });
+        me.group = surface.getGroup(me.seriesId);
+        if (me.showMarkers) {
+            me.markerGroup = surface.getGroup(me.seriesId + '-markers');
+        }
+        if (shadow) {
+            for (i = 0, l = this.shadowAttributes.length; i < l; i++) {
+                me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
+            }
+        }
+    },
+    
+    // @private makes an average of points when there are more data points than pixels to be rendered.
+    shrink: function(xValues, yValues, size) {
+        // Start at the 2nd point...
+        var len = xValues.length,
+            ratio = Math.floor(len / size),
+            i = 1,
+            xSum = 0,
+            ySum = 0,
+            xRes = [xValues[0]],
+            yRes = [yValues[0]];
+        
+        for (; i < len; ++i) {
+            xSum += xValues[i] || 0;
+            ySum += yValues[i] || 0;
+            if (i % ratio == 0) {
+                xRes.push(xSum/ratio);
+                yRes.push(ySum/ratio);
+                xSum = 0;
+                ySum = 0;
+            }
+        }
+        return {
+            x: xRes,
+            y: yRes
+        };
+    },
+
+    /**
+     * Draws the series for the current chart.
+     */
+    drawSeries: function() {
+        var me = this,
+            chart = me.chart,
+            store = chart.substore || chart.store,
+            surface = chart.surface,
+            chartBBox = chart.chartBBox,
+            bbox = {},
+            group = me.group,
+            gutterX = chart.maxGutter[0],
+            gutterY = chart.maxGutter[1],
+            showMarkers = me.showMarkers,
+            markerGroup = me.markerGroup,
+            enableShadows = chart.shadow,
+            shadowGroups = me.shadowGroups,
+            shadowAttributes = this.shadowAttributes,
+            lnsh = shadowGroups.length,
+            dummyPath = ["M"],
+            path = ["M"],
+            markerIndex = chart.markerIndex,
+            axes = [].concat(me.axis),
+            shadowGroup,
+            shadowBarAttr,
+            xValues = [],
+            yValues = [],
+            onbreak = false,
+            markerStyle = me.markerStyle,
+            seriesStyle = me.seriesStyle,
+            seriesLabelStyle = me.seriesLabelStyle,
+            colorArrayStyle = me.colorArrayStyle,
+            colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
+            seriesIdx = me.seriesIdx, shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
+            x, y, prevX, prevY, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
+            yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
+            endLineStyle, type, props, firstMarker;
+        
+        //if store is empty then there's nothing to draw.
+        if (!store || !store.getCount()) {
+            return;
+        }
+        
+        //prepare style objects for line and markers
+        endMarkerStyle = Ext.apply(markerStyle, me.markerConfig);
+        type = endMarkerStyle.type;
+        delete endMarkerStyle.type;
+        endLineStyle = Ext.apply(seriesStyle, me.style);
+        //if no stroke with is specified force it to 0.5 because this is
+        //about making *lines*
+        if (!endLineStyle['stroke-width']) {
+            endLineStyle['stroke-width'] = 0.5;
+        }
+        //If we're using a time axis and we need to translate the points,
+        //then reuse the first markers as the last markers.
+        if (markerIndex && markerGroup && markerGroup.getCount()) {
+            for (i = 0; i < markerIndex; i++) {
+                marker = markerGroup.getAt(i);
+                markerGroup.remove(marker);
+                markerGroup.add(marker);
+                markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
+                marker.setAttributes({
+                    x: 0,
+                    y: 0,
+                    translate: {
+                        x: markerAux.attr.translation.x,
+                        y: markerAux.attr.translation.y
+                    }
+                }, true);
+            }
+        }
+        
+        me.unHighlightItem();
+        me.cleanHighlights();
+
+        me.setBBox();
+        bbox = me.bbox;
+
+        me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
+
+        for (i = 0, ln = axes.length; i < ln; i++) { 
+            axis = chart.axes.get(axes[i]);
+            if (axis) {
+                ends = axis.calcEnds();
+                if (axis.position == 'top' || axis.position == 'bottom') {
+                    minX = ends.from;
+                    maxX = ends.to;
+                }
+                else {
+                    minY = ends.from;
+                    maxY = ends.to;
+                }
+            }
+        }
+        // If a field was specified without a corresponding axis, create one to get bounds
+        //only do this for the axis where real values are bound (that's why we check for
+        //me.axis)
+        if (me.xField && !Ext.isNumber(minX)
+            && (me.axis == 'bottom' || me.axis == 'top')) {
+            axis = Ext.create('Ext.chart.axis.Axis', {
+                chart: chart,
+                fields: [].concat(me.xField)
+            }).calcEnds();
+            minX = axis.from;
+            maxX = axis.to;
+        }
+        if (me.yField && !Ext.isNumber(minY)
+            && (me.axis == 'right' || me.axis == 'left')) {
+            axis = Ext.create('Ext.chart.axis.Axis', {
+                chart: chart,
+                fields: [].concat(me.yField)
+            }).calcEnds();
+            minY = axis.from;
+            maxY = axis.to;
+        }
+
+        if (isNaN(minX)) {
+            minX = 0;
+            xScale = bbox.width / (store.getCount() - 1);
+        }
+        else {
+            xScale = bbox.width / (maxX - minX);
+        }
+
+        if (isNaN(minY)) {
+            minY = 0;
+            yScale = bbox.height / (store.getCount() - 1);
+        } 
+        else {
+            yScale = bbox.height / (maxY - minY);
+        }
+
+        store.each(function(record, i) {
+            xValue = record.get(me.xField);
+            yValue = record.get(me.yField);
+            //skip undefined values
+            if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
+                //<debug warn>
+                if (Ext.isDefined(Ext.global.console)) {
+                    Ext.global.console.warn("[Ext.chart.series.Line]  Skipping a store element with an undefined value at ", record, xValue, yValue);
+                }
+                //</debug>
+                return;
+            }
+            // Ensure a value
+            if (typeof xValue == 'string' || typeof xValue == 'object'
+                //set as uniform distribution if the axis is a category axis.
+                || (me.axis != 'top' && me.axis != 'bottom')) {
+                xValue = i;
+            }
+            if (typeof yValue == 'string' || typeof yValue == 'object'
+                //set as uniform distribution if the axis is a category axis.
+                || (me.axis != 'left' && me.axis != 'right')) {
+                yValue = i;
+            }
+            xValues.push(xValue);
+            yValues.push(yValue);
+        }, me);
+
+        ln = xValues.length;
+        if (ln > bbox.width) {
+            coords = me.shrink(xValues, yValues, bbox.width);
+            xValues = coords.x;
+            yValues = coords.y;
+        }
+
+        me.items = [];
+
+        ln = xValues.length;
+        for (i = 0; i < ln; i++) {
+            xValue = xValues[i];
+            yValue = yValues[i];
+            if (yValue === false) {
+                if (path.length == 1) {
+                    path = [];
+                }
+                onbreak = true;
+                me.items.push(false);
+                continue;
+            } else {
+                x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
+                y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
+                if (onbreak) {
+                    onbreak = false;
+                    path.push('M');
+                } 
+                path = path.concat([x, y]);
+            }
+            if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
+                firstY = y;
+            }
+            // If this is the first line, create a dummypath to animate in from.
+            if (!me.line || chart.resizing) {
+                dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
+            }
+
+            // When resizing, reset before animating
+            if (chart.animate && chart.resizing && me.line) {
+                me.line.setAttributes({
+                    path: dummyPath
+                }, true);
+                if (me.fillPath) {
+                    me.fillPath.setAttributes({
+                        path: dummyPath,
+                        opacity: 0.2
+                    }, true);
+                }
+                if (me.line.shadows) {
+                    shadows = me.line.shadows;
+                    for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
+                        shadow = shadows[j];
+                        shadow.setAttributes({
+                            path: dummyPath
+                        }, true);
+                    }
+                }
+            }
+            if (showMarkers) {
+                marker = markerGroup.getAt(i);
+                if (!marker) {
+                    marker = Ext.chart.Shape[type](surface, Ext.apply({
+                        group: [group, markerGroup],
+                        x: 0, y: 0,
+                        translate: {
+                            x: prevX || x, 
+                            y: prevY || (bbox.y + bbox.height / 2)
+                        },
+                        value: '"' + xValue + ', ' + yValue + '"'
+                    }, endMarkerStyle));
+                    marker._to = {
+                        translate: {
+                            x: x,
+                            y: y
+                        }
+                    };
+                } else {
+                    marker.setAttributes({
+                        value: '"' + xValue + ', ' + yValue + '"',
+                        x: 0, y: 0,
+                        hidden: false
+                    }, true);
+                    marker._to = {
+                        translate: {
+                            x: x, y: y
+                        }
+                    };
+                }
+            }
+            me.items.push({
+                series: me,
+                value: [xValue, yValue],
+                point: [x, y],
+                sprite: marker,
+                storeItem: store.getAt(i)
+            });
+            prevX = x;
+            prevY = y;
+        }
+        
+        if (path.length <= 1) {
+            //nothing to be rendered
+            return;    
+        }
+        
+        if (me.smooth) {
+            path = Ext.draw.Draw.smooth(path, 6);
+        }
+        
+        //Correct path if we're animating timeAxis intervals
+        if (chart.markerIndex && me.previousPath) {
+            fromPath = me.previousPath;
+            fromPath.splice(1, 2);
+        } else {
+            fromPath = path;
+        }
+
+        // Only create a line if one doesn't exist.
+        if (!me.line) {
+            me.line = surface.add(Ext.apply({
+                type: 'path',
+                group: group,
+                path: dummyPath,
+                stroke: endLineStyle.stroke || endLineStyle.fill
+            }, endLineStyle || {}));
+            //unset fill here (there's always a default fill withing the themes).
+            me.line.setAttributes({
+                fill: 'none'
+            });
+            if (!endLineStyle.stroke && colorArrayLength) {
+                me.line.setAttributes({
+                    stroke: colorArrayStyle[seriesIdx % colorArrayLength]
+                }, true);
+            }
+            if (enableShadows) {
+                //create shadows
+                shadows = me.line.shadows = [];                
+                for (shindex = 0; shindex < lnsh; shindex++) {
+                    shadowBarAttr = shadowAttributes[shindex];
+                    shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
+                    shadow = chart.surface.add(Ext.apply({}, {
+                        type: 'path',
+                        group: shadowGroups[shindex]
+                    }, shadowBarAttr));
+                    shadows.push(shadow);
+                }
+            }
+        }
+        if (me.fill) {
+            fillPath = path.concat([
+                ["L", x, bbox.y + bbox.height],
+                ["L", bbox.x, bbox.y + bbox.height],
+                ["L", bbox.x, firstY]
+            ]);
+            if (!me.fillPath) {
+                me.fillPath = surface.add({
+                    group: group,
+                    type: 'path',
+                    opacity: endLineStyle.opacity || 0.3,
+                    fill: colorArrayStyle[seriesIdx % colorArrayLength] || endLineStyle.fill,
+                    path: dummyPath
+                });
+            }
+        }
+        markerCount = showMarkers && markerGroup.getCount();
+        if (chart.animate) {
+            fill = me.fill;
+            line = me.line;
+            //Add renderer to line. There is not unique record associated with this.
+            rendererAttributes = me.renderer(line, false, { path: path }, i, store);
+            Ext.apply(rendererAttributes, endLineStyle || {}, {
+                stroke: endLineStyle.stroke || endLineStyle.fill
+            });
+            //fill should not be used here but when drawing the special fill path object
+            delete rendererAttributes.fill;
+            if (chart.markerIndex && me.previousPath) {
+                me.animation = animation = me.onAnimate(line, {
+                    to: rendererAttributes,
+                    from: {
+                        path: fromPath
+                    }
+                });
+            } else {
+                me.animation = animation = me.onAnimate(line, {
+                    to: rendererAttributes
+                });
+            }
+            //animate shadows
+            if (enableShadows) {
+                shadows = line.shadows;
+                for(j = 0; j < lnsh; j++) {
+                    if (chart.markerIndex && me.previousPath) {
+                        me.onAnimate(shadows[j], {
+                            to: { path: path },
+                            from: { path: fromPath }
+                        });
+                    } else {
+                        me.onAnimate(shadows[j], {
+                            to: { path: path }
+                        });
+                    }
+                }
+            }
+            //animate fill path
+            if (fill) {
+                me.onAnimate(me.fillPath, {
+                    to: Ext.apply({}, {
+                        path: fillPath,
+                        fill: colorArrayStyle[seriesIdx % colorArrayLength] || endLineStyle.fill
+                    }, endLineStyle || {})
+                });
+            }
+            //animate markers
+            if (showMarkers) {
+                for(i = 0; i < ln; i++) {
+                    item = markerGroup.getAt(i);
+                    if (item) {
+                        if (me.items[i]) {
+                            rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
+                            me.onAnimate(item, {
+                                to: Ext.apply(rendererAttributes, endMarkerStyle || {})
+                            });
+                        } else {
+                            item.setAttributes(Ext.apply({
+                                hidden: true 
+                            }, item._to), true);
+                        }
+                    }
+                }
+                for(; i < markerCount; i++) {
+                    item = markerGroup.getAt(i);
+                    item.hide(true);
+                }
+//                for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
+//                    item = markerGroup.getAt(i);
+//                    item.hide(true);
+//                }
+            }
+        } else {
+            rendererAttributes = me.renderer(me.line, false, { path: path, hidden: false }, i, store);
+            Ext.apply(rendererAttributes, endLineStyle || {}, {
+                stroke: endLineStyle.stroke || endLineStyle.fill
+            });
+            //fill should not be used here but when drawing the special fill path object
+            delete rendererAttributes.fill;
+            me.line.setAttributes(rendererAttributes, true);
+            //set path for shadows
+            if (enableShadows) {
+                shadows = me.line.shadows;
+                for(j = 0; j < lnsh; j++) {
+                    shadows[j].setAttributes({
+                        path: path
+                    }, true);
+                }
+            }
+            if (me.fill) {
+                me.fillPath.setAttributes({
+                    path: fillPath
+                }, true);
+            }
+            if (showMarkers) {
+                for(i = 0; i < ln; i++) {
+                    item = markerGroup.getAt(i);
+                    if (item) {
+                        if (me.items[i]) {
+                            rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
+                            item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
+                        } else {
+                            item.hide(true);
+                        }
+                    }
+                }
+                for(; i < markerCount; i++) {
+                    item = markerGroup.getAt(i);
+                    item.hide(true);
+                }
+            }
+        }
+
+        if (chart.markerIndex) {
+            path.splice(1, 0, path[1], path[2]);
+            me.previousPath = path;
+        }
+        me.renderLabels();
+        me.renderCallouts();
+    },
+    
+    // @private called when a label is to be created.
+    onCreateLabel: function(storeItem, item, i, display) {
+        var me = this,
+            group = me.labelsGroup,
+            config = me.label,
+            bbox = me.bbox,
+            endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
+
+        return me.chart.surface.add(Ext.apply({
+            'type': 'text',
+            'text-anchor': 'middle',
+            'group': group,
+            'x': item.point[0],
+            'y': bbox.y + bbox.height / 2
+        }, endLabelStyle || {}));
+    },
+    
+    // @private called when a label is to be created.
+    onPlaceLabel: function(label, storeItem, item, i, display, animate) {
+        var me = this,
+            chart = me.chart,
+            resizing = chart.resizing,
+            config = me.label,
+            format = config.renderer,
+            field = config.field,
+            bbox = me.bbox,
+            x = item.point[0],
+            y = item.point[1],
+            radius = item.sprite.attr.radius,
+            bb, width, height;
+        
+        label.setAttributes({
+            text: format(storeItem.get(field)),
+            hidden: true
+        }, true);
+        
+        if (display == 'rotate') {
+            label.setAttributes({
+                'text-anchor': 'start',
+                'rotation': {
+                    x: x,
+                    y: y,
+                    degrees: -45
+                }
+            }, true);
+            //correct label position to fit into the box
+            bb = label.getBBox();
+            width = bb.width;
+            height = bb.height;
+            x = x < bbox.x? bbox.x : x;
+            x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
+            y = (y - height < bbox.y)? bbox.y + height : y;
+        
+        } else if (display == 'under' || display == 'over') {
+            //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
+            bb = item.sprite.getBBox();
+            bb.width = bb.width || (radius * 2);
+            bb.height = bb.height || (radius * 2);
+            y = y + (display == 'over'? -bb.height : bb.height);
+            //correct label position to fit into the box
+            bb = label.getBBox();
+            width = bb.width/2;
+            height = bb.height/2;
+            x = x - width < bbox.x? bbox.x + width : x;
+            x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
+            y = y - height < bbox.y? bbox.y + height : y;
+            y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
+        }
+        
+        if (me.chart.animate && !me.chart.resizing) {
+            label.show(true);
+            me.onAnimate(label, {
+                to: {
+                    x: x,
+                    y: y
+                }
+            });
+        } else {
+            label.setAttributes({
+                x: x,
+                y: y
+            }, true);
+            if (resizing) {
+                me.animation.on('afteranimate', function() {
+                    label.show(true);
+                });
+            } else {
+                label.show(true);
+            }
+        }
+    },
+
+    //@private Overriding highlights.js highlightItem method.
+    highlightItem: function() {
+        var me = this;
+        me.callParent(arguments);
+        if (this.line && !this.highlighted) {
+            if (!('__strokeWidth' in this.line)) {
+                this.line.__strokeWidth = this.line.attr['stroke-width'] || 0;
+            }
+            if (this.line.__anim) {
+                this.line.__anim.paused = true;
+            }
+            this.line.__anim = Ext.create('Ext.fx.Anim', {
+                target: this.line,
+                to: {
+                    'stroke-width': this.line.__strokeWidth + 3
+                }
+            });
+            this.highlighted = true;
+        }
+    },
+
+    //@private Overriding highlights.js unHighlightItem method.
+    unHighlightItem: function() {
+        var me = this;
+        me.callParent(arguments);
+        if (this.line && this.highlighted) {
+            this.line.__anim = Ext.create('Ext.fx.Anim', {
+                target: this.line,
+                to: {
+                    'stroke-width': this.line.__strokeWidth
+                }
+            });
+            this.highlighted = false;
+        }
+    },
+
+    //@private called when a callout needs to be placed.
+    onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
+        if (!display) {
+            return;
+        }
+        
+        var me = this,
+            chart = me.chart,
+            surface = chart.surface,
+            resizing = chart.resizing,
+            config = me.callouts,
+            items = me.items,
+            prev = i == 0? false : items[i -1].point,
+            next = (i == items.length -1)? false : items[i +1].point,
+            cur = [+item.point[0], +item.point[1]],
+            dir, norm, normal, a, aprev, anext,
+            offsetFromViz = config.offsetFromViz || 30,
+            offsetToSide = config.offsetToSide || 10,
+            offsetBox = config.offsetBox || 3,
+            boxx, boxy, boxw, boxh,
+            p, clipRect = me.clipRect,
+            bbox = {
+                width: config.styles.width || 10,
+                height: config.styles.height || 10
+            },
+            x, y;
+
+        //get the right two points
+        if (!prev) {
+            prev = cur;
+        }
+        if (!next) {
+            next = cur;
+        }
+        a = (next[1] - prev[1]) / (next[0] - prev[0]);
+        aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
+        anext = (next[1] - cur[1]) / (next[0] - cur[0]);
+        
+        norm = Math.sqrt(1 + a * a);
+        dir = [1 / norm, a / norm];
+        normal = [-dir[1], dir[0]];
+        
+        //keep the label always on the outer part of the "elbow"
+        if (aprev > 0 && anext < 0 && normal[1] < 0
+            || aprev < 0 && anext > 0 && normal[1] > 0) {
+            normal[0] *= -1;
+            normal[1] *= -1;
+        } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
+                   || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
+            normal[0] *= -1;
+            normal[1] *= -1;
+        }
+        //position
+        x = cur[0] + normal[0] * offsetFromViz;
+        y = cur[1] + normal[1] * offsetFromViz;
+
+        //box position and dimensions
+        boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
+        boxy = y - bbox.height /2 - offsetBox;
+        boxw = bbox.width + 2 * offsetBox;
+        boxh = bbox.height + 2 * offsetBox;
+        
+        //now check if we're out of bounds and invert the normal vector correspondingly
+        //this may add new overlaps between labels (but labels won't be out of bounds).
+        if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
+            normal[0] *= -1;
+        }
+        if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
+            normal[1] *= -1;
+        }
+
+        //update positions
+        x = cur[0] + normal[0] * offsetFromViz;
+        y = cur[1] + normal[1] * offsetFromViz;
+        
+        //update box position and dimensions
+        boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
+        boxy = y - bbox.height /2 - offsetBox;
+        boxw = bbox.width + 2 * offsetBox;
+        boxh = bbox.height + 2 * offsetBox;
+        
+        if (chart.animate) {
+            //set the line from the middle of the pie to the box.
+            me.onAnimate(callout.lines, {
+                to: {
+                    path: ["M", cur[0], cur[1], "L", x, y, "Z"]
+                }
+            });
+            //set component position
+            if (callout.panel) {
+                callout.panel.setPosition(boxx, boxy, true);
+            }
+        }
+        else {
+            //set the line from the middle of the pie to the box.
+            callout.lines.setAttributes({
+                path: ["M", cur[0], cur[1], "L", x, y, "Z"]
+            }, true);
+            //set component position
+            if (callout.panel) {
+                callout.panel.setPosition(boxx, boxy);
+            }
+        }
+        for (p in callout) {
+            callout[p].show(true);
+        }
+    },
+    
+    isItemInPoint: function(x, y, item, i) {
+        var me = this,
+            items = me.items,
+            tolerance = me.selectionTolerance,
+            result = null,
+            prevItem,
+            nextItem,
+            prevPoint,
+            nextPoint,
+            ln,
+            x1,
+            y1,
+            x2,
+            y2,
+            xIntersect,
+            yIntersect,
+            dist1, dist2, dist, midx, midy,
+            sqrt = Math.sqrt, abs = Math.abs;
+        
+        nextItem = items[i];
+        prevItem = i && items[i - 1];
+        
+        if (i >= ln) {
+            prevItem = items[ln - 1];
+        }
+        prevPoint = prevItem && prevItem.point;
+        nextPoint = nextItem && nextItem.point;
+        x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
+        y1 = prevItem ? prevPoint[1] : nextPoint[1];
+        x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
+        y2 = nextItem ? nextPoint[1] : prevPoint[1];
+        dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
+        dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
+        dist = Math.min(dist1, dist2);
+        
+        if (dist <= tolerance) {
+            return dist == dist1? prevItem : nextItem;
+        }
+        return false;
+    },
+    
+    // @private toggle visibility of all series elements (markers, sprites).
+    toggleAll: function(show) {
+        var me = this,
+            i, ln, shadow, shadows;
+        if (!show) {
+            Ext.chart.series.Line.superclass.hideAll.call(me);
+        }
+        else {
+            Ext.chart.series.Line.superclass.showAll.call(me);
+        }
+        if (me.line) {
+            me.line.setAttributes({
+                hidden: !show
+            }, true);
+            //hide shadows too
+            if (me.line.shadows) {
+                for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
+                    shadow = shadows[i];
+                    shadow.setAttributes({
+                        hidden: !show
+                    }, true);
+                }
+            }
+        }
+        if (me.fillPath) {
+            me.fillPath.setAttributes({
+                hidden: !show
+            }, true);
+        }
+    },
+    
+    // @private hide all series elements (markers, sprites).
+    hideAll: function() {
+        this.toggleAll(false);
+    },
+    
+    // @private hide all series elements (markers, sprites).
+    showAll: function() {
+        this.toggleAll(true);
+    }
+});
\ No newline at end of file