1 | define(["dojo/_base/declare", "dojox/gfx", "./ScaleBase", "./_circularUtils"], function(declare, gfx, ScaleBase, _circularUtils){ |
---|
2 | return declare("dojox.dgauges.CircularScale", ScaleBase, { |
---|
3 | // summary: |
---|
4 | // The circular scale. A scaler must be set to use this class. |
---|
5 | |
---|
6 | // originX: Number |
---|
7 | // The origin x-coordinate of the scale in pixels. |
---|
8 | originX: 50, |
---|
9 | // originY: Number |
---|
10 | // The origin y-coordinate of the scale in pixels. |
---|
11 | originY: 50, |
---|
12 | // radius: Number |
---|
13 | // The outer radius in pixels of the scale. |
---|
14 | radius: 50, |
---|
15 | // startAngle: Number |
---|
16 | // The start angle of the scale in degrees. |
---|
17 | startAngle: 0, |
---|
18 | // endAngle: Number |
---|
19 | // The end angle of the scale in degrees. |
---|
20 | endAngle: 180, |
---|
21 | // orientation: String |
---|
22 | // The orientation of the scale. Can be "clockwise" or "cclockwise". |
---|
23 | // The default value is "clockwise". |
---|
24 | orientation: "clockwise", |
---|
25 | |
---|
26 | constructor: function(){ |
---|
27 | |
---|
28 | this.labelPosition = "inside"; |
---|
29 | |
---|
30 | this.addInvalidatingProperties(["originX", "originY", "radius", "startAngle", "endAngle", "orientation"]); |
---|
31 | }, |
---|
32 | |
---|
33 | _getOrientationNum: function(){ |
---|
34 | // summary: |
---|
35 | // Internal method. |
---|
36 | // tags: |
---|
37 | // private |
---|
38 | return this.orientation == "cclockwise" ? -1 : 1; |
---|
39 | }, |
---|
40 | |
---|
41 | positionForValue: function(/* Number */value){ |
---|
42 | // summary: |
---|
43 | // Transforms a value into an angle using the associated scaler. |
---|
44 | // returns: Number |
---|
45 | // An angle in degrees. |
---|
46 | var totalAngle = _circularUtils.computeTotalAngle(this.startAngle, this.endAngle, this.orientation); |
---|
47 | var relativePos = this.scaler.positionForValue(value); |
---|
48 | return _circularUtils.modAngle(this.startAngle + this._getOrientationNum() * totalAngle * relativePos, 360); |
---|
49 | }, |
---|
50 | |
---|
51 | _positionForTickItem: function(tickItem){ |
---|
52 | // summary: |
---|
53 | // Internal method. |
---|
54 | // tags: |
---|
55 | // private |
---|
56 | var totalAngle = _circularUtils.computeTotalAngle(this.startAngle, this.endAngle, this.orientation); |
---|
57 | return _circularUtils.modAngle(this.startAngle + this._getOrientationNum() * totalAngle * tickItem.position, 360); |
---|
58 | }, |
---|
59 | |
---|
60 | valueForPosition: function(/* Number */angle){ |
---|
61 | // summary: |
---|
62 | // Transforms an angle in degrees into a value using the associated scaler. |
---|
63 | // returns: Number |
---|
64 | // The value represented by angle. |
---|
65 | if(!this.positionInRange(angle)){ |
---|
66 | var min1 = _circularUtils.modAngle(this.startAngle - angle, 360); |
---|
67 | var min2 = 360 - min1; |
---|
68 | var max1 = _circularUtils.modAngle(this.endAngle - angle, 360); |
---|
69 | var max2 = 360 - max1; |
---|
70 | var pos; |
---|
71 | if(Math.min(min1, min2) < Math.min(max1, max2)){ |
---|
72 | pos = 0; |
---|
73 | }else{ |
---|
74 | pos = 1; |
---|
75 | } |
---|
76 | }else{ |
---|
77 | var relativeAngle = _circularUtils.modAngle(this._getOrientationNum() * (angle - this.startAngle), 360); |
---|
78 | var totalAngle = _circularUtils.computeTotalAngle(this.startAngle, this.endAngle, this.orientation); |
---|
79 | pos = relativeAngle / totalAngle; |
---|
80 | } |
---|
81 | return this.scaler.valueForPosition(pos); |
---|
82 | }, |
---|
83 | |
---|
84 | positionInRange: function(/* Number */value){ |
---|
85 | // summary: |
---|
86 | // Returns true if the value parameter is between the accepted scale positions. |
---|
87 | // returns: Boolean |
---|
88 | // True if the value parameter is between the accepted scale positions. |
---|
89 | if(this.startAngle == this.endAngle){ |
---|
90 | return true; |
---|
91 | } |
---|
92 | value = _circularUtils.modAngle(value, 360); |
---|
93 | if(this._getOrientationNum() == 1){ |
---|
94 | if(this.startAngle < this.endAngle){ |
---|
95 | return value >= this.startAngle && value <= this.endAngle; |
---|
96 | }else{ |
---|
97 | return !(value > this.endAngle && value < this.startAngle); |
---|
98 | } |
---|
99 | }else{ |
---|
100 | if(this.startAngle < this.endAngle){ |
---|
101 | return !(value > this.startAngle && value < this.endAngle); |
---|
102 | }else{ |
---|
103 | return value >= this.endAngle && value <= this.startAngle; |
---|
104 | } |
---|
105 | } |
---|
106 | }, |
---|
107 | |
---|
108 | _distance: function(x1, y1, x2, y2){ |
---|
109 | // summary: |
---|
110 | // Internal method. |
---|
111 | // tags: |
---|
112 | // private |
---|
113 | return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); |
---|
114 | }, |
---|
115 | |
---|
116 | _layoutLabel: function(label, txt, ox, oy, lrad, angle, labelPlacement){ |
---|
117 | // summary: |
---|
118 | // Internal method. |
---|
119 | // tags: |
---|
120 | // private |
---|
121 | var font = this._getFont(); |
---|
122 | var box = gfx._base._getTextBox(txt, { |
---|
123 | font: gfx.makeFontString(gfx.makeParameters(gfx.defaultFont, font)) |
---|
124 | }); |
---|
125 | var tw = box.w; |
---|
126 | var fz = font.size; |
---|
127 | var th = gfx.normalizedLength(fz); |
---|
128 | |
---|
129 | var tfx = ox + Math.cos(angle) * lrad - tw / 2; |
---|
130 | var tfy = oy - Math.sin(angle) * lrad - th / 2; |
---|
131 | var side; |
---|
132 | |
---|
133 | var intersections = []; |
---|
134 | |
---|
135 | // Intersection with top segment |
---|
136 | side = tfx; |
---|
137 | var ipx = side; |
---|
138 | var ipy = -Math.tan(angle) * side + oy + Math.tan(angle) * ox; |
---|
139 | // Verify if intersection is on segment |
---|
140 | if(ipy >= tfy && ipy <= tfy + th){ |
---|
141 | intersections.push({ |
---|
142 | x: ipx, |
---|
143 | y: ipy |
---|
144 | }); |
---|
145 | } |
---|
146 | |
---|
147 | // Intersection with bottom segment |
---|
148 | side = tfx + tw; |
---|
149 | ipx = side; |
---|
150 | ipy = -Math.tan(angle) * side + oy + Math.tan(angle) * ox; |
---|
151 | // Verify if intersection is on segment |
---|
152 | if(ipy >= tfy && ipy <= tfy + th){ |
---|
153 | intersections.push({ |
---|
154 | x: ipx, |
---|
155 | y: ipy |
---|
156 | }); |
---|
157 | } |
---|
158 | // Intersection with left segment |
---|
159 | side = tfy; |
---|
160 | ipx = -1 / Math.tan(angle) * side + ox + 1 / Math.tan(angle) * oy; |
---|
161 | ipy = side; |
---|
162 | // Verify if intersection is on segment |
---|
163 | if(ipx >= tfx && ipx <= tfx + tw){ |
---|
164 | intersections.push({ |
---|
165 | x: ipx, |
---|
166 | y: ipy |
---|
167 | }); |
---|
168 | } |
---|
169 | // Intersection with right segment |
---|
170 | side = tfy + th; |
---|
171 | ipx = -1 / Math.tan(angle) * side + ox + 1 / Math.tan(angle) * oy; |
---|
172 | ipy = side; |
---|
173 | // Verify if intersection is on segment |
---|
174 | if(ipx >= tfx && ipx <= tfx + tw){ |
---|
175 | intersections.push({ |
---|
176 | x: ipx, |
---|
177 | y: ipy |
---|
178 | }); |
---|
179 | } |
---|
180 | var dif; |
---|
181 | if(labelPlacement == "inside"){ |
---|
182 | for(var it = 0; it < intersections.length; it++){ |
---|
183 | var ip = intersections[it]; |
---|
184 | dif = this._distance(ip.x, ip.y, ox, oy) - lrad; |
---|
185 | if(dif >= 0){ |
---|
186 | // Place reference intersection point on reference circle |
---|
187 | tfx = ox + Math.cos(angle) * (lrad - dif) - tw / 2; |
---|
188 | tfy = oy - Math.sin(angle) * (lrad - dif) - th / 2; |
---|
189 | break; |
---|
190 | } |
---|
191 | } |
---|
192 | }else{// "outside" placement |
---|
193 | for(it = 0; it < intersections.length; it++){ |
---|
194 | ip = intersections[it]; |
---|
195 | dif = this._distance(ip.x, ip.y, ox, oy) - lrad; |
---|
196 | if(dif <= 0){ |
---|
197 | // Place reference intersection point on reference circle |
---|
198 | tfx = ox + Math.cos(angle) * (lrad - dif) - tw / 2; |
---|
199 | tfy = oy - Math.sin(angle) * (lrad - dif) - th / 2; |
---|
200 | |
---|
201 | break; |
---|
202 | } |
---|
203 | } |
---|
204 | } |
---|
205 | if(label){ |
---|
206 | label.setTransform([{ |
---|
207 | dx: tfx + tw / 2, |
---|
208 | dy: tfy + th |
---|
209 | }]); |
---|
210 | } |
---|
211 | }, |
---|
212 | |
---|
213 | refreshRendering: function(){ |
---|
214 | this.inherited(arguments); |
---|
215 | if(!this._gfxGroup || !this.scaler){ |
---|
216 | return; |
---|
217 | } |
---|
218 | |
---|
219 | // Normalize angles |
---|
220 | this.startAngle = _circularUtils.modAngle(this.startAngle, 360); |
---|
221 | this.endAngle = _circularUtils.modAngle(this.endAngle, 360); |
---|
222 | |
---|
223 | this._ticksGroup.clear(); |
---|
224 | |
---|
225 | var renderer; |
---|
226 | var label; |
---|
227 | var labelText; |
---|
228 | |
---|
229 | // Layout ticks |
---|
230 | var allTicks = this.scaler.computeTicks(); |
---|
231 | |
---|
232 | var tickBB; |
---|
233 | for(var i = 0; i < allTicks.length; i++){ |
---|
234 | var tickItem = allTicks[i]; |
---|
235 | renderer = this.tickShapeFunc(this._ticksGroup, this, tickItem); |
---|
236 | |
---|
237 | tickBB = this._gauge._computeBoundingBox(renderer); |
---|
238 | var a; |
---|
239 | if(tickItem.position){ |
---|
240 | a = this._positionForTickItem(tickItem); |
---|
241 | }else{ |
---|
242 | a = this.positionForValue(tickItem.value); |
---|
243 | } |
---|
244 | if(renderer){ |
---|
245 | renderer.setTransform([{ |
---|
246 | dx: this.originX, |
---|
247 | dy: this.originY |
---|
248 | }, gfx.matrix.rotateg(a), { |
---|
249 | dx: this.radius - tickBB.width - 2 * tickBB.x, |
---|
250 | dy: 0 |
---|
251 | }]); |
---|
252 | } |
---|
253 | labelText = this.tickLabelFunc(tickItem); |
---|
254 | if(labelText){ |
---|
255 | label = this._ticksGroup.createText({ |
---|
256 | x: 0, |
---|
257 | y: 0, |
---|
258 | text: labelText, |
---|
259 | align: "middle" |
---|
260 | }).setFont(this._getFont()).setFill(this._getFont().color ? this._getFont().color : "black"); |
---|
261 | var rad = this.radius; |
---|
262 | if(this.labelPosition == "inside"){ |
---|
263 | rad -= (tickBB.width + this.labelGap); |
---|
264 | }else{ |
---|
265 | rad += this.labelGap; |
---|
266 | } |
---|
267 | this._layoutLabel(label, labelText, this.originX, this.originY, rad, _circularUtils.toRadians(360 - a), this.labelPosition); |
---|
268 | } |
---|
269 | } |
---|
270 | |
---|
271 | for(var key in this._indicatorsIndex){ |
---|
272 | this._indicatorsRenderers[key] = this._indicatorsIndex[key].invalidateRendering(); |
---|
273 | } |
---|
274 | } |
---|
275 | }); |
---|
276 | }); |
---|