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