Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / src / widgets / chart / Chart.js
1 /*!
2  * Ext JS Library 3.0.3
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**\r
8  * @class Ext.chart.Chart\r
9  * @extends Ext.FlashComponent\r
10  * The Ext.chart package provides the capability to visualize data with flash based charting.\r
11  * Each chart binds directly to an Ext.data.Store enabling automatic updates of the chart.\r
12  * @constructor\r
13  * @xtype chart\r
14  */\r
15  \r
16  Ext.chart.Chart = Ext.extend(Ext.FlashComponent, {\r
17     refreshBuffer: 100,\r
18 \r
19     /**\r
20      * @cfg {Object} chartStyle\r
21      * Sets styles for this chart. Contains a number of default values. Modifying this property will override\r
22      * the base styles on the chart.\r
23      */\r
24     chartStyle: {\r
25         padding: 10,\r
26         animationEnabled: true,\r
27         font: {\r
28             name: 'Tahoma',\r
29             color: 0x444444,\r
30             size: 11\r
31         },\r
32         dataTip: {\r
33             padding: 5,\r
34             border: {\r
35                 color: 0x99bbe8,\r
36                 size:1\r
37             },\r
38             background: {\r
39                 color: 0xDAE7F6,\r
40                 alpha: .9\r
41             },\r
42             font: {\r
43                 name: 'Tahoma',\r
44                 color: 0x15428B,\r
45                 size: 10,\r
46                 bold: true\r
47             }\r
48         }\r
49     },\r
50     \r
51     /**\r
52      * @cfg {String} url\r
53      * The url to load the chart from. This defaults to Ext.chart.Chart.CHART_URL, which should\r
54      * be modified to point to the local charts resource.\r
55      */\r
56     \r
57     /**\r
58      * @cfg {Object} extraStyle\r
59      * Contains extra styles that will be added or overwritten to the default chartStyle. Defaults to <tt>null</tt>.\r
60      */\r
61     extraStyle: null,\r
62     \r
63     /**\r
64      * @cfg {Boolean} disableCaching\r
65      * True to add a "cache buster" to the end of the chart url. Defaults to true for Opera and IE.\r
66      */\r
67     disableCaching: Ext.isIE || Ext.isOpera,\r
68     disableCacheParam: '_dc',\r
69 \r
70     initComponent : function(){\r
71         Ext.chart.Chart.superclass.initComponent.call(this);\r
72         if(!this.url){\r
73             this.url = Ext.chart.Chart.CHART_URL;\r
74         }\r
75         if(this.disableCaching){\r
76             this.url = Ext.urlAppend(this.url, String.format('{0}={1}', this.disableCacheParam, new Date().getTime()));\r
77         }\r
78         this.addEvents(\r
79             'itemmouseover',\r
80             'itemmouseout',\r
81             'itemclick',\r
82             'itemdoubleclick',\r
83             'itemdragstart',\r
84             'itemdrag',\r
85             'itemdragend'\r
86         );\r
87         this.store = Ext.StoreMgr.lookup(this.store);\r
88     },\r
89 \r
90     /**\r
91      * Sets a single style value on the Chart instance.\r
92      *\r
93      * @param name {String} Name of the Chart style value to change.\r
94      * @param value {Object} New value to pass to the Chart style.\r
95      */\r
96      setStyle: function(name, value){\r
97          this.swf.setStyle(name, Ext.encode(value));\r
98      },\r
99 \r
100     /**\r
101      * Resets all styles on the Chart instance.\r
102      *\r
103      * @param styles {Object} Initializer for all Chart styles.\r
104      */\r
105     setStyles: function(styles){\r
106         this.swf.setStyles(Ext.encode(styles));\r
107     },\r
108 \r
109     /**\r
110      * Sets the styles on all series in the Chart.\r
111      *\r
112      * @param styles {Array} Initializer for all Chart series styles.\r
113      */\r
114     setSeriesStyles: function(styles){\r
115         var s = [];\r
116         Ext.each(styles, function(style){\r
117             s.push(Ext.encode(style));\r
118         });\r
119         this.swf.setSeriesStyles(s);\r
120     },\r
121 \r
122     setCategoryNames : function(names){\r
123         this.swf.setCategoryNames(names);\r
124     },\r
125 \r
126     setTipRenderer : function(fn){\r
127         var chart = this;\r
128         this.tipFnName = this.createFnProxy(function(item, index, series){\r
129             var record = chart.store.getAt(index);\r
130             return fn(chart, record, index, series);\r
131         }, this.tipFnName);\r
132         this.swf.setDataTipFunction(this.tipFnName);\r
133     },\r
134 \r
135     setSeries : function(series){\r
136         this.series = series;\r
137         this.refresh();\r
138     },\r
139 \r
140     /**\r
141      * Changes the data store bound to this chart and refreshes it.\r
142      * @param {Store} store The store to bind to this chart\r
143      */\r
144     bindStore : function(store, initial){\r
145         if(!initial && this.store){\r
146             if(store !== this.store && this.store.autoDestroy){\r
147                 this.store.destroy();\r
148             }else{\r
149                 this.store.un("datachanged", this.refresh, this);\r
150                 this.store.un("add", this.delayRefresh, this);\r
151                 this.store.un("remove", this.delayRefresh, this);\r
152                 this.store.un("update", this.delayRefresh, this);\r
153                 this.store.un("clear", this.refresh, this);\r
154             }\r
155         }\r
156         if(store){\r
157             store = Ext.StoreMgr.lookup(store);\r
158             store.on({\r
159                 scope: this,\r
160                 datachanged: this.refresh,\r
161                 add: this.delayRefresh,\r
162                 remove: this.delayRefresh,\r
163                 update: this.delayRefresh,\r
164                 clear: this.refresh\r
165             });\r
166         }\r
167         this.store = store;\r
168         if(store && !initial){\r
169             this.refresh();\r
170         }\r
171     },\r
172 \r
173     onSwfReady : function(isReset){\r
174         Ext.chart.Chart.superclass.onSwfReady.call(this, isReset);\r
175         this.swf.setType(this.type);\r
176 \r
177         if(this.chartStyle){\r
178             this.setStyles(Ext.apply({}, this.extraStyle, this.chartStyle));\r
179         }\r
180 \r
181         if(this.categoryNames){\r
182             this.setCategoryNames(this.categoryNames);\r
183         }\r
184 \r
185         if(this.tipRenderer){\r
186             this.setTipRenderer(this.tipRenderer);\r
187         }\r
188         if(!isReset){\r
189             this.bindStore(this.store, true);\r
190         }\r
191         this.refresh.defer(10, this);\r
192     },\r
193 \r
194     delayRefresh : function(){\r
195         if(!this.refreshTask){\r
196             this.refreshTask = new Ext.util.DelayedTask(this.refresh, this);\r
197         }\r
198         this.refreshTask.delay(this.refreshBuffer);\r
199     },\r
200 \r
201     refresh : function(){\r
202         var styleChanged = false;\r
203         // convert the store data into something YUI charts can understand\r
204         var data = [], rs = this.store.data.items;\r
205         for(var j = 0, len = rs.length; j < len; j++){\r
206             data[j] = rs[j].data;\r
207         }\r
208         //make a copy of the series definitions so that we aren't\r
209         //editing them directly.\r
210         var dataProvider = [];\r
211         var seriesCount = 0;\r
212         var currentSeries = null;\r
213         var i = 0;\r
214         if(this.series){\r
215             seriesCount = this.series.length;\r
216             for(i = 0; i < seriesCount; i++){\r
217                 currentSeries = this.series[i];\r
218                 var clonedSeries = {};\r
219                 for(var prop in currentSeries){\r
220                     if(prop == "style" && currentSeries.style !== null){\r
221                         clonedSeries.style = Ext.encode(currentSeries.style);\r
222                         styleChanged = true;\r
223                         //we don't want to modify the styles again next time\r
224                         //so null out the style property.\r
225                         // this causes issues\r
226                         // currentSeries.style = null;\r
227                     } else{\r
228                         clonedSeries[prop] = currentSeries[prop];\r
229                     }\r
230                 }\r
231                 dataProvider.push(clonedSeries);\r
232             }\r
233         }\r
234 \r
235         if(seriesCount > 0){\r
236             for(i = 0; i < seriesCount; i++){\r
237                 currentSeries = dataProvider[i];\r
238                 if(!currentSeries.type){\r
239                     currentSeries.type = this.type;\r
240                 }\r
241                 currentSeries.dataProvider = data;\r
242             }\r
243         } else{\r
244             dataProvider.push({type: this.type, dataProvider: data});\r
245         }\r
246         this.swf.setDataProvider(dataProvider);\r
247     },\r
248 \r
249     createFnProxy : function(fn, old){\r
250         if(old){\r
251             delete window[old];\r
252         }\r
253         var fnName = "extFnProxy" + (++Ext.chart.Chart.PROXY_FN_ID);\r
254         window[fnName] = fn;\r
255         return fnName;\r
256     },\r
257     \r
258     onDestroy: function(){\r
259         Ext.chart.Chart.superclass.onDestroy.call(this);\r
260         this.bindStore(null);\r
261         var tip = this.tipFnName;\r
262         if(!Ext.isEmpty(tip)){\r
263             delete window[tip];\r
264         }\r
265     }\r
266 });\r
267 Ext.reg('chart', Ext.chart.Chart);\r
268 Ext.chart.Chart.PROXY_FN_ID = 0;\r
269 \r
270 /**\r
271  * Sets the url to load the chart from. This should be set to a local resource.\r
272  * @static\r
273  * @type String\r
274  */\r
275 Ext.chart.Chart.CHART_URL = 'http:/' + '/yui.yahooapis.com/2.7.0/build/charts/assets/charts.swf';\r
276 \r
277 /**\r
278  * @class Ext.chart.PieChart\r
279  * @extends Ext.chart.Chart\r
280  * @constructor\r
281  * @xtype piechart\r
282  */\r
283 Ext.chart.PieChart = Ext.extend(Ext.chart.Chart, {\r
284     type: 'pie',\r
285 \r
286     onSwfReady : function(isReset){\r
287         Ext.chart.PieChart.superclass.onSwfReady.call(this, isReset);\r
288 \r
289         this.setDataField(this.dataField);\r
290         this.setCategoryField(this.categoryField);\r
291     },\r
292 \r
293     setDataField : function(field){\r
294         this.dataField = field;\r
295         this.swf.setDataField(field);\r
296     },\r
297 \r
298     setCategoryField : function(field){\r
299         this.categoryField = field;\r
300         this.swf.setCategoryField(field);\r
301     }\r
302 });\r
303 Ext.reg('piechart', Ext.chart.PieChart);\r
304 \r
305 /**\r
306  * @class Ext.chart.CartesianChart\r
307  * @extends Ext.chart.Chart\r
308  * @constructor\r
309  * @xtype cartesianchart\r
310  */\r
311 Ext.chart.CartesianChart = Ext.extend(Ext.chart.Chart, {\r
312     onSwfReady : function(isReset){\r
313         Ext.chart.CartesianChart.superclass.onSwfReady.call(this, isReset);\r
314 \r
315         if(this.xField){\r
316             this.setXField(this.xField);\r
317         }\r
318         if(this.yField){\r
319             this.setYField(this.yField);\r
320         }\r
321         if(this.xAxis){\r
322             this.setXAxis(this.xAxis);\r
323         }\r
324         if(this.yAxis){\r
325             this.setYAxis(this.yAxis);\r
326         }\r
327     },\r
328 \r
329     setXField : function(value){\r
330         this.xField = value;\r
331         this.swf.setHorizontalField(value);\r
332     },\r
333 \r
334     setYField : function(value){\r
335         this.yField = value;\r
336         this.swf.setVerticalField(value);\r
337     },\r
338 \r
339     setXAxis : function(value){\r
340         this.xAxis = this.createAxis('xAxis', value);\r
341         this.swf.setHorizontalAxis(this.xAxis);\r
342     },\r
343 \r
344     setYAxis : function(value){\r
345         this.yAxis = this.createAxis('yAxis', value);\r
346         this.swf.setVerticalAxis(this.yAxis);\r
347     },\r
348 \r
349     createAxis : function(axis, value){\r
350         var o = Ext.apply({}, value), oldFn = null;\r
351         if(this[axis]){\r
352             oldFn = this[axis].labelFunction;\r
353         }\r
354         if(o.labelRenderer){\r
355             var fn = o.labelRenderer;\r
356             o.labelFunction = this.createFnProxy(function(v){\r
357                 return fn(v);\r
358             }, oldFn);\r
359             delete o.labelRenderer;\r
360         }\r
361         return o;\r
362     }\r
363 });\r
364 Ext.reg('cartesianchart', Ext.chart.CartesianChart);\r
365 \r
366 /**\r
367  * @class Ext.chart.LineChart\r
368  * @extends Ext.chart.CartesianChart\r
369  * @constructor\r
370  * @xtype linechart\r
371  */\r
372 Ext.chart.LineChart = Ext.extend(Ext.chart.CartesianChart, {\r
373     type: 'line'\r
374 });\r
375 Ext.reg('linechart', Ext.chart.LineChart);\r
376 \r
377 /**\r
378  * @class Ext.chart.ColumnChart\r
379  * @extends Ext.chart.CartesianChart\r
380  * @constructor\r
381  * @xtype columnchart\r
382  */\r
383 Ext.chart.ColumnChart = Ext.extend(Ext.chart.CartesianChart, {\r
384     type: 'column'\r
385 });\r
386 Ext.reg('columnchart', Ext.chart.ColumnChart);\r
387 \r
388 /**\r
389  * @class Ext.chart.StackedColumnChart\r
390  * @extends Ext.chart.CartesianChart\r
391  * @constructor\r
392  * @xtype stackedcolumnchart\r
393  */\r
394 Ext.chart.StackedColumnChart = Ext.extend(Ext.chart.CartesianChart, {\r
395     type: 'stackcolumn'\r
396 });\r
397 Ext.reg('stackedcolumnchart', Ext.chart.StackedColumnChart);\r
398 \r
399 /**\r
400  * @class Ext.chart.BarChart\r
401  * @extends Ext.chart.CartesianChart\r
402  * @constructor\r
403  * @xtype barchart\r
404  */\r
405 Ext.chart.BarChart = Ext.extend(Ext.chart.CartesianChart, {\r
406     type: 'bar'\r
407 });\r
408 Ext.reg('barchart', Ext.chart.BarChart);\r
409 \r
410 /**\r
411  * @class Ext.chart.StackedBarChart\r
412  * @extends Ext.chart.CartesianChart\r
413  * @constructor\r
414  * @xtype stackedbarchart\r
415  */\r
416 Ext.chart.StackedBarChart = Ext.extend(Ext.chart.CartesianChart, {\r
417     type: 'stackbar'\r
418 });\r
419 Ext.reg('stackedbarchart', Ext.chart.StackedBarChart);\r
420 \r
421 \r
422 \r
423 /**\r
424  * @class Ext.chart.Axis\r
425  * Defines a CartesianChart's vertical or horizontal axis.\r
426  * @constructor\r
427  */\r
428 Ext.chart.Axis = function(config){\r
429     Ext.apply(this, config);\r
430 };\r
431 \r
432 Ext.chart.Axis.prototype =\r
433 {\r
434     /**\r
435      * The type of axis.\r
436      *\r
437      * @property type\r
438      * @type String\r
439      */\r
440     type: null,\r
441 \r
442     /**\r
443      * The direction in which the axis is drawn. May be "horizontal" or "vertical".\r
444      *\r
445      * @property orientation\r
446      * @type String\r
447      */\r
448     orientation: "horizontal",\r
449 \r
450     /**\r
451      * If true, the items on the axis will be drawn in opposite direction.\r
452      *\r
453      * @property reverse\r
454      * @type Boolean\r
455      */\r
456     reverse: false,\r
457 \r
458     /**\r
459      * A string reference to the globally-accessible function that may be called to\r
460      * determine each of the label values for this axis.\r
461      *\r
462      * @property labelFunction\r
463      * @type String\r
464      */\r
465     labelFunction: null,\r
466 \r
467     /**\r
468      * If true, labels that overlap previously drawn labels on the axis will be hidden.\r
469      *\r
470      * @property hideOverlappingLabels\r
471      * @type Boolean\r
472      */\r
473     hideOverlappingLabels: true\r
474 };\r
475 \r
476 /**\r
477  * @class Ext.chart.NumericAxis\r
478  * @extends Ext.chart.Axis\r
479  * A type of axis whose units are measured in numeric values.\r
480  * @constructor\r
481  */\r
482 Ext.chart.NumericAxis = Ext.extend(Ext.chart.Axis, {\r
483     type: "numeric",\r
484 \r
485     /**\r
486      * The minimum value drawn by the axis. If not set explicitly, the axis minimum\r
487      * will be calculated automatically.\r
488      *\r
489      * @property minimum\r
490      * @type Number\r
491      */\r
492     minimum: NaN,\r
493 \r
494     /**\r
495      * The maximum value drawn by the axis. If not set explicitly, the axis maximum\r
496      * will be calculated automatically.\r
497      *\r
498      * @property maximum\r
499      * @type Number\r
500      */\r
501     maximum: NaN,\r
502 \r
503     /**\r
504      * The spacing between major intervals on this axis.\r
505      *\r
506      * @property majorUnit\r
507      * @type Number\r
508      */\r
509     majorUnit: NaN,\r
510 \r
511     /**\r
512      * The spacing between minor intervals on this axis.\r
513      *\r
514      * @property minorUnit\r
515      * @type Number\r
516      */\r
517     minorUnit: NaN,\r
518 \r
519     /**\r
520      * If true, the labels, ticks, gridlines, and other objects will snap to\r
521      * the nearest major or minor unit. If false, their position will be based\r
522      * on the minimum value.\r
523      *\r
524      * @property snapToUnits\r
525      * @type Boolean\r
526      */\r
527     snapToUnits: true,\r
528 \r
529     /**\r
530      * If true, and the bounds are calculated automatically, either the minimum or\r
531      * maximum will be set to zero.\r
532      *\r
533      * @property alwaysShowZero\r
534      * @type Boolean\r
535      */\r
536     alwaysShowZero: true,\r
537 \r
538     /**\r
539      * The scaling algorithm to use on this axis. May be "linear" or "logarithmic".\r
540      *\r
541      * @property scale\r
542      * @type String\r
543      */\r
544     scale: "linear"\r
545 });\r
546 \r
547 /**\r
548  * @class Ext.chart.TimeAxis\r
549  * @extends Ext.chart.Axis\r
550  * A type of axis whose units are measured in time-based values.\r
551  * @constructor\r
552  */\r
553 Ext.chart.TimeAxis = Ext.extend(Ext.chart.Axis, {\r
554     type: "time",\r
555 \r
556     /**\r
557      * The minimum value drawn by the axis. If not set explicitly, the axis minimum\r
558      * will be calculated automatically.\r
559      *\r
560      * @property minimum\r
561      * @type Date\r
562      */\r
563     minimum: null,\r
564 \r
565     /**\r
566      * The maximum value drawn by the axis. If not set explicitly, the axis maximum\r
567      * will be calculated automatically.\r
568      *\r
569      * @property maximum\r
570      * @type Number\r
571      */\r
572     maximum: null,\r
573 \r
574     /**\r
575      * The spacing between major intervals on this axis.\r
576      *\r
577      * @property majorUnit\r
578      * @type Number\r
579      */\r
580     majorUnit: NaN,\r
581 \r
582     /**\r
583      * The time unit used by the majorUnit.\r
584      *\r
585      * @property majorTimeUnit\r
586      * @type String\r
587      */\r
588     majorTimeUnit: null,\r
589 \r
590     /**\r
591      * The spacing between minor intervals on this axis.\r
592      *\r
593      * @property majorUnit\r
594      * @type Number\r
595      */\r
596     minorUnit: NaN,\r
597 \r
598     /**\r
599      * The time unit used by the minorUnit.\r
600      *\r
601      * @property majorTimeUnit\r
602      * @type String\r
603      */\r
604     minorTimeUnit: null,\r
605 \r
606     /**\r
607      * If true, the labels, ticks, gridlines, and other objects will snap to\r
608      * the nearest major or minor unit. If false, their position will be based\r
609      * on the minimum value.\r
610      *\r
611      * @property snapToUnits\r
612      * @type Boolean\r
613      */\r
614     snapToUnits: true\r
615 });\r
616 \r
617 /**\r
618  * @class Ext.chart.CategoryAxis\r
619  * @extends Ext.chart.Axis\r
620  * A type of axis that displays items in categories.\r
621  * @constructor\r
622  */\r
623 Ext.chart.CategoryAxis = Ext.extend(Ext.chart.Axis, {\r
624     type: "category",\r
625 \r
626     /**\r
627      * A list of category names to display along this axis.\r
628      *\r
629      * @property categoryNames\r
630      * @type Array\r
631      */\r
632     categoryNames: null\r
633 });\r
634 \r
635 /**\r
636  * @class Ext.chart.Series\r
637  * Series class for the charts widget.\r
638  * @constructor\r
639  */\r
640 Ext.chart.Series = function(config) { Ext.apply(this, config); };\r
641 \r
642 Ext.chart.Series.prototype =\r
643 {\r
644     /**\r
645      * The type of series.\r
646      *\r
647      * @property type\r
648      * @type String\r
649      */\r
650     type: null,\r
651 \r
652     /**\r
653      * The human-readable name of the series.\r
654      *\r
655      * @property displayName\r
656      * @type String\r
657      */\r
658     displayName: null\r
659 };\r
660 \r
661 /**\r
662  * @class Ext.chart.CartesianSeries\r
663  * @extends Ext.chart.Series\r
664  * CartesianSeries class for the charts widget.\r
665  * @constructor\r
666  */\r
667 Ext.chart.CartesianSeries = Ext.extend(Ext.chart.Series, {\r
668     /**\r
669      * The field used to access the x-axis value from the items from the data source.\r
670      *\r
671      * @property xField\r
672      * @type String\r
673      */\r
674     xField: null,\r
675 \r
676     /**\r
677      * The field used to access the y-axis value from the items from the data source.\r
678      *\r
679      * @property yField\r
680      * @type String\r
681      */\r
682     yField: null\r
683 });\r
684 \r
685 /**\r
686  * @class Ext.chart.ColumnSeries\r
687  * @extends Ext.chart.CartesianSeries\r
688  * ColumnSeries class for the charts widget.\r
689  * @constructor\r
690  */\r
691 Ext.chart.ColumnSeries = Ext.extend(Ext.chart.CartesianSeries, {\r
692     type: "column"\r
693 });\r
694 \r
695 /**\r
696  * @class Ext.chart.LineSeries\r
697  * @extends Ext.chart.CartesianSeries\r
698  * LineSeries class for the charts widget.\r
699  * @constructor\r
700  */\r
701 Ext.chart.LineSeries = Ext.extend(Ext.chart.CartesianSeries, {\r
702     type: "line"\r
703 });\r
704 \r
705 /**\r
706  * @class Ext.chart.BarSeries\r
707  * @extends Ext.chart.CartesianSeries\r
708  * BarSeries class for the charts widget.\r
709  * @constructor\r
710  */\r
711 Ext.chart.BarSeries = Ext.extend(Ext.chart.CartesianSeries, {\r
712     type: "bar"\r
713 });\r
714 \r
715 \r
716 /**\r
717  * @class Ext.chart.PieSeries\r
718  * @extends Ext.chart.Series\r
719  * PieSeries class for the charts widget.\r
720  * @constructor\r
721  */\r
722 Ext.chart.PieSeries = Ext.extend(Ext.chart.Series, {\r
723     type: "pie",\r
724     dataField: null,\r
725     categoryField: null\r
726 });