Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / docs / source / Pie.html
1 <!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-chart.series.Pie'>/**
2 </span> * @class Ext.chart.series.Pie
3  * @extends Ext.chart.series.Series
4  * 
5  * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different 
6  * categories that also have a meaning as a whole.
7  * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart 
8  * documentation for more information. A typical configuration object for the pie series could be:
9  * 
10  * {@img Ext.chart.series.Pie/Ext.chart.series.Pie.png Ext.chart.series.Pie chart series}
11  *
12  *     var store = Ext.create('Ext.data.JsonStore', {
13  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
14  *         data: [
15  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
16  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
17  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
18  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
19  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
20  *         ]
21  *     });
22  *     
23  *     Ext.create('Ext.chart.Chart', {
24  *         renderTo: Ext.getBody(),
25  *         width: 500,
26  *         height: 300,
27  *         animate: true,
28  *         store: store,
29  *         theme: 'Base:gradients',
30  *         series: [{
31  *             type: 'pie',
32  *             field: 'data1',
33  *             showInLegend: true,
34  *             tips: {
35  *               trackMouse: true,
36  *               width: 140,
37  *               height: 28,
38  *               renderer: function(storeItem, item) {
39  *                 //calculate and display percentage on hover
40  *                 var total = 0;
41  *                 store.each(function(rec) {
42  *                     total += rec.get('data1');
43  *                 });
44  *                 this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
45  *               }
46  *             },
47  *             highlight: {
48  *               segment: {
49  *                 margin: 20
50  *               }
51  *             },
52  *             label: {
53  *                 field: 'name',
54  *                 display: 'rotate',
55  *                 contrast: true,
56  *                 font: '18px Arial'
57  *             }
58  *         }]    
59  *     });
60  * 
61  * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options 
62  * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item. 
63  * We set `data1` as the value of the field to determine the angle span for each pie slice. We also set a label configuration object 
64  * where we set the field name of the store field to be renderer as text for the label. The labels will also be displayed rotated. 
65  * We set `contrast` to `true` to flip the color of the label if it is to similar to the background color. Finally, we set the font family 
66  * and size through the `font` parameter. 
67  * 
68  * @xtype pie
69  */
70 Ext.define('Ext.chart.series.Pie', {
71
72     /* Begin Definitions */
73
74     alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
75
76     extend: 'Ext.chart.series.Series',
77
78     /* End Definitions */
79
80     type: &quot;pie&quot;,
81     
82     alias: 'series.pie',
83
84     rad: Math.PI / 180,
85
86 <span id='Ext-chart.series.Pie-cfg-highlightDuration'>    /**
87 </span>     * @cfg {Number} highlightDuration
88      * The duration for the pie slice highlight effect.
89      */
90     highlightDuration: 150,
91
92 <span id='Ext-chart.series.Pie-cfg-angleField'>    /**
93 </span>     * @cfg {String} angleField
94      * The store record field name to be used for the pie angles.
95      * The values bound to this field name must be positive real numbers.
96      * This parameter is required.
97      */
98     angleField: false,
99
100 <span id='Ext-chart.series.Pie-cfg-lengthField'>    /**
101 </span>     * @cfg {String} lengthField
102      * The store record field name to be used for the pie slice lengths.
103      * The values bound to this field name must be positive real numbers.
104      * This parameter is optional.
105      */
106     lengthField: false,
107
108 <span id='Ext-chart.series.Pie-cfg-donut'>    /**
109 </span>     * @cfg {Boolean|Number} donut
110      * Whether to set the pie chart as donut chart.
111      * Default's false. Can be set to a particular percentage to set the radius
112      * of the donut chart.
113      */
114     donut: false,
115
116 <span id='Ext-chart.series.Pie-cfg-showInLegend'>    /**
117 </span>     * @cfg {Boolean} showInLegend
118      * Whether to add the pie chart elements as legend items. Default's false.
119      */
120     showInLegend: false,
121
122 <span id='Ext-chart.series.Pie-cfg-colorSet'>    /**
123 </span>     * @cfg {Array} colorSet
124      * An array of color values which will be used, in order, as the pie slice fill colors.
125      */
126     
127 <span id='Ext-chart.series.Pie-cfg-style'>    /**
128 </span>     * @cfg {Object} style
129      * An object containing styles for overriding series styles from Theming.
130      */
131     style: {},
132     
133     constructor: function(config) {
134         this.callParent(arguments);
135         var me = this,
136             chart = me.chart,
137             surface = chart.surface,
138             store = chart.store,
139             shadow = chart.shadow, i, l, cfg;
140         Ext.applyIf(me, {
141             highlightCfg: {
142                 segment: {
143                     margin: 20
144                 }
145             }
146         });
147         Ext.apply(me, config, {            
148             shadowAttributes: [{
149                 &quot;stroke-width&quot;: 6,
150                 &quot;stroke-opacity&quot;: 1,
151                 stroke: 'rgb(200, 200, 200)',
152                 translate: {
153                     x: 1.2,
154                     y: 2
155                 }
156             },
157             {
158                 &quot;stroke-width&quot;: 4,
159                 &quot;stroke-opacity&quot;: 1,
160                 stroke: 'rgb(150, 150, 150)',
161                 translate: {
162                     x: 0.9,
163                     y: 1.5
164                 }
165             },
166             {
167                 &quot;stroke-width&quot;: 2,
168                 &quot;stroke-opacity&quot;: 1,
169                 stroke: 'rgb(100, 100, 100)',
170                 translate: {
171                     x: 0.6,
172                     y: 1
173                 }
174             }]
175         });
176         me.group = surface.getGroup(me.seriesId);
177         if (shadow) {
178             for (i = 0, l = me.shadowAttributes.length; i &lt; l; i++) {
179                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
180             }
181         }
182         surface.customAttributes.segment = function(opt) {
183             return me.getSegment(opt);
184         };
185     },
186     
187     //@private updates some onbefore render parameters.
188     initialize: function() {
189         var me = this,
190             store = me.chart.substore || me.chart.store;
191         //Add yFields to be used in Legend.js
192         me.yField = [];
193         if (me.label.field) {
194             store.each(function(rec) {
195                 me.yField.push(rec.get(me.label.field));
196             });
197         }
198     },
199
200     // @private returns an object with properties for a PieSlice.
201     getSegment: function(opt) {
202         var me = this,
203             rad = me.rad,
204             cos = Math.cos,
205             sin = Math.sin,
206             abs = Math.abs,
207             x = me.centerX,
208             y = me.centerY,
209             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
210             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
211             delta = 1e-2,
212             r = opt.endRho - opt.startRho,
213             startAngle = opt.startAngle,
214             endAngle = opt.endAngle,
215             midAngle = (startAngle + endAngle) / 2 * rad,
216             margin = opt.margin || 0,
217             flag = abs(endAngle - startAngle) &gt; 180,
218             a1 = Math.min(startAngle, endAngle) * rad,
219             a2 = Math.max(startAngle, endAngle) * rad,
220             singleSlice = false;
221
222         x += margin * cos(midAngle);
223         y += margin * sin(midAngle);
224
225         x1 = x + opt.startRho * cos(a1);
226         y1 = y + opt.startRho * sin(a1);
227
228         x2 = x + opt.endRho * cos(a1);
229         y2 = y + opt.endRho * sin(a1);
230
231         x3 = x + opt.startRho * cos(a2);
232         y3 = y + opt.startRho * sin(a2);
233
234         x4 = x + opt.endRho * cos(a2);
235         y4 = y + opt.endRho * sin(a2);
236
237         if (abs(x1 - x3) &lt;= delta &amp;&amp; abs(y1 - y3) &lt;= delta) {
238             singleSlice = true;
239         }
240         //Solves mysterious clipping bug with IE
241         if (singleSlice) {
242             return {
243                 path: [
244                 [&quot;M&quot;, x1, y1],
245                 [&quot;L&quot;, x2, y2],
246                 [&quot;A&quot;, opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
247                 [&quot;Z&quot;]]
248             };
249         } else {
250             return {
251                 path: [
252                 [&quot;M&quot;, x1, y1],
253                 [&quot;L&quot;, x2, y2],
254                 [&quot;A&quot;, opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
255                 [&quot;L&quot;, x3, y3],
256                 [&quot;A&quot;, opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
257                 [&quot;Z&quot;]]
258             };
259         }
260     },
261
262     // @private utility function to calculate the middle point of a pie slice.
263     calcMiddle: function(item) {
264         var me = this,
265             rad = me.rad,
266             slice = item.slice,
267             x = me.centerX,
268             y = me.centerY,
269             startAngle = slice.startAngle,
270             endAngle = slice.endAngle,
271             donut = +me.donut,
272             a1 = Math.min(startAngle, endAngle) * rad,
273             a2 = Math.max(startAngle, endAngle) * rad,
274             midAngle = -(a1 + (a2 - a1) / 2),
275             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
276             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
277
278         item.middle = {
279             x: xm,
280             y: ym
281         };
282     },
283
284 <span id='Ext-chart.series.Pie-method-drawSeries'>    /**
285 </span>     * Draws the series for the current chart.
286      */
287     drawSeries: function() {
288         var me = this,
289             store = me.chart.substore || me.chart.store,
290             group = me.group,
291             animate = me.chart.animate,
292             field = me.angleField || me.field || me.xField,
293             lenField = [].concat(me.lengthField),
294             totalLenField = 0,
295             colors = me.colorSet,
296             chart = me.chart,
297             surface = chart.surface,
298             chartBBox = chart.chartBBox,
299             enableShadows = chart.shadow,
300             shadowGroups = me.shadowGroups,
301             shadowAttributes = me.shadowAttributes,
302             lnsh = shadowGroups.length,
303             rad = me.rad,
304             layers = lenField.length,
305             rhoAcum = 0,
306             donut = +me.donut,
307             layerTotals = [],
308             values = {},
309             fieldLength,
310             items = [],
311             passed = false,
312             totalField = 0,
313             maxLenField = 0,
314             cut = 9,
315             defcut = true,
316             angle = 0,
317             seriesStyle = me.seriesStyle,
318             seriesLabelStyle = me.seriesLabelStyle,
319             colorArrayStyle = me.colorArrayStyle,
320             colorArrayLength = colorArrayStyle &amp;&amp; colorArrayStyle.length || 0,
321             gutterX = chart.maxGutter[0],
322             gutterY = chart.maxGutter[1],
323             rendererAttributes,
324             shadowGroup,
325             shadowAttr,
326             shadows,
327             shadow,
328             shindex,
329             centerX,
330             centerY,
331             deltaRho,
332             first = 0,
333             slice,
334             slices,
335             sprite,
336             value,
337             item,
338             lenValue,
339             ln,
340             record,
341             i,
342             j,
343             startAngle,
344             endAngle,
345             middleAngle,
346             sliceLength,
347             path,
348             p,
349             spriteOptions, bbox;
350         
351         Ext.apply(seriesStyle, me.style || {});
352
353         me.setBBox();
354         bbox = me.bbox;
355
356         //override theme colors
357         if (me.colorSet) {
358             colorArrayStyle = me.colorSet;
359             colorArrayLength = colorArrayStyle.length;
360         }
361         
362         //if not store or store is empty then there's nothing to draw
363         if (!store || !store.getCount()) {
364             return;
365         }
366         
367         me.unHighlightItem();
368         me.cleanHighlights();
369
370         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
371         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
372         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
373         me.slices = slices = [];
374         me.items = items = [];
375
376         store.each(function(record, i) {
377             if (this.__excludes &amp;&amp; this.__excludes[i]) {
378                 //hidden series
379                 return;
380             }
381             totalField += +record.get(field);
382             if (lenField[0]) {
383                 for (j = 0, totalLenField = 0; j &lt; layers; j++) {
384                     totalLenField += +record.get(lenField[j]);
385                 }
386                 layerTotals[i] = totalLenField;
387                 maxLenField = Math.max(maxLenField, totalLenField);
388             }
389         }, this);
390
391         store.each(function(record, i) {
392             if (this.__excludes &amp;&amp; this.__excludes[i]) {
393                 //hidden series
394                 return;
395             } 
396             value = record.get(field);
397             middleAngle = angle - 360 * value / totalField / 2;
398             // TODO - Put up an empty circle
399             if (isNaN(middleAngle)) {
400                 middleAngle = 360;
401                 value = 1;
402                 totalField = 1;
403             }
404             // First slice
405             if (!i || first == 0) {
406                 angle = 360 - middleAngle;
407                 me.firstAngle = angle;
408                 middleAngle = angle - 360 * value / totalField / 2;
409             }
410             endAngle = angle - 360 * value / totalField;
411             slice = {
412                 series: me,
413                 value: value,
414                 startAngle: angle,
415                 endAngle: endAngle,
416                 storeItem: record
417             };
418             if (lenField[0]) {
419                 lenValue = layerTotals[i];
420                 slice.rho = me.radius * (lenValue / maxLenField);
421             } else {
422                 slice.rho = me.radius;
423             }
424             slices[i] = slice;
425             if((slice.startAngle % 360) == (slice.endAngle % 360)) {
426                 slice.startAngle -= 0.0001;
427             }
428             angle = endAngle;
429             first++;
430         }, me);
431         
432         //do all shadows first.
433         if (enableShadows) {
434             for (i = 0, ln = slices.length; i &lt; ln; i++) {
435                 if (this.__excludes &amp;&amp; this.__excludes[i]) {
436                     //hidden series
437                     continue;
438                 }
439                 slice = slices[i];
440                 slice.shadowAttrs = [];
441                 for (j = 0, rhoAcum = 0, shadows = []; j &lt; layers; j++) {
442                     sprite = group.getAt(i * layers + j);
443                     deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
444                     //set pie slice properties
445                     rendererAttributes = {
446                         segment: {
447                             startAngle: slice.startAngle,
448                             endAngle: slice.endAngle,
449                             margin: 0,
450                             rho: slice.rho,
451                             startRho: rhoAcum + (deltaRho * donut / 100),
452                             endRho: rhoAcum + deltaRho
453                         }
454                     };
455                     //create shadows
456                     for (shindex = 0, shadows = []; shindex &lt; lnsh; shindex++) {
457                         shadowAttr = shadowAttributes[shindex];
458                         shadow = shadowGroups[shindex].getAt(i);
459                         if (!shadow) {
460                             shadow = chart.surface.add(Ext.apply({},
461                             {
462                                 type: 'path',
463                                 group: shadowGroups[shindex],
464                                 strokeLinejoin: &quot;round&quot;
465                             },
466                             rendererAttributes, shadowAttr));
467                         }
468                         if (animate) {
469                             rendererAttributes = me.renderer(shadow, store.getAt(i), Ext.apply({},
470                             rendererAttributes, shadowAttr), i, store);
471                             me.onAnimate(shadow, {
472                                 to: rendererAttributes
473                             });
474                         } else {
475                             rendererAttributes = me.renderer(shadow, store.getAt(i), Ext.apply(shadowAttr, {
476                                 hidden: false
477                             }), i, store);
478                             shadow.setAttributes(rendererAttributes, true);
479                         }
480                         shadows.push(shadow);
481                     }
482                     slice.shadowAttrs[j] = shadows;
483                 }
484             }
485         }
486         //do pie slices after.
487         for (i = 0, ln = slices.length; i &lt; ln; i++) {
488             if (this.__excludes &amp;&amp; this.__excludes[i]) {
489                 //hidden series
490                 continue;
491             }
492             slice = slices[i];
493             for (j = 0, rhoAcum = 0; j &lt; layers; j++) {
494                 sprite = group.getAt(i * layers + j);
495                 deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
496                 //set pie slice properties
497                 rendererAttributes = Ext.apply({
498                     segment: {
499                         startAngle: slice.startAngle,
500                         endAngle: slice.endAngle,
501                         margin: 0,
502                         rho: slice.rho,
503                         startRho: rhoAcum + (deltaRho * donut / 100),
504                         endRho: rhoAcum + deltaRho
505                     } 
506                 }, Ext.apply(seriesStyle, colorArrayStyle &amp;&amp; { fill: colorArrayStyle[(layers &gt; 1? j : i) % colorArrayLength] } || {}));
507                 item = Ext.apply({},
508                 rendererAttributes.segment, {
509                     slice: slice,
510                     series: me,
511                     storeItem: slice.storeItem,
512                     index: i
513                 });
514                 me.calcMiddle(item);
515                 if (enableShadows) {
516                     item.shadows = slice.shadowAttrs[j];
517                 }
518                 items[i] = item;
519                 // Create a new sprite if needed (no height)
520                 if (!sprite) {
521                     spriteOptions = Ext.apply({
522                         type: &quot;path&quot;,
523                         group: group,
524                         middle: item.middle
525                     }, Ext.apply(seriesStyle, colorArrayStyle &amp;&amp; { fill: colorArrayStyle[(layers &gt; 1? j : i) % colorArrayLength] } || {}));
526                     sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
527                 }
528                 slice.sprite = slice.sprite || [];
529                 item.sprite = sprite;
530                 slice.sprite.push(sprite);
531                 slice.point = [item.middle.x, item.middle.y];
532                 if (animate) {
533                     rendererAttributes = me.renderer(sprite, store.getAt(i), rendererAttributes, i, store);
534                     sprite._to = rendererAttributes;
535                     sprite._animating = true;
536                     me.onAnimate(sprite, {
537                         to: rendererAttributes,
538                         listeners: {
539                             afteranimate: {
540                                 fn: function() {
541                                     this._animating = false;
542                                 },
543                                 scope: sprite
544                             }
545                         }
546                     });
547                 } else {
548                     rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
549                         hidden: false
550                     }), i, store);
551                     sprite.setAttributes(rendererAttributes, true);
552                 }
553                 rhoAcum += deltaRho;
554             }
555         }
556         
557         // Hide unused bars
558         ln = group.getCount();
559         for (i = 0; i &lt; ln; i++) {
560             if (!slices[(i / layers) &gt;&gt; 0] &amp;&amp; group.getAt(i)) {
561                 group.getAt(i).hide(true);
562             }
563         }
564         if (enableShadows) {
565             lnsh = shadowGroups.length;
566             for (shindex = 0; shindex &lt; ln; shindex++) {
567                 if (!slices[(shindex / layers) &gt;&gt; 0]) {
568                     for (j = 0; j &lt; lnsh; j++) {
569                         if (shadowGroups[j].getAt(shindex)) {
570                             shadowGroups[j].getAt(shindex).hide(true);
571                         }
572                     }
573                 }
574             }
575         }
576         me.renderLabels();
577         me.renderCallouts();
578     },
579
580     // @private callback for when creating a label sprite.
581     onCreateLabel: function(storeItem, item, i, display) {
582         var me = this,
583             group = me.labelsGroup,
584             config = me.label,
585             centerX = me.centerX,
586             centerY = me.centerY,
587             middle = item.middle,
588             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
589         
590         return me.chart.surface.add(Ext.apply({
591             'type': 'text',
592             'text-anchor': 'middle',
593             'group': group,
594             'x': middle.x,
595             'y': middle.y
596         }, endLabelStyle));
597     },
598
599     // @private callback for when placing a label sprite.
600     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
601         var me = this,
602             chart = me.chart,
603             resizing = chart.resizing,
604             config = me.label,
605             format = config.renderer,
606             field = [].concat(config.field),
607             centerX = me.centerX,
608             centerY = me.centerY,
609             middle = item.middle,
610             opt = {
611                 x: middle.x,
612                 y: middle.y
613             },
614             x = middle.x - centerX,
615             y = middle.y - centerY,
616             from = {},
617             rho = 1,
618             theta = Math.atan2(y, x || 1),
619             dg = theta * 180 / Math.PI,
620             prevDg;
621         
622         function fixAngle(a) {
623             if (a &lt; 0) a += 360;
624             return a % 360;
625         }
626
627         label.setAttributes({
628             text: format(storeItem.get(field[index]))
629         }, true);
630
631         switch (display) {
632         case 'outside':
633             rho = Math.sqrt(x * x + y * y) * 2;
634             //update positions
635             opt.x = rho * Math.cos(theta) + centerX;
636             opt.y = rho * Math.sin(theta) + centerY;
637             break;
638
639         case 'rotate':
640             dg = fixAngle(dg);
641             dg = (dg &gt; 90 &amp;&amp; dg &lt; 270) ? dg + 180: dg;
642
643             prevDg = label.attr.rotation.degrees;
644             if (prevDg != null &amp;&amp; Math.abs(prevDg - dg) &gt; 180) {
645                 if (dg &gt; prevDg) {
646                     dg -= 360;
647                 } else {
648                     dg += 360;
649                 }
650                 dg = dg % 360;
651             } else {
652                 dg = fixAngle(dg);
653             }
654             //update rotation angle
655             opt.rotate = {
656                 degrees: dg,
657                 x: opt.x,
658                 y: opt.y
659             };
660             break;
661
662         default:
663             break;
664         }
665         //ensure the object has zero translation
666         opt.translate = {
667             x: 0, y: 0    
668         };
669         if (animate &amp;&amp; !resizing &amp;&amp; (display != 'rotate' || prevDg != null)) {
670             me.onAnimate(label, {
671                 to: opt
672             });
673         } else {
674             label.setAttributes(opt, true);
675         }
676         label._from = from;
677     },
678
679     // @private callback for when placing a callout sprite.
680     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
681         var me = this,
682             chart = me.chart,
683             resizing = chart.resizing,
684             config = me.callouts,
685             centerX = me.centerX,
686             centerY = me.centerY,
687             middle = item.middle,
688             opt = {
689                 x: middle.x,
690                 y: middle.y
691             },
692             x = middle.x - centerX,
693             y = middle.y - centerY,
694             rho = 1,
695             rhoCenter,
696             theta = Math.atan2(y, x || 1),
697             bbox = callout.label.getBBox(),
698             offsetFromViz = 20,
699             offsetToSide = 10,
700             offsetBox = 10,
701             p;
702
703         //should be able to config this.
704         rho = item.endRho + offsetFromViz;
705         rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
706         //update positions
707         opt.x = rho * Math.cos(theta) + centerX;
708         opt.y = rho * Math.sin(theta) + centerY;
709
710         x = rhoCenter * Math.cos(theta);
711         y = rhoCenter * Math.sin(theta);
712
713         if (chart.animate) {
714             //set the line from the middle of the pie to the box.
715             me.onAnimate(callout.lines, {
716                 to: {
717                     path: [&quot;M&quot;, x + centerX, y + centerY, &quot;L&quot;, opt.x, opt.y, &quot;Z&quot;, &quot;M&quot;, opt.x, opt.y, &quot;l&quot;, x &gt; 0 ? offsetToSide: -offsetToSide, 0, &quot;z&quot;]
718                 }
719             });
720             //set box position
721             me.onAnimate(callout.box, {
722                 to: {
723                     x: opt.x + (x &gt; 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
724                     y: opt.y + (y &gt; 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
725                     width: bbox.width + 2 * offsetBox,
726                     height: bbox.height + 2 * offsetBox
727                 }
728             });
729             //set text position
730             me.onAnimate(callout.label, {
731                 to: {
732                     x: opt.x + (x &gt; 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
733                     y: opt.y + (y &gt; 0 ? -bbox.height / 4: -bbox.height / 4)
734                 }
735             });
736         } else {
737             //set the line from the middle of the pie to the box.
738             callout.lines.setAttributes({
739                 path: [&quot;M&quot;, x + centerX, y + centerY, &quot;L&quot;, opt.x, opt.y, &quot;Z&quot;, &quot;M&quot;, opt.x, opt.y, &quot;l&quot;, x &gt; 0 ? offsetToSide: -offsetToSide, 0, &quot;z&quot;]
740             },
741             true);
742             //set box position
743             callout.box.setAttributes({
744                 x: opt.x + (x &gt; 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
745                 y: opt.y + (y &gt; 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
746                 width: bbox.width + 2 * offsetBox,
747                 height: bbox.height + 2 * offsetBox
748             },
749             true);
750             //set text position
751             callout.label.setAttributes({
752                 x: opt.x + (x &gt; 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
753                 y: opt.y + (y &gt; 0 ? -bbox.height / 4: -bbox.height / 4)
754             },
755             true);
756         }
757         for (p in callout) {
758             callout[p].show(true);
759         }
760     },
761
762     // @private handles sprite animation for the series.
763     onAnimate: function(sprite, attr) {
764         sprite.show();
765         return this.callParent(arguments);
766     },
767
768     isItemInPoint: function(x, y, item, i) {
769         var me = this,
770             cx = me.centerX,
771             cy = me.centerY,
772             abs = Math.abs,
773             dx = abs(x - cx),
774             dy = abs(y - cy),
775             startAngle = item.startAngle,
776             endAngle = item.endAngle,
777             rho = Math.sqrt(dx * dx + dy * dy),
778             angle = Math.atan2(y - cy, x - cx) / me.rad + 360;
779         
780         // normalize to the same range of angles created by drawSeries
781         if (angle &gt; me.firstAngle) {
782             angle -= 360;
783         }
784         return (angle &lt;= startAngle &amp;&amp; angle &gt; endAngle
785                 &amp;&amp; rho &gt;= item.startRho &amp;&amp; rho &lt;= item.endRho);
786     },
787     
788     // @private hides all elements in the series.
789     hideAll: function() {
790         var i, l, shadow, shadows, sh, lsh, sprite;
791         if (!isNaN(this._index)) {
792             this.__excludes = this.__excludes || [];
793             this.__excludes[this._index] = true;
794             sprite = this.slices[this._index].sprite;
795             for (sh = 0, lsh = sprite.length; sh &lt; lsh; sh++) {
796                 sprite[sh].setAttributes({
797                     hidden: true
798                 }, true);
799             }
800             if (this.slices[this._index].shadowAttrs) {
801                 for (i = 0, shadows = this.slices[this._index].shadowAttrs, l = shadows.length; i &lt; l; i++) {
802                     shadow = shadows[i];
803                     for (sh = 0, lsh = shadow.length; sh &lt; lsh; sh++) {
804                         shadow[sh].setAttributes({
805                             hidden: true
806                         }, true);
807                     }
808                 }
809             }
810             this.drawSeries();
811         }
812     },
813     
814     // @private shows all elements in the series.
815     showAll: function() {
816         if (!isNaN(this._index)) {
817             this.__excludes[this._index] = false;
818             this.drawSeries();
819         }
820     },
821
822 <span id='Ext-chart.series.Pie-method-highlightItem'>    /**
823 </span>     * Highlight the specified item. If no item is provided the whole series will be highlighted.
824      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
825      */
826     highlightItem: function(item) {
827         var me = this,
828             rad = me.rad;
829         item = item || this.items[this._index];
830         
831         //TODO(nico): sometimes in IE itemmouseover is triggered
832         //twice without triggering itemmouseout in between. This
833         //fixes the highlighting bug. Eventually, events should be
834         //changed to trigger one itemmouseout between two itemmouseovers.
835         this.unHighlightItem();
836         
837         if (!item || item.sprite &amp;&amp; item.sprite._animating) {
838             return;
839         }
840         me.callParent([item]);
841         if (!me.highlight) {
842             return;
843         }
844         if ('segment' in me.highlightCfg) {
845             var highlightSegment = me.highlightCfg.segment,
846                 animate = me.chart.animate,
847                 attrs, i, shadows, shadow, ln, to, itemHighlightSegment, prop;
848             //animate labels
849             if (me.labelsGroup) {
850                 var group = me.labelsGroup,
851                     display = me.label.display,
852                     label = group.getAt(item.index),
853                     middle = (item.startAngle + item.endAngle) / 2 * rad,
854                     r = highlightSegment.margin || 0,
855                     x = r * Math.cos(middle),
856                     y = r * Math.sin(middle);
857
858                 //TODO(nico): rounding to 1e-10
859                 //gives the right translation. Translation
860                 //was buggy for very small numbers. In this
861                 //case we're not looking to translate to very small
862                 //numbers but not to translate at all.
863                 if (Math.abs(x) &lt; 1e-10) {
864                     x = 0;
865                 }
866                 if (Math.abs(y) &lt; 1e-10) {
867                     y = 0;
868                 }
869                 
870                 if (animate) {
871                     label.stopAnimation();
872                     label.animate({
873                         to: {
874                             translate: {
875                                 x: x,
876                                 y: y
877                             }
878                         },
879                         duration: me.highlightDuration
880                     });
881                 }
882                 else {
883                     label.setAttributes({
884                         translate: {
885                             x: x,
886                             y: y
887                         }
888                     }, true);
889                 }
890             }
891             //animate shadows
892             if (me.chart.shadow &amp;&amp; item.shadows) {
893                 i = 0;
894                 shadows = item.shadows;
895                 ln = shadows.length;
896                 for (; i &lt; ln; i++) {
897                     shadow = shadows[i];
898                     to = {};
899                     itemHighlightSegment = item.sprite._from.segment;
900                     for (prop in itemHighlightSegment) {
901                         if (! (prop in highlightSegment)) {
902                             to[prop] = itemHighlightSegment[prop];
903                         }
904                     }
905                     attrs = {
906                         segment: Ext.applyIf(to, me.highlightCfg.segment)
907                     };
908                     if (animate) {
909                         shadow.stopAnimation();
910                         shadow.animate({
911                             to: attrs,
912                             duration: me.highlightDuration
913                         });
914                     }
915                     else {
916                         shadow.setAttributes(attrs, true);
917                     }
918                 }
919             }
920         }
921     },
922
923 <span id='Ext-chart.series.Pie-method-unHighlightItem'>    /**
924 </span>     * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
925      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
926      */
927     unHighlightItem: function() {
928         var me = this;
929         if (!me.highlight) {
930             return;
931         }
932
933         if (('segment' in me.highlightCfg) &amp;&amp; me.items) {
934             var items = me.items,
935                 animate = me.chart.animate,
936                 shadowsEnabled = !!me.chart.shadow,
937                 group = me.labelsGroup,
938                 len = items.length,
939                 i = 0,
940                 j = 0,
941                 display = me.label.display,
942                 shadowLen, p, to, ihs, hs, sprite, shadows, shadow, item, label, attrs;
943
944             for (; i &lt; len; i++) {
945                 item = items[i];
946                 if (!item) {
947                     continue;
948                 }
949                 sprite = item.sprite;
950                 if (sprite &amp;&amp; sprite._highlighted) {
951                     //animate labels
952                     if (group) {
953                         label = group.getAt(item.index);
954                         attrs = Ext.apply({
955                             translate: {
956                                 x: 0,
957                                 y: 0
958                             }
959                         },
960                         display == 'rotate' ? {
961                             rotate: {
962                                 x: label.attr.x,
963                                 y: label.attr.y,
964                                 degrees: label.attr.rotation.degrees
965                             }
966                         }: {});
967                         if (animate) {
968                             label.stopAnimation();
969                             label.animate({
970                                 to: attrs,
971                                 duration: me.highlightDuration
972                             });
973                         }
974                         else {
975                             label.setAttributes(attrs, true);
976                         }
977                     }
978                     if (shadowsEnabled) {
979                         shadows = item.shadows;
980                         shadowLen = shadows.length;
981                         for (; j &lt; shadowLen; j++) {
982                             to = {};
983                             ihs = item.sprite._to.segment;
984                             hs = item.sprite._from.segment;
985                             Ext.apply(to, hs);
986                             for (p in ihs) {
987                                 if (! (p in hs)) {
988                                     to[p] = ihs[p];
989                                 }
990                             }
991                             shadow = shadows[j];
992                             if (animate) {
993                                 shadow.stopAnimation();
994                                 shadow.animate({
995                                     to: {
996                                         segment: to
997                                     },
998                                     duration: me.highlightDuration
999                                 });
1000                             }
1001                             else {
1002                                 shadow.setAttributes({ segment: to }, true);
1003                             }
1004                         }
1005                     }
1006                 }
1007             }
1008         }
1009         me.callParent(arguments);
1010     },
1011     
1012 <span id='Ext-chart.series.Pie-method-getLegendColor'>    /**
1013 </span>     * Returns the color of the series (to be displayed as color for the series legend item).
1014      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
1015      */
1016     getLegendColor: function(index) {
1017         var me = this;
1018         return me.colorArrayStyle[index % me.colorArrayStyle.length];
1019     }
1020 });
1021
1022 </pre></pre></body></html>