Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / docs / source / Line.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-chart-series-Line'>/**
19 </span> * @class Ext.chart.series.Line
20  * @extends Ext.chart.series.Cartesian
21  * 
22  * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different 
23  * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
24  * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart 
25  * documentation for more information. A typical configuration object for the line series could be:
26  *
27  * {@img Ext.chart.series.Line/Ext.chart.series.Line.png Ext.chart.series.Line chart series}
28  *
29  *     var store = Ext.create('Ext.data.JsonStore', {
30  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
31  *         data: [
32  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
33  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
34  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
35  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
36  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
37  *         ]
38  *     });
39  *     
40  *     Ext.create('Ext.chart.Chart', {
41  *         renderTo: Ext.getBody(),
42  *         width: 500,
43  *         height: 300,
44  *         animate: true,
45  *         store: store,
46  *         axes: [{
47  *             type: 'Numeric',
48  *             position: 'bottom',
49  *             fields: ['data1'],
50  *             label: {
51  *                 renderer: Ext.util.Format.numberRenderer('0,0')
52  *             },
53  *             title: 'Sample Values',
54  *             grid: true,
55  *             minimum: 0
56  *         }, {
57  *             type: 'Category',
58  *             position: 'left',
59  *             fields: ['name'],
60  *             title: 'Sample Metrics'
61  *         }],
62  *         series: [{
63  *             type: 'line',
64  *             highlight: {
65  *                 size: 7,
66  *                 radius: 7
67  *             },
68  *             axis: 'left',
69  *             xField: 'name',
70  *             yField: 'data1',
71  *             markerCfg: {
72  *                 type: 'cross',
73  *                 size: 4,
74  *                 radius: 4,
75  *                 'stroke-width': 0
76  *             }
77  *         }, {
78  *             type: 'line',
79  *             highlight: {
80  *                 size: 7,
81  *                 radius: 7
82  *             },
83  *             axis: 'left',
84  *             fill: true,
85  *             xField: 'name',
86  *             yField: 'data3',
87  *             markerCfg: {
88  *                 type: 'circle',
89  *                 size: 4,
90  *                 radius: 4,
91  *                 'stroke-width': 0
92  *             }
93  *         }]
94  *     });
95  *  
96  * In this configuration we're adding two series (or lines), one bound to the `data1` 
97  * property of the store and the other to `data3`. The type for both configurations is 
98  * `line`. The `xField` for both series is the same, the name propert of the store. 
99  * Both line series share the same axis, the left axis. You can set particular marker 
100  * configuration by adding properties onto the markerConfig object. Both series have 
101  * an object as highlight so that markers animate smoothly to the properties in highlight 
102  * when hovered. The second series has `fill=true` which means that the line will also 
103  * have an area below it of the same color.
104  *
105  * **Note:** In the series definition remember to explicitly set the axis to bind the 
106  * values of the line series to. This can be done by using the `axis` configuration property.
107  */
108 Ext.define('Ext.chart.series.Line', {
109
110     /* Begin Definitions */
111
112     extend: 'Ext.chart.series.Cartesian',
113
114     alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
115
116     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
117
118     /* End Definitions */
119
120     type: 'line',
121     
122     alias: 'series.line',
123     
124 <span id='Ext-chart-series-Line-cfg-axis'>    /**
125 </span>     * @cfg {String} axis
126      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
127      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
128      * relative scale will be used.
129      */
130
131 <span id='Ext-chart-series-Line-cfg-selectionTolerance'>    /**
132 </span>     * @cfg {Number} selectionTolerance
133      * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
134      */
135     selectionTolerance: 20,
136     
137 <span id='Ext-chart-series-Line-cfg-showMarkers'>    /**
138 </span>     * @cfg {Boolean} showMarkers
139      * Whether markers should be displayed at the data points along the line. If true,
140      * then the {@link #markerConfig} config item will determine the markers' styling.
141      */
142     showMarkers: true,
143
144 <span id='Ext-chart-series-Line-cfg-markerConfig'>    /**
145 </span>     * @cfg {Object} markerConfig
146      * The display style for the markers. Only used if {@link #showMarkers} is true.
147      * The markerConfig is a configuration object containing the same set of properties defined in
148      * the Sprite class. For example, if we were to set red circles as markers to the line series we could
149      * pass the object:
150      *
151      &lt;pre&gt;&lt;code&gt;
152         markerConfig: {
153             type: 'circle',
154             radius: 4,
155             'fill': '#f00'
156         }
157      &lt;/code&gt;&lt;/pre&gt;
158      
159      */
160     markerConfig: {},
161
162 <span id='Ext-chart-series-Line-cfg-style'>    /**
163 </span>     * @cfg {Object} style
164      * An object containing styles for the visualization lines. These styles will override the theme styles. 
165      * Some options contained within the style object will are described next.
166      */
167     style: {},
168     
169 <span id='Ext-chart-series-Line-cfg-smooth'>    /**
170 </span>     * @cfg {Boolean/Number} smooth
171      * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
172      * straight line segments will be drawn.
173      *
174      * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
175      * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
176      *
177      * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
178      */
179     smooth: false,
180
181 <span id='Ext-chart-series-Line-property-defaultSmoothness'>    /**
182 </span>     * @private Default numeric smoothing value to be used when {@link #smooth} = true.
183      */
184     defaultSmoothness: 3,
185
186 <span id='Ext-chart-series-Line-cfg-fill'>    /**
187 </span>     * @cfg {Boolean} fill
188      * If true, the area below the line will be filled in using the {@link #style.eefill} and
189      * {@link #style.opacity} config properties. Defaults to false.
190      */
191     fill: false,
192
193     constructor: function(config) {
194         this.callParent(arguments);
195         var me = this,
196             surface = me.chart.surface,
197             shadow = me.chart.shadow,
198             i, l;
199         Ext.apply(me, config, {
200             highlightCfg: {
201                 'stroke-width': 3
202             },
203             shadowAttributes: [{
204                 &quot;stroke-width&quot;: 6,
205                 &quot;stroke-opacity&quot;: 0.05,
206                 stroke: 'rgb(0, 0, 0)',
207                 translate: {
208                     x: 1,
209                     y: 1
210                 }
211             }, {
212                 &quot;stroke-width&quot;: 4,
213                 &quot;stroke-opacity&quot;: 0.1,
214                 stroke: 'rgb(0, 0, 0)',
215                 translate: {
216                     x: 1,
217                     y: 1
218                 }
219             }, {
220                 &quot;stroke-width&quot;: 2,
221                 &quot;stroke-opacity&quot;: 0.15,
222                 stroke: 'rgb(0, 0, 0)',
223                 translate: {
224                     x: 1,
225                     y: 1
226                 }
227             }]
228         });
229         me.group = surface.getGroup(me.seriesId);
230         if (me.showMarkers) {
231             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
232         }
233         if (shadow) {
234             for (i = 0, l = this.shadowAttributes.length; i &lt; l; i++) {
235                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
236             }
237         }
238     },
239     
240     // @private makes an average of points when there are more data points than pixels to be rendered.
241     shrink: function(xValues, yValues, size) {
242         // Start at the 2nd point...
243         var len = xValues.length,
244             ratio = Math.floor(len / size),
245             i = 1,
246             xSum = 0,
247             ySum = 0,
248             xRes = [xValues[0]],
249             yRes = [yValues[0]];
250         
251         for (; i &lt; len; ++i) {
252             xSum += xValues[i] || 0;
253             ySum += yValues[i] || 0;
254             if (i % ratio == 0) {
255                 xRes.push(xSum/ratio);
256                 yRes.push(ySum/ratio);
257                 xSum = 0;
258                 ySum = 0;
259             }
260         }
261         return {
262             x: xRes,
263             y: yRes
264         };
265     },
266
267 <span id='Ext-chart-series-Line-method-drawSeries'>    /**
268 </span>     * Draws the series for the current chart.
269      */
270     drawSeries: function() {
271         var me = this,
272             chart = me.chart,
273             store = chart.substore || chart.store,
274             surface = chart.surface,
275             chartBBox = chart.chartBBox,
276             bbox = {},
277             group = me.group,
278             gutterX = chart.maxGutter[0],
279             gutterY = chart.maxGutter[1],
280             showMarkers = me.showMarkers,
281             markerGroup = me.markerGroup,
282             enableShadows = chart.shadow,
283             shadowGroups = me.shadowGroups,
284             shadowAttributes = me.shadowAttributes,
285             smooth = me.smooth,
286             lnsh = shadowGroups.length,
287             dummyPath = [&quot;M&quot;],
288             path = [&quot;M&quot;],
289             markerIndex = chart.markerIndex,
290             axes = [].concat(me.axis),
291             shadowGroup,
292             shadowBarAttr,
293             xValues = [],
294             yValues = [],
295             storeIndices = [],
296             numericAxis = true,
297             axisCount = 0,
298             onbreak = false,
299             markerStyle = me.markerStyle,
300             seriesStyle = me.seriesStyle,
301             seriesLabelStyle = me.seriesLabelStyle,
302             colorArrayStyle = me.colorArrayStyle,
303             colorArrayLength = colorArrayStyle &amp;&amp; colorArrayStyle.length || 0,
304             posHash = {
305                 'left': 'right',
306                 'right': 'left',
307                 'top': 'bottom',
308                 'bottom': 'top'
309             },
310             isNumber = Ext.isNumber,
311             seriesIdx = me.seriesIdx, shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
312             x, y, prevX, prevY, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
313             yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
314             endLineStyle, type, props, firstMarker, count, smoothPath, renderPath;
315         
316         //if store is empty then there's nothing to draw.
317         if (!store || !store.getCount()) {
318             return;
319         }
320         
321         //prepare style objects for line and markers
322         endMarkerStyle = Ext.apply(markerStyle, me.markerConfig);
323         type = endMarkerStyle.type;
324         delete endMarkerStyle.type;
325         endLineStyle = Ext.apply(seriesStyle, me.style);
326         //if no stroke with is specified force it to 0.5 because this is
327         //about making *lines*
328         if (!endLineStyle['stroke-width']) {
329             endLineStyle['stroke-width'] = 0.5;
330         }
331         //If we're using a time axis and we need to translate the points,
332         //then reuse the first markers as the last markers.
333         if (markerIndex &amp;&amp; markerGroup &amp;&amp; markerGroup.getCount()) {
334             for (i = 0; i &lt; markerIndex; i++) {
335                 marker = markerGroup.getAt(i);
336                 markerGroup.remove(marker);
337                 markerGroup.add(marker);
338                 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
339                 marker.setAttributes({
340                     x: 0,
341                     y: 0,
342                     translate: {
343                         x: markerAux.attr.translation.x,
344                         y: markerAux.attr.translation.y
345                     }
346                 }, true);
347             }
348         }
349         
350         me.unHighlightItem();
351         me.cleanHighlights();
352
353         me.setBBox();
354         bbox = me.bbox;
355
356         me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
357
358         chart.axes.each(function(axis) {
359             //only apply position calculations to axes that affect this series
360             //this means the axis in the position referred by this series and also
361             //the axis in the other coordinate for this series. For example: (left, top|bottom),
362             //or (top, left|right), etc.
363             if (axis.position == me.axis || axis.position != posHash[me.axis]) {
364                 axisCount++;
365                 if (axis.type != 'Numeric') {
366                     numericAxis = false;
367                     return;
368                 }
369                 numericAxis = (numericAxis &amp;&amp; axis.type == 'Numeric');
370                 if (axis) {
371                     ends = axis.calcEnds();
372                     if (axis.position == 'top' || axis.position == 'bottom') {
373                         minX = ends.from;
374                         maxX = ends.to;
375                     }
376                     else {
377                         minY = ends.from;
378                         maxY = ends.to;
379                     }
380                 }
381             }
382         });
383         
384         //If there's only one axis specified for a series, then we set the default type of the other
385         //axis to a category axis. So in this case numericAxis, which would be true if both axes affecting
386         //the series are numeric should be false.
387         if (numericAxis &amp;&amp; axisCount == 1) {
388             numericAxis = false;
389         }
390         
391         // If a field was specified without a corresponding axis, create one to get bounds
392         //only do this for the axis where real values are bound (that's why we check for
393         //me.axis)
394         if (me.xField &amp;&amp; !isNumber(minX)) {
395             if (me.axis == 'bottom' || me.axis == 'top') {
396                 axis = Ext.create('Ext.chart.axis.Axis', {
397                     chart: chart,
398                     fields: [].concat(me.xField)
399                 }).calcEnds();
400                 minX = axis.from;
401                 maxX = axis.to;
402             } else if (numericAxis) {
403                 axis = Ext.create('Ext.chart.axis.Axis', {
404                     chart: chart,
405                     fields: [].concat(me.xField),
406                     forceMinMax: true
407                 }).calcEnds();
408                 minX = axis.from;
409                 maxX = axis.to;
410             }
411         }
412         
413         if (me.yField &amp;&amp; !isNumber(minY)) {
414             if (me.axis == 'right' || me.axis == 'left') {
415                 axis = Ext.create('Ext.chart.axis.Axis', {
416                     chart: chart,
417                     fields: [].concat(me.yField)
418                 }).calcEnds();
419                 minY = axis.from;
420                 maxY = axis.to;
421             } else if (numericAxis) {
422                 axis = Ext.create('Ext.chart.axis.Axis', {
423                     chart: chart,
424                     fields: [].concat(me.yField),
425                     forceMinMax: true
426                 }).calcEnds();
427                 minY = axis.from;
428                 maxY = axis.to;
429             }
430         }
431         
432         if (isNaN(minX)) {
433             minX = 0;
434             xScale = bbox.width / (store.getCount() - 1);
435         }
436         else {
437             //In case some person decides to set an axis' minimum and maximum
438             //configuration properties to the same value, then fallback the
439             //denominator to a &gt; 0 value.
440             xScale = bbox.width / ((maxX - minX) || (store.getCount() - 1));
441         }
442
443         if (isNaN(minY)) {
444             minY = 0;
445             yScale = bbox.height / (store.getCount() - 1);
446         } 
447         else {
448             //In case some person decides to set an axis' minimum and maximum
449             //configuration properties to the same value, then fallback the
450             //denominator to a &gt; 0 value.
451             yScale = bbox.height / ((maxY - minY) || (store.getCount() - 1));
452         }
453         
454         store.each(function(record, i) {
455             xValue = record.get(me.xField);
456             yValue = record.get(me.yField);
457             //skip undefined values
458             if (typeof yValue == 'undefined' || (typeof yValue == 'string' &amp;&amp; !yValue)) {
459                 //&lt;debug warn&gt;
460                 if (Ext.isDefined(Ext.global.console)) {
461                     Ext.global.console.warn(&quot;[Ext.chart.series.Line]  Skipping a store element with an undefined value at &quot;, record, xValue, yValue);
462                 }
463                 //&lt;/debug&gt;
464                 return;
465             }
466             // Ensure a value
467             if (typeof xValue == 'string' || typeof xValue == 'object'
468                 //set as uniform distribution if the axis is a category axis.
469                 || (me.axis != 'top' &amp;&amp; me.axis != 'bottom' &amp;&amp; !numericAxis)) {
470                 xValue = i;
471             }
472             if (typeof yValue == 'string' || typeof yValue == 'object'
473                 //set as uniform distribution if the axis is a category axis.
474                 || (me.axis != 'left' &amp;&amp; me.axis != 'right' &amp;&amp; !numericAxis)) {
475                 yValue = i;
476             }
477             storeIndices.push(i);
478             xValues.push(xValue);
479             yValues.push(yValue);
480         }, me);
481
482         ln = xValues.length;
483         if (ln &gt; bbox.width) {
484             coords = me.shrink(xValues, yValues, bbox.width);
485             xValues = coords.x;
486             yValues = coords.y;
487         }
488
489         me.items = [];
490
491         count = 0;
492         ln = xValues.length;
493         for (i = 0; i &lt; ln; i++) {
494             xValue = xValues[i];
495             yValue = yValues[i];
496             if (yValue === false) {
497                 if (path.length == 1) {
498                     path = [];
499                 }
500                 onbreak = true;
501                 me.items.push(false);
502                 continue;
503             } else {
504                 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
505                 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
506                 if (onbreak) {
507                     onbreak = false;
508                     path.push('M');
509                 } 
510                 path = path.concat([x, y]);
511             }
512             if ((typeof firstY == 'undefined') &amp;&amp; (typeof y != 'undefined')) {
513                 firstY = y;
514             }
515             // If this is the first line, create a dummypath to animate in from.
516             if (!me.line || chart.resizing) {
517                 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
518             }
519
520             // When resizing, reset before animating
521             if (chart.animate &amp;&amp; chart.resizing &amp;&amp; me.line) {
522                 me.line.setAttributes({
523                     path: dummyPath
524                 }, true);
525                 if (me.fillPath) {
526                     me.fillPath.setAttributes({
527                         path: dummyPath,
528                         opacity: 0.2
529                     }, true);
530                 }
531                 if (me.line.shadows) {
532                     shadows = me.line.shadows;
533                     for (j = 0, lnsh = shadows.length; j &lt; lnsh; j++) {
534                         shadow = shadows[j];
535                         shadow.setAttributes({
536                             path: dummyPath
537                         }, true);
538                     }
539                 }
540             }
541             if (showMarkers) {
542                 marker = markerGroup.getAt(count++);
543                 if (!marker) {
544                     marker = Ext.chart.Shape[type](surface, Ext.apply({
545                         group: [group, markerGroup],
546                         x: 0, y: 0,
547                         translate: {
548                             x: prevX || x, 
549                             y: prevY || (bbox.y + bbox.height / 2)
550                         },
551                         value: '&quot;' + xValue + ', ' + yValue + '&quot;'
552                     }, endMarkerStyle));
553                     marker._to = {
554                         translate: {
555                             x: x,
556                             y: y
557                         }
558                     };
559                 } else {
560                     marker.setAttributes({
561                         value: '&quot;' + xValue + ', ' + yValue + '&quot;',
562                         x: 0, y: 0,
563                         hidden: false
564                     }, true);
565                     marker._to = {
566                         translate: {
567                             x: x, y: y
568                         }
569                     };
570                 }
571             }
572
573             me.items.push({
574                 series: me,
575                 value: [xValue, yValue],
576                 point: [x, y],
577                 sprite: marker,
578                 storeItem: store.getAt(storeIndices[i])
579             });
580             prevX = x;
581             prevY = y;
582         }
583         
584         if (path.length &lt;= 1) {
585             //nothing to be rendered
586             return;    
587         }
588     
589         if (smooth) {
590             smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
591         }
592         
593         renderPath = smooth ? smoothPath : path;
594
595         //Correct path if we're animating timeAxis intervals
596         if (chart.markerIndex &amp;&amp; me.previousPath) {
597             fromPath = me.previousPath;
598             if (!smooth) {
599                 Ext.Array.erase(fromPath, 1, 2);
600             }
601         } else {
602             fromPath = path;
603         }
604         
605         // Only create a line if one doesn't exist.
606         if (!me.line) {
607             me.line = surface.add(Ext.apply({
608                 type: 'path',
609                 group: group,
610                 path: dummyPath,
611                 stroke: endLineStyle.stroke || endLineStyle.fill
612             }, endLineStyle || {}));
613             //unset fill here (there's always a default fill withing the themes).
614             me.line.setAttributes({
615                 fill: 'none'
616             });
617             if (!endLineStyle.stroke &amp;&amp; colorArrayLength) {
618                 me.line.setAttributes({
619                     stroke: colorArrayStyle[seriesIdx % colorArrayLength]
620                 }, true);
621             }
622             if (enableShadows) {
623                 //create shadows
624                 shadows = me.line.shadows = [];                
625                 for (shindex = 0; shindex &lt; lnsh; shindex++) {
626                     shadowBarAttr = shadowAttributes[shindex];
627                     shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
628                     shadow = chart.surface.add(Ext.apply({}, {
629                         type: 'path',
630                         group: shadowGroups[shindex]
631                     }, shadowBarAttr));
632                     shadows.push(shadow);
633                 }
634             }
635         }
636         if (me.fill) {
637             fillPath = renderPath.concat([
638                 [&quot;L&quot;, x, bbox.y + bbox.height],
639                 [&quot;L&quot;, bbox.x, bbox.y + bbox.height],
640                 [&quot;L&quot;, bbox.x, firstY]
641             ]);
642             if (!me.fillPath) {
643                 me.fillPath = surface.add({
644                     group: group,
645                     type: 'path',
646                     opacity: endLineStyle.opacity || 0.3,
647                     fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
648                     path: dummyPath
649                 });
650             }
651         }
652         markerCount = showMarkers &amp;&amp; markerGroup.getCount();
653         if (chart.animate) {
654             fill = me.fill;
655             line = me.line;
656             //Add renderer to line. There is not unique record associated with this.
657             rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
658             Ext.apply(rendererAttributes, endLineStyle || {}, {
659                 stroke: endLineStyle.stroke || endLineStyle.fill
660             });
661             //fill should not be used here but when drawing the special fill path object
662             delete rendererAttributes.fill;
663             if (chart.markerIndex &amp;&amp; me.previousPath) {
664                 me.animation = animation = me.onAnimate(line, {
665                     to: rendererAttributes,
666                     from: {
667                         path: fromPath
668                     }
669                 });
670             } else {
671                 me.animation = animation = me.onAnimate(line, {
672                     to: rendererAttributes
673                 });
674             }
675             //animate shadows
676             if (enableShadows) {
677                 shadows = line.shadows;
678                 for(j = 0; j &lt; lnsh; j++) {
679                     if (chart.markerIndex &amp;&amp; me.previousPath) {
680                         me.onAnimate(shadows[j], {
681                             to: { path: renderPath },
682                             from: { path: fromPath }
683                         });
684                     } else {
685                         me.onAnimate(shadows[j], {
686                             to: { path: renderPath }
687                         });
688                     }
689                 }
690             }
691             //animate fill path
692             if (fill) {
693                 me.onAnimate(me.fillPath, {
694                     to: Ext.apply({}, {
695                         path: fillPath,
696                         fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength]
697                     }, endLineStyle || {})
698                 });
699             }
700             //animate markers
701             if (showMarkers) {
702                 count = 0;
703                 for(i = 0; i &lt; ln; i++) {
704                     if (me.items[i]) {
705                         item = markerGroup.getAt(count++);
706                         if (item) {
707                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
708                             me.onAnimate(item, {
709                                 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
710                             });
711                         }
712                     } 
713                 }
714                 for(; count &lt; markerCount; count++) {
715                     item = markerGroup.getAt(count);
716                     item.hide(true);
717                 }
718             }
719         } else {
720             rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
721             Ext.apply(rendererAttributes, endLineStyle || {}, {
722                 stroke: endLineStyle.stroke || endLineStyle.fill
723             });
724             //fill should not be used here but when drawing the special fill path object
725             delete rendererAttributes.fill;
726             me.line.setAttributes(rendererAttributes, true);
727             //set path for shadows
728             if (enableShadows) {
729                 shadows = me.line.shadows;
730                 for(j = 0; j &lt; lnsh; j++) {
731                     shadows[j].setAttributes({
732                         path: renderPath
733                     }, true);
734                 }
735             }
736             if (me.fill) {
737                 me.fillPath.setAttributes({
738                     path: fillPath
739                 }, true);
740             }
741             if (showMarkers) {
742                 count = 0;
743                 for(i = 0; i &lt; ln; i++) {
744                     if (me.items[i]) {
745                         item = markerGroup.getAt(count++);
746                         if (item) {
747                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
748                             item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
749                         }
750                     } 
751                 }
752                 for(; count &lt; markerCount; count++) {
753                     item = markerGroup.getAt(count);
754                     item.hide(true);
755                 }
756             }
757         }
758
759         if (chart.markerIndex) {
760             if (me.smooth) {
761                 Ext.Array.erase(path, 1, 2);
762             } else {
763                 Ext.Array.splice(path, 1, 0, path[1], path[2]);
764             }
765             me.previousPath = path;
766         }
767         me.renderLabels();
768         me.renderCallouts();
769     },
770     
771     // @private called when a label is to be created.
772     onCreateLabel: function(storeItem, item, i, display) {
773         var me = this,
774             group = me.labelsGroup,
775             config = me.label,
776             bbox = me.bbox,
777             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
778
779         return me.chart.surface.add(Ext.apply({
780             'type': 'text',
781             'text-anchor': 'middle',
782             'group': group,
783             'x': item.point[0],
784             'y': bbox.y + bbox.height / 2
785         }, endLabelStyle || {}));
786     },
787     
788     // @private called when a label is to be created.
789     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
790         var me = this,
791             chart = me.chart,
792             resizing = chart.resizing,
793             config = me.label,
794             format = config.renderer,
795             field = config.field,
796             bbox = me.bbox,
797             x = item.point[0],
798             y = item.point[1],
799             radius = item.sprite.attr.radius,
800             bb, width, height;
801         
802         label.setAttributes({
803             text: format(storeItem.get(field)),
804             hidden: true
805         }, true);
806         
807         if (display == 'rotate') {
808             label.setAttributes({
809                 'text-anchor': 'start',
810                 'rotation': {
811                     x: x,
812                     y: y,
813                     degrees: -45
814                 }
815             }, true);
816             //correct label position to fit into the box
817             bb = label.getBBox();
818             width = bb.width;
819             height = bb.height;
820             x = x &lt; bbox.x? bbox.x : x;
821             x = (x + width &gt; bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
822             y = (y - height &lt; bbox.y)? bbox.y + height : y;
823         
824         } else if (display == 'under' || display == 'over') {
825             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
826             bb = item.sprite.getBBox();
827             bb.width = bb.width || (radius * 2);
828             bb.height = bb.height || (radius * 2);
829             y = y + (display == 'over'? -bb.height : bb.height);
830             //correct label position to fit into the box
831             bb = label.getBBox();
832             width = bb.width/2;
833             height = bb.height/2;
834             x = x - width &lt; bbox.x? bbox.x + width : x;
835             x = (x + width &gt; bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
836             y = y - height &lt; bbox.y? bbox.y + height : y;
837             y = (y + height &gt; bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
838         }
839         
840         if (me.chart.animate &amp;&amp; !me.chart.resizing) {
841             label.show(true);
842             me.onAnimate(label, {
843                 to: {
844                     x: x,
845                     y: y
846                 }
847             });
848         } else {
849             label.setAttributes({
850                 x: x,
851                 y: y
852             }, true);
853             if (resizing) {
854                 me.animation.on('afteranimate', function() {
855                     label.show(true);
856                 });
857             } else {
858                 label.show(true);
859             }
860         }
861     },
862
863     //@private Overriding highlights.js highlightItem method.
864     highlightItem: function() {
865         var me = this;
866         me.callParent(arguments);
867         if (this.line &amp;&amp; !this.highlighted) {
868             if (!('__strokeWidth' in this.line)) {
869                 this.line.__strokeWidth = this.line.attr['stroke-width'] || 0;
870             }
871             if (this.line.__anim) {
872                 this.line.__anim.paused = true;
873             }
874             this.line.__anim = Ext.create('Ext.fx.Anim', {
875                 target: this.line,
876                 to: {
877                     'stroke-width': this.line.__strokeWidth + 3
878                 }
879             });
880             this.highlighted = true;
881         }
882     },
883
884     //@private Overriding highlights.js unHighlightItem method.
885     unHighlightItem: function() {
886         var me = this;
887         me.callParent(arguments);
888         if (this.line &amp;&amp; this.highlighted) {
889             this.line.__anim = Ext.create('Ext.fx.Anim', {
890                 target: this.line,
891                 to: {
892                     'stroke-width': this.line.__strokeWidth
893                 }
894             });
895             this.highlighted = false;
896         }
897     },
898
899     //@private called when a callout needs to be placed.
900     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
901         if (!display) {
902             return;
903         }
904         
905         var me = this,
906             chart = me.chart,
907             surface = chart.surface,
908             resizing = chart.resizing,
909             config = me.callouts,
910             items = me.items,
911             prev = i == 0? false : items[i -1].point,
912             next = (i == items.length -1)? false : items[i +1].point,
913             cur = [+item.point[0], +item.point[1]],
914             dir, norm, normal, a, aprev, anext,
915             offsetFromViz = config.offsetFromViz || 30,
916             offsetToSide = config.offsetToSide || 10,
917             offsetBox = config.offsetBox || 3,
918             boxx, boxy, boxw, boxh,
919             p, clipRect = me.clipRect,
920             bbox = {
921                 width: config.styles.width || 10,
922                 height: config.styles.height || 10
923             },
924             x, y;
925
926         //get the right two points
927         if (!prev) {
928             prev = cur;
929         }
930         if (!next) {
931             next = cur;
932         }
933         a = (next[1] - prev[1]) / (next[0] - prev[0]);
934         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
935         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
936         
937         norm = Math.sqrt(1 + a * a);
938         dir = [1 / norm, a / norm];
939         normal = [-dir[1], dir[0]];
940         
941         //keep the label always on the outer part of the &quot;elbow&quot;
942         if (aprev &gt; 0 &amp;&amp; anext &lt; 0 &amp;&amp; normal[1] &lt; 0
943             || aprev &lt; 0 &amp;&amp; anext &gt; 0 &amp;&amp; normal[1] &gt; 0) {
944             normal[0] *= -1;
945             normal[1] *= -1;
946         } else if (Math.abs(aprev) &lt; Math.abs(anext) &amp;&amp; normal[0] &lt; 0
947                    || Math.abs(aprev) &gt; Math.abs(anext) &amp;&amp; normal[0] &gt; 0) {
948             normal[0] *= -1;
949             normal[1] *= -1;
950         }
951         //position
952         x = cur[0] + normal[0] * offsetFromViz;
953         y = cur[1] + normal[1] * offsetFromViz;
954
955         //box position and dimensions
956         boxx = x + (normal[0] &gt; 0? 0 : -(bbox.width + 2 * offsetBox));
957         boxy = y - bbox.height /2 - offsetBox;
958         boxw = bbox.width + 2 * offsetBox;
959         boxh = bbox.height + 2 * offsetBox;
960         
961         //now check if we're out of bounds and invert the normal vector correspondingly
962         //this may add new overlaps between labels (but labels won't be out of bounds).
963         if (boxx &lt; clipRect[0] || (boxx + boxw) &gt; (clipRect[0] + clipRect[2])) {
964             normal[0] *= -1;
965         }
966         if (boxy &lt; clipRect[1] || (boxy + boxh) &gt; (clipRect[1] + clipRect[3])) {
967             normal[1] *= -1;
968         }
969
970         //update positions
971         x = cur[0] + normal[0] * offsetFromViz;
972         y = cur[1] + normal[1] * offsetFromViz;
973         
974         //update box position and dimensions
975         boxx = x + (normal[0] &gt; 0? 0 : -(bbox.width + 2 * offsetBox));
976         boxy = y - bbox.height /2 - offsetBox;
977         boxw = bbox.width + 2 * offsetBox;
978         boxh = bbox.height + 2 * offsetBox;
979         
980         if (chart.animate) {
981             //set the line from the middle of the pie to the box.
982             me.onAnimate(callout.lines, {
983                 to: {
984                     path: [&quot;M&quot;, cur[0], cur[1], &quot;L&quot;, x, y, &quot;Z&quot;]
985                 }
986             });
987             //set component position
988             if (callout.panel) {
989                 callout.panel.setPosition(boxx, boxy, true);
990             }
991         }
992         else {
993             //set the line from the middle of the pie to the box.
994             callout.lines.setAttributes({
995                 path: [&quot;M&quot;, cur[0], cur[1], &quot;L&quot;, x, y, &quot;Z&quot;]
996             }, true);
997             //set component position
998             if (callout.panel) {
999                 callout.panel.setPosition(boxx, boxy);
1000             }
1001         }
1002         for (p in callout) {
1003             callout[p].show(true);
1004         }
1005     },
1006     
1007     isItemInPoint: function(x, y, item, i) {
1008         var me = this,
1009             items = me.items,
1010             tolerance = me.selectionTolerance,
1011             result = null,
1012             prevItem,
1013             nextItem,
1014             prevPoint,
1015             nextPoint,
1016             ln,
1017             x1,
1018             y1,
1019             x2,
1020             y2,
1021             xIntersect,
1022             yIntersect,
1023             dist1, dist2, dist, midx, midy,
1024             sqrt = Math.sqrt, abs = Math.abs;
1025         
1026         nextItem = items[i];
1027         prevItem = i &amp;&amp; items[i - 1];
1028         
1029         if (i &gt;= ln) {
1030             prevItem = items[ln - 1];
1031         }
1032         prevPoint = prevItem &amp;&amp; prevItem.point;
1033         nextPoint = nextItem &amp;&amp; nextItem.point;
1034         x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
1035         y1 = prevItem ? prevPoint[1] : nextPoint[1];
1036         x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
1037         y2 = nextItem ? nextPoint[1] : prevPoint[1];
1038         dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
1039         dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
1040         dist = Math.min(dist1, dist2);
1041         
1042         if (dist &lt;= tolerance) {
1043             return dist == dist1? prevItem : nextItem;
1044         }
1045         return false;
1046     },
1047     
1048     // @private toggle visibility of all series elements (markers, sprites).
1049     toggleAll: function(show) {
1050         var me = this,
1051             i, ln, shadow, shadows;
1052         if (!show) {
1053             Ext.chart.series.Line.superclass.hideAll.call(me);
1054         }
1055         else {
1056             Ext.chart.series.Line.superclass.showAll.call(me);
1057         }
1058         if (me.line) {
1059             me.line.setAttributes({
1060                 hidden: !show
1061             }, true);
1062             //hide shadows too
1063             if (me.line.shadows) {
1064                 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i &lt; ln; i++) {
1065                     shadow = shadows[i];
1066                     shadow.setAttributes({
1067                         hidden: !show
1068                     }, true);
1069                 }
1070             }
1071         }
1072         if (me.fillPath) {
1073             me.fillPath.setAttributes({
1074                 hidden: !show
1075             }, true);
1076         }
1077     },
1078     
1079     // @private hide all series elements (markers, sprites).
1080     hideAll: function() {
1081         this.toggleAll(false);
1082     },
1083     
1084     // @private hide all series elements (markers, sprites).
1085     showAll: function() {
1086         this.toggleAll(true);
1087     }
1088 });</pre>
1089 </body>
1090 </html>