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