Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / chart / series / Series.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.series.Series
17  * 
18  * Series is the abstract class containing the common logic to all chart series. Series includes 
19  * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling 
20  * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
21  *
22  * ## Listeners
23  *
24  * The series class supports listeners via the Observable syntax. Some of these listeners are:
25  *
26  *  - `itemmouseup` When the user interacts with a marker.
27  *  - `itemmousedown` When the user interacts with a marker.
28  *  - `itemmousemove` When the user iteracts with a marker.
29  *  - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
30  *
31  * For example:
32  *
33  *     series: [{
34  *             type: 'column',
35  *             axis: 'left',
36  *             listeners: {
37  *                     'afterrender': function() {
38  *                             console('afterrender');
39  *                     }
40  *             },
41  *             xField: 'category',
42  *             yField: 'data1'
43  *     }]
44  *     
45  */
46 Ext.define('Ext.chart.series.Series', {
47
48     /* Begin Definitions */
49
50     mixins: {
51         observable: 'Ext.util.Observable',
52         labels: 'Ext.chart.Label',
53         highlights: 'Ext.chart.Highlight',
54         tips: 'Ext.chart.Tip',
55         callouts: 'Ext.chart.Callout'
56     },
57
58     /* End Definitions */
59
60     /**
61      * @cfg {Boolean|Object} highlight
62      * If set to `true` it will highlight the markers or the series when hovering
63      * with the mouse. This parameter can also be an object with the same style
64      * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
65      * styles to markers and series.
66      */
67
68     /**
69      * @cfg {Object} tips
70      * Add tooltips to the visualization's markers. The options for the tips are the
71      * same configuration used with {@link Ext.tip.ToolTip}. For example:
72      *
73      *     tips: {
74      *       trackMouse: true,
75      *       width: 140,
76      *       height: 28,
77      *       renderer: function(storeItem, item) {
78      *         this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
79      *       }
80      *     },
81      */
82
83     /**
84      * @cfg {String} type
85      * The type of series. Set in subclasses.
86      */
87     type: null,
88
89     /**
90      * @cfg {String} title
91      * The human-readable name of the series.
92      */
93     title: null,
94
95     /**
96      * @cfg {Boolean} showInLegend
97      * Whether to show this series in the legend.
98      */
99     showInLegend: true,
100
101     /**
102      * @cfg {Function} renderer
103      * A function that can be overridden to set custom styling properties to each rendered element.
104      * Passes in (sprite, record, attributes, index, store) to the function.
105      */
106     renderer: function(sprite, record, attributes, index, store) {
107         return attributes;
108     },
109
110     /**
111      * @cfg {Array} shadowAttributes
112      * An array with shadow attributes
113      */
114     shadowAttributes: null,
115     
116     //@private triggerdrawlistener flag
117     triggerAfterDraw: false,
118
119     /**
120      * @cfg {Object} listeners  
121      * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
122      *  
123      *  <ul>
124      *      <li>itemmouseover</li>
125      *      <li>itemmouseout</li>
126      *      <li>itemmousedown</li>
127      *      <li>itemmouseup</li>
128      *  </ul>
129      */
130     
131     constructor: function(config) {
132         var me = this;
133         if (config) {
134             Ext.apply(me, config);
135         }
136         
137         me.shadowGroups = [];
138         
139         me.mixins.labels.constructor.call(me, config);
140         me.mixins.highlights.constructor.call(me, config);
141         me.mixins.tips.constructor.call(me, config);
142         me.mixins.callouts.constructor.call(me, config);
143
144         me.addEvents({
145             scope: me,
146             itemmouseover: true,
147             itemmouseout: true,
148             itemmousedown: true,
149             itemmouseup: true,
150             mouseleave: true,
151             afterdraw: true,
152
153             /**
154              * @event titlechange
155              * Fires when the series title is changed via {@link #setTitle}.
156              * @param {String} title The new title value
157              * @param {Number} index The index in the collection of titles
158              */
159             titlechange: true
160         });
161
162         me.mixins.observable.constructor.call(me, config);
163
164         me.on({
165             scope: me,
166             itemmouseover: me.onItemMouseOver,
167             itemmouseout: me.onItemMouseOut,
168             mouseleave: me.onMouseLeave
169         });
170     },
171
172     // @private set the bbox and clipBox for the series
173     setBBox: function(noGutter) {
174         var me = this,
175             chart = me.chart,
176             chartBBox = chart.chartBBox,
177             gutterX = noGutter ? 0 : chart.maxGutter[0],
178             gutterY = noGutter ? 0 : chart.maxGutter[1],
179             clipBox, bbox;
180
181         clipBox = {
182             x: chartBBox.x,
183             y: chartBBox.y,
184             width: chartBBox.width,
185             height: chartBBox.height
186         };
187         me.clipBox = clipBox;
188
189         bbox = {
190             x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
191             y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
192             width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
193             height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
194         };
195         me.bbox = bbox;
196     },
197
198     // @private set the animation for the sprite
199     onAnimate: function(sprite, attr) {
200         var me = this;
201         sprite.stopAnimation();
202         if (me.triggerAfterDraw) {
203             return sprite.animate(Ext.applyIf(attr, me.chart.animate));
204         } else {
205             me.triggerAfterDraw = true;
206             return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
207                 listeners: {
208                     'afteranimate': function() {
209                         me.triggerAfterDraw = false;
210                         me.fireEvent('afterrender');
211                     }    
212                 }    
213             }));
214         }
215     },
216     
217     // @private return the gutter.
218     getGutters: function() {
219         return [0, 0];
220     },
221
222     // @private wrapper for the itemmouseover event.
223     onItemMouseOver: function(item) { 
224         var me = this;
225         if (item.series === me) {
226             if (me.highlight) {
227                 me.highlightItem(item);
228             }
229             if (me.tooltip) {
230                 me.showTip(item);
231             }
232         }
233     },
234
235     // @private wrapper for the itemmouseout event.
236     onItemMouseOut: function(item) {
237         var me = this;
238         if (item.series === me) {
239             me.unHighlightItem();
240             if (me.tooltip) {
241                 me.hideTip(item);
242             }
243         }
244     },
245
246     // @private wrapper for the mouseleave event.
247     onMouseLeave: function() {
248         var me = this;
249         me.unHighlightItem();
250         if (me.tooltip) {
251             me.hideTip();
252         }
253     },
254
255     /**
256      * For a given x/y point relative to the Surface, find a corresponding item from this
257      * series, if any.
258      * @param {Number} x
259      * @param {Number} y
260      * @return {Object} An object describing the item, or null if there is no matching item. The exact contents of
261      *                  this object will vary by series type, but should always contain at least the following:
262      *                  <ul>
263      *                    <li>{Ext.chart.series.Series} series - the Series object to which the item belongs</li>
264      *                    <li>{Object} value - the value(s) of the item's data point</li>
265      *                    <li>{Array} point - the x/y coordinates relative to the chart box of a single point
266      *                        for this data item, which can be used as e.g. a tooltip anchor point.</li>
267      *                    <li>{Ext.draw.Sprite} sprite - the item's rendering Sprite.
268      *                  </ul>
269      */
270     getItemForPoint: function(x, y) {
271         //if there are no items to query just return null.
272         if (!this.items || !this.items.length || this.seriesIsHidden) {
273             return null;
274         }
275         var me = this,
276             items = me.items,
277             bbox = me.bbox,
278             item, i, ln;
279         // Check bounds
280         if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
281             return null;
282         }
283         for (i = 0, ln = items.length; i < ln; i++) {
284             if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
285                 return items[i];
286             }
287         }
288         
289         return null;
290     },
291     
292     isItemInPoint: function(x, y, item, i) {
293         return false;
294     },
295
296     /**
297      * Hides all the elements in the series.
298      */
299     hideAll: function() {
300         var me = this,
301             items = me.items,
302             item, len, i, sprite;
303
304         me.seriesIsHidden = true;
305         me._prevShowMarkers = me.showMarkers;
306
307         me.showMarkers = false;
308         //hide all labels
309         me.hideLabels(0);
310         //hide all sprites
311         for (i = 0, len = items.length; i < len; i++) {
312             item = items[i];
313             sprite = item.sprite;
314             if (sprite) {
315                 sprite.setAttributes({
316                     hidden: true
317                 }, true);
318             }
319         }
320     },
321
322     /**
323      * Shows all the elements in the series.
324      */
325     showAll: function() {
326         var me = this,
327             prevAnimate = me.chart.animate;
328         me.chart.animate = false;
329         me.seriesIsHidden = false;
330         me.showMarkers = me._prevShowMarkers;
331         me.drawSeries();
332         me.chart.animate = prevAnimate;
333     },
334     
335     /**
336      * Returns a string with the color to be used for the series legend item. 
337      */
338     getLegendColor: function(index) {
339         var me = this, fill, stroke;
340         if (me.seriesStyle) {
341             fill = me.seriesStyle.fill;
342             stroke = me.seriesStyle.stroke;
343             if (fill && fill != 'none') {
344                 return fill;
345             }
346             return stroke;
347         }
348         return '#000';
349     },
350     
351     /**
352      * Checks whether the data field should be visible in the legend
353      * @private
354      * @param {Number} index The index of the current item
355      */
356     visibleInLegend: function(index){
357         var excludes = this.__excludes;
358         if (excludes) {
359             return !excludes[index];
360         }
361         return !this.seriesIsHidden;
362     },
363
364     /**
365      * Changes the value of the {@link #title} for the series.
366      * Arguments can take two forms:
367      * <ul>
368      * <li>A single String value: this will be used as the new single title for the series (applies
369      * to series with only one yField)</li>
370      * <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
371      * </ul>
372      * @param {Number} index
373      * @param {String} title
374      */
375     setTitle: function(index, title) {
376         var me = this,
377             oldTitle = me.title;
378
379         if (Ext.isString(index)) {
380             title = index;
381             index = 0;
382         }
383
384         if (Ext.isArray(oldTitle)) {
385             oldTitle[index] = title;
386         } else {
387             me.title = title;
388         }
389
390         me.fireEvent('titlechange', title, index);
391     }
392 });
393