| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124 | <!DOCTYPE html><html><head>  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  <title>The source code</title>  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>  <style type="text/css">    .highlight { display: block; background-color: #ddd; }  </style>  <script type="text/javascript">    function highlight() {      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";    }  </script></head><body onload="prettyPrint(); highlight();">  <pre class="prettyprint lang-js"><span id='Ext-chart-series-Line'>/**</span> * @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: * *     @example *     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': 4,  'data2': 4,  '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: 'left', *                 fields: ['data1', 'data2'], *                 label: { *                     renderer: Ext.util.Format.numberRenderer('0,0') *                 }, *                 title: 'Sample Values', *                 grid: true, *                 minimum: 0 *             }, *             { *                 type: 'Category', *                 position: 'bottom', *                 fields: ['name'], *                 title: 'Sample Metrics' *             } *         ], *         series: [ *             { *                 type: 'line', *                 highlight: { *                     size: 7, *                     radius: 7 *                 }, *                 axis: 'left', *                 xField: 'name', *                 yField: 'data1', *                 markerConfig: { *                     type: 'cross', *                     size: 4, *                     radius: 4, *                     'stroke-width': 0 *                 } *             }, *             { *                 type: 'line', *                 highlight: { *                     size: 7, *                     radius: 7 *                 }, *                 axis: 'left', *                 fill: true, *                 xField: 'name', *                 yField: 'data2', *                 markerConfig: { *                     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. * * **Note:** In the series definition remember to explicitly set the axis to bind the * values of the line series to. This can be done by using the `axis` configuration property. */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',<span id='Ext-chart-series-Line-cfg-axis'>    /**</span>     * @cfg {String} axis     * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.     * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a     * relative scale will be used.     */<span id='Ext-chart-series-Line-cfg-selectionTolerance'>    /**</span>     * @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,<span id='Ext-chart-series-Line-cfg-showMarkers'>    /**</span>     * @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,<span id='Ext-chart-series-Line-cfg-markerConfig'>    /**</span>     * @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: {},<span id='Ext-chart-series-Line-cfg-style'>    /**</span>     * @cfg {Object} style     * An object containing style properties for the visualization lines and fill.     * These styles will override the theme styles.  The following are valid style properties:     *     * - `stroke` - an rgb or hex color string for the background color of the line     * - `stroke-width` - the width of the stroke (integer)     * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`     * - `opacity` - the opacity of the line and the fill color (decimal)     *     * Example usage:     *     *     style: {     *         stroke: '#00ff00',     *         'stroke-width': 10,     *         fill: '#80A080',     *         opacity: 0.2     *     }     */    style: {},<span id='Ext-chart-series-Line-cfg-smooth'>    /**</span>     * @cfg {Boolean/Number} smooth     * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise     * straight line segments will be drawn.     *     * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in     * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.     *     * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.     */    smooth: false,<span id='Ext-chart-series-Line-property-defaultSmoothness'>    /**</span>     * @private Default numeric smoothing value to be used when {@link #smooth} = true.     */    defaultSmoothness: 3,<span id='Ext-chart-series-Line-cfg-fill'>    /**</span>     * @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;        config.highlightCfg = Ext.Object.merge({ 'stroke-width': 3 }, config.highlightCfg);        Ext.apply(me, config, {            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 = me.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        };    },<span id='Ext-chart-series-Line-method-drawSeries'>    /**</span>     * Draws the series for the current chart.     */    drawSeries: function() {        var me = this,            chart = me.chart,            chartAxes = chart.axes,            store = chart.getChartStore(),            data = store.data.items,            record,            storeCount = store.getCount(),            surface = me.chart.surface,            bbox = {},            group = me.group,            showMarkers = me.showMarkers,            markerGroup = me.markerGroup,            enableShadows = chart.shadow,            shadowGroups = me.shadowGroups,            shadowAttributes = me.shadowAttributes,            smooth = me.smooth,            lnsh = shadowGroups.length,            dummyPath = ["M"],            path = ["M"],            renderPath = ["M"],            smoothPath = ["M"],            markerIndex = chart.markerIndex,            axes = [].concat(me.axis),            shadowBarAttr,            xValues = [],            xValueMap = {},            yValues = [],            yValueMap = {},            onbreak = false,            storeIndices = [],            markerStyle = me.markerStyle,            seriesStyle = me.seriesStyle,            colorArrayStyle = me.colorArrayStyle,            colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,            isNumber = Ext.isNumber,            seriesIdx = me.seriesIdx,             boundAxes = me.getAxesForXAndYFields(),            boundXAxis = boundAxes.xAxis,            boundYAxis = boundAxes.yAxis,            shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,            x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,            yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,            endLineStyle, type, count, opacity, lineOpacity, fillOpacity, fillDefaultValue;        if (me.fireEvent('beforedraw', me) === false) {            return;        }        //if store is empty or the series is excluded in the legend then there's nothing to draw.        if (!storeCount || me.seriesIsHidden) {            me.hide();            me.items = [];            if (me.line) {                me.line.hide(true);                if (me.line.shadows) {                    shadows = me.line.shadows;                    for (j = 0, lnsh = shadows.length; j < lnsh; j++) {                        shadow = shadows[j];                        shadow.hide(true);                    }                }                if (me.fillPath) {                    me.fillPath.hide(true);                }            }            me.line = null;            me.fillPath = null;            return;        }        //prepare style objects for line and markers        endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig, {            fill: me.seriesStyle.fill || colorArrayStyle[seriesIdx % colorArrayStyle.length]        });        type = endMarkerStyle.type;        delete endMarkerStyle.type;        endLineStyle = seriesStyle;        //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;        }                //set opacity values        opacity = 'opacity' in endLineStyle ? endLineStyle.opacity : 1;        fillDefaultValue = 'opacity' in endLineStyle ? endLineStyle.opacity : 0.3;        lineOpacity = 'lineOpacity' in endLineStyle ? endLineStyle.lineOpacity : opacity;        fillOpacity = 'fillOpacity' in endLineStyle ? endLineStyle.fillOpacity : fillDefaultValue;        //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];        if (axis = chartAxes.get(boundXAxis)) {            ends = axis.applyData();            minX = ends.from;            maxX = ends.to;        }        if (axis = chartAxes.get(boundYAxis)) {            ends = axis.applyData();            minY = ends.from;            maxY = ends.to;        }        // If a field was specified without a corresponding axis, create one to get bounds        if (me.xField && !Ext.isNumber(minX)) {            axis = me.getMinMaxXValues();            minX = axis[0];            maxX = axis[1];        }        if (me.yField && !Ext.isNumber(minY)) {            axis = me.getMinMaxYValues();            minY = axis[0];            maxY = axis[1];        }                if (isNaN(minX)) {            minX = 0;            xScale = bbox.width / ((storeCount - 1) || 1);        }        else {            xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);        }        if (isNaN(minY)) {            minY = 0;            yScale = bbox.height / ((storeCount - 1) || 1);        }        else {            yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);        }        // Extract all x and y values from the store        for (i = 0, ln = data.length; i < ln; i++) {            record = data[i];            xValue = record.get(me.xField);            // Ensure a value            if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)                //set as uniform distribution if the axis is a category axis.                || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {                    if (xValue in xValueMap) {                        xValue = xValueMap[xValue];                    } else {                        xValue = xValueMap[xValue] = i;                    }            }            // Filter out values that don't fit within the pan/zoom buffer area            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>                continue;            }            // Ensure a value            if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)                //set as uniform distribution if the axis is a category axis.                || boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {                yValue = i;            }            storeIndices.push(i);            xValues.push(xValue);            yValues.push(yValue);        }        ln = xValues.length;        if (ln > bbox.width) {            coords = me.shrink(xValues, yValues, bbox.width);            xValues = coords.x;            yValues = coords.y;        }        me.items = [];        count = 0;        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;                firstX = x;            }            // 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,                    opacity: lineOpacity                }, true);                if (me.fillPath) {                    me.fillPath.setAttributes({                        path: dummyPath,                        opacity: fillOpacity                    }, 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(count++);                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 + '"',                        zIndex: 4000                    }, 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(storeIndices[i])            });            prevX = x;            prevY = y;        }        if (path.length <= 1) {            //nothing to be rendered            return;        }        if (me.smooth) {            smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);        }        renderPath = smooth ? smoothPath : path;        //Correct path if we're animating timeAxis intervals        if (chart.markerIndex && me.previousPath) {            fromPath = me.previousPath;            if (!smooth) {                Ext.Array.erase(fromPath, 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 || {}));            //set configuration opacity            me.line.setAttributes({                opacity: lineOpacity            }, true);            if (enableShadows) {                me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);            }            //unset fill here (there's always a default fill withing the themes).            me.line.setAttributes({                fill: 'none',                zIndex: 3000            });            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 = surface.add(Ext.apply({}, {                        type: 'path',                        group: shadowGroups[shindex]                    }, shadowBarAttr));                    shadows.push(shadow);                }            }        }        if (me.fill) {            fillPath = renderPath.concat([                ["L", x, bbox.y + bbox.height],                ["L", firstX, bbox.y + bbox.height],                ["L", firstX, firstY]            ]);            if (!me.fillPath) {                me.fillPath = surface.add({                    group: group,                    type: 'path',                    fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],                    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: renderPath }, 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;            line.show(true);            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++) {                    shadows[j].show(true);                    if (chart.markerIndex && me.previousPath) {                        me.onAnimate(shadows[j], {                            to: { path: renderPath },                            from: { path: fromPath }                        });                    } else {                        me.onAnimate(shadows[j], {                            to: { path: renderPath }                        });                    }                }            }            //animate fill path            if (fill) {                me.fillPath.show(true);                me.onAnimate(me.fillPath, {                    to: Ext.apply({}, {                        path: fillPath,                        fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],                        'stroke-width': 0,                        opacity: fillOpacity                    }, endLineStyle || {})                });            }            //animate markers            if (showMarkers) {                count = 0;                for(i = 0; i < ln; i++) {                    if (me.items[i]) {                        item = markerGroup.getAt(count++);                        if (item) {                            rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);                            me.onAnimate(item, {                                to: Ext.apply(rendererAttributes, endMarkerStyle || {})                            });                            item.show(true);                        }                    }                }                for(; count < markerCount; count++) {                    item = markerGroup.getAt(count);                    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: renderPath, 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);            me.line.setAttributes({                opacity: lineOpacity            }, true);            //set path for shadows            if (enableShadows) {                shadows = me.line.shadows;                for(j = 0; j < lnsh; j++) {                    shadows[j].setAttributes({                        path: renderPath,                        hidden: false                    }, true);                }            }            if (me.fill) {                me.fillPath.setAttributes({                    path: fillPath,                    hidden: false,                    opacity: fillOpacity                }, true);            }            if (showMarkers) {                count = 0;                for(i = 0; i < ln; i++) {                    if (me.items[i]) {                        item = markerGroup.getAt(count++);                        if (item) {                            rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);                            item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);                            if (!item.attr.hidden) {                                item.show(true);                            }                        }                    }                }                for(; count < markerCount; count++) {                    item = markerGroup.getAt(count);                    item.hide(true);                }            }        }        if (chart.markerIndex) {            if (me.smooth) {                Ext.Array.erase(path, 1, 2);            } else {                Ext.Array.splice(path, 1, 0, path[1], path[2]);            }            me.previousPath = path;        }        me.renderLabels();        me.renderCallouts();        me.fireEvent('draw', me);    },    // @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) {                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 (me.line && !me.highlighted) {            if (!('__strokeWidth' in me.line)) {                me.line.__strokeWidth = parseFloat(me.line.attr['stroke-width']) || 0;            }            if (me.line.__anim) {                me.line.__anim.paused = true;            }            me.line.__anim = Ext.create('Ext.fx.Anim', {                target: me.line,                to: {                    'stroke-width': me.line.__strokeWidth + 3                }            });            me.highlighted = true;        }    },    // @private Overriding highlights.js unHighlightItem method.    unHighlightItem: function() {        var me = this;        me.callParent(arguments);        if (me.line && me.highlighted) {            me.line.__anim = Ext.create('Ext.fx.Anim', {                target: me.line,                to: {                    'stroke-width': me.line.__strokeWidth                }            });            me.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.Cartesian.prototype.hideAll.call(me);        }        else {            Ext.chart.series.Cartesian.prototype.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);    }});</pre></body></html>
 |