Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / chart / Chart.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  * Charts provide a flexible way to achieve a wide range of data visualization capablitities.
17  * Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
18  * updates its display whenever data in the Store changes. In addition, the look and feel
19  * of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
20  * 
21  * ## Creating a Simple Chart
22  * 
23  * Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
24  * an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
25  * and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
26  * 
27  * ### 1. Creating a Store
28  * 
29  * The first step is to create a {@link Ext.data.Model Model} that represents the type of
30  * data that will be displayed in the Chart. For example the data for a chart that displays
31  * a weather forecast could be represented as a series of "WeatherPoint" data points with
32  * two fields - "temperature", and "date":
33  * 
34  *     Ext.define('WeatherPoint', {
35  *         extend: 'Ext.data.Model',
36  *         fields: ['temperature', 'date']
37  *     });
38  * 
39  * Next a {@link Ext.data.Store Store} must be created.  The store contains a collection of "WeatherPoint" Model instances.
40  * The data could be loaded dynamically, but for sake of ease this example uses inline data:
41  * 
42  *     var store = Ext.create('Ext.data.Store', {
43  *         model: 'WeatherPoint',
44  *         data: [
45  *             { temperature: 58, date: new Date(2011, 1, 1, 8) },
46  *             { temperature: 63, date: new Date(2011, 1, 1, 9) },
47  *             { temperature: 73, date: new Date(2011, 1, 1, 10) },
48  *             { temperature: 78, date: new Date(2011, 1, 1, 11) },
49  *             { temperature: 81, date: new Date(2011, 1, 1, 12) }
50  *         ]
51  *     });
52  *    
53  * For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
54  * 
55  * ### 2. Creating the Chart object
56  * 
57  * Now that a Store has been created it can be used in a Chart:
58  * 
59  *     Ext.create('Ext.chart.Chart', {
60  *        renderTo: Ext.getBody(),
61  *        width: 400,
62  *        height: 300,
63  *        store: store
64  *     });
65  *    
66  * That's all it takes to create a Chart instance that is backed by a Store.
67  * However, if the above code is run in a browser, a blank screen will be displayed.
68  * This is because the two pieces that are responsible for the visual display,
69  * the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
70  * 
71  * ### 3. Configuring the Axes
72  * 
73  * {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
74  * This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
75  * 
76  *     Ext.create('Ext.chart.Chart', {
77  *         ...
78  *         axes: [
79  *             {
80  *                 title: 'Temperature',
81  *                 type: 'Numeric',
82  *                 position: 'left',
83  *                 fields: ['temperature'],
84  *                 minimum: 0,
85  *                 maximum: 100
86  *             },
87  *             {
88  *                 title: 'Time',
89  *                 type: 'Time',
90  *                 position: 'bottom',
91  *                 fields: ['date'],
92  *                 dateFormat: 'ga'
93  *             }
94  *         ]
95  *     });
96  *    
97  * The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
98  * It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
99  * defined above. The minimum value for this axis is "0", and the maximum is "100".
100  * 
101  * The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
102  * It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
103  * The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
104  * configuration tells the Time Axis how to format it's labels.
105  * 
106  * Here's what the Chart looks like now that it has its Axes configured:
107  * 
108  * {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
109  * 
110  * ### 4. Configuring the Series
111  * 
112  * The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
113  * Series are responsible for the visual representation of the data points contained in the Store.
114  * This example only has one Series:
115  * 
116  *     Ext.create('Ext.chart.Chart', {
117  *         ...
118  *         axes: [
119  *             ...
120  *         ],
121  *         series: [
122  *             {
123  *                 type: 'line',
124  *                 xField: 'date',
125  *                 yField: 'temperature'
126  *             }
127  *         ]
128  *     });
129  *     
130  * This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
131  * from the "WeatherPoint" Models in the Store to plot its data points:
132  * 
133  * {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
134  * 
135  * See the [Simple Chart Example](doc-resources/Ext.chart.Chart/examples/simple_chart/index.html) for a live demo.
136  * 
137  * ## Themes
138  * 
139  * The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
140  * 
141  *     Ext.create('Ext.chart.Chart', {
142  *         ...
143  *         theme: 'Green',
144  *         ...
145  *     });
146  * 
147  * {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
148  * 
149  * For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
150  * 
151  */
152 Ext.define('Ext.chart.Chart', {
153
154     /* Begin Definitions */
155
156     alias: 'widget.chart',
157
158     extend: 'Ext.draw.Component',
159     
160     mixins: {
161         themeManager: 'Ext.chart.theme.Theme',
162         mask: 'Ext.chart.Mask',
163         navigation: 'Ext.chart.Navigation'
164     },
165
166     requires: [
167         'Ext.util.MixedCollection',
168         'Ext.data.StoreManager',
169         'Ext.chart.Legend',
170         'Ext.util.DelayedTask'
171     ],
172
173     /* End Definitions */
174
175     // @private
176     viewBox: false,
177
178     /**
179      * @cfg {String} theme
180      * The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
181      * on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
182      * 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
183      * is 'Base'.
184      */
185
186     /**
187      * @cfg {Boolean/Object} animate
188      * True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
189      * object to be used for default chart animations. Defaults to false.
190      */
191     animate: false,
192
193     /**
194      * @cfg {Boolean/Object} legend
195      * True for the default legend display or a legend config object. Defaults to false.
196      */
197     legend: false,
198
199     /**
200      * @cfg {Number} insetPadding
201      * The amount of inset padding in pixels for the chart. Defaults to 10.
202      */
203     insetPadding: 10,
204
205     /**
206      * @cfg {String[]} enginePriority
207      * Defines the priority order for which Surface implementation to use. The first one supported by the current
208      * environment will be used. Defaults to `['Svg', 'Vml']`.
209      */
210     enginePriority: ['Svg', 'Vml'],
211
212     /**
213      * @cfg {Object/Boolean} background
214      * The chart background. This can be a gradient object, image, or color. Defaults to false for no
215      * background. For example, if `background` were to be a color we could set the object as
216      *
217      *     background: {
218      *         //color string
219      *         fill: '#ccc'
220      *     }
221      *
222      * You can specify an image by using:
223      *
224      *     background: {
225      *         image: 'http://path.to.image/'
226      *     }
227      *
228      * Also you can specify a gradient by using the gradient object syntax:
229      *
230      *     background: {
231      *         gradient: {
232      *             id: 'gradientId',
233      *             angle: 45,
234      *             stops: {
235      *                 0: {
236      *                     color: '#555'
237      *                 }
238      *                 100: {
239      *                     color: '#ddd'
240      *                 }
241      *             }
242      *         }
243      *     }
244      */
245     background: false,
246
247     /**
248      * @cfg {Object[]} gradients
249      * Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
250      * array of objects with the following properties:
251      *
252      * - **id** - string - The unique name of the gradient.
253      * - **angle** - number, optional - The angle of the gradient in degrees.
254      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
255      *
256      * For example:
257      *
258      *     gradients: [{
259      *         id: 'gradientId',
260      *         angle: 45,
261      *         stops: {
262      *             0: {
263      *                 color: '#555'
264      *             },
265      *             100: {
266      *                 color: '#ddd'
267      *             }
268      *         }
269      *     }, {
270      *         id: 'gradientId2',
271      *         angle: 0,
272      *         stops: {
273      *             0: {
274      *                 color: '#590'
275      *             },
276      *             20: {
277      *                 color: '#599'
278      *             },
279      *             100: {
280      *                 color: '#ddd'
281      *             }
282      *         }
283      *     }]
284      *
285      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
286      *
287      *     sprite.setAttributes({
288      *         fill: 'url(#gradientId)'
289      *     }, true);
290      */
291
292     /**
293      * @cfg {Ext.data.Store} store
294      * The store that supplies data to this chart.
295      */
296
297     /**
298      * @cfg {Ext.chart.series.Series[]} series
299      * Array of {@link Ext.chart.series.Series Series} instances or config objects.  For example:
300      * 
301      *     series: [{
302      *         type: 'column',
303      *         axis: 'left',
304      *         listeners: {
305      *             'afterrender': function() {
306      *                 console('afterrender');
307      *             }
308      *         },
309      *         xField: 'category',
310      *         yField: 'data1'
311      *     }]
312      */
313
314     /**
315      * @cfg {Ext.chart.axis.Axis[]} axes
316      * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.  For example:
317      * 
318      *     axes: [{
319      *         type: 'Numeric',
320      *         position: 'left',
321      *         fields: ['data1'],
322      *         title: 'Number of Hits',
323      *         minimum: 0,
324      *         //one minor tick between two major ticks
325      *         minorTickSteps: 1
326      *     }, {
327      *         type: 'Category',
328      *         position: 'bottom',
329      *         fields: ['name'],
330      *         title: 'Month of the Year'
331      *     }]
332      */
333
334     constructor: function(config) {
335         var me = this,
336             defaultAnim;
337             
338         config = Ext.apply({}, config);
339         me.initTheme(config.theme || me.theme);
340         if (me.gradients) {
341             Ext.apply(config, { gradients: me.gradients });
342         }
343         if (me.background) {
344             Ext.apply(config, { background: me.background });
345         }
346         if (config.animate) {
347             defaultAnim = {
348                 easing: 'ease',
349                 duration: 500
350             };
351             if (Ext.isObject(config.animate)) {
352                 config.animate = Ext.applyIf(config.animate, defaultAnim);
353             }
354             else {
355                 config.animate = defaultAnim;
356             }
357         }
358         me.mixins.mask.constructor.call(me, config);
359         me.mixins.navigation.constructor.call(me, config);
360         me.callParent([config]);
361     },
362     
363     getChartStore: function(){
364         return this.substore || this.store;    
365     },
366
367     initComponent: function() {
368         var me = this,
369             axes,
370             series;
371         me.callParent();
372         me.addEvents(
373             'itemmousedown',
374             'itemmouseup',
375             'itemmouseover',
376             'itemmouseout',
377             'itemclick',
378             'itemdoubleclick',
379             'itemdragstart',
380             'itemdrag',
381             'itemdragend',
382             /**
383              * @event beforerefresh
384              * Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
385              * {@link #refresh} action will be cancelled.
386              * @param {Ext.chart.Chart} this
387              */
388             'beforerefresh',
389             /**
390              * @event refresh
391              * Fires after the chart data has been refreshed.
392              * @param {Ext.chart.Chart} this
393              */
394             'refresh'
395         );
396         Ext.applyIf(me, {
397             zoom: {
398                 width: 1,
399                 height: 1,
400                 x: 0,
401                 y: 0
402             }
403         });
404         me.maxGutter = [0, 0];
405         me.store = Ext.data.StoreManager.lookup(me.store);
406         axes = me.axes;
407         me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
408         if (axes) {
409             me.axes.addAll(axes);
410         }
411         series = me.series;
412         me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
413         if (series) {
414             me.series.addAll(series);
415         }
416         if (me.legend !== false) {
417             me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
418         }
419
420         me.on({
421             mousemove: me.onMouseMove,
422             mouseleave: me.onMouseLeave,
423             mousedown: me.onMouseDown,
424             mouseup: me.onMouseUp,
425             scope: me
426         });
427     },
428
429     // @private overrides the component method to set the correct dimensions to the chart.
430     afterComponentLayout: function(width, height) {
431         var me = this;
432         if (Ext.isNumber(width) && Ext.isNumber(height)) {
433             me.curWidth = width;
434             me.curHeight = height;
435             me.redraw(true);
436         }
437         this.callParent(arguments);
438     },
439
440     /**
441      * Redraws the chart. If animations are set this will animate the chart too. 
442      * @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
443      */
444     redraw: function(resize) {
445         var me = this,
446             chartBBox = me.chartBBox = {
447                 x: 0,
448                 y: 0,
449                 height: me.curHeight,
450                 width: me.curWidth
451             },
452             legend = me.legend;
453         me.surface.setSize(chartBBox.width, chartBBox.height);
454         // Instantiate Series and Axes
455         me.series.each(me.initializeSeries, me);
456         me.axes.each(me.initializeAxis, me);
457         //process all views (aggregated data etc) on stores
458         //before rendering.
459         me.axes.each(function(axis) {
460             axis.processView();
461         });
462         me.axes.each(function(axis) {
463             axis.drawAxis(true);
464         });
465
466         // Create legend if not already created
467         if (legend !== false) {
468             legend.create();
469         }
470
471         // Place axes properly, including influence from each other
472         me.alignAxes();
473
474         // Reposition legend based on new axis alignment
475         if (me.legend !== false) {
476             legend.updatePosition();
477         }
478
479         // Find the max gutter
480         me.getMaxGutter();
481
482         // Draw axes and series
483         me.resizing = !!resize;
484
485         me.axes.each(me.drawAxis, me);
486         me.series.each(me.drawCharts, me);
487         me.resizing = false;
488     },
489
490     // @private set the store after rendering the chart.
491     afterRender: function() {
492         var ref,
493             me = this;
494         this.callParent();
495
496         if (me.categoryNames) {
497             me.setCategoryNames(me.categoryNames);
498         }
499
500         if (me.tipRenderer) {
501             ref = me.getFunctionRef(me.tipRenderer);
502             me.setTipRenderer(ref.fn, ref.scope);
503         }
504         me.bindStore(me.store, true);
505         me.refresh();
506     },
507
508     // @private get x and y position of the mouse cursor.
509     getEventXY: function(e) {
510         var me = this,
511             box = this.surface.getRegion(),
512             pageXY = e.getXY(),
513             x = pageXY[0] - box.left,
514             y = pageXY[1] - box.top;
515         return [x, y];
516     },
517
518     // @private wrap the mouse down position to delegate the event to the series.
519     onClick: function(e) {
520         var me = this,
521             position = me.getEventXY(e),
522             item;
523
524         // Ask each series if it has an item corresponding to (not necessarily exactly
525         // on top of) the current mouse coords. Fire itemclick event.
526         me.series.each(function(series) {
527             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
528                 if (series.getItemForPoint) {
529                     item = series.getItemForPoint(position[0], position[1]);
530                     if (item) {
531                         series.fireEvent('itemclick', item);
532                     }
533                 }
534             }
535         }, me);
536     },
537
538     // @private wrap the mouse down position to delegate the event to the series.
539     onMouseDown: function(e) {
540         var me = this,
541             position = me.getEventXY(e),
542             item;
543
544         if (me.mask) {
545             me.mixins.mask.onMouseDown.call(me, e);
546         }
547         // Ask each series if it has an item corresponding to (not necessarily exactly
548         // on top of) the current mouse coords. Fire mousedown event.
549         me.series.each(function(series) {
550             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
551                 if (series.getItemForPoint) {
552                     item = series.getItemForPoint(position[0], position[1]);
553                     if (item) {
554                         series.fireEvent('itemmousedown', item);
555                     }
556                 }
557             }
558         }, me);
559     },
560
561     // @private wrap the mouse up event to delegate it to the series.
562     onMouseUp: function(e) {
563         var me = this,
564             position = me.getEventXY(e),
565             item;
566
567         if (me.mask) {
568             me.mixins.mask.onMouseUp.call(me, e);
569         }
570         // Ask each series if it has an item corresponding to (not necessarily exactly
571         // on top of) the current mouse coords. Fire mousedown event.
572         me.series.each(function(series) {
573             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
574                 if (series.getItemForPoint) {
575                     item = series.getItemForPoint(position[0], position[1]);
576                     if (item) {
577                         series.fireEvent('itemmouseup', item);
578                     }
579                 }
580             }
581         }, me);
582     },
583
584     // @private wrap the mouse move event so it can be delegated to the series.
585     onMouseMove: function(e) {
586         var me = this,
587             position = me.getEventXY(e),
588             item, last, storeItem, storeField;
589
590         if (me.mask) {
591             me.mixins.mask.onMouseMove.call(me, e);
592         }
593         // Ask each series if it has an item corresponding to (not necessarily exactly
594         // on top of) the current mouse coords. Fire itemmouseover/out events.
595         me.series.each(function(series) {
596             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
597                 if (series.getItemForPoint) {
598                     item = series.getItemForPoint(position[0], position[1]);
599                     last = series._lastItemForPoint;
600                     storeItem = series._lastStoreItem;
601                     storeField = series._lastStoreField;
602
603
604                     if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
605                         if (last) {
606                             series.fireEvent('itemmouseout', last);
607                             delete series._lastItemForPoint;
608                             delete series._lastStoreField;
609                             delete series._lastStoreItem;
610                         }
611                         if (item) {
612                             series.fireEvent('itemmouseover', item);
613                             series._lastItemForPoint = item;
614                             series._lastStoreItem = item.storeItem;
615                             series._lastStoreField = item.storeField;
616                         }
617                     }
618                 }
619             } else {
620                 last = series._lastItemForPoint;
621                 if (last) {
622                     series.fireEvent('itemmouseout', last);
623                     delete series._lastItemForPoint;
624                     delete series._lastStoreField;
625                     delete series._lastStoreItem;
626                 }
627             }
628         }, me);
629     },
630
631     // @private handle mouse leave event.
632     onMouseLeave: function(e) {
633         var me = this;
634         if (me.mask) {
635             me.mixins.mask.onMouseLeave.call(me, e);
636         }
637         me.series.each(function(series) {
638             delete series._lastItemForPoint;
639         });
640     },
641
642     // @private buffered refresh for when we update the store
643     delayRefresh: function() {
644         var me = this;
645         if (!me.refreshTask) {
646             me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
647         }
648         me.refreshTask.delay(me.refreshBuffer);
649     },
650
651     // @private
652     refresh: function() {
653         var me = this;
654         if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
655             if (me.fireEvent('beforerefresh', me) !== false) {
656                 me.redraw();
657                 me.fireEvent('refresh', me);
658             }
659         }
660     },
661
662     /**
663      * Changes the data store bound to this chart and refreshes it.
664      * @param {Ext.data.Store} store The store to bind to this chart
665      */
666     bindStore: function(store, initial) {
667         var me = this;
668         if (!initial && me.store) {
669             if (store !== me.store && me.store.autoDestroy) {
670                 me.store.destroyStore();
671             }
672             else {
673                 me.store.un('datachanged', me.refresh, me);
674                 me.store.un('add', me.delayRefresh, me);
675                 me.store.un('remove', me.delayRefresh, me);
676                 me.store.un('update', me.delayRefresh, me);
677                 me.store.un('clear', me.refresh, me);
678             }
679         }
680         if (store) {
681             store = Ext.data.StoreManager.lookup(store);
682             store.on({
683                 scope: me,
684                 datachanged: me.refresh,
685                 add: me.delayRefresh,
686                 remove: me.delayRefresh,
687                 update: me.delayRefresh,
688                 clear: me.refresh
689             });
690         }
691         me.store = store;
692         if (store && !initial) {
693             me.refresh();
694         }
695     },
696
697     // @private Create Axis
698     initializeAxis: function(axis) {
699         var me = this,
700             chartBBox = me.chartBBox,
701             w = chartBBox.width,
702             h = chartBBox.height,
703             x = chartBBox.x,
704             y = chartBBox.y,
705             themeAttrs = me.themeAttrs,
706             config = {
707                 chart: me
708             };
709         if (themeAttrs) {
710             config.axisStyle = Ext.apply({}, themeAttrs.axis);
711             config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
712             config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
713             config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
714             config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
715             config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
716             config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
717             config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
718             config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
719         }
720         switch (axis.position) {
721             case 'top':
722                 Ext.apply(config, {
723                     length: w,
724                     width: h,
725                     x: x,
726                     y: y
727                 });
728             break;
729             case 'bottom':
730                 Ext.apply(config, {
731                     length: w,
732                     width: h,
733                     x: x,
734                     y: h
735                 });
736             break;
737             case 'left':
738                 Ext.apply(config, {
739                     length: h,
740                     width: w,
741                     x: x,
742                     y: h
743                 });
744             break;
745             case 'right':
746                 Ext.apply(config, {
747                     length: h,
748                     width: w,
749                     x: w,
750                     y: h
751                 });
752             break;
753         }
754         if (!axis.chart) {
755             Ext.apply(config, axis);
756             axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
757         }
758         else {
759             Ext.apply(axis, config);
760         }
761     },
762
763
764     /**
765      * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
766      * for the space taken up on each side by the axes and legend.
767      */
768     alignAxes: function() {
769         var me = this,
770             axes = me.axes,
771             legend = me.legend,
772             edges = ['top', 'right', 'bottom', 'left'],
773             chartBBox,
774             insetPadding = me.insetPadding,
775             insets = {
776                 top: insetPadding,
777                 right: insetPadding,
778                 bottom: insetPadding,
779                 left: insetPadding
780             };
781
782         function getAxis(edge) {
783             var i = axes.findIndex('position', edge);
784             return (i < 0) ? null : axes.getAt(i);
785         }
786
787         // Find the space needed by axes and legend as a positive inset from each edge
788         Ext.each(edges, function(edge) {
789             var isVertical = (edge === 'left' || edge === 'right'),
790                 axis = getAxis(edge),
791                 bbox;
792
793             // Add legend size if it's on this edge
794             if (legend !== false) {
795                 if (legend.position === edge) {
796                     bbox = legend.getBBox();
797                     insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
798                 }
799             }
800
801             // Add axis size if there's one on this edge only if it has been
802             //drawn before.
803             if (axis && axis.bbox) {
804                 bbox = axis.bbox;
805                 insets[edge] += (isVertical ? bbox.width : bbox.height);
806             }
807         });
808         // Build the chart bbox based on the collected inset values
809         chartBBox = {
810             x: insets.left,
811             y: insets.top,
812             width: me.curWidth - insets.left - insets.right,
813             height: me.curHeight - insets.top - insets.bottom
814         };
815         me.chartBBox = chartBBox;
816
817         // Go back through each axis and set its length and position based on the
818         // corresponding edge of the chartBBox
819         axes.each(function(axis) {
820             var pos = axis.position,
821                 isVertical = (pos === 'left' || pos === 'right');
822
823             axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
824             axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
825             axis.width = (isVertical ? chartBBox.width : chartBBox.height);
826             axis.length = (isVertical ? chartBBox.height : chartBBox.width);
827         });
828     },
829
830     // @private initialize the series.
831     initializeSeries: function(series, idx) {
832         var me = this,
833             themeAttrs = me.themeAttrs,
834             seriesObj, markerObj, seriesThemes, st,
835             markerThemes, colorArrayStyle = [],
836             i = 0, l,
837             config = {
838                 chart: me,
839                 seriesId: series.seriesId
840             };
841         if (themeAttrs) {
842             seriesThemes = themeAttrs.seriesThemes;
843             markerThemes = themeAttrs.markerThemes;
844             seriesObj = Ext.apply({}, themeAttrs.series);
845             markerObj = Ext.apply({}, themeAttrs.marker);
846             config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
847             config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
848             config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
849             if (themeAttrs.colors) {
850                 config.colorArrayStyle = themeAttrs.colors;
851             } else {
852                 colorArrayStyle = [];
853                 for (l = seriesThemes.length; i < l; i++) {
854                     st = seriesThemes[i];
855                     if (st.fill || st.stroke) {
856                         colorArrayStyle.push(st.fill || st.stroke);
857                     }
858                 }
859                 if (colorArrayStyle.length) {
860                     config.colorArrayStyle = colorArrayStyle;
861                 }
862             }
863             config.seriesIdx = idx;
864         }
865         if (series instanceof Ext.chart.series.Series) {
866             Ext.apply(series, config);
867         } else {
868             Ext.applyIf(config, series);
869             series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
870         }
871         if (series.initialize) {
872             series.initialize();
873         }
874     },
875
876     // @private
877     getMaxGutter: function() {
878         var me = this,
879             maxGutter = [0, 0];
880         me.series.each(function(s) {
881             var gutter = s.getGutters && s.getGutters() || [0, 0];
882             maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
883             maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
884         });
885         me.maxGutter = maxGutter;
886     },
887
888     // @private draw axis.
889     drawAxis: function(axis) {
890         axis.drawAxis();
891     },
892
893     // @private draw series.
894     drawCharts: function(series) {
895         series.triggerafterrender = false;
896         series.drawSeries();
897         if (!this.animate) {
898             series.fireEvent('afterrender');
899         }
900     },
901
902     // @private remove gently.
903     destroy: function() {
904         Ext.destroy(this.surface);
905         this.bindStore(null);
906         this.callParent(arguments);
907     }
908 });
909