Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / chart / series / Area.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.chart.series.Area
17  * @extends Ext.chart.series.Cartesian
18  *
19  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
20  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
21  * documentation for more information. A typical configuration object for the area series could be:
22  *
23  *     @example
24  *     var store = Ext.create('Ext.data.JsonStore', {
25  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
26  *         data: [
27  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
28  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
29  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
30  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
31  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
32  *         ]
33  *     });
34  *
35  *     Ext.create('Ext.chart.Chart', {
36  *         renderTo: Ext.getBody(),
37  *         width: 500,
38  *         height: 300,
39  *         store: store,
40  *         axes: [
41  *             {
42  *                 type: 'Numeric',
43  *                 grid: true,
44  *                 position: 'left',
45  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
46  *                 title: 'Sample Values',
47  *                 grid: {
48  *                     odd: {
49  *                         opacity: 1,
50  *                         fill: '#ddd',
51  *                         stroke: '#bbb',
52  *                         'stroke-width': 1
53  *                     }
54  *                 },
55  *                 minimum: 0,
56  *                 adjustMinimumByMajorUnit: 0
57  *             },
58  *             {
59  *                 type: 'Category',
60  *                 position: 'bottom',
61  *                 fields: ['name'],
62  *                 title: 'Sample Metrics',
63  *                 grid: true,
64  *                 label: {
65  *                     rotate: {
66  *                         degrees: 315
67  *                     }
68  *                 }
69  *             }
70  *         ],
71  *         series: [{
72  *             type: 'area',
73  *             highlight: false,
74  *             axis: 'left',
75  *             xField: 'name',
76  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
77  *             style: {
78  *                 opacity: 0.93
79  *             }
80  *         }]
81  *     });
82  *
83  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
84  * take the left axis to measure the data in the area series, set as xField (x values) the name field of each element in the store,
85  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
86  * to the style object.
87  *
88  * @xtype area
89  */
90 Ext.define('Ext.chart.series.Area', {
91
92     /* Begin Definitions */
93
94     extend: 'Ext.chart.series.Cartesian',
95
96     alias: 'series.area',
97
98     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
99
100     /* End Definitions */
101
102     type: 'area',
103
104     // @private Area charts are alyways stacked
105     stacked: true,
106
107     /**
108      * @cfg {Object} style
109      * Append styling properties to this object for it to override theme properties.
110      */
111     style: {},
112
113     constructor: function(config) {
114         this.callParent(arguments);
115         var me = this,
116             surface = me.chart.surface,
117             i, l;
118         Ext.apply(me, config, {
119             __excludes: [],
120             highlightCfg: {
121                 lineWidth: 3,
122                 stroke: '#55c',
123                 opacity: 0.8,
124                 color: '#f00'
125             }
126         });
127         if (me.highlight) {
128             me.highlightSprite = surface.add({
129                 type: 'path',
130                 path: ['M', 0, 0],
131                 zIndex: 1000,
132                 opacity: 0.3,
133                 lineWidth: 5,
134                 hidden: true,
135                 stroke: '#444'
136             });
137         }
138         me.group = surface.getGroup(me.seriesId);
139     },
140
141     // @private Shrinks dataSets down to a smaller size
142     shrink: function(xValues, yValues, size) {
143         var len = xValues.length,
144             ratio = Math.floor(len / size),
145             i, j,
146             xSum = 0,
147             yCompLen = this.areas.length,
148             ySum = [],
149             xRes = [],
150             yRes = [];
151         //initialize array
152         for (j = 0; j < yCompLen; ++j) {
153             ySum[j] = 0;
154         }
155         for (i = 0; i < len; ++i) {
156             xSum += xValues[i];
157             for (j = 0; j < yCompLen; ++j) {
158                 ySum[j] += yValues[i][j];
159             }
160             if (i % ratio == 0) {
161                 //push averages
162                 xRes.push(xSum/ratio);
163                 for (j = 0; j < yCompLen; ++j) {
164                     ySum[j] /= ratio;
165                 }
166                 yRes.push(ySum);
167                 //reset sum accumulators
168                 xSum = 0;
169                 for (j = 0, ySum = []; j < yCompLen; ++j) {
170                     ySum[j] = 0;
171                 }
172             }
173         }
174         return {
175             x: xRes,
176             y: yRes
177         };
178     },
179
180     // @private Get chart and data boundaries
181     getBounds: function() {
182         var me = this,
183             chart = me.chart,
184             store = chart.getChartStore(),
185             areas = [].concat(me.yField),
186             areasLen = areas.length,
187             xValues = [],
188             yValues = [],
189             infinity = Infinity,
190             minX = infinity,
191             minY = infinity,
192             maxX = -infinity,
193             maxY = -infinity,
194             math = Math,
195             mmin = math.min,
196             mmax = math.max,
197             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
198
199         me.setBBox();
200         bbox = me.bbox;
201
202         // Run through the axis
203         if (me.axis) {
204             axis = chart.axes.get(me.axis);
205             if (axis) {
206                 out = axis.calcEnds();
207                 minY = out.from || axis.prevMin;
208                 maxY = mmax(out.to || axis.prevMax, 0);
209             }
210         }
211
212         if (me.yField && !Ext.isNumber(minY)) {
213             axis = Ext.create('Ext.chart.axis.Axis', {
214                 chart: chart,
215                 fields: [].concat(me.yField)
216             });
217             out = axis.calcEnds();
218             minY = out.from || axis.prevMin;
219             maxY = mmax(out.to || axis.prevMax, 0);
220         }
221
222         if (!Ext.isNumber(minY)) {
223             minY = 0;
224         }
225         if (!Ext.isNumber(maxY)) {
226             maxY = 0;
227         }
228
229         store.each(function(record, i) {
230             xValue = record.get(me.xField);
231             yValue = [];
232             if (typeof xValue != 'number') {
233                 xValue = i;
234             }
235             xValues.push(xValue);
236             acumY = 0;
237             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
238                 areaElem = record.get(areas[areaIndex]);
239                 if (typeof areaElem == 'number') {
240                     minY = mmin(minY, areaElem);
241                     yValue.push(areaElem);
242                     acumY += areaElem;
243                 }
244             }
245             minX = mmin(minX, xValue);
246             maxX = mmax(maxX, xValue);
247             maxY = mmax(maxY, acumY);
248             yValues.push(yValue);
249         }, me);
250
251         xScale = bbox.width / ((maxX - minX) || 1);
252         yScale = bbox.height / ((maxY - minY) || 1);
253
254         ln = xValues.length;
255         if ((ln > bbox.width) && me.areas) {
256             sumValues = me.shrink(xValues, yValues, bbox.width);
257             xValues = sumValues.x;
258             yValues = sumValues.y;
259         }
260
261         return {
262             bbox: bbox,
263             minX: minX,
264             minY: minY,
265             xValues: xValues,
266             yValues: yValues,
267             xScale: xScale,
268             yScale: yScale,
269             areasLen: areasLen
270         };
271     },
272
273     // @private Build an array of paths for the chart
274     getPaths: function() {
275         var me = this,
276             chart = me.chart,
277             store = chart.getChartStore(),
278             first = true,
279             bounds = me.getBounds(),
280             bbox = bounds.bbox,
281             items = me.items = [],
282             componentPaths = [],
283             componentPath,
284             paths = [],
285             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
286
287         ln = bounds.xValues.length;
288         // Start the path
289         for (i = 0; i < ln; i++) {
290             xValue = bounds.xValues[i];
291             yValue = bounds.yValues[i];
292             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
293             acumY = 0;
294             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
295                 // Excluded series
296                 if (me.__excludes[areaIndex]) {
297                     continue;
298                 }
299                 if (!componentPaths[areaIndex]) {
300                     componentPaths[areaIndex] = [];
301                 }
302                 areaElem = yValue[areaIndex];
303                 acumY += areaElem;
304                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
305                 if (!paths[areaIndex]) {
306                     paths[areaIndex] = ['M', x, y];
307                     componentPaths[areaIndex].push(['L', x, y]);
308                 } else {
309                     paths[areaIndex].push('L', x, y);
310                     componentPaths[areaIndex].push(['L', x, y]);
311                 }
312                 if (!items[areaIndex]) {
313                     items[areaIndex] = {
314                         pointsUp: [],
315                         pointsDown: [],
316                         series: me
317                     };
318                 }
319                 items[areaIndex].pointsUp.push([x, y]);
320             }
321         }
322
323         // Close the paths
324         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
325             // Excluded series
326             if (me.__excludes[areaIndex]) {
327                 continue;
328             }
329             path = paths[areaIndex];
330             // Close bottom path to the axis
331             if (areaIndex == 0 || first) {
332                 first = false;
333                 path.push('L', x, bbox.y + bbox.height,
334                           'L', bbox.x, bbox.y + bbox.height,
335                           'Z');
336             }
337             // Close other paths to the one before them
338             else {
339                 componentPath = componentPaths[prevAreaIndex];
340                 componentPath.reverse();
341                 path.push('L', x, componentPath[0][2]);
342                 for (i = 0; i < ln; i++) {
343                     path.push(componentPath[i][0],
344                               componentPath[i][1],
345                               componentPath[i][2]);
346                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
347                 }
348                 path.push('L', bbox.x, path[2], 'Z');
349             }
350             prevAreaIndex = areaIndex;
351         }
352         return {
353             paths: paths,
354             areasLen: bounds.areasLen
355         };
356     },
357
358     /**
359      * Draws the series for the current chart.
360      */
361     drawSeries: function() {
362         var me = this,
363             chart = me.chart,
364             store = chart.getChartStore(),
365             surface = chart.surface,
366             animate = chart.animate,
367             group = me.group,
368             endLineStyle = Ext.apply(me.seriesStyle, me.style),
369             colorArrayStyle = me.colorArrayStyle,
370             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
371             areaIndex, areaElem, paths, path, rendererAttributes;
372
373         me.unHighlightItem();
374         me.cleanHighlights();
375
376         if (!store || !store.getCount()) {
377             return;
378         }
379
380         paths = me.getPaths();
381
382         if (!me.areas) {
383             me.areas = [];
384         }
385
386         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
387             // Excluded series
388             if (me.__excludes[areaIndex]) {
389                 continue;
390             }
391             if (!me.areas[areaIndex]) {
392                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
393                     type: 'path',
394                     group: group,
395                     // 'clip-rect': me.clipBox,
396                     path: paths.paths[areaIndex],
397                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
398                     fill: colorArrayStyle[areaIndex % colorArrayLength]
399                 }, endLineStyle || {}));
400             }
401             areaElem = me.areas[areaIndex];
402             path = paths.paths[areaIndex];
403             if (animate) {
404                 //Add renderer to line. There is not a unique record associated with this.
405                 rendererAttributes = me.renderer(areaElem, false, {
406                     path: path,
407                     // 'clip-rect': me.clipBox,
408                     fill: colorArrayStyle[areaIndex % colorArrayLength],
409                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
410                 }, areaIndex, store);
411                 //fill should not be used here but when drawing the special fill path object
412                 me.animation = me.onAnimate(areaElem, {
413                     to: rendererAttributes
414                 });
415             } else {
416                 rendererAttributes = me.renderer(areaElem, false, {
417                     path: path,
418                     // 'clip-rect': me.clipBox,
419                     hidden: false,
420                     fill: colorArrayStyle[areaIndex % colorArrayLength],
421                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
422                 }, areaIndex, store);
423                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
424             }
425         }
426         me.renderLabels();
427         me.renderCallouts();
428     },
429
430     // @private
431     onAnimate: function(sprite, attr) {
432         sprite.show();
433         return this.callParent(arguments);
434     },
435
436     // @private
437     onCreateLabel: function(storeItem, item, i, display) {
438         var me = this,
439             group = me.labelsGroup,
440             config = me.label,
441             bbox = me.bbox,
442             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
443
444         return me.chart.surface.add(Ext.apply({
445             'type': 'text',
446             'text-anchor': 'middle',
447             'group': group,
448             'x': item.point[0],
449             'y': bbox.y + bbox.height / 2
450         }, endLabelStyle || {}));
451     },
452
453     // @private
454     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
455         var me = this,
456             chart = me.chart,
457             resizing = chart.resizing,
458             config = me.label,
459             format = config.renderer,
460             field = config.field,
461             bbox = me.bbox,
462             x = item.point[0],
463             y = item.point[1],
464             bb, width, height;
465
466         label.setAttributes({
467             text: format(storeItem.get(field[index])),
468             hidden: true
469         }, true);
470
471         bb = label.getBBox();
472         width = bb.width / 2;
473         height = bb.height / 2;
474
475         x = x - width < bbox.x? bbox.x + width : x;
476         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
477         y = y - height < bbox.y? bbox.y + height : y;
478         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
479
480         if (me.chart.animate && !me.chart.resizing) {
481             label.show(true);
482             me.onAnimate(label, {
483                 to: {
484                     x: x,
485                     y: y
486                 }
487             });
488         } else {
489             label.setAttributes({
490                 x: x,
491                 y: y
492             }, true);
493             if (resizing) {
494                 me.animation.on('afteranimate', function() {
495                     label.show(true);
496                 });
497             } else {
498                 label.show(true);
499             }
500         }
501     },
502
503     // @private
504     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
505         var me = this,
506             chart = me.chart,
507             surface = chart.surface,
508             resizing = chart.resizing,
509             config = me.callouts,
510             items = me.items,
511             prev = (i == 0) ? false : items[i -1].point,
512             next = (i == items.length -1) ? false : items[i +1].point,
513             cur = item.point,
514             dir, norm, normal, a, aprev, anext,
515             bbox = callout.label.getBBox(),
516             offsetFromViz = 30,
517             offsetToSide = 10,
518             offsetBox = 3,
519             boxx, boxy, boxw, boxh,
520             p, clipRect = me.clipRect,
521             x, y;
522
523         //get the right two points
524         if (!prev) {
525             prev = cur;
526         }
527         if (!next) {
528             next = cur;
529         }
530         a = (next[1] - prev[1]) / (next[0] - prev[0]);
531         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
532         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
533
534         norm = Math.sqrt(1 + a * a);
535         dir = [1 / norm, a / norm];
536         normal = [-dir[1], dir[0]];
537
538         //keep the label always on the outer part of the "elbow"
539         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
540             normal[0] *= -1;
541             normal[1] *= -1;
542         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
543             normal[0] *= -1;
544             normal[1] *= -1;
545         }
546
547         //position
548         x = cur[0] + normal[0] * offsetFromViz;
549         y = cur[1] + normal[1] * offsetFromViz;
550
551         //box position and dimensions
552         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
553         boxy = y - bbox.height /2 - offsetBox;
554         boxw = bbox.width + 2 * offsetBox;
555         boxh = bbox.height + 2 * offsetBox;
556
557         //now check if we're out of bounds and invert the normal vector correspondingly
558         //this may add new overlaps between labels (but labels won't be out of bounds).
559         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
560             normal[0] *= -1;
561         }
562         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
563             normal[1] *= -1;
564         }
565
566         //update positions
567         x = cur[0] + normal[0] * offsetFromViz;
568         y = cur[1] + normal[1] * offsetFromViz;
569
570         //update box position and dimensions
571         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
572         boxy = y - bbox.height /2 - offsetBox;
573         boxw = bbox.width + 2 * offsetBox;
574         boxh = bbox.height + 2 * offsetBox;
575
576         //set the line from the middle of the pie to the box.
577         callout.lines.setAttributes({
578             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
579         }, true);
580         //set box position
581         callout.box.setAttributes({
582             x: boxx,
583             y: boxy,
584             width: boxw,
585             height: boxh
586         }, true);
587         //set text position
588         callout.label.setAttributes({
589             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
590             y: y
591         }, true);
592         for (p in callout) {
593             callout[p].show(true);
594         }
595     },
596
597     isItemInPoint: function(x, y, item, i) {
598         var me = this,
599             pointsUp = item.pointsUp,
600             pointsDown = item.pointsDown,
601             abs = Math.abs,
602             dist = Infinity, p, pln, point;
603
604         for (p = 0, pln = pointsUp.length; p < pln; p++) {
605             point = [pointsUp[p][0], pointsUp[p][1]];
606             if (dist > abs(x - point[0])) {
607                 dist = abs(x - point[0]);
608             } else {
609                 point = pointsUp[p -1];
610                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
611                     item.storeIndex = p -1;
612                     item.storeField = me.yField[i];
613                     item.storeItem = me.chart.store.getAt(p -1);
614                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
615                     return true;
616                 } else {
617                     break;
618                 }
619             }
620         }
621         return false;
622     },
623
624     /**
625      * Highlight this entire series.
626      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
627      */
628     highlightSeries: function() {
629         var area, to, fillColor;
630         if (this._index !== undefined) {
631             area = this.areas[this._index];
632             if (area.__highlightAnim) {
633                 area.__highlightAnim.paused = true;
634             }
635             area.__highlighted = true;
636             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
637             area.__prevFill = area.__prevFill || area.attr.fill;
638             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
639             fillColor = Ext.draw.Color.fromString(area.__prevFill);
640             to = {
641                 lineWidth: (area.__prevLineWidth || 0) + 2
642             };
643             if (fillColor) {
644                 to.fill = fillColor.getLighter(0.2).toString();
645             }
646             else {
647                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
648             }
649             if (this.chart.animate) {
650                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
651                     target: area,
652                     to: to
653                 }, this.chart.animate));
654             }
655             else {
656                 area.setAttributes(to, true);
657             }
658         }
659     },
660
661     /**
662      * UnHighlight this entire series.
663      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
664      */
665     unHighlightSeries: function() {
666         var area;
667         if (this._index !== undefined) {
668             area = this.areas[this._index];
669             if (area.__highlightAnim) {
670                 area.__highlightAnim.paused = true;
671             }
672             if (area.__highlighted) {
673                 area.__highlighted = false;
674                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
675                     target: area,
676                     to: {
677                         fill: area.__prevFill,
678                         opacity: area.__prevOpacity,
679                         lineWidth: area.__prevLineWidth
680                     }
681                 });
682             }
683         }
684     },
685
686     /**
687      * Highlight the specified item. If no item is provided the whole series will be highlighted.
688      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
689      */
690     highlightItem: function(item) {
691         var me = this,
692             points, path;
693         if (!item) {
694             this.highlightSeries();
695             return;
696         }
697         points = item._points;
698         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
699                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
700         me.highlightSprite.setAttributes({
701             path: path,
702             hidden: false
703         }, true);
704     },
705
706     /**
707      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
708      * @param {Object} item Info about the item; same format as returned by #getItemForPoint
709      */
710     unHighlightItem: function(item) {
711         if (!item) {
712             this.unHighlightSeries();
713         }
714
715         if (this.highlightSprite) {
716             this.highlightSprite.hide(true);
717         }
718     },
719
720     // @private
721     hideAll: function() {
722         if (!isNaN(this._index)) {
723             this.__excludes[this._index] = true;
724             this.areas[this._index].hide(true);
725             this.drawSeries();
726         }
727     },
728
729     // @private
730     showAll: function() {
731         if (!isNaN(this._index)) {
732             this.__excludes[this._index] = false;
733             this.areas[this._index].show(true);
734             this.drawSeries();
735         }
736     },
737
738     /**
739      * Returns the color of the series (to be displayed as color for the series legend item).
740      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
741      */
742     getLegendColor: function(index) {
743         var me = this;
744         return me.colorArrayStyle[index % me.colorArrayStyle.length];
745     }
746 });
747 /**
748  * @class Ext.chart.series.Area
749  * @extends Ext.chart.series.Cartesian
750  *
751  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
752  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
753  * documentation for more information. A typical configuration object for the area series could be:
754  *
755  *     @example
756  *     var store = Ext.create('Ext.data.JsonStore', {
757  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
758  *         data: [
759  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
760  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
761  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
762  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
763  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
764  *         ]
765  *     });
766  *
767  *     Ext.create('Ext.chart.Chart', {
768  *         renderTo: Ext.getBody(),
769  *         width: 500,
770  *         height: 300,
771  *         store: store,
772  *         axes: [
773  *             {
774  *                 type: 'Numeric',
775  *                 grid: true,
776  *                 position: 'left',
777  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
778  *                 title: 'Sample Values',
779  *                 grid: {
780  *                     odd: {
781  *                         opacity: 1,
782  *                         fill: '#ddd',
783  *                         stroke: '#bbb',
784  *                         'stroke-width': 1
785  *                     }
786  *                 },
787  *                 minimum: 0,
788  *                 adjustMinimumByMajorUnit: 0
789  *             },
790  *             {
791  *                 type: 'Category',
792  *                 position: 'bottom',
793  *                 fields: ['name'],
794  *                 title: 'Sample Metrics',
795  *                 grid: true,
796  *                 label: {
797  *                     rotate: {
798  *                         degrees: 315
799  *                     }
800  *                 }
801  *             }
802  *         ],
803  *         series: [{
804  *             type: 'area',
805  *             highlight: false,
806  *             axis: 'left',
807  *             xField: 'name',
808  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
809  *             style: {
810  *                 opacity: 0.93
811  *             }
812  *         }]
813  *     });
814  *
815  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
816  * take the left axis to measure the data in the area series, set as xField (x values) the name field of each element in the store,
817  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
818  * to the style object.
819  *
820  * @xtype area
821  */
822 Ext.define('Ext.chart.series.Area', {
823
824     /* Begin Definitions */
825
826     extend: 'Ext.chart.series.Cartesian',
827
828     alias: 'series.area',
829
830     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
831
832     /* End Definitions */
833
834     type: 'area',
835
836     // @private Area charts are alyways stacked
837     stacked: true,
838
839     /**
840      * @cfg {Object} style
841      * Append styling properties to this object for it to override theme properties.
842      */
843     style: {},
844
845     constructor: function(config) {
846         this.callParent(arguments);
847         var me = this,
848             surface = me.chart.surface,
849             i, l;
850         Ext.apply(me, config, {
851             __excludes: [],
852             highlightCfg: {
853                 lineWidth: 3,
854                 stroke: '#55c',
855                 opacity: 0.8,
856                 color: '#f00'
857             }
858         });
859         if (me.highlight) {
860             me.highlightSprite = surface.add({
861                 type: 'path',
862                 path: ['M', 0, 0],
863                 zIndex: 1000,
864                 opacity: 0.3,
865                 lineWidth: 5,
866                 hidden: true,
867                 stroke: '#444'
868             });
869         }
870         me.group = surface.getGroup(me.seriesId);
871     },
872
873     // @private Shrinks dataSets down to a smaller size
874     shrink: function(xValues, yValues, size) {
875         var len = xValues.length,
876             ratio = Math.floor(len / size),
877             i, j,
878             xSum = 0,
879             yCompLen = this.areas.length,
880             ySum = [],
881             xRes = [],
882             yRes = [];
883         //initialize array
884         for (j = 0; j < yCompLen; ++j) {
885             ySum[j] = 0;
886         }
887         for (i = 0; i < len; ++i) {
888             xSum += xValues[i];
889             for (j = 0; j < yCompLen; ++j) {
890                 ySum[j] += yValues[i][j];
891             }
892             if (i % ratio == 0) {
893                 //push averages
894                 xRes.push(xSum/ratio);
895                 for (j = 0; j < yCompLen; ++j) {
896                     ySum[j] /= ratio;
897                 }
898                 yRes.push(ySum);
899                 //reset sum accumulators
900                 xSum = 0;
901                 for (j = 0, ySum = []; j < yCompLen; ++j) {
902                     ySum[j] = 0;
903                 }
904             }
905         }
906         return {
907             x: xRes,
908             y: yRes
909         };
910     },
911
912     // @private Get chart and data boundaries
913     getBounds: function() {
914         var me = this,
915             chart = me.chart,
916             store = chart.getChartStore(),
917             areas = [].concat(me.yField),
918             areasLen = areas.length,
919             xValues = [],
920             yValues = [],
921             infinity = Infinity,
922             minX = infinity,
923             minY = infinity,
924             maxX = -infinity,
925             maxY = -infinity,
926             math = Math,
927             mmin = math.min,
928             mmax = math.max,
929             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
930
931         me.setBBox();
932         bbox = me.bbox;
933
934         // Run through the axis
935         if (me.axis) {
936             axis = chart.axes.get(me.axis);
937             if (axis) {
938                 out = axis.calcEnds();
939                 minY = out.from || axis.prevMin;
940                 maxY = mmax(out.to || axis.prevMax, 0);
941             }
942         }
943
944         if (me.yField && !Ext.isNumber(minY)) {
945             axis = Ext.create('Ext.chart.axis.Axis', {
946                 chart: chart,
947                 fields: [].concat(me.yField)
948             });
949             out = axis.calcEnds();
950             minY = out.from || axis.prevMin;
951             maxY = mmax(out.to || axis.prevMax, 0);
952         }
953
954         if (!Ext.isNumber(minY)) {
955             minY = 0;
956         }
957         if (!Ext.isNumber(maxY)) {
958             maxY = 0;
959         }
960
961         store.each(function(record, i) {
962             xValue = record.get(me.xField);
963             yValue = [];
964             if (typeof xValue != 'number') {
965                 xValue = i;
966             }
967             xValues.push(xValue);
968             acumY = 0;
969             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
970                 areaElem = record.get(areas[areaIndex]);
971                 if (typeof areaElem == 'number') {
972                     minY = mmin(minY, areaElem);
973                     yValue.push(areaElem);
974                     acumY += areaElem;
975                 }
976             }
977             minX = mmin(minX, xValue);
978             maxX = mmax(maxX, xValue);
979             maxY = mmax(maxY, acumY);
980             yValues.push(yValue);
981         }, me);
982
983         xScale = bbox.width / ((maxX - minX) || 1);
984         yScale = bbox.height / ((maxY - minY) || 1);
985
986         ln = xValues.length;
987         if ((ln > bbox.width) && me.areas) {
988             sumValues = me.shrink(xValues, yValues, bbox.width);
989             xValues = sumValues.x;
990             yValues = sumValues.y;
991         }
992
993         return {
994             bbox: bbox,
995             minX: minX,
996             minY: minY,
997             xValues: xValues,
998             yValues: yValues,
999             xScale: xScale,
1000             yScale: yScale,
1001             areasLen: areasLen
1002         };
1003     },
1004
1005     // @private Build an array of paths for the chart
1006     getPaths: function() {
1007         var me = this,
1008             chart = me.chart,
1009             store = chart.getChartStore(),
1010             first = true,
1011             bounds = me.getBounds(),
1012             bbox = bounds.bbox,
1013             items = me.items = [],
1014             componentPaths = [],
1015             componentPath,
1016             paths = [],
1017             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
1018
1019         ln = bounds.xValues.length;
1020         // Start the path
1021         for (i = 0; i < ln; i++) {
1022             xValue = bounds.xValues[i];
1023             yValue = bounds.yValues[i];
1024             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
1025             acumY = 0;
1026             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
1027                 // Excluded series
1028                 if (me.__excludes[areaIndex]) {
1029                     continue;
1030                 }
1031                 if (!componentPaths[areaIndex]) {
1032                     componentPaths[areaIndex] = [];
1033                 }
1034                 areaElem = yValue[areaIndex];
1035                 acumY += areaElem;
1036                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
1037                 if (!paths[areaIndex]) {
1038                     paths[areaIndex] = ['M', x, y];
1039                     componentPaths[areaIndex].push(['L', x, y]);
1040                 } else {
1041                     paths[areaIndex].push('L', x, y);
1042                     componentPaths[areaIndex].push(['L', x, y]);
1043                 }
1044                 if (!items[areaIndex]) {
1045                     items[areaIndex] = {
1046                         pointsUp: [],
1047                         pointsDown: [],
1048                         series: me
1049                     };
1050                 }
1051                 items[areaIndex].pointsUp.push([x, y]);
1052             }
1053         }
1054
1055         // Close the paths
1056         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
1057             // Excluded series
1058             if (me.__excludes[areaIndex]) {
1059                 continue;
1060             }
1061             path = paths[areaIndex];
1062             // Close bottom path to the axis
1063             if (areaIndex == 0 || first) {
1064                 first = false;
1065                 path.push('L', x, bbox.y + bbox.height,
1066                           'L', bbox.x, bbox.y + bbox.height,
1067                           'Z');
1068             }
1069             // Close other paths to the one before them
1070             else {
1071                 componentPath = componentPaths[prevAreaIndex];
1072                 componentPath.reverse();
1073                 path.push('L', x, componentPath[0][2]);
1074                 for (i = 0; i < ln; i++) {
1075                     path.push(componentPath[i][0],
1076                               componentPath[i][1],
1077                               componentPath[i][2]);
1078                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
1079                 }
1080                 path.push('L', bbox.x, path[2], 'Z');
1081             }
1082             prevAreaIndex = areaIndex;
1083         }
1084         return {
1085             paths: paths,
1086             areasLen: bounds.areasLen
1087         };
1088     },
1089
1090     /**
1091      * Draws the series for the current chart.
1092      */
1093     drawSeries: function() {
1094         var me = this,
1095             chart = me.chart,
1096             store = chart.getChartStore(),
1097             surface = chart.surface,
1098             animate = chart.animate,
1099             group = me.group,
1100             endLineStyle = Ext.apply(me.seriesStyle, me.style),
1101             colorArrayStyle = me.colorArrayStyle,
1102             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
1103             areaIndex, areaElem, paths, path, rendererAttributes;
1104
1105         me.unHighlightItem();
1106         me.cleanHighlights();
1107
1108         if (!store || !store.getCount()) {
1109             return;
1110         }
1111
1112         paths = me.getPaths();
1113
1114         if (!me.areas) {
1115             me.areas = [];
1116         }
1117
1118         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
1119             // Excluded series
1120             if (me.__excludes[areaIndex]) {
1121                 continue;
1122             }
1123             if (!me.areas[areaIndex]) {
1124                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
1125                     type: 'path',
1126                     group: group,
1127                     // 'clip-rect': me.clipBox,
1128                     path: paths.paths[areaIndex],
1129                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
1130                     fill: colorArrayStyle[areaIndex % colorArrayLength]
1131                 }, endLineStyle || {}));
1132             }
1133             areaElem = me.areas[areaIndex];
1134             path = paths.paths[areaIndex];
1135             if (animate) {
1136                 //Add renderer to line. There is not a unique record associated with this.
1137                 rendererAttributes = me.renderer(areaElem, false, {
1138                     path: path,
1139                     // 'clip-rect': me.clipBox,
1140                     fill: colorArrayStyle[areaIndex % colorArrayLength],
1141                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
1142                 }, areaIndex, store);
1143                 //fill should not be used here but when drawing the special fill path object
1144                 me.animation = me.onAnimate(areaElem, {
1145                     to: rendererAttributes
1146                 });
1147             } else {
1148                 rendererAttributes = me.renderer(areaElem, false, {
1149                     path: path,
1150                     // 'clip-rect': me.clipBox,
1151                     hidden: false,
1152                     fill: colorArrayStyle[areaIndex % colorArrayLength],
1153                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
1154                 }, areaIndex, store);
1155                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
1156             }
1157         }
1158         me.renderLabels();
1159         me.renderCallouts();
1160     },
1161
1162     // @private
1163     onAnimate: function(sprite, attr) {
1164         sprite.show();
1165         return this.callParent(arguments);
1166     },
1167
1168     // @private
1169     onCreateLabel: function(storeItem, item, i, display) {
1170         var me = this,
1171             group = me.labelsGroup,
1172             config = me.label,
1173             bbox = me.bbox,
1174             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
1175
1176         return me.chart.surface.add(Ext.apply({
1177             'type': 'text',
1178             'text-anchor': 'middle',
1179             'group': group,
1180             'x': item.point[0],
1181             'y': bbox.y + bbox.height / 2
1182         }, endLabelStyle || {}));
1183     },
1184
1185     // @private
1186     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
1187         var me = this,
1188             chart = me.chart,
1189             resizing = chart.resizing,
1190             config = me.label,
1191             format = config.renderer,
1192             field = config.field,
1193             bbox = me.bbox,
1194             x = item.point[0],
1195             y = item.point[1],
1196             bb, width, height;
1197
1198         label.setAttributes({
1199             text: format(storeItem.get(field[index])),
1200             hidden: true
1201         }, true);
1202
1203         bb = label.getBBox();
1204         width = bb.width / 2;
1205         height = bb.height / 2;
1206
1207         x = x - width < bbox.x? bbox.x + width : x;
1208         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
1209         y = y - height < bbox.y? bbox.y + height : y;
1210         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
1211
1212         if (me.chart.animate && !me.chart.resizing) {
1213             label.show(true);
1214             me.onAnimate(label, {
1215                 to: {
1216                     x: x,
1217                     y: y
1218                 }
1219             });
1220         } else {
1221             label.setAttributes({
1222                 x: x,
1223                 y: y
1224             }, true);
1225             if (resizing) {
1226                 me.animation.on('afteranimate', function() {
1227                     label.show(true);
1228                 });
1229             } else {
1230                 label.show(true);
1231             }
1232         }
1233     },
1234
1235     // @private
1236     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
1237         var me = this,
1238             chart = me.chart,
1239             surface = chart.surface,
1240             resizing = chart.resizing,
1241             config = me.callouts,
1242             items = me.items,
1243             prev = (i == 0) ? false : items[i -1].point,
1244             next = (i == items.length -1) ? false : items[i +1].point,
1245             cur = item.point,
1246             dir, norm, normal, a, aprev, anext,
1247             bbox = callout.label.getBBox(),
1248             offsetFromViz = 30,
1249             offsetToSide = 10,
1250             offsetBox = 3,
1251             boxx, boxy, boxw, boxh,
1252             p, clipRect = me.clipRect,
1253             x, y;
1254
1255         //get the right two points
1256         if (!prev) {
1257             prev = cur;
1258         }
1259         if (!next) {
1260             next = cur;
1261         }
1262         a = (next[1] - prev[1]) / (next[0] - prev[0]);
1263         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
1264         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
1265
1266         norm = Math.sqrt(1 + a * a);
1267         dir = [1 / norm, a / norm];
1268         normal = [-dir[1], dir[0]];
1269
1270         //keep the label always on the outer part of the "elbow"
1271         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
1272             normal[0] *= -1;
1273             normal[1] *= -1;
1274         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
1275             normal[0] *= -1;
1276             normal[1] *= -1;
1277         }
1278
1279         //position
1280         x = cur[0] + normal[0] * offsetFromViz;
1281         y = cur[1] + normal[1] * offsetFromViz;
1282
1283         //box position and dimensions
1284         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
1285         boxy = y - bbox.height /2 - offsetBox;
1286         boxw = bbox.width + 2 * offsetBox;
1287         boxh = bbox.height + 2 * offsetBox;
1288
1289         //now check if we're out of bounds and invert the normal vector correspondingly
1290         //this may add new overlaps between labels (but labels won't be out of bounds).
1291         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
1292             normal[0] *= -1;
1293         }
1294         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
1295             normal[1] *= -1;
1296         }
1297
1298         //update positions
1299         x = cur[0] + normal[0] * offsetFromViz;
1300         y = cur[1] + normal[1] * offsetFromViz;
1301
1302         //update box position and dimensions
1303         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
1304         boxy = y - bbox.height /2 - offsetBox;
1305         boxw = bbox.width + 2 * offsetBox;
1306         boxh = bbox.height + 2 * offsetBox;
1307
1308         //set the line from the middle of the pie to the box.
1309         callout.lines.setAttributes({
1310             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
1311         }, true);
1312         //set box position
1313         callout.box.setAttributes({
1314             x: boxx,
1315             y: boxy,
1316             width: boxw,
1317             height: boxh
1318         }, true);
1319         //set text position
1320         callout.label.setAttributes({
1321             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
1322             y: y
1323         }, true);
1324         for (p in callout) {
1325             callout[p].show(true);
1326         }
1327     },
1328
1329     isItemInPoint: function(x, y, item, i) {
1330         var me = this,
1331             pointsUp = item.pointsUp,
1332             pointsDown = item.pointsDown,
1333             abs = Math.abs,
1334             dist = Infinity, p, pln, point;
1335
1336         for (p = 0, pln = pointsUp.length; p < pln; p++) {
1337             point = [pointsUp[p][0], pointsUp[p][1]];
1338             if (dist > abs(x - point[0])) {
1339                 dist = abs(x - point[0]);
1340             } else {
1341                 point = pointsUp[p -1];
1342                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
1343                     item.storeIndex = p -1;
1344                     item.storeField = me.yField[i];
1345                     item.storeItem = me.chart.store.getAt(p -1);
1346                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
1347                     return true;
1348                 } else {
1349                     break;
1350                 }
1351             }
1352         }
1353         return false;
1354     },
1355
1356     /**
1357      * Highlight this entire series.
1358      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
1359      */
1360     highlightSeries: function() {
1361         var area, to, fillColor;
1362         if (this._index !== undefined) {
1363             area = this.areas[this._index];
1364             if (area.__highlightAnim) {
1365                 area.__highlightAnim.paused = true;
1366             }
1367             area.__highlighted = true;
1368             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
1369             area.__prevFill = area.__prevFill || area.attr.fill;
1370             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
1371             fillColor = Ext.draw.Color.fromString(area.__prevFill);
1372             to = {
1373                 lineWidth: (area.__prevLineWidth || 0) + 2
1374             };
1375             if (fillColor) {
1376                 to.fill = fillColor.getLighter(0.2).toString();
1377             }
1378             else {
1379                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
1380             }
1381             if (this.chart.animate) {
1382                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
1383                     target: area,
1384                     to: to
1385                 }, this.chart.animate));
1386             }
1387             else {
1388                 area.setAttributes(to, true);
1389             }
1390         }
1391     },
1392
1393     /**
1394      * UnHighlight this entire series.
1395      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
1396      */
1397     unHighlightSeries: function() {
1398         var area;
1399         if (this._index !== undefined) {
1400             area = this.areas[this._index];
1401             if (area.__highlightAnim) {
1402                 area.__highlightAnim.paused = true;
1403             }
1404             if (area.__highlighted) {
1405                 area.__highlighted = false;
1406                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
1407                     target: area,
1408                     to: {
1409                         fill: area.__prevFill,
1410                         opacity: area.__prevOpacity,
1411                         lineWidth: area.__prevLineWidth
1412                     }
1413                 });
1414             }
1415         }
1416     },
1417
1418     /**
1419      * Highlight the specified item. If no item is provided the whole series will be highlighted.
1420      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
1421      */
1422     highlightItem: function(item) {
1423         var me = this,
1424             points, path;
1425         if (!item) {
1426             this.highlightSeries();
1427             return;
1428         }
1429         points = item._points;
1430         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
1431                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
1432         me.highlightSprite.setAttributes({
1433             path: path,
1434             hidden: false
1435         }, true);
1436     },
1437
1438     /**
1439      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
1440      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
1441      */
1442     unHighlightItem: function(item) {
1443         if (!item) {
1444             this.unHighlightSeries();
1445         }
1446
1447         if (this.highlightSprite) {
1448             this.highlightSprite.hide(true);
1449         }
1450     },
1451
1452     // @private
1453     hideAll: function() {
1454         if (!isNaN(this._index)) {
1455             this.__excludes[this._index] = true;
1456             this.areas[this._index].hide(true);
1457             this.drawSeries();
1458         }
1459     },
1460
1461     // @private
1462     showAll: function() {
1463         if (!isNaN(this._index)) {
1464             this.__excludes[this._index] = false;
1465             this.areas[this._index].show(true);
1466             this.drawSeries();
1467         }
1468     },
1469
1470     /**
1471      * Returns the color of the series (to be displayed as color for the series legend item).
1472      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
1473      */
1474     getLegendColor: function(index) {
1475         var me = this;
1476         return me.colorArrayStyle[index % me.colorArrayStyle.length];
1477     }
1478 });
1479