Upgrade to ExtJS 4.0.1 - Released 05/18/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} smooth
171      * If true, the line will be smoothed/rounded around its points, otherwise straight line
172      * segments will be drawn. Defaults to false.
173      */
174     smooth: false,
175
176 <span id='Ext-chart-series-Line-cfg-fill'>    /**
177 </span>     * @cfg {Boolean} fill
178      * If true, the area below the line will be filled in using the {@link #style.eefill} and
179      * {@link #style.opacity} config properties. Defaults to false.
180      */
181     fill: false,
182
183     constructor: function(config) {
184         this.callParent(arguments);
185         var me = this,
186             surface = me.chart.surface,
187             shadow = me.chart.shadow,
188             i, l;
189         Ext.apply(me, config, {
190             highlightCfg: {
191                 'stroke-width': 3
192             },
193             shadowAttributes: [{
194                 &quot;stroke-width&quot;: 6,
195                 &quot;stroke-opacity&quot;: 0.05,
196                 stroke: 'rgb(0, 0, 0)',
197                 translate: {
198                     x: 1,
199                     y: 1
200                 }
201             }, {
202                 &quot;stroke-width&quot;: 4,
203                 &quot;stroke-opacity&quot;: 0.1,
204                 stroke: 'rgb(0, 0, 0)',
205                 translate: {
206                     x: 1,
207                     y: 1
208                 }
209             }, {
210                 &quot;stroke-width&quot;: 2,
211                 &quot;stroke-opacity&quot;: 0.15,
212                 stroke: 'rgb(0, 0, 0)',
213                 translate: {
214                     x: 1,
215                     y: 1
216                 }
217             }]
218         });
219         me.group = surface.getGroup(me.seriesId);
220         if (me.showMarkers) {
221             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
222         }
223         if (shadow) {
224             for (i = 0, l = this.shadowAttributes.length; i &lt; l; i++) {
225                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
226             }
227         }
228     },
229     
230     // @private makes an average of points when there are more data points than pixels to be rendered.
231     shrink: function(xValues, yValues, size) {
232         // Start at the 2nd point...
233         var len = xValues.length,
234             ratio = Math.floor(len / size),
235             i = 1,
236             xSum = 0,
237             ySum = 0,
238             xRes = [xValues[0]],
239             yRes = [yValues[0]];
240         
241         for (; i &lt; len; ++i) {
242             xSum += xValues[i] || 0;
243             ySum += yValues[i] || 0;
244             if (i % ratio == 0) {
245                 xRes.push(xSum/ratio);
246                 yRes.push(ySum/ratio);
247                 xSum = 0;
248                 ySum = 0;
249             }
250         }
251         return {
252             x: xRes,
253             y: yRes
254         };
255     },
256
257 <span id='Ext-chart-series-Line-method-drawSeries'>    /**
258 </span>     * Draws the series for the current chart.
259      */
260     drawSeries: function() {
261         var me = this,
262             chart = me.chart,
263             store = chart.substore || chart.store,
264             surface = chart.surface,
265             chartBBox = chart.chartBBox,
266             bbox = {},
267             group = me.group,
268             gutterX = chart.maxGutter[0],
269             gutterY = chart.maxGutter[1],
270             showMarkers = me.showMarkers,
271             markerGroup = me.markerGroup,
272             enableShadows = chart.shadow,
273             shadowGroups = me.shadowGroups,
274             shadowAttributes = this.shadowAttributes,
275             lnsh = shadowGroups.length,
276             dummyPath = [&quot;M&quot;],
277             path = [&quot;M&quot;],
278             markerIndex = chart.markerIndex,
279             axes = [].concat(me.axis),
280             shadowGroup,
281             shadowBarAttr,
282             xValues = [],
283             yValues = [],
284             numericAxis = true,
285             axisCount = 0,
286             onbreak = false,
287             markerStyle = me.markerStyle,
288             seriesStyle = me.seriesStyle,
289             seriesLabelStyle = me.seriesLabelStyle,
290             colorArrayStyle = me.colorArrayStyle,
291             colorArrayLength = colorArrayStyle &amp;&amp; colorArrayStyle.length || 0,
292             posHash = {
293                 'left': 'right',
294                 'right': 'left',
295                 'top': 'bottom',
296                 'bottom': 'top'
297             },
298             seriesIdx = me.seriesIdx, shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
299             x, y, prevX, prevY, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
300             yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
301             endLineStyle, type, props, firstMarker, count;
302         
303         //if store is empty then there's nothing to draw.
304         if (!store || !store.getCount()) {
305             return;
306         }
307         
308         //prepare style objects for line and markers
309         endMarkerStyle = Ext.apply(markerStyle, me.markerConfig);
310         type = endMarkerStyle.type;
311         delete endMarkerStyle.type;
312         endLineStyle = Ext.apply(seriesStyle, me.style);
313         //if no stroke with is specified force it to 0.5 because this is
314         //about making *lines*
315         if (!endLineStyle['stroke-width']) {
316             endLineStyle['stroke-width'] = 0.5;
317         }
318         //If we're using a time axis and we need to translate the points,
319         //then reuse the first markers as the last markers.
320         if (markerIndex &amp;&amp; markerGroup &amp;&amp; markerGroup.getCount()) {
321             for (i = 0; i &lt; markerIndex; i++) {
322                 marker = markerGroup.getAt(i);
323                 markerGroup.remove(marker);
324                 markerGroup.add(marker);
325                 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
326                 marker.setAttributes({
327                     x: 0,
328                     y: 0,
329                     translate: {
330                         x: markerAux.attr.translation.x,
331                         y: markerAux.attr.translation.y
332                     }
333                 }, true);
334             }
335         }
336         
337         me.unHighlightItem();
338         me.cleanHighlights();
339
340         me.setBBox();
341         bbox = me.bbox;
342
343         me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
344
345         chart.axes.each(function(axis) {
346             //only apply position calculations to axes that affect this series
347             //this means the axis in the position referred by this series and also
348             //the axis in the other coordinate for this series. For example: (left, top|bottom),
349             //or (top, left|right), etc.
350             if (axis.position == me.axis || axis.position != posHash[me.axis]) {
351                 axisCount++;
352                 if (axis.type != 'Numeric') {
353                     numericAxis = false;
354                     return;
355                 }
356                 numericAxis = (numericAxis &amp;&amp; axis.type == 'Numeric');
357                 if (axis) {
358                     ends = axis.calcEnds();
359                     if (axis.position == 'top' || axis.position == 'bottom') {
360                         minX = ends.from;
361                         maxX = ends.to;
362                     }
363                     else {
364                         minY = ends.from;
365                         maxY = ends.to;
366                     }
367                 }
368             }
369         });
370         
371         //If there's only one axis specified for a series, then we set the default type of the other
372         //axis to a category axis. So in this case numericAxis, which would be true if both axes affecting
373         //the series are numeric should be false.
374         if (numericAxis &amp;&amp; axisCount == 1) {
375             numericAxis = false;
376         }
377         
378         // If a field was specified without a corresponding axis, create one to get bounds
379         //only do this for the axis where real values are bound (that's why we check for
380         //me.axis)
381         if (me.xField &amp;&amp; !Ext.isNumber(minX)) {
382             if (me.axis == 'bottom' || me.axis == 'top') {
383                 axis = Ext.create('Ext.chart.axis.Axis', {
384                     chart: chart,
385                     fields: [].concat(me.xField)
386                 }).calcEnds();
387                 minX = axis.from;
388                 maxX = axis.to;
389             } else if (numericAxis) {
390                 axis = Ext.create('Ext.chart.axis.Axis', {
391                     chart: chart,
392                     fields: [].concat(me.xField),
393                     forceMinMax: true
394                 }).calcEnds();
395                 minX = axis.from;
396                 maxX = axis.to;
397             }
398         }
399         
400         if (me.yField &amp;&amp; !Ext.isNumber(minY)) {
401             if (me.axis == 'right' || me.axis == 'left') {
402                 axis = Ext.create('Ext.chart.axis.Axis', {
403                     chart: chart,
404                     fields: [].concat(me.yField)
405                 }).calcEnds();
406                 minY = axis.from;
407                 maxY = axis.to;
408             } else if (numericAxis) {
409                 axis = Ext.create('Ext.chart.axis.Axis', {
410                     chart: chart,
411                     fields: [].concat(me.yField),
412                     forceMinMax: true
413                 }).calcEnds();
414                 minY = axis.from;
415                 maxY = axis.to;
416             }
417         }
418         
419         if (isNaN(minX)) {
420             minX = 0;
421             xScale = bbox.width / (store.getCount() - 1);
422         }
423         else {
424             xScale = bbox.width / (maxX - minX);
425         }
426
427         if (isNaN(minY)) {
428             minY = 0;
429             yScale = bbox.height / (store.getCount() - 1);
430         } 
431         else {
432             yScale = bbox.height / (maxY - minY);
433         }
434         
435         store.each(function(record, i) {
436             xValue = record.get(me.xField);
437             yValue = record.get(me.yField);
438             //skip undefined values
439             if (typeof yValue == 'undefined' || (typeof yValue == 'string' &amp;&amp; !yValue)) {
440                 //&lt;debug warn&gt;
441                 if (Ext.isDefined(Ext.global.console)) {
442                     Ext.global.console.warn(&quot;[Ext.chart.series.Line]  Skipping a store element with an undefined value at &quot;, record, xValue, yValue);
443                 }
444                 //&lt;/debug&gt;
445                 return;
446             }
447             // Ensure a value
448             if (typeof xValue == 'string' || typeof xValue == 'object'
449                 //set as uniform distribution if the axis is a category axis.
450                 || (me.axis != 'top' &amp;&amp; me.axis != 'bottom' &amp;&amp; !numericAxis)) {
451                 xValue = i;
452             }
453             if (typeof yValue == 'string' || typeof yValue == 'object'
454                 //set as uniform distribution if the axis is a category axis.
455                 || (me.axis != 'left' &amp;&amp; me.axis != 'right' &amp;&amp; !numericAxis)) {
456                 yValue = i;
457             }
458             xValues.push(xValue);
459             yValues.push(yValue);
460         }, me);
461
462         ln = xValues.length;
463         if (ln &gt; bbox.width) {
464             coords = me.shrink(xValues, yValues, bbox.width);
465             xValues = coords.x;
466             yValues = coords.y;
467         }
468
469         me.items = [];
470
471         count = 0;
472         ln = xValues.length;
473         for (i = 0; i &lt; ln; i++) {
474             xValue = xValues[i];
475             yValue = yValues[i];
476             if (yValue === false) {
477                 if (path.length == 1) {
478                     path = [];
479                 }
480                 onbreak = true;
481                 me.items.push(false);
482                 continue;
483             } else {
484                 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
485                 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
486                 if (onbreak) {
487                     onbreak = false;
488                     path.push('M');
489                 } 
490                 path = path.concat([x, y]);
491             }
492             if ((typeof firstY == 'undefined') &amp;&amp; (typeof y != 'undefined')) {
493                 firstY = y;
494             }
495             // If this is the first line, create a dummypath to animate in from.
496             if (!me.line || chart.resizing) {
497                 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
498             }
499
500             // When resizing, reset before animating
501             if (chart.animate &amp;&amp; chart.resizing &amp;&amp; me.line) {
502                 me.line.setAttributes({
503                     path: dummyPath
504                 }, true);
505                 if (me.fillPath) {
506                     me.fillPath.setAttributes({
507                         path: dummyPath,
508                         opacity: 0.2
509                     }, true);
510                 }
511                 if (me.line.shadows) {
512                     shadows = me.line.shadows;
513                     for (j = 0, lnsh = shadows.length; j &lt; lnsh; j++) {
514                         shadow = shadows[j];
515                         shadow.setAttributes({
516                             path: dummyPath
517                         }, true);
518                     }
519                 }
520             }
521             if (showMarkers) {
522                 marker = markerGroup.getAt(count++);
523                 if (!marker) {
524                     marker = Ext.chart.Shape[type](surface, Ext.apply({
525                         group: [group, markerGroup],
526                         x: 0, y: 0,
527                         translate: {
528                             x: prevX || x, 
529                             y: prevY || (bbox.y + bbox.height / 2)
530                         },
531                         value: '&quot;' + xValue + ', ' + yValue + '&quot;'
532                     }, endMarkerStyle));
533                     marker._to = {
534                         translate: {
535                             x: x,
536                             y: y
537                         }
538                     };
539                 } else {
540                     marker.setAttributes({
541                         value: '&quot;' + xValue + ', ' + yValue + '&quot;',
542                         x: 0, y: 0,
543                         hidden: false
544                     }, true);
545                     marker._to = {
546                         translate: {
547                             x: x, y: y
548                         }
549                     };
550                 }
551             }
552             me.items.push({
553                 series: me,
554                 value: [xValue, yValue],
555                 point: [x, y],
556                 sprite: marker,
557                 storeItem: store.getAt(i)
558             });
559             prevX = x;
560             prevY = y;
561         }
562         
563         if (path.length &lt;= 1) {
564             //nothing to be rendered
565             return;    
566         }
567         
568         if (me.smooth) {
569             path = Ext.draw.Draw.smooth(path, 6);
570         }
571         
572         //Correct path if we're animating timeAxis intervals
573         if (chart.markerIndex &amp;&amp; me.previousPath) {
574             fromPath = me.previousPath;
575             fromPath.splice(1, 2);
576         } else {
577             fromPath = path;
578         }
579
580         // Only create a line if one doesn't exist.
581         if (!me.line) {
582             me.line = surface.add(Ext.apply({
583                 type: 'path',
584                 group: group,
585                 path: dummyPath,
586                 stroke: endLineStyle.stroke || endLineStyle.fill
587             }, endLineStyle || {}));
588             //unset fill here (there's always a default fill withing the themes).
589             me.line.setAttributes({
590                 fill: 'none'
591             });
592             if (!endLineStyle.stroke &amp;&amp; colorArrayLength) {
593                 me.line.setAttributes({
594                     stroke: colorArrayStyle[seriesIdx % colorArrayLength]
595                 }, true);
596             }
597             if (enableShadows) {
598                 //create shadows
599                 shadows = me.line.shadows = [];                
600                 for (shindex = 0; shindex &lt; lnsh; shindex++) {
601                     shadowBarAttr = shadowAttributes[shindex];
602                     shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
603                     shadow = chart.surface.add(Ext.apply({}, {
604                         type: 'path',
605                         group: shadowGroups[shindex]
606                     }, shadowBarAttr));
607                     shadows.push(shadow);
608                 }
609             }
610         }
611         if (me.fill) {
612             fillPath = path.concat([
613                 [&quot;L&quot;, x, bbox.y + bbox.height],
614                 [&quot;L&quot;, bbox.x, bbox.y + bbox.height],
615                 [&quot;L&quot;, bbox.x, firstY]
616             ]);
617             if (!me.fillPath) {
618                 me.fillPath = surface.add({
619                     group: group,
620                     type: 'path',
621                     opacity: endLineStyle.opacity || 0.3,
622                     fill: colorArrayStyle[seriesIdx % colorArrayLength] || endLineStyle.fill,
623                     path: dummyPath
624                 });
625             }
626         }
627         markerCount = showMarkers &amp;&amp; markerGroup.getCount();
628         if (chart.animate) {
629             fill = me.fill;
630             line = me.line;
631             //Add renderer to line. There is not unique record associated with this.
632             rendererAttributes = me.renderer(line, false, { path: path }, i, store);
633             Ext.apply(rendererAttributes, endLineStyle || {}, {
634                 stroke: endLineStyle.stroke || endLineStyle.fill
635             });
636             //fill should not be used here but when drawing the special fill path object
637             delete rendererAttributes.fill;
638             if (chart.markerIndex &amp;&amp; me.previousPath) {
639                 me.animation = animation = me.onAnimate(line, {
640                     to: rendererAttributes,
641                     from: {
642                         path: fromPath
643                     }
644                 });
645             } else {
646                 me.animation = animation = me.onAnimate(line, {
647                     to: rendererAttributes
648                 });
649             }
650             //animate shadows
651             if (enableShadows) {
652                 shadows = line.shadows;
653                 for(j = 0; j &lt; lnsh; j++) {
654                     if (chart.markerIndex &amp;&amp; me.previousPath) {
655                         me.onAnimate(shadows[j], {
656                             to: { path: path },
657                             from: { path: fromPath }
658                         });
659                     } else {
660                         me.onAnimate(shadows[j], {
661                             to: { path: path }
662                         });
663                     }
664                 }
665             }
666             //animate fill path
667             if (fill) {
668                 me.onAnimate(me.fillPath, {
669                     to: Ext.apply({}, {
670                         path: fillPath,
671                         fill: colorArrayStyle[seriesIdx % colorArrayLength] || endLineStyle.fill
672                     }, endLineStyle || {})
673                 });
674             }
675             //animate markers
676             if (showMarkers) {
677                 count = 0;
678                 for(i = 0; i &lt; ln; i++) {
679                     if (me.items[i]) {
680                         item = markerGroup.getAt(count++);
681                         if (item) {
682                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
683                             me.onAnimate(item, {
684                                 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
685                             });
686                         }
687                     } 
688                 }
689                 for(; count &lt; markerCount; count++) {
690                     item = markerGroup.getAt(count);
691                     item.hide(true);
692                 }
693             }
694         } else {
695             rendererAttributes = me.renderer(me.line, false, { path: path, hidden: false }, i, store);
696             Ext.apply(rendererAttributes, endLineStyle || {}, {
697                 stroke: endLineStyle.stroke || endLineStyle.fill
698             });
699             //fill should not be used here but when drawing the special fill path object
700             delete rendererAttributes.fill;
701             me.line.setAttributes(rendererAttributes, true);
702             //set path for shadows
703             if (enableShadows) {
704                 shadows = me.line.shadows;
705                 for(j = 0; j &lt; lnsh; j++) {
706                     shadows[j].setAttributes({
707                         path: path
708                     }, true);
709                 }
710             }
711             if (me.fill) {
712                 me.fillPath.setAttributes({
713                     path: fillPath
714                 }, true);
715             }
716             if (showMarkers) {
717                 count = 0;
718                 for(i = 0; i &lt; ln; i++) {
719                     if (me.items[i]) {
720                         item = markerGroup.getAt(count++);
721                         if (item) {
722                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
723                             item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
724                         }
725                     } 
726                 }
727                 for(; count &lt; markerCount; count++) {
728                     item = markerGroup.getAt(count);
729                     item.hide(true);
730                 }
731             }
732         }
733
734         if (chart.markerIndex) {
735             path.splice(1, 0, path[1], path[2]);
736             me.previousPath = path;
737         }
738         me.renderLabels();
739         me.renderCallouts();
740     },
741     
742     // @private called when a label is to be created.
743     onCreateLabel: function(storeItem, item, i, display) {
744         var me = this,
745             group = me.labelsGroup,
746             config = me.label,
747             bbox = me.bbox,
748             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
749
750         return me.chart.surface.add(Ext.apply({
751             'type': 'text',
752             'text-anchor': 'middle',
753             'group': group,
754             'x': item.point[0],
755             'y': bbox.y + bbox.height / 2
756         }, endLabelStyle || {}));
757     },
758     
759     // @private called when a label is to be created.
760     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
761         var me = this,
762             chart = me.chart,
763             resizing = chart.resizing,
764             config = me.label,
765             format = config.renderer,
766             field = config.field,
767             bbox = me.bbox,
768             x = item.point[0],
769             y = item.point[1],
770             radius = item.sprite.attr.radius,
771             bb, width, height;
772         
773         label.setAttributes({
774             text: format(storeItem.get(field)),
775             hidden: true
776         }, true);
777         
778         if (display == 'rotate') {
779             label.setAttributes({
780                 'text-anchor': 'start',
781                 'rotation': {
782                     x: x,
783                     y: y,
784                     degrees: -45
785                 }
786             }, true);
787             //correct label position to fit into the box
788             bb = label.getBBox();
789             width = bb.width;
790             height = bb.height;
791             x = x &lt; bbox.x? bbox.x : x;
792             x = (x + width &gt; bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
793             y = (y - height &lt; bbox.y)? bbox.y + height : y;
794         
795         } else if (display == 'under' || display == 'over') {
796             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
797             bb = item.sprite.getBBox();
798             bb.width = bb.width || (radius * 2);
799             bb.height = bb.height || (radius * 2);
800             y = y + (display == 'over'? -bb.height : bb.height);
801             //correct label position to fit into the box
802             bb = label.getBBox();
803             width = bb.width/2;
804             height = bb.height/2;
805             x = x - width &lt; bbox.x? bbox.x + width : x;
806             x = (x + width &gt; bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
807             y = y - height &lt; bbox.y? bbox.y + height : y;
808             y = (y + height &gt; bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
809         }
810         
811         if (me.chart.animate &amp;&amp; !me.chart.resizing) {
812             label.show(true);
813             me.onAnimate(label, {
814                 to: {
815                     x: x,
816                     y: y
817                 }
818             });
819         } else {
820             label.setAttributes({
821                 x: x,
822                 y: y
823             }, true);
824             if (resizing) {
825                 me.animation.on('afteranimate', function() {
826                     label.show(true);
827                 });
828             } else {
829                 label.show(true);
830             }
831         }
832     },
833
834     //@private Overriding highlights.js highlightItem method.
835     highlightItem: function() {
836         var me = this;
837         me.callParent(arguments);
838         if (this.line &amp;&amp; !this.highlighted) {
839             if (!('__strokeWidth' in this.line)) {
840                 this.line.__strokeWidth = this.line.attr['stroke-width'] || 0;
841             }
842             if (this.line.__anim) {
843                 this.line.__anim.paused = true;
844             }
845             this.line.__anim = Ext.create('Ext.fx.Anim', {
846                 target: this.line,
847                 to: {
848                     'stroke-width': this.line.__strokeWidth + 3
849                 }
850             });
851             this.highlighted = true;
852         }
853     },
854
855     //@private Overriding highlights.js unHighlightItem method.
856     unHighlightItem: function() {
857         var me = this;
858         me.callParent(arguments);
859         if (this.line &amp;&amp; this.highlighted) {
860             this.line.__anim = Ext.create('Ext.fx.Anim', {
861                 target: this.line,
862                 to: {
863                     'stroke-width': this.line.__strokeWidth
864                 }
865             });
866             this.highlighted = false;
867         }
868     },
869
870     //@private called when a callout needs to be placed.
871     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
872         if (!display) {
873             return;
874         }
875         
876         var me = this,
877             chart = me.chart,
878             surface = chart.surface,
879             resizing = chart.resizing,
880             config = me.callouts,
881             items = me.items,
882             prev = i == 0? false : items[i -1].point,
883             next = (i == items.length -1)? false : items[i +1].point,
884             cur = [+item.point[0], +item.point[1]],
885             dir, norm, normal, a, aprev, anext,
886             offsetFromViz = config.offsetFromViz || 30,
887             offsetToSide = config.offsetToSide || 10,
888             offsetBox = config.offsetBox || 3,
889             boxx, boxy, boxw, boxh,
890             p, clipRect = me.clipRect,
891             bbox = {
892                 width: config.styles.width || 10,
893                 height: config.styles.height || 10
894             },
895             x, y;
896
897         //get the right two points
898         if (!prev) {
899             prev = cur;
900         }
901         if (!next) {
902             next = cur;
903         }
904         a = (next[1] - prev[1]) / (next[0] - prev[0]);
905         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
906         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
907         
908         norm = Math.sqrt(1 + a * a);
909         dir = [1 / norm, a / norm];
910         normal = [-dir[1], dir[0]];
911         
912         //keep the label always on the outer part of the &quot;elbow&quot;
913         if (aprev &gt; 0 &amp;&amp; anext &lt; 0 &amp;&amp; normal[1] &lt; 0
914             || aprev &lt; 0 &amp;&amp; anext &gt; 0 &amp;&amp; normal[1] &gt; 0) {
915             normal[0] *= -1;
916             normal[1] *= -1;
917         } else if (Math.abs(aprev) &lt; Math.abs(anext) &amp;&amp; normal[0] &lt; 0
918                    || Math.abs(aprev) &gt; Math.abs(anext) &amp;&amp; normal[0] &gt; 0) {
919             normal[0] *= -1;
920             normal[1] *= -1;
921         }
922         //position
923         x = cur[0] + normal[0] * offsetFromViz;
924         y = cur[1] + normal[1] * offsetFromViz;
925
926         //box position and dimensions
927         boxx = x + (normal[0] &gt; 0? 0 : -(bbox.width + 2 * offsetBox));
928         boxy = y - bbox.height /2 - offsetBox;
929         boxw = bbox.width + 2 * offsetBox;
930         boxh = bbox.height + 2 * offsetBox;
931         
932         //now check if we're out of bounds and invert the normal vector correspondingly
933         //this may add new overlaps between labels (but labels won't be out of bounds).
934         if (boxx &lt; clipRect[0] || (boxx + boxw) &gt; (clipRect[0] + clipRect[2])) {
935             normal[0] *= -1;
936         }
937         if (boxy &lt; clipRect[1] || (boxy + boxh) &gt; (clipRect[1] + clipRect[3])) {
938             normal[1] *= -1;
939         }
940
941         //update positions
942         x = cur[0] + normal[0] * offsetFromViz;
943         y = cur[1] + normal[1] * offsetFromViz;
944         
945         //update box position and dimensions
946         boxx = x + (normal[0] &gt; 0? 0 : -(bbox.width + 2 * offsetBox));
947         boxy = y - bbox.height /2 - offsetBox;
948         boxw = bbox.width + 2 * offsetBox;
949         boxh = bbox.height + 2 * offsetBox;
950         
951         if (chart.animate) {
952             //set the line from the middle of the pie to the box.
953             me.onAnimate(callout.lines, {
954                 to: {
955                     path: [&quot;M&quot;, cur[0], cur[1], &quot;L&quot;, x, y, &quot;Z&quot;]
956                 }
957             });
958             //set component position
959             if (callout.panel) {
960                 callout.panel.setPosition(boxx, boxy, true);
961             }
962         }
963         else {
964             //set the line from the middle of the pie to the box.
965             callout.lines.setAttributes({
966                 path: [&quot;M&quot;, cur[0], cur[1], &quot;L&quot;, x, y, &quot;Z&quot;]
967             }, true);
968             //set component position
969             if (callout.panel) {
970                 callout.panel.setPosition(boxx, boxy);
971             }
972         }
973         for (p in callout) {
974             callout[p].show(true);
975         }
976     },
977     
978     isItemInPoint: function(x, y, item, i) {
979         var me = this,
980             items = me.items,
981             tolerance = me.selectionTolerance,
982             result = null,
983             prevItem,
984             nextItem,
985             prevPoint,
986             nextPoint,
987             ln,
988             x1,
989             y1,
990             x2,
991             y2,
992             xIntersect,
993             yIntersect,
994             dist1, dist2, dist, midx, midy,
995             sqrt = Math.sqrt, abs = Math.abs;
996         
997         nextItem = items[i];
998         prevItem = i &amp;&amp; items[i - 1];
999         
1000         if (i &gt;= ln) {
1001             prevItem = items[ln - 1];
1002         }
1003         prevPoint = prevItem &amp;&amp; prevItem.point;
1004         nextPoint = nextItem &amp;&amp; nextItem.point;
1005         x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
1006         y1 = prevItem ? prevPoint[1] : nextPoint[1];
1007         x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
1008         y2 = nextItem ? nextPoint[1] : prevPoint[1];
1009         dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
1010         dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
1011         dist = Math.min(dist1, dist2);
1012         
1013         if (dist &lt;= tolerance) {
1014             return dist == dist1? prevItem : nextItem;
1015         }
1016         return false;
1017     },
1018     
1019     // @private toggle visibility of all series elements (markers, sprites).
1020     toggleAll: function(show) {
1021         var me = this,
1022             i, ln, shadow, shadows;
1023         if (!show) {
1024             Ext.chart.series.Line.superclass.hideAll.call(me);
1025         }
1026         else {
1027             Ext.chart.series.Line.superclass.showAll.call(me);
1028         }
1029         if (me.line) {
1030             me.line.setAttributes({
1031                 hidden: !show
1032             }, true);
1033             //hide shadows too
1034             if (me.line.shadows) {
1035                 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i &lt; ln; i++) {
1036                     shadow = shadows[i];
1037                     shadow.setAttributes({
1038                         hidden: !show
1039                     }, true);
1040                 }
1041             }
1042         }
1043         if (me.fillPath) {
1044             me.fillPath.setAttributes({
1045                 hidden: !show
1046             }, true);
1047         }
1048     },
1049     
1050     // @private hide all series elements (markers, sprites).
1051     hideAll: function() {
1052         this.toggleAll(false);
1053     },
1054     
1055     // @private hide all series elements (markers, sprites).
1056     showAll: function() {
1057         this.toggleAll(true);
1058     }
1059 });</pre>
1060 </body>
1061 </html>