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