Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / chart / series / Bar.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  * Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
17  * different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
18  * Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
19  * A typical configuration object for the bar series could be:
20  *
21  * {@img Ext.chart.series.Bar/Ext.chart.series.Bar.png Ext.chart.series.Bar chart series}
22  *
23  *     var store = Ext.create('Ext.data.JsonStore', {
24  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
25  *         data: [
26  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
27  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
28  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
29  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
30  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
31  *         ]
32  *     });
33  *     
34  *     Ext.create('Ext.chart.Chart', {
35  *         renderTo: Ext.getBody(),
36  *         width: 500,
37  *         height: 300,
38  *         animate: true,
39  *         store: store,
40  *         axes: [{
41  *             type: 'Numeric',
42  *             position: 'bottom',
43  *             fields: ['data1'],
44  *             label: {
45  *                 renderer: Ext.util.Format.numberRenderer('0,0')
46  *             },
47  *             title: 'Sample Values',
48  *             grid: true,
49  *             minimum: 0
50  *         }, {
51  *             type: 'Category',
52  *             position: 'left',
53  *             fields: ['name'],
54  *             title: 'Sample Metrics'
55  *         }],
56  *         series: [{
57  *             type: 'bar',
58  *             axis: 'bottom',
59  *             highlight: true,
60  *             tips: {
61  *               trackMouse: true,
62  *               width: 140,
63  *               height: 28,
64  *               renderer: function(storeItem, item) {
65  *                 this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
66  *               }
67  *             },
68  *             label: {
69  *               display: 'insideEnd',
70  *                 field: 'data1',
71  *                 renderer: Ext.util.Format.numberRenderer('0'),
72  *                 orientation: 'horizontal',
73  *                 color: '#333',
74  *                 'text-anchor': 'middle'
75  *             },
76  *             xField: 'name',
77  *             yField: ['data1']
78  *         }]
79  *     });
80  *
81  * In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
82  * xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
83  * animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
84  * to display the information found in the `data1` property of each element store, to render a formated text with the
85  * `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
86  * other styles like `color`, `text-anchor`, etc.
87  */
88 Ext.define('Ext.chart.series.Bar', {
89
90     /* Begin Definitions */
91
92     extend: 'Ext.chart.series.Cartesian',
93
94     alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
95
96     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
97
98     /* End Definitions */
99
100     type: 'bar',
101
102     alias: 'series.bar',
103     /**
104      * @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
105      */
106     column: false,
107     
108     /**
109      * @cfg style Style properties that will override the theming series styles.
110      */
111     style: {},
112     
113     /**
114      * @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
115      */
116     gutter: 38.2,
117
118     /**
119      * @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
120      */
121     groupGutter: 38.2,
122
123     /**
124      * @cfg {Number} xPadding Padding between the left/right axes and the bars
125      */
126     xPadding: 0,
127
128     /**
129      * @cfg {Number} yPadding Padding between the top/bottom axes and the bars
130      */
131     yPadding: 10,
132
133     constructor: function(config) {
134         this.callParent(arguments);
135         var me = this,
136             surface = me.chart.surface,
137             shadow = me.chart.shadow,
138             i, l;
139         Ext.apply(me, config, {
140             highlightCfg: {
141                 lineWidth: 3,
142                 stroke: '#55c',
143                 opacity: 0.8,
144                 color: '#f00'
145             },
146             
147             shadowAttributes: [{
148                 "stroke-width": 6,
149                 "stroke-opacity": 0.05,
150                 stroke: 'rgb(200, 200, 200)',
151                 translate: {
152                     x: 1.2,
153                     y: 1.2
154                 }
155             }, {
156                 "stroke-width": 4,
157                 "stroke-opacity": 0.1,
158                 stroke: 'rgb(150, 150, 150)',
159                 translate: {
160                     x: 0.9,
161                     y: 0.9
162                 }
163             }, {
164                 "stroke-width": 2,
165                 "stroke-opacity": 0.15,
166                 stroke: 'rgb(100, 100, 100)',
167                 translate: {
168                     x: 0.6,
169                     y: 0.6
170                 }
171             }]
172         });
173         me.group = surface.getGroup(me.seriesId + '-bars');
174         if (shadow) {
175             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
176                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
177             }
178         }
179     },
180
181     // @private sets the bar girth.
182     getBarGirth: function() {
183         var me = this,
184             store = me.chart.store,
185             column = me.column,
186             ln = store.getCount(),
187             gutter = me.gutter / 100;
188         
189         return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
190     },
191
192     // @private returns the gutters.
193     getGutters: function() {
194         var me = this,
195             column = me.column,
196             gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
197         return me.column ? [gutter, 0] : [0, gutter];
198     },
199
200     // @private Get chart and data boundaries
201     getBounds: function() {
202         var me = this,
203             chart = me.chart,
204             store = chart.substore || chart.store,
205             bars = [].concat(me.yField),
206             barsLen = bars.length,
207             groupBarsLen = barsLen,
208             groupGutter = me.groupGutter / 100,
209             column = me.column,
210             xPadding = me.xPadding,
211             yPadding = me.yPadding,
212             stacked = me.stacked,
213             barWidth = me.getBarGirth(),
214             math = Math,
215             mmax = math.max,
216             mabs = math.abs,
217             groupBarWidth, bbox, minY, maxY, axis, out,
218             scale, zero, total, rec, j, plus, minus;
219
220         me.setBBox(true);
221         bbox = me.bbox;
222
223         //Skip excluded series
224         if (me.__excludes) {
225             for (j = 0, total = me.__excludes.length; j < total; j++) {
226                 if (me.__excludes[j]) {
227                     groupBarsLen--;
228                 }
229             }
230         }
231
232         if (me.axis) {
233             axis = chart.axes.get(me.axis);
234             if (axis) {
235                 out = axis.calcEnds();
236                 minY = out.from || axis.prevMin;
237                 maxY = mmax(out.to || axis.prevMax, 0);
238             }
239         }
240
241         if (me.yField && !Ext.isNumber(minY)) {
242             axis = Ext.create('Ext.chart.axis.Axis', {
243                 chart: chart,
244                 fields: [].concat(me.yField)
245             });
246             out = axis.calcEnds();
247             minY = out.from || axis.prevMin;
248             maxY = mmax(out.to || axis.prevMax, 0);
249         }
250
251         if (!Ext.isNumber(minY)) {
252             minY = 0;
253         }
254         if (!Ext.isNumber(maxY)) {
255             maxY = 0;
256         }
257         scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
258         groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
259         zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
260
261         if (stacked) {
262             total = [[], []];
263             store.each(function(record, i) {
264                 total[0][i] = total[0][i] || 0;
265                 total[1][i] = total[1][i] || 0;
266                 for (j = 0; j < barsLen; j++) {
267                     if (me.__excludes && me.__excludes[j]) {
268                         continue;
269                     }
270                     rec = record.get(bars[j]);
271                     total[+(rec > 0)][i] += mabs(rec);
272                 }
273             });
274             total[+(maxY > 0)].push(mabs(maxY));
275             total[+(minY > 0)].push(mabs(minY));
276             minus = mmax.apply(math, total[0]);
277             plus = mmax.apply(math, total[1]);
278             scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
279             zero = zero + minus * scale * (column ? -1 : 1);
280         }
281         else if (minY / maxY < 0) {
282             zero = zero - minY * scale * (column ? -1 : 1);
283         }
284         return {
285             bars: bars,
286             bbox: bbox,
287             barsLen: barsLen,
288             groupBarsLen: groupBarsLen,
289             barWidth: barWidth,
290             groupBarWidth: groupBarWidth,
291             scale: scale,
292             zero: zero,
293             xPadding: xPadding,
294             yPadding: yPadding,
295             signed: minY / maxY < 0,
296             minY: minY,
297             maxY: maxY
298         };
299     },
300
301     // @private Build an array of paths for the chart
302     getPaths: function() {
303         var me = this,
304             chart = me.chart,
305             store = chart.substore || chart.store,
306             bounds = me.bounds = me.getBounds(),
307             items = me.items = [],
308             gutter = me.gutter / 100,
309             groupGutter = me.groupGutter / 100,
310             animate = chart.animate,
311             column = me.column,
312             group = me.group,
313             enableShadows = chart.shadow,
314             shadowGroups = me.shadowGroups,
315             shadowAttributes = me.shadowAttributes,
316             shadowGroupsLn = shadowGroups.length,
317             bbox = bounds.bbox,
318             xPadding = me.xPadding,
319             yPadding = me.yPadding,
320             stacked = me.stacked,
321             barsLen = bounds.barsLen,
322             colors = me.colorArrayStyle,
323             colorLength = colors && colors.length || 0,
324             math = Math,
325             mmax = math.max,
326             mmin = math.min,
327             mabs = math.abs,
328             j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
329             shadowIndex, shadow, sprite, offset, floorY;
330
331         store.each(function(record, i, total) {
332             bottom = bounds.zero;
333             top = bounds.zero;
334             totalDim = 0;
335             totalNegDim = 0;
336             hasShadow = false; 
337             for (j = 0, counter = 0; j < barsLen; j++) {
338                 // Excluded series
339                 if (me.__excludes && me.__excludes[j]) {
340                     continue;
341                 }
342                 yValue = record.get(bounds.bars[j]);
343                 height = Math.round((yValue - ((bounds.minY < 0) ? 0 : bounds.minY)) * bounds.scale);
344                 barAttr = {
345                     fill: colors[(barsLen > 1 ? j : 0) % colorLength]
346                 };
347                 if (column) {
348                     Ext.apply(barAttr, {
349                         height: height,
350                         width: mmax(bounds.groupBarWidth, 0),
351                         x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
352                         y: bottom - height
353                     });
354                 }
355                 else {
356                     // draw in reverse order
357                     offset = (total - 1) - i;
358                     Ext.apply(barAttr, {
359                         height: mmax(bounds.groupBarWidth, 0),
360                         width: height + (bottom == bounds.zero),
361                         x: bottom + (bottom != bounds.zero),
362                         y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
363                     });
364                 }
365                 if (height < 0) {
366                     if (column) {
367                         barAttr.y = top;
368                         barAttr.height = mabs(height);
369                     } else {
370                         barAttr.x = top + height;
371                         barAttr.width = mabs(height);
372                     }
373                 }
374                 if (stacked) {
375                     if (height < 0) {
376                         top += height * (column ? -1 : 1);
377                     } else {
378                         bottom += height * (column ? -1 : 1);
379                     }
380                     totalDim += mabs(height);
381                     if (height < 0) {
382                         totalNegDim += mabs(height);
383                     }
384                 }
385                 barAttr.x = Math.floor(barAttr.x) + 1;
386                 floorY = Math.floor(barAttr.y);
387                 if (!Ext.isIE9 && barAttr.y > floorY) {
388                     floorY--;
389                 }
390                 barAttr.y = floorY;
391                 barAttr.width = Math.floor(barAttr.width);
392                 barAttr.height = Math.floor(barAttr.height);
393                 items.push({
394                     series: me,
395                     storeItem: record,
396                     value: [record.get(me.xField), yValue],
397                     attr: barAttr,
398                     point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
399                                     [yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
400                 });
401                 // When resizing, reset before animating
402                 if (animate && chart.resizing) {
403                     attrs = column ? {
404                         x: barAttr.x,
405                         y: bounds.zero,
406                         width: barAttr.width,
407                         height: 0
408                     } : {
409                         x: bounds.zero,
410                         y: barAttr.y,
411                         width: 0,
412                         height: barAttr.height
413                     };
414                     if (enableShadows && (stacked && !hasShadow || !stacked)) {
415                         hasShadow = true;
416                         //update shadows
417                         for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
418                             shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
419                             if (shadow) {
420                                 shadow.setAttributes(attrs, true);
421                             }
422                         }
423                     }
424                     //update sprite position and width/height
425                     sprite = group.getAt(i * barsLen + j);
426                     if (sprite) {
427                         sprite.setAttributes(attrs, true);
428                     }
429                 }
430                 counter++;
431             }
432             if (stacked && items.length) {
433                 items[i * counter].totalDim = totalDim;
434                 items[i * counter].totalNegDim = totalNegDim;
435             }
436         }, me);
437     },
438
439     // @private render/setAttributes on the shadows
440     renderShadows: function(i, barAttr, baseAttrs, bounds) {
441         var me = this,
442             chart = me.chart,
443             surface = chart.surface,
444             animate = chart.animate,
445             stacked = me.stacked,
446             shadowGroups = me.shadowGroups,
447             shadowAttributes = me.shadowAttributes,
448             shadowGroupsLn = shadowGroups.length,
449             store = chart.substore || chart.store,
450             column = me.column,
451             items = me.items,
452             shadows = [],
453             zero = bounds.zero,
454             shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
455
456         if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
457             j = i / bounds.groupBarsLen;
458             //create shadows
459             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
460                 shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
461                 shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
462                 Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
463                 if (!shadow) {
464                     shadow = surface.add(Ext.apply({
465                         type: 'rect',
466                         group: shadowGroups[shadowIndex]
467                     }, Ext.apply({}, baseAttrs, shadowBarAttr)));
468                 }
469                 if (stacked) {
470                     totalDim = items[i].totalDim;
471                     totalNegDim = items[i].totalNegDim;
472                     if (column) {
473                         shadowBarAttr.y = zero - totalNegDim;
474                         shadowBarAttr.height = totalDim;
475                     }
476                     else {
477                         shadowBarAttr.x = zero - totalNegDim;
478                         shadowBarAttr.width = totalDim;
479                     }
480                 }
481                 if (animate) {
482                     if (!stacked) {
483                         rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
484                         me.onAnimate(shadow, { to: rendererAttributes });
485                     }
486                     else {
487                         rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: true }), i, store);
488                         shadow.setAttributes(rendererAttributes, true);
489                     }
490                 }
491                 else {
492                     rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: false }), i, store);
493                     shadow.setAttributes(rendererAttributes, true);
494                 }
495                 shadows.push(shadow);
496             }
497         }
498         return shadows;
499     },
500
501     /**
502      * Draws the series for the current chart.
503      */
504     drawSeries: function() {
505         var me = this,
506             chart = me.chart,
507             store = chart.substore || chart.store,
508             surface = chart.surface,
509             animate = chart.animate,
510             stacked = me.stacked,
511             column = me.column,
512             enableShadows = chart.shadow,
513             shadowGroups = me.shadowGroups,
514             shadowGroupsLn = shadowGroups.length,
515             group = me.group,
516             seriesStyle = me.seriesStyle,
517             items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
518             bounds, endSeriesStyle, barAttr, attrs, anim;
519         
520         if (!store || !store.getCount()) {
521             return;
522         }
523         
524         //fill colors are taken from the colors array.
525         delete seriesStyle.fill;
526         endSeriesStyle = Ext.apply(seriesStyle, this.style);
527         me.unHighlightItem();
528         me.cleanHighlights();
529
530         me.getPaths();
531         bounds = me.bounds;
532         items = me.items;
533
534         baseAttrs = column ? {
535             y: bounds.zero,
536             height: 0
537         } : {
538             x: bounds.zero,
539             width: 0
540         };
541         ln = items.length;
542         // Create new or reuse sprites and animate/display
543         for (i = 0; i < ln; i++) {
544             sprite = group.getAt(i);
545             barAttr = items[i].attr;
546
547             if (enableShadows) {
548                 items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
549             }
550
551             // Create a new sprite if needed (no height)
552             if (!sprite) {
553                 attrs = Ext.apply({}, baseAttrs, barAttr);
554                 attrs = Ext.apply(attrs, endSeriesStyle || {});
555                 sprite = surface.add(Ext.apply({}, {
556                     type: 'rect',
557                     group: group
558                 }, attrs));
559             }
560             if (animate) {
561                 rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
562                 sprite._to = rendererAttributes;
563                 anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
564                 if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
565                     j = i / bounds.barsLen;
566                     for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
567                         anim.on('afteranimate', function() {
568                             this.show(true);
569                         }, shadowGroups[shadowIndex].getAt(j));
570                     }
571                 }
572             }
573             else {
574                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
575                 sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
576             }
577             items[i].sprite = sprite;
578         }
579
580         // Hide unused sprites
581         ln = group.getCount();
582         for (j = i; j < ln; j++) {
583             group.getAt(j).hide(true);
584         }
585         // Hide unused shadows
586         if (enableShadows) {
587             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
588                 shadowGroup = shadowGroups[shadowIndex];
589                 ln = shadowGroup.getCount();
590                 for (j = i; j < ln; j++) {
591                     shadowGroup.getAt(j).hide(true);
592                 }
593             }
594         }
595         me.renderLabels();
596     },
597     
598     // @private handled when creating a label.
599     onCreateLabel: function(storeItem, item, i, display) {
600         var me = this,
601             surface = me.chart.surface,
602             group = me.labelsGroup,
603             config = me.label,
604             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
605             sprite;
606         return surface.add(Ext.apply({
607             type: 'text',
608             group: group
609         }, endLabelStyle || {}));
610     },
611     
612     // @private callback used when placing a label.
613     onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
614         // Determine the label's final position. Starts with the configured preferred value but
615         // may get flipped from inside to outside or vice-versa depending on space.
616         var me = this,
617             opt = me.bounds,
618             groupBarWidth = opt.groupBarWidth,
619             column = me.column,
620             chart = me.chart,
621             chartBBox = chart.chartBBox,
622             resizing = chart.resizing,
623             xValue = item.value[0],
624             yValue = item.value[1],
625             attr = item.attr,
626             config = me.label,
627             rotate = config.orientation == 'vertical',
628             field = [].concat(config.field),
629             format = config.renderer,
630             text = format(storeItem.get(field[index])),
631             size = me.getLabelSize(text),
632             width = size.width,
633             height = size.height,
634             zero = opt.zero,
635             outside = 'outside',
636             insideStart = 'insideStart',
637             insideEnd = 'insideEnd',
638             offsetX = 10,
639             offsetY = 6,
640             signed = opt.signed,
641             x, y, finalAttr;
642
643         label.setAttributes({
644             text: text
645         });
646
647         label.isOutside = false;
648         if (column) {
649             if (display == outside) {
650                 if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
651                     display = insideEnd;
652                 }
653             } else {
654                 if (height + offsetY > attr.height) {
655                     display = outside;
656                     label.isOutside = true;
657                 }
658             }
659             x = attr.x + groupBarWidth / 2;
660             y = display == insideStart ?
661                     (zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
662                     (yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
663                                    (attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
664         }
665         else {
666             if (display == outside) {
667                 if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
668                     display = insideEnd;
669                 }
670             }
671             else {
672                 if (width + offsetX > attr.width) {
673                     display = outside;
674                     label.isOutside = true;
675                 }
676             }
677             x = display == insideStart ?
678                 (zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
679                 (yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
680                 (attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
681             y = attr.y + groupBarWidth / 2;
682         }
683         //set position
684         finalAttr = {
685             x: x,
686             y: y
687         };
688         //rotate
689         if (rotate) {
690             finalAttr.rotate = {
691                 x: x,
692                 y: y,
693                 degrees: 270
694             };
695         }
696         //check for resizing
697         if (animate && resizing) {
698             if (column) {
699                 x = attr.x + attr.width / 2;
700                 y = zero;
701             } else {
702                 x = zero;
703                 y = attr.y + attr.height / 2;
704             }
705             label.setAttributes({
706                 x: x,
707                 y: y
708             }, true);
709             if (rotate) {
710                 label.setAttributes({
711                     rotate: {
712                         x: x,
713                         y: y,
714                         degrees: 270
715                     }
716                 }, true);
717             }
718         }
719         //handle animation
720         if (animate) {
721             me.onAnimate(label, { to: finalAttr });
722         }
723         else {
724             label.setAttributes(Ext.apply(finalAttr, {
725                 hidden: false
726             }), true);
727         }
728     },
729
730     /* @private
731      * Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
732      * changing visible sprites.
733      * @param value
734      */
735     getLabelSize: function(value) {
736         var tester = this.testerLabel,
737             config = this.label,
738             endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
739             rotated = config.orientation === 'vertical',
740             bbox, w, h,
741             undef;
742         if (!tester) {
743             tester = this.testerLabel = this.chart.surface.add(Ext.apply({
744                 type: 'text',
745                 opacity: 0
746             }, endLabelStyle));
747         }
748         tester.setAttributes({
749             text: value
750         }, true);
751
752         // Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
753         bbox = tester.getBBox();
754         w = bbox.width;
755         h = bbox.height;
756         return {
757             width: rotated ? h : w,
758             height: rotated ? w : h
759         };
760     },
761
762     // @private used to animate label, markers and other sprites.
763     onAnimate: function(sprite, attr) {
764         sprite.show();
765         return this.callParent(arguments);
766     },
767     
768     isItemInPoint: function(x, y, item) {
769         var bbox = item.sprite.getBBox();
770         return bbox.x <= x && bbox.y <= y
771             && (bbox.x + bbox.width) >= x
772             && (bbox.y + bbox.height) >= y;
773     },
774     
775     // @private hide all markers
776     hideAll: function() {
777         var axes = this.chart.axes;
778         if (!isNaN(this._index)) {
779             if (!this.__excludes) {
780                 this.__excludes = [];
781             }
782             this.__excludes[this._index] = true;
783             this.drawSeries();
784             axes.each(function(axis) {
785                 axis.drawAxis();
786             });
787         }
788     },
789
790     // @private show all markers
791     showAll: function() {
792         var axes = this.chart.axes;
793         if (!isNaN(this._index)) {
794             if (!this.__excludes) {
795                 this.__excludes = [];
796             }
797             this.__excludes[this._index] = false;
798             this.drawSeries();
799             axes.each(function(axis) {
800                 axis.drawAxis();
801             });
802         }
803     },
804     
805     /**
806      * Returns a string with the color to be used for the series legend item.
807      * @param index
808      */
809     getLegendColor: function(index) {
810         var me = this,
811             colorLength = me.colorArrayStyle.length;
812         
813         if (me.style && me.style.fill) {
814             return me.style.fill;
815         } else {
816             return me.colorArrayStyle[index % colorLength];
817         }
818     },
819
820     highlightItem: function(item) {
821         this.callParent(arguments);
822         this.renderLabels();
823     },
824
825     unHighlightItem: function() {
826         this.callParent(arguments);
827         this.renderLabels();
828     },
829
830     cleanHighlights: function() {
831         this.callParent(arguments);
832         this.renderLabels();
833     }
834 });