Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / chart / axis / Axis.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.axis.Axis
17  * @extends Ext.chart.axis.Abstract
18  * 
19  * Defines axis for charts. The axis position, type, style can be configured.
20  * The axes are defined in an axes array of configuration objects where the type, 
21  * field, grid and other configuration options can be set. To know more about how 
22  * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
23  * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
24  * 
25  *     axes: [{
26  *         type: 'Numeric',
27  *         grid: true,
28  *         position: 'left',
29  *         fields: ['data1', 'data2', 'data3'],
30  *         title: 'Number of Hits',
31  *         grid: {
32  *             odd: {
33  *                 opacity: 1,
34  *                 fill: '#ddd',
35  *                 stroke: '#bbb',
36  *                 'stroke-width': 1
37  *             }
38  *         },
39  *         minimum: 0
40  *     }, {
41  *         type: 'Category',
42  *         position: 'bottom',
43  *         fields: ['name'],
44  *         title: 'Month of the Year',
45  *         grid: true,
46  *         label: {
47  *             rotate: {
48  *                 degrees: 315
49  *             }
50  *         }
51  *     }]
52  * 
53  * In this case we use a `Numeric` axis for displaying the values of the Area series and a `Category` axis for displaying the names of
54  * the store elements. The numeric axis is placed on the left of the screen, while the category axis is placed at the bottom of the chart. 
55  * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the 
56  * category axis the labels will be rotated so they can fit the space better.
57  */
58 Ext.define('Ext.chart.axis.Axis', {
59
60     /* Begin Definitions */
61
62     extend: 'Ext.chart.axis.Abstract',
63
64     alternateClassName: 'Ext.chart.Axis',
65
66     requires: ['Ext.draw.Draw'],
67
68     /* End Definitions */
69
70     /**
71      * @cfg {Boolean | Object} grid 
72      * The grid configuration enables you to set a background grid for an axis.
73      * If set to *true* on a vertical axis, vertical lines will be drawn.
74      * If set to *true* on a horizontal axis, horizontal lines will be drawn.
75      * If both are set, a proper grid with horizontal and vertical lines will be drawn.
76      *
77      * You can set specific options for the grid configuration for odd and/or even lines/rows.
78      * Since the rows being drawn are rectangle sprites, you can set to an odd or even property
79      * all styles that apply to {@link Ext.draw.Sprite}. For more information on all the style
80      * properties you can set please take a look at {@link Ext.draw.Sprite}. Some useful style properties are `opacity`, `fill`, `stroke`, `stroke-width`, etc.
81      *
82      * The possible values for a grid option are then *true*, *false*, or an object with `{ odd, even }` properties
83      * where each property contains a sprite style descriptor object that is defined in {@link Ext.draw.Sprite}.
84      *
85      * For example:
86      *
87      *     axes: [{
88      *         type: 'Numeric',
89      *         grid: true,
90      *         position: 'left',
91      *         fields: ['data1', 'data2', 'data3'],
92      *         title: 'Number of Hits',
93      *         grid: {
94      *             odd: {
95      *                 opacity: 1,
96      *                 fill: '#ddd',
97      *                 stroke: '#bbb',
98      *                 'stroke-width': 1
99      *             }
100      *         }
101      *     }, {
102      *         type: 'Category',
103      *         position: 'bottom',
104      *         fields: ['name'],
105      *         title: 'Month of the Year',
106      *         grid: true
107      *     }]
108      * 
109      */
110
111     /**
112      * @cfg {Number} majorTickSteps 
113      * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
114      */
115
116     /**
117      * @cfg {Number} minorTickSteps 
118      * The number of small ticks between two major ticks. Default is zero.
119      */
120     
121     //@private force min/max values from store
122     forceMinMax: false,
123     
124     /**
125      * @cfg {Number} dashSize 
126      * The size of the dash marker. Default's 3.
127      */
128     dashSize: 3,
129     
130     /**
131      * @cfg {String} position
132      * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
133      */
134     position: 'bottom',
135     
136     // @private
137     skipFirst: false,
138     
139     /**
140      * @cfg {Number} length
141      * Offset axis position. Default's 0.
142      */
143     length: 0,
144     
145     /**
146      * @cfg {Number} width
147      * Offset axis width. Default's 0.
148      */
149     width: 0,
150     
151     majorTickSteps: false,
152
153     // @private
154     applyData: Ext.emptyFn,
155
156     // @private creates a structure with start, end and step points.
157     calcEnds: function() {
158         var me = this,
159             math = Math,
160             mmax = math.max,
161             mmin = math.min,
162             store = me.chart.substore || me.chart.store,
163             series = me.chart.series.items,
164             fields = me.fields,
165             ln = fields.length,
166             min = isNaN(me.minimum) ? Infinity : me.minimum,
167             max = isNaN(me.maximum) ? -Infinity : me.maximum,
168             prevMin = me.prevMin,
169             prevMax = me.prevMax,
170             aggregate = false,
171             total = 0,
172             excludes = [],
173             outfrom, outto,
174             i, l, values, rec, out;
175
176         //if one series is stacked I have to aggregate the values
177         //for the scale.
178         for (i = 0, l = series.length; !aggregate && i < l; i++) {
179             aggregate = aggregate || series[i].stacked;
180             excludes = series[i].__excludes || excludes;
181         }
182         store.each(function(record) {
183             if (aggregate) {
184                 if (!isFinite(min)) {
185                     min = 0;
186                 }
187                 for (values = [0, 0], i = 0; i < ln; i++) {
188                     if (excludes[i]) {
189                         continue;
190                     }
191                     rec = record.get(fields[i]);
192                     values[+(rec > 0)] += math.abs(rec);
193                 }
194                 max = mmax(max, -values[0], values[1]);
195                 min = mmin(min, -values[0], values[1]);
196             }
197             else {
198                 for (i = 0; i < ln; i++) {
199                     if (excludes[i]) {
200                         continue;
201                     }
202                     value = record.get(fields[i]);
203                     max = mmax(max, value);
204                     min = mmin(min, value);
205                 }
206             }
207         });
208         if (!isFinite(max)) {
209             max = me.prevMax || 0;
210         }
211         if (!isFinite(min)) {
212             min = me.prevMin || 0;
213         }
214         //normalize min max for snapEnds.
215         if (min != max && (max != (max >> 0))) {
216             max = (max >> 0) + 1;
217         }
218         out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ?  (me.majorTickSteps +1) : me.steps);
219         outfrom = out.from;
220         outto = out.to;
221         if (me.forceMinMax) {
222             if (!isNaN(max)) {
223                 out.to = max;
224             }
225             if (!isNaN(min)) {
226                 out.from = min;
227             }
228         }
229         if (!isNaN(me.maximum)) {
230             //TODO(nico) users are responsible for their own minimum/maximum values set.
231             //Clipping should be added to remove lines in the chart which are below the axis.
232             out.to = me.maximum;
233         }
234         if (!isNaN(me.minimum)) {
235             //TODO(nico) users are responsible for their own minimum/maximum values set.
236             //Clipping should be added to remove lines in the chart which are below the axis.
237             out.from = me.minimum;
238         }
239         
240         //Adjust after adjusting minimum and maximum
241         out.step = (out.to - out.from) / (outto - outfrom) * out.step;
242         
243         if (me.adjustMaximumByMajorUnit) {
244             out.to += out.step;
245         }
246         if (me.adjustMinimumByMajorUnit) {
247             out.from -= out.step;
248         }
249         me.prevMin = min == max? 0 : min;
250         me.prevMax = max;
251         return out;
252     },
253
254     /**
255      * Renders the axis into the screen and updates it's position.
256      */
257     drawAxis: function (init) {
258         var me = this,
259             i, j,
260             x = me.x,
261             y = me.y,
262             gutterX = me.chart.maxGutter[0],
263             gutterY = me.chart.maxGutter[1],
264             dashSize = me.dashSize,
265             subDashesX = me.minorTickSteps || 0,
266             subDashesY = me.minorTickSteps || 0,
267             length = me.length,
268             position = me.position,
269             inflections = [],
270             calcLabels = false,
271             stepCalcs = me.applyData(),
272             step = stepCalcs.step,
273             steps = stepCalcs.steps,
274             from = stepCalcs.from,
275             to = stepCalcs.to,
276             trueLength,
277             currentX,
278             currentY,
279             path,
280             prev,
281             dashesX,
282             dashesY,
283             delta;
284         
285         //If no steps are specified
286         //then don't draw the axis. This generally happens
287         //when an empty store.
288         if (me.hidden || isNaN(step) || (from == to)) {
289             return;
290         }
291
292         me.from = stepCalcs.from;
293         me.to = stepCalcs.to;
294         if (position == 'left' || position == 'right') {
295             currentX = Math.floor(x) + 0.5;
296             path = ["M", currentX, y, "l", 0, -length];
297             trueLength = length - (gutterY * 2);
298         }
299         else {
300             currentY = Math.floor(y) + 0.5;
301             path = ["M", x, currentY, "l", length, 0];
302             trueLength = length - (gutterX * 2);
303         }
304         
305         delta = trueLength / (steps || 1);
306         dashesX = Math.max(subDashesX +1, 0);
307         dashesY = Math.max(subDashesY +1, 0);
308         if (me.type == 'Numeric') {
309             calcLabels = true;
310             me.labels = [stepCalcs.from];
311         }
312         if (position == 'right' || position == 'left') {
313             currentY = y - gutterY;
314             currentX = x - ((position == 'left') * dashSize * 2);
315             while (currentY >= y - gutterY - trueLength) {
316                 path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
317                 if (currentY != y - gutterY) {
318                     for (i = 1; i < dashesY; i++) {
319                         path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
320                     }
321                 }
322                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
323                 currentY -= delta;
324                 if (calcLabels) {
325                     me.labels.push(me.labels[me.labels.length -1] + step);
326                 }
327                 if (delta === 0) {
328                     break;
329                 }
330             }
331             if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
332                 path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
333                 for (i = 1; i < dashesY; i++) {
334                     path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
335                 }
336                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
337                 if (calcLabels) {
338                     me.labels.push(me.labels[me.labels.length -1] + step);
339                 }
340             }
341         } else {
342             currentX = x + gutterX;
343             currentY = y - ((position == 'top') * dashSize * 2);
344             while (currentX <= x + gutterX + trueLength) {
345                 path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
346                 if (currentX != x + gutterX) {
347                     for (i = 1; i < dashesX; i++) {
348                         path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
349                     }
350                 }
351                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
352                 currentX += delta;
353                 if (calcLabels) {
354                     me.labels.push(me.labels[me.labels.length -1] + step);
355                 }
356                 if (delta === 0) {
357                     break;
358                 }
359             }
360             if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
361                 path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
362                 for (i = 1; i < dashesX; i++) {
363                     path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
364                 }
365                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
366                 if (calcLabels) {
367                     me.labels.push(me.labels[me.labels.length -1] + step);
368                 }
369             }
370         }
371         if (!me.axis) {
372             me.axis = me.chart.surface.add(Ext.apply({
373                 type: 'path',
374                 path: path
375             }, me.axisStyle));
376         }
377         me.axis.setAttributes({
378             path: path
379         }, true);
380         me.inflections = inflections;
381         if (!init && me.grid) {
382             me.drawGrid();
383         }
384         me.axisBBox = me.axis.getBBox();
385         me.drawLabel();
386     },
387
388     /**
389      * Renders an horizontal and/or vertical grid into the Surface.
390      */
391     drawGrid: function() {
392         var me = this,
393             surface = me.chart.surface, 
394             grid = me.grid,
395             odd = grid.odd,
396             even = grid.even,
397             inflections = me.inflections,
398             ln = inflections.length - ((odd || even)? 0 : 1),
399             position = me.position,
400             gutter = me.chart.maxGutter,
401             width = me.width - 2,
402             vert = false,
403             point, prevPoint,
404             i = 1,
405             path = [], styles, lineWidth, dlineWidth,
406             oddPath = [], evenPath = [];
407         
408         if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
409             (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
410             i = 0;
411             ln++;
412         }
413         for (; i < ln; i++) {
414             point = inflections[i];
415             prevPoint = inflections[i - 1];
416             if (odd || even) {
417                 path = (i % 2)? oddPath : evenPath;
418                 styles = ((i % 2)? odd : even) || {};
419                 lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
420                 dlineWidth = 2 * lineWidth;
421                 if (position == 'left') {
422                     path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth, 
423                               "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
424                               "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
425                               "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
426                 }
427                 else if (position == 'right') {
428                     path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth, 
429                               "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
430                               "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
431                               "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
432                 }
433                 else if (position == 'top') {
434                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth, 
435                               "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
436                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
437                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
438                 }
439                 else {
440                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth, 
441                             "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
442                             "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
443                             "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
444                 }
445             } else {
446                 if (position == 'left') {
447                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
448                 }
449                 else if (position == 'right') {
450                     path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
451                 }
452                 else if (position == 'top') {
453                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
454                 }
455                 else {
456                     path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
457                 }
458             }
459         }
460         if (odd || even) {
461             if (oddPath.length) {
462                 if (!me.gridOdd && oddPath.length) {
463                     me.gridOdd = surface.add({
464                         type: 'path',
465                         path: oddPath
466                     });
467                 }
468                 me.gridOdd.setAttributes(Ext.apply({
469                     path: oddPath,
470                     hidden: false
471                 }, odd || {}), true);
472             }
473             if (evenPath.length) {
474                 if (!me.gridEven) {
475                     me.gridEven = surface.add({
476                         type: 'path',
477                         path: evenPath
478                     });
479                 } 
480                 me.gridEven.setAttributes(Ext.apply({
481                     path: evenPath,
482                     hidden: false
483                 }, even || {}), true);
484             }
485         }
486         else {
487             if (path.length) {
488                 if (!me.gridLines) {
489                     me.gridLines = me.chart.surface.add({
490                         type: 'path',
491                         path: path,
492                         "stroke-width": me.lineWidth || 1,
493                         stroke: me.gridColor || '#ccc'
494                     });
495                 }
496                 me.gridLines.setAttributes({
497                     hidden: false,
498                     path: path
499                 }, true);
500             }
501             else if (me.gridLines) {
502                 me.gridLines.hide(true);
503             }
504         }
505     },
506
507     //@private
508     getOrCreateLabel: function(i, text) {
509         var me = this,
510             labelGroup = me.labelGroup,
511             textLabel = labelGroup.getAt(i),
512             surface = me.chart.surface;
513         if (textLabel) {
514             if (text != textLabel.attr.text) {
515                 textLabel.setAttributes(Ext.apply({
516                     text: text
517                 }, me.label), true);
518                 textLabel._bbox = textLabel.getBBox();
519             }
520         }
521         else {
522             textLabel = surface.add(Ext.apply({
523                 group: labelGroup,
524                 type: 'text',
525                 x: 0,
526                 y: 0,
527                 text: text
528             }, me.label));
529             surface.renderItem(textLabel);
530             textLabel._bbox = textLabel.getBBox();
531         }
532         //get untransformed bounding box
533         if (me.label.rotation) {
534             textLabel.setAttributes({
535                 rotation: {
536                     degrees: 0    
537                 }    
538             }, true);
539             textLabel._ubbox = textLabel.getBBox();
540             textLabel.setAttributes(me.label, true);
541         } else {
542             textLabel._ubbox = textLabel._bbox;
543         }
544         return textLabel;
545     },
546     
547     rect2pointArray: function(sprite) {
548         var surface = this.chart.surface,
549             rect = surface.getBBox(sprite, true),
550             p1 = [rect.x, rect.y],
551             p1p = p1.slice(),
552             p2 = [rect.x + rect.width, rect.y],
553             p2p = p2.slice(),
554             p3 = [rect.x + rect.width, rect.y + rect.height],
555             p3p = p3.slice(),
556             p4 = [rect.x, rect.y + rect.height],
557             p4p = p4.slice(),
558             matrix = sprite.matrix;
559         //transform the points
560         p1[0] = matrix.x.apply(matrix, p1p);
561         p1[1] = matrix.y.apply(matrix, p1p);
562         
563         p2[0] = matrix.x.apply(matrix, p2p);
564         p2[1] = matrix.y.apply(matrix, p2p);
565         
566         p3[0] = matrix.x.apply(matrix, p3p);
567         p3[1] = matrix.y.apply(matrix, p3p);
568         
569         p4[0] = matrix.x.apply(matrix, p4p);
570         p4[1] = matrix.y.apply(matrix, p4p);
571         return [p1, p2, p3, p4];
572     },
573     
574     intersect: function(l1, l2) {
575         var r1 = this.rect2pointArray(l1),
576             r2 = this.rect2pointArray(l2);
577         return !!Ext.draw.Draw.intersect(r1, r2).length;
578     },
579     
580     drawHorizontalLabels: function() {
581        var  me = this,
582             labelConf = me.label,
583             floor = Math.floor,
584             max = Math.max,
585             axes = me.chart.axes,
586             position = me.position,
587             inflections = me.inflections,
588             ln = inflections.length,
589             labels = me.labels,
590             labelGroup = me.labelGroup,
591             maxHeight = 0,
592             ratio,
593             gutterY = me.chart.maxGutter[1],
594             ubbox, bbox, point, prevX, prevLabel,
595             projectedWidth = 0,
596             textLabel, attr, textRight, text,
597             label, last, x, y, i, firstLabel;
598
599         last = ln - 1;
600         //get a reference to the first text label dimensions
601         point = inflections[0];
602         firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
603         ratio = Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)) >> 0;
604         
605         for (i = 0; i < ln; i++) {
606             point = inflections[i];
607             text = me.label.renderer(labels[i]);
608             textLabel = me.getOrCreateLabel(i, text);
609             bbox = textLabel._bbox;
610             maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
611             x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
612             if (me.chart.maxGutter[0] == 0) {
613                 if (i == 0 && axes.findIndex('position', 'left') == -1) {
614                     x = point[0];
615                 }
616                 else if (i == last && axes.findIndex('position', 'right') == -1) {
617                     x = point[0] - bbox.width;
618                 }
619             }
620             if (position == 'top') {
621                 y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
622             }
623             else {
624                 y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
625             }
626             
627             textLabel.setAttributes({
628                 hidden: false,
629                 x: x,
630                 y: y
631             }, true);
632
633             // Skip label if there isn't available minimum space
634             if (i != 0 && (me.intersect(textLabel, prevLabel)
635                 || me.intersect(textLabel, firstLabel))) {
636                 textLabel.hide(true);
637                 continue;
638             }
639             
640             prevLabel = textLabel;
641         }
642
643         return maxHeight;
644     },
645     
646     drawVerticalLabels: function() {
647         var me = this,
648             inflections = me.inflections,
649             position = me.position,
650             ln = inflections.length,
651             labels = me.labels,
652             maxWidth = 0,
653             max = Math.max,
654             floor = Math.floor,
655             ceil = Math.ceil,
656             axes = me.chart.axes,
657             gutterY = me.chart.maxGutter[1],
658             ubbox, bbox, point, prevLabel,
659             projectedWidth = 0,
660             textLabel, attr, textRight, text,
661             label, last, x, y, i;
662
663         last = ln;
664         for (i = 0; i < last; i++) {
665             point = inflections[i];
666             text = me.label.renderer(labels[i]);
667             textLabel = me.getOrCreateLabel(i, text);
668             bbox = textLabel._bbox;
669             
670             maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
671             y = point[1];
672             if (gutterY < bbox.height / 2) {
673                 if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
674                     y = me.y - me.length + ceil(bbox.height / 2);
675                 }
676                 else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
677                     y = me.y - floor(bbox.height / 2);
678                 }
679             }
680             if (position == 'left') {
681                 x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
682             }
683             else {
684                 x = point[0] + me.dashSize + me.label.padding + 2;
685             }    
686             textLabel.setAttributes(Ext.apply({
687                 hidden: false,
688                 x: x,
689                 y: y
690             }, me.label), true);
691             // Skip label if there isn't available minimum space
692             if (i != 0 && me.intersect(textLabel, prevLabel)) {
693                 textLabel.hide(true);
694                 continue;
695             }
696             prevLabel = textLabel;
697         }
698         
699         return maxWidth;
700     },
701
702     /**
703      * Renders the labels in the axes.
704      */
705     drawLabel: function() {
706         var me = this,
707             position = me.position,
708             labelGroup = me.labelGroup,
709             inflections = me.inflections,
710             maxWidth = 0,
711             maxHeight = 0,
712             ln, i;
713
714         if (position == 'left' || position == 'right') {
715             maxWidth = me.drawVerticalLabels();    
716         } else {
717             maxHeight = me.drawHorizontalLabels();
718         }
719
720         // Hide unused bars
721         ln = labelGroup.getCount();
722         i = inflections.length;
723         for (; i < ln; i++) {
724             labelGroup.getAt(i).hide(true);
725         }
726
727         me.bbox = {};
728         Ext.apply(me.bbox, me.axisBBox);
729         me.bbox.height = maxHeight;
730         me.bbox.width = maxWidth;
731         if (Ext.isString(me.title)) {
732             me.drawTitle(maxWidth, maxHeight);
733         }
734     },
735
736     // @private creates the elipsis for the text.
737     elipsis: function(sprite, text, desiredWidth, minWidth, center) {
738         var bbox,
739             x;
740
741         if (desiredWidth < minWidth) {
742             sprite.hide(true);
743             return false;
744         }
745         while (text.length > 4) {
746             text = text.substr(0, text.length - 4) + "...";
747             sprite.setAttributes({
748                 text: text
749             }, true);
750             bbox = sprite.getBBox();
751             if (bbox.width < desiredWidth) {
752                 if (typeof center == 'number') {
753                     sprite.setAttributes({
754                         x: Math.floor(center - (bbox.width / 2))
755                     }, true);
756                 }
757                 break;
758             }
759         }
760         return true;
761     },
762
763     /**
764      * Updates the {@link #title} of this axis.
765      * @param {String} title
766      */
767     setTitle: function(title) {
768         this.title = title;
769         this.drawLabel();
770     },
771
772     // @private draws the title for the axis.
773     drawTitle: function(maxWidth, maxHeight) {
774         var me = this,
775             position = me.position,
776             surface = me.chart.surface,
777             displaySprite = me.displaySprite,
778             title = me.title,
779             rotate = (position == 'left' || position == 'right'),
780             x = me.x,
781             y = me.y,
782             base, bbox, pad;
783
784         if (displaySprite) {
785             displaySprite.setAttributes({text: title}, true);
786         } else {
787             base = {
788                 type: 'text',
789                 x: 0,
790                 y: 0,
791                 text: title
792             };
793             displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
794             surface.renderItem(displaySprite);
795         }
796         bbox = displaySprite.getBBox();
797         pad = me.dashSize + me.label.padding;
798
799         if (rotate) {
800             y -= ((me.length / 2) - (bbox.height / 2));
801             if (position == 'left') {
802                 x -= (maxWidth + pad + (bbox.width / 2));
803             }
804             else {
805                 x += (maxWidth + pad + bbox.width - (bbox.width / 2));
806             }
807             me.bbox.width += bbox.width + 10;
808         }
809         else {
810             x += (me.length / 2) - (bbox.width * 0.5);
811             if (position == 'top') {
812                 y -= (maxHeight + pad + (bbox.height * 0.3));
813             }
814             else {
815                 y += (maxHeight + pad + (bbox.height * 0.8));
816             }
817             me.bbox.height += bbox.height + 10;
818         }
819         displaySprite.setAttributes({
820             translate: {
821                 x: x,
822                 y: y
823             }
824         }, true);
825     }
826 });