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; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
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
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:
27 * {@img Ext.chart.series.Pie/Ext.chart.series.Pie.png Ext.chart.series.Pie chart series}
29 * var store = Ext.create('Ext.data.JsonStore', {
30 * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
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}
40 * Ext.create('Ext.chart.Chart', {
41 * renderTo: Ext.getBody(),
46 * theme: 'Base:gradients',
55 * renderer: function(storeItem, item) {
56 * //calculate and display percentage on hover
58 * store.each(function(rec) {
59 * total += rec.get('data1');
61 * this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
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.
87 Ext.define('Ext.chart.series.Pie', {
89 /* Begin Definitions */
91 alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
93 extend: 'Ext.chart.series.Series',
97 type: "pie",
103 <span id='Ext-chart-series-Pie-cfg-highlightDuration'> /**
104 </span> * @cfg {Number} highlightDuration
105 * The duration for the pie slice highlight effect.
107 highlightDuration: 150,
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.
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.
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.
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.
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.
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.
150 constructor: function(config) {
151 this.callParent(arguments);
154 surface = chart.surface,
156 shadow = chart.shadow, i, l, cfg;
164 Ext.apply(me, config, {
166 "stroke-width": 6,
167 "stroke-opacity": 1,
168 stroke: 'rgb(200, 200, 200)',
175 "stroke-width": 4,
176 "stroke-opacity": 1,
177 stroke: 'rgb(150, 150, 150)',
184 "stroke-width": 2,
185 "stroke-opacity": 1,
186 stroke: 'rgb(100, 100, 100)',
193 me.group = surface.getGroup(me.seriesId);
195 for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
196 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
199 surface.customAttributes.segment = function(opt) {
200 return me.getSegment(opt);
204 //@private updates some onbefore render parameters.
205 initialize: function() {
207 store = me.chart.substore || me.chart.store;
208 //Add yFields to be used in Legend.js
210 if (me.label.field) {
211 store.each(function(rec) {
212 me.yField.push(rec.get(me.label.field));
217 // @private returns an object with properties for a PieSlice.
218 getSegment: function(opt) {
226 x1 = 0, x2 = 0, x3 = 0, x4 = 0,
227 y1 = 0, y2 = 0, y3 = 0, y4 = 0,
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) > 180,
235 a1 = Math.min(startAngle, endAngle) * rad,
236 a2 = Math.max(startAngle, endAngle) * rad,
239 x += margin * cos(midAngle);
240 y += margin * sin(midAngle);
242 x1 = x + opt.startRho * cos(a1);
243 y1 = y + opt.startRho * sin(a1);
245 x2 = x + opt.endRho * cos(a1);
246 y2 = y + opt.endRho * sin(a1);
248 x3 = x + opt.startRho * cos(a2);
249 y3 = y + opt.startRho * sin(a2);
251 x4 = x + opt.endRho * cos(a2);
252 y4 = y + opt.endRho * sin(a2);
254 if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
257 //Solves mysterious clipping bug with IE
261 ["M", x1, y1],
262 ["L", x2, y2],
263 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
269 ["M", x1, y1],
270 ["L", x2, y2],
271 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
272 ["L", x3, y3],
273 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
279 // @private utility function to calculate the middle point of a pie slice.
280 calcMiddle: function(item) {
286 startAngle = slice.startAngle,
287 endAngle = slice.endAngle,
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);
301 <span id='Ext-chart-series-Pie-method-drawSeries'> /**
302 </span> * Draws the series for the current chart.
304 drawSeries: function() {
306 store = me.chart.substore || me.chart.store,
308 animate = me.chart.animate,
309 field = me.angleField || me.field || me.xField,
310 lenField = [].concat(me.lengthField),
312 colors = me.colorSet,
314 surface = chart.surface,
315 chartBBox = chart.chartBBox,
316 enableShadows = chart.shadow,
317 shadowGroups = me.shadowGroups,
318 shadowAttributes = me.shadowAttributes,
319 lnsh = shadowGroups.length,
321 layers = lenField.length,
334 seriesStyle = me.seriesStyle,
335 seriesLabelStyle = me.seriesLabelStyle,
336 colorArrayStyle = me.colorArrayStyle,
337 colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
338 gutterX = chart.maxGutter[0],
339 gutterY = chart.maxGutter[1],
368 Ext.apply(seriesStyle, me.style || {});
373 //override theme colors
375 colorArrayStyle = me.colorSet;
376 colorArrayLength = colorArrayStyle.length;
379 //if not store or store is empty then there's nothing to draw
380 if (!store || !store.getCount()) {
384 me.unHighlightItem();
385 me.cleanHighlights();
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 = [];
393 store.each(function(record, i) {
394 if (this.__excludes && this.__excludes[i]) {
398 totalField += +record.get(field);
400 for (j = 0, totalLenField = 0; j < layers; j++) {
401 totalLenField += +record.get(lenField[j]);
403 layerTotals[i] = totalLenField;
404 maxLenField = Math.max(maxLenField, totalLenField);
408 store.each(function(record, i) {
409 if (this.__excludes && this.__excludes[i]) {
413 value = record.get(field);
414 middleAngle = angle - 360 * value / totalField / 2;
415 // TODO - Put up an empty circle
416 if (isNaN(middleAngle)) {
422 if (!i || first == 0) {
423 angle = 360 - middleAngle;
424 me.firstAngle = angle;
425 middleAngle = angle - 360 * value / totalField / 2;
427 endAngle = angle - 360 * value / totalField;
436 lenValue = layerTotals[i];
437 slice.rho = me.radius * (lenValue / maxLenField);
439 slice.rho = me.radius;
442 if((slice.startAngle % 360) == (slice.endAngle % 360)) {
443 slice.startAngle -= 0.0001;
449 //do all shadows first.
451 for (i = 0, ln = slices.length; i < ln; i++) {
452 if (this.__excludes && this.__excludes[i]) {
457 slice.shadowAttrs = [];
458 for (j = 0, rhoAcum = 0, shadows = []; j < 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 = {
464 startAngle: slice.startAngle,
465 endAngle: slice.endAngle,
468 startRho: rhoAcum + (deltaRho * donut / 100),
469 endRho: rhoAcum + deltaRho
473 for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
474 shadowAttr = shadowAttributes[shindex];
475 shadow = shadowGroups[shindex].getAt(i);
477 shadow = chart.surface.add(Ext.apply({}, {
479 group: shadowGroups[shindex],
480 strokeLinejoin: "round"
481 }, rendererAttributes, shadowAttr));
484 shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply({}, rendererAttributes, shadowAttr), i, store);
485 me.onAnimate(shadow, {
489 shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply(shadowAttr, {
492 shadow.setAttributes(shadowAttr, true);
494 shadows.push(shadow);
496 slice.shadowAttrs[j] = shadows;
500 //do pie slices after.
501 for (i = 0, ln = slices.length; i < ln; i++) {
502 if (this.__excludes && this.__excludes[i]) {
507 for (j = 0, rhoAcum = 0; j < 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({
513 startAngle: slice.startAngle,
514 endAngle: slice.endAngle,
517 startRho: rhoAcum + (deltaRho * donut / 100),
518 endRho: rhoAcum + deltaRho
520 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
522 rendererAttributes.segment, {
525 storeItem: slice.storeItem,
530 item.shadows = slice.shadowAttrs[j];
533 // Create a new sprite if needed (no height)
535 spriteOptions = Ext.apply({
536 type: "path",
539 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
540 sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
542 slice.sprite = slice.sprite || [];
543 item.sprite = sprite;
544 slice.sprite.push(sprite);
545 slice.point = [item.middle.x, item.middle.y];
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,
555 this._animating = false;
562 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
565 sprite.setAttributes(rendererAttributes, true);
572 ln = group.getCount();
573 for (i = 0; i < ln; i++) {
574 if (!slices[(i / layers) >> 0] && group.getAt(i)) {
575 group.getAt(i).hide(true);
579 lnsh = shadowGroups.length;
580 for (shindex = 0; shindex < ln; shindex++) {
581 if (!slices[(shindex / layers) >> 0]) {
582 for (j = 0; j < lnsh; j++) {
583 if (shadowGroups[j].getAt(shindex)) {
584 shadowGroups[j].getAt(shindex).hide(true);
594 // @private callback for when creating a label sprite.
595 onCreateLabel: function(storeItem, item, i, display) {
597 group = me.labelsGroup,
599 centerX = me.centerX,
600 centerY = me.centerY,
601 middle = item.middle,
602 endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
604 return me.chart.surface.add(Ext.apply({
606 'text-anchor': 'middle',
613 // @private callback for when placing a label sprite.
614 onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
617 resizing = chart.resizing,
619 format = config.renderer,
620 field = [].concat(config.field),
621 centerX = me.centerX,
622 centerY = me.centerY,
623 middle = item.middle,
628 x = middle.x - centerX,
629 y = middle.y - centerY,
632 theta = Math.atan2(y, x || 1),
633 dg = theta * 180 / Math.PI,
636 function fixAngle(a) {
637 if (a < 0) a += 360;
641 label.setAttributes({
642 text: format(storeItem.get(field[index]))
647 rho = Math.sqrt(x * x + y * y) * 2;
649 opt.x = rho * Math.cos(theta) + centerX;
650 opt.y = rho * Math.sin(theta) + centerY;
655 dg = (dg > 90 && dg < 270) ? dg + 180: dg;
657 prevDg = label.attr.rotation.degrees;
658 if (prevDg != null && Math.abs(prevDg - dg) > 180) {
659 if (dg > prevDg) {
668 //update rotation angle
679 //ensure the object has zero translation
683 if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
684 me.onAnimate(label, {
688 label.setAttributes(opt, true);
693 // @private callback for when placing a callout sprite.
694 onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
697 resizing = chart.resizing,
698 config = me.callouts,
699 centerX = me.centerX,
700 centerY = me.centerY,
701 middle = item.middle,
706 x = middle.x - centerX,
707 y = middle.y - centerY,
710 theta = Math.atan2(y, x || 1),
711 bbox = callout.label.getBBox(),
717 //should be able to config this.
718 rho = item.endRho + offsetFromViz;
719 rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
721 opt.x = rho * Math.cos(theta) + centerX;
722 opt.y = rho * Math.sin(theta) + centerY;
724 x = rhoCenter * Math.cos(theta);
725 y = rhoCenter * Math.sin(theta);
728 //set the line from the middle of the pie to the box.
729 me.onAnimate(callout.lines, {
731 path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
735 me.onAnimate(callout.box, {
737 x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
738 y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
739 width: bbox.width + 2 * offsetBox,
740 height: bbox.height + 2 * offsetBox
744 me.onAnimate(callout.label, {
746 x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
747 y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
751 //set the line from the middle of the pie to the box.
752 callout.lines.setAttributes({
753 path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
757 callout.box.setAttributes({
758 x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
759 y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
760 width: bbox.width + 2 * offsetBox,
761 height: bbox.height + 2 * offsetBox
765 callout.label.setAttributes({
766 x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
767 y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
772 callout[p].show(true);
776 // @private handles sprite animation for the series.
777 onAnimate: function(sprite, attr) {
779 return this.callParent(arguments);
782 isItemInPoint: function(x, y, item, i) {
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;
794 // normalize to the same range of angles created by drawSeries
795 if (angle > me.firstAngle) {
798 return (angle <= startAngle && angle > endAngle
799 && rho >= item.startRho && rho <= item.endRho);
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 < lsh; sh++) {
810 sprite[sh].setAttributes({
814 if (this.slices[this._index].shadowAttrs) {
815 for (i = 0, shadows = this.slices[this._index].shadowAttrs, l = shadows.length; i < l; i++) {
817 for (sh = 0, lsh = shadow.length; sh < lsh; sh++) {
818 shadow[sh].setAttributes({
828 // @private shows all elements in the series.
829 showAll: function() {
830 if (!isNaN(this._index)) {
831 this.__excludes[this._index] = false;
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
840 highlightItem: function(item) {
843 item = item || this.items[this._index];
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();
851 if (!item || item.sprite && item.sprite._animating) {
854 me.callParent([item]);
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;
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);
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) < 1e-10) {
880 if (Math.abs(y) < 1e-10) {
885 label.stopAnimation();
893 duration: me.highlightDuration
897 label.setAttributes({
906 if (me.chart.shadow && item.shadows) {
908 shadows = item.shadows;
910 for (; i < ln; i++) {
913 itemHighlightSegment = item.sprite._from.segment;
914 for (prop in itemHighlightSegment) {
915 if (! (prop in highlightSegment)) {
916 to[prop] = itemHighlightSegment[prop];
920 segment: Ext.applyIf(to, me.highlightCfg.segment)
923 shadow.stopAnimation();
926 duration: me.highlightDuration
930 shadow.setAttributes(attrs, true);
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
941 unHighlightItem: function() {
947 if (('segment' in me.highlightCfg) && me.items) {
948 var items = me.items,
949 animate = me.chart.animate,
950 shadowsEnabled = !!me.chart.shadow,
951 group = me.labelsGroup,
955 display = me.label.display,
956 shadowLen, p, to, ihs, hs, sprite, shadows, shadow, item, label, attrs;
958 for (; i < len; i++) {
963 sprite = item.sprite;
964 if (sprite && sprite._highlighted) {
967 label = group.getAt(item.index);
974 display == 'rotate' ? {
978 degrees: label.attr.rotation.degrees
982 label.stopAnimation();
985 duration: me.highlightDuration
989 label.setAttributes(attrs, true);
992 if (shadowsEnabled) {
993 shadows = item.shadows;
994 shadowLen = shadows.length;
995 for (; j < shadowLen; j++) {
997 ihs = item.sprite._to.segment;
998 hs = item.sprite._from.segment;
1005 shadow = shadows[j];
1007 shadow.stopAnimation();
1012 duration: me.highlightDuration
1016 shadow.setAttributes({ segment: to }, true);
1023 me.callParent(arguments);
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
1030 getLegendColor: function(index) {
1032 return me.colorArrayStyle[index % me.colorArrayStyle.length];