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