[483] | 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 | }); |
---|