]> git.proxmox.com Git - sencha-touch.git/blob - src/src/chart/series/sprite/PieSlice.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / chart / series / sprite / PieSlice.js
1 /**
2 * @class Ext.chart.series.sprite.PieSlice
3 *
4 * Pie slice sprite.
5 */
6 Ext.define('Ext.chart.series.sprite.PieSlice', {
7 alias: 'sprite.pieslice',
8 mixins: {
9 markerHolder: 'Ext.chart.MarkerHolder'
10 },
11 extend: 'Ext.draw.sprite.Sector',
12
13 inheritableStatics: {
14 def: {
15 processors: {
16 /**
17 * @cfg {Boolean} [doCallout=true] 'true' if the pie series uses label callouts.
18 */
19 doCallout: 'bool',
20
21 /**
22 * @cfg {Boolean} [rotateLabels=true] 'true' if the labels are rotated for easier reading.
23 */
24 rotateLabels: 'bool',
25
26 /**
27 * @cfg {String} [label=''] Label associated with the Pie sprite.
28 */
29 label: 'string',
30
31 /**
32 * @cfg {Number} [labelOverflowPadding=10] Padding around labels to determine overlap.
33 * Any negative number allows the labels to overlap.
34 */
35 labelOverflowPadding: 'number',
36
37 renderer: 'default'
38 },
39 defaults: {
40 doCallout: true,
41 rotateLabels: true,
42 label: '',
43 labelOverflowPadding: 10,
44 renderer: null
45 }
46 }
47 },
48
49 config: {
50 /**
51 * @private
52 * @cfg {Object} rendererData The object that is passed to the renderer.
53 *
54 * For instance when the PieSlice sprite is used in a Gauge chart, the object
55 * contains the 'store' and 'field' properties, and the 'value' as well
56 * for that one PieSlice that is used to draw the needle of the Gauge.
57 */
58 rendererData: null,
59 rendererIndex: 0
60 },
61
62 render: function (ctx, surface, clipRegion) {
63 var me = this,
64 attr = me.attr,
65 itemCfg = {},
66 changes;
67
68 if (attr.renderer) {
69 itemCfg = {
70 type: 'sector',
71 text: attr.text,
72 centerX: attr.centerX,
73 centerY: attr.centerY,
74 margin: attr.margin,
75 startAngle: Math.min(attr.startAngle, attr.endAngle),
76 endAngle: Math.max(attr.startAngle, attr.endAngle),
77 startRho: Math.min(attr.startRho, attr.endRho),
78 endRho: Math.max(attr.startRho, attr.endRho)
79 };
80 changes = attr.renderer.call(me, me, itemCfg, me.rendererData, me.rendererIndex);
81 Ext.apply(me.attr, changes);
82 }
83
84 // Draw the sector
85 me.callSuper(arguments);
86
87 // Draw the labels
88 if (attr.label && me.getBoundMarker('labels')) {
89 me.placeLabel();
90 }
91 },
92
93 placeLabel: function () {
94 var me = this,
95 attr = me.attr,
96 startAngle = Math.min(attr.startAngle, attr.endAngle),
97 endAngle = Math.max(attr.startAngle, attr.endAngle),
98 midAngle = (startAngle + endAngle) * 0.5,
99 margin = attr.margin,
100 centerX = attr.centerX,
101 centerY = attr.centerY,
102 startRho = Math.min(attr.startRho, attr.endRho) + margin,
103 endRho = Math.max(attr.startRho, attr.endRho) + margin,
104 midRho = (startRho + endRho) * 0.5,
105 surfaceMatrix = me.surfaceMatrix,
106 labelCfg = me.labelCfg || (me.labelCfg = {}),
107 labelTpl = me.getBoundMarker('labels')[0].getTemplate(),
108 labelBox, x, y, changes;
109
110 surfaceMatrix.appendMatrix(attr.matrix);
111
112 labelCfg.text = attr.label;
113
114 x = centerX + Math.cos(midAngle) * midRho;
115 y = centerY + Math.sin(midAngle) * midRho;
116 labelCfg.x = surfaceMatrix.x(x, y);
117 labelCfg.y = surfaceMatrix.y(x, y);
118
119 x = centerX + Math.cos(midAngle) * endRho;
120 y = centerY + Math.sin(midAngle) * endRho;
121 labelCfg.calloutStartX = surfaceMatrix.x(x, y);
122 labelCfg.calloutStartY = surfaceMatrix.y(x, y);
123
124 x = centerX + Math.cos(midAngle) * (endRho + 40);
125 y = centerY + Math.sin(midAngle) * (endRho + 40);
126 labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
127 labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
128
129 labelCfg.rotationRads = (attr.rotateLabels ? midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0)) : 0);
130 labelCfg.calloutColor = me.attr.fillStyle;
131 labelCfg.globalAlpha = attr.globalAlpha * attr.fillOpacity;
132
133 // If a slice is empty, don't display the label.
134 // This behavior can be overridden by a renderer.
135 labelCfg.hidden = (attr.startAngle == attr.endAngle);
136
137 if (attr.renderer) {
138 labelCfg.type = 'label';
139 changes = attr.renderer.call(me, me, labelCfg, me.rendererData, me.rendererIndex);
140 Ext.apply(labelCfg, changes);
141 }
142 me.putMarker('labels', labelCfg, me.attr.attributeId);
143
144 labelBox = me.getMarkerBBox('labels', me.attr.attributeId, true);
145 if (labelBox) {
146 if (attr.doCallout) {
147 if (labelTpl.attr.display === 'outside') {
148 me.putMarker('labels', {callout: 1}, me.attr.attributeId);
149 } else {
150 me.putMarker('labels', {callout: 1 - +me.sliceContainsLabel(attr, labelBox)}, me.attr.attributeId);
151 }
152 } else {
153 me.putMarker('labels', {globalAlpha: +me.sliceContainsLabel(attr, labelBox)}, me.attr.attributeId);
154 }
155 }
156 },
157
158 sliceContainsLabel: function (attr, bbox) {
159 var padding = attr.labelOverflowPadding,
160 middle = (attr.endRho + attr.startRho) / 2,
161 outer = middle + (bbox.width + padding) / 2,
162 inner = middle - (bbox.width + padding) / 2,
163 sliceAngle, l1, l2, l3;
164
165 if (padding < 0) {
166 return 1;
167 }
168 if (bbox.width + padding * 2 > (attr.endRho - attr.startRho)) {
169 return 0;
170 }
171 l1 = Math.sqrt(attr.endRho * attr.endRho - outer * outer);
172 l2 = Math.sqrt(attr.endRho * attr.endRho - inner * inner);
173 sliceAngle = Math.abs(attr.endAngle - attr.startAngle);
174 l3 = (sliceAngle > Math.PI/2 ? inner : Math.abs(Math.tan(sliceAngle / 2)) * inner);
175 if (bbox.height + padding * 2 > Math.min(l1, l2, l3) * 2) {
176 return 0;
177 }
178 return 1;
179 }
180 });