1 /* 2 Copyright 2008-2011 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software: you can redistribute it and/or modify 13 it under the terms of the GNU Lesser General Public License as published by 14 the Free Software Foundation, either version 3 of the License, or 15 (at your option) any later version. 16 17 JSXGraph is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public License 23 along with JSXGraph. If not, see <http://www.gnu.org/licenses/>. 24 25 */ 26 27 /** 28 * @class A circular sector is a subarea of the area enclosed by a circle. It is enclosed by two radii and an arc. 29 * @pseudo 30 * @name Sector 31 * @augments JXG.Curve 32 * @constructor 33 * @type JXG.Curve 34 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 35 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A sector is defined by three points: The sector's center <tt>p1</tt>, 36 * a second point <tt>p2</tt> defining the radius and a third point <tt>p3</tt> defining the angle of the sector. The 37 * Sector is always drawn counter clockwise from <tt>p2</tt> to <tt>p3</tt> 38 * @example 39 * // Create an arc out of three free points 40 * var p1 = board.create('point', [1.5, 5.0]), 41 * p2 = board.create('point', [1.0, 0.5]), 42 * p3 = board.create('point', [5.0, 3.0]), 43 * 44 * a = board.create('sector', [p1, p2, p3]); 45 * </pre><div id="49f59123-f013-4681-bfd9-338b89893156" style="width: 300px; height: 300px;"></div> 46 * <script type="text/javascript"> 47 * (function () { 48 * var board = JXG.JSXGraph.initBoard('49f59123-f013-4681-bfd9-338b89893156', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 49 * p1 = board.create('point', [1.5, 5.0]), 50 * p2 = board.create('point', [1.0, 0.5]), 51 * p3 = board.create('point', [5.0, 3.0]), 52 * 53 * a = board.create('sector', [p1, p2, p3]); 54 * })(); 55 * </script><pre> 56 */ 57 JXG.createSector = function(board, parents, attributes) { 58 var el, attr; 59 60 // Alles 3 Punkte? 61 if ( !(JXG.isPoint(parents[0]) && JXG.isPoint(parents[1]) && JXG.isPoint(parents[2]))) { 62 throw new Error("JSXGraph: Can't create Sector with parent types '" + 63 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + 64 (typeof parents[2]) + "'."); 65 } 66 67 attr = JXG.copyAttributes(attributes, board.options, 'sector'); 68 69 el = board.create('curve', [[0], [0]], attr); 70 el.type = JXG.OBJECT_TYPE_SECTOR; 71 72 el.elType = 'sector'; 73 el.parents = [parents[0].id, parents[1].id, parents[2].id]; 74 75 /** 76 * Midpoint of the sector. 77 * @memberOf Sector.prototype 78 * @name point1 79 * @type JXG.Point 80 */ 81 el.point1 = JXG.getReference(board, parents[0]); 82 83 /** 84 * This point together with {@link Sector#point1} defines the radius.. 85 * @memberOf Sector.prototype 86 * @name point2 87 * @type JXG.Point 88 */ 89 el.point2 = JXG.getReference(board, parents[1]); 90 91 /** 92 * Defines the sector's angle. 93 * @memberOf Sector.prototype 94 * @name point3 95 * @type JXG.Point 96 */ 97 el.point3 = JXG.getReference(board, parents[2]); 98 99 /* Add arc as child to defining points */ 100 el.point1.addChild(el); 101 el.point2.addChild(el); 102 el.point3.addChild(el); 103 104 el.useDirection = attributes['usedirection']; // useDirection is necessary for circumCircleSectors 105 106 /** 107 * documented in JXG.Curve 108 * @ignore 109 */ 110 el.updateDataArray = function() { 111 var A = this.point2, 112 B = this.point1, 113 C = this.point3, 114 beta, co, si, matrix, 115 phi = JXG.Math.Geometry.rad(A,B,C), 116 i, 117 n = Math.ceil(phi/Math.PI*90)+1, 118 delta = phi/n, 119 x = B.X(), 120 y = B.Y(), 121 v, 122 det, p0c, p1c, p2c; 123 124 if (this.useDirection) { // This is true for circumCircleArcs. In that case there is 125 // a fourth parent element: [midpoint, point1, point3, point2] 126 p0c = parents[1].coords.usrCoords, 127 p1c = parents[3].coords.usrCoords, 128 p2c = parents[2].coords.usrCoords; 129 det = (p0c[1]-p2c[1])*(p0c[2]-p1c[2]) - (p0c[2]-p2c[2])*(p0c[1]-p1c[1]); 130 if(det < 0) { 131 this.point2 = parents[1]; 132 this.point3 = parents[2]; 133 } 134 else { 135 this.point2 = parents[2]; 136 this.point3 = parents[1]; 137 } 138 } 139 this.dataX = [B.X(),A.X()]; 140 this.dataY = [B.Y(),A.Y()]; 141 for (beta=delta,i=1; i<=n; i++, beta+=delta) { 142 co = Math.cos(beta); 143 si = Math.sin(beta); 144 matrix = [[1, 0, 0], 145 [x*(1-co)+y*si,co,-si], 146 [y*(1-co)-x*si,si, co]]; 147 v = JXG.Math.matVecMult(matrix,A.coords.usrCoords); 148 this.dataX.push(v[1]/v[0]); 149 this.dataY.push(v[2]/v[0]); 150 } 151 this.dataX.push(B.X()); 152 this.dataY.push(B.Y()); 153 }; 154 155 /** 156 * Returns the radius of the sector. 157 * @memberOf Sector.prototype 158 * @name Radius 159 * @function 160 * @returns {Number} The distance between {@link Sector#point1} and {@link Sector#point2}. 161 */ 162 el.Radius = function() { 163 return this.point2.Dist(this.point1); 164 }; 165 166 /** 167 * deprecated 168 * @ignore 169 */ 170 el.getRadius = function() { 171 return this.Radius(); 172 }; 173 174 /** 175 * Checks whether (x,y) is within the area defined by the sector. 176 * @memberOf Sector.prototype 177 * @name hasPointSector 178 * @function 179 * @param {Number} x Coordinate in x direction, screen coordinates. 180 * @param {Number} y Coordinate in y direction, screen coordinates. 181 * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise. 182 */ 183 el.hasPointSector = function (x, y) { 184 var checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board), 185 r = this.Radius(), 186 dist = this.point1.coords.distance(JXG.COORDS_BY_USER,checkPoint), 187 has = (dist<r), 188 angle; 189 190 if(has) { 191 angle = JXG.Math.Geometry.rad(this.point2,this.point1,checkPoint.usrCoords.slice(1)); 192 if (angle>JXG.Math.Geometry.rad(this.point2,this.point1,this.point3)) { has = false; } 193 } 194 return has; 195 }; 196 197 /** 198 * documented in GeometryElement 199 * @ignore 200 */ 201 el.getTextAnchor = function() { 202 return this.point1.coords; 203 }; 204 205 /** 206 * documented in GeometryElement 207 * @ignore 208 */ 209 el.getLabelAnchor = function() { 210 var angle = JXG.Math.Geometry.rad(this.point2, this.point1, this.point3), 211 dx = 10/(this.board.unitX), 212 dy = 10/(this.board.unitY), 213 p2c = this.point2.coords.usrCoords, 214 pmc = this.point1.coords.usrCoords, 215 bxminusax = p2c[1] - pmc[1], 216 byminusay = p2c[2] - pmc[2], 217 coords, vecx, vecy, len; 218 219 if(this.label.content != null) { 220 this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0],this.board); 221 } 222 223 coords = new JXG.Coords(JXG.COORDS_BY_USER, 224 [pmc[1]+ Math.cos(angle*0.5)*bxminusax - Math.sin(angle*0.5)*byminusay, 225 pmc[2]+ Math.sin(angle*0.5)*bxminusax + Math.cos(angle*0.5)*byminusay], 226 this.board); 227 228 vecx = coords.usrCoords[1] - pmc[1]; 229 vecy = coords.usrCoords[2] - pmc[2]; 230 231 len = Math.sqrt(vecx*vecx+vecy*vecy); 232 vecx = vecx*(len+dx)/len; 233 vecy = vecy*(len+dy)/len; 234 235 return new JXG.Coords(JXG.COORDS_BY_USER, [pmc[1]+vecx,pmc[2]+vecy],this.board); 236 }; 237 238 el.prepareUpdate().update(); 239 240 return el; 241 }; 242 243 JXG.JSXGraph.registerElement('sector', JXG.createSector); 244 245 246 /** 247 * @class A circumcircle sector is different from a {@link Sector} mostly in the way the parent elements are interpreted. 248 * At first, the circum centre is determined from the three given points. Then the sector is drawn from <tt>p1</tt> through 249 * <tt>p2</tt> to <tt>p3</tt>. 250 * @pseudo 251 * @name Circumcirclesector 252 * @augments Sector 253 * @constructor 254 * @type Sector 255 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 256 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A circumcircle sector is defined by the circumcircle which is determined 257 * by these three given points. The circumcircle sector is always drawn from <tt>p1</tt> through <tt>p2</tt> to <tt>p3</tt>. 258 * @example 259 * // Create an arc out of three free points 260 * var p1 = board.create('point', [1.5, 5.0]), 261 * p2 = board.create('point', [1.0, 0.5]), 262 * p3 = board.create('point', [5.0, 3.0]), 263 * 264 * a = board.create('circumcirclesector', [p1, p2, p3]); 265 * </pre><div id="695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04" style="width: 300px; height: 300px;"></div> 266 * <script type="text/javascript"> 267 * (function () { 268 * var board = JXG.JSXGraph.initBoard('695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 269 * p1 = board.create('point', [1.5, 5.0]), 270 * p2 = board.create('point', [1.0, 0.5]), 271 * p3 = board.create('point', [5.0, 3.0]), 272 * 273 * a = board.create('circumcirclesector', [p1, p2, p3]); 274 * })(); 275 * </script><pre> 276 */ 277 JXG.createCircumcircleSector = function(board, parents, attributes) { 278 var el, mp, attr; 279 280 if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) && (JXG.isPoint(parents[2]))) { 281 attr = JXG.copyAttributes(attributes, board.options, 'circumcirclesector', 'center'); 282 mp = board.create('circumcenter',[parents[0], parents[1], parents[2]], attr); 283 284 mp.dump = false; 285 286 attr = JXG.copyAttributes(attributes, board.options, 'circumcirclesector'); 287 el = board.create('sector', [mp,parents[0],parents[2],parents[1]], attr); 288 289 el.elType = 'circumcirclesector'; 290 el.parents = [parents[0].id, parents[1].id, parents[2].id]; 291 292 /** 293 * Center of the circumcirclesector 294 * @memberOf CircumcircleSector.prototype 295 * @name center 296 * @type Circumcenter 297 */ 298 el.center = mp; 299 el.subs = { 300 center: mp 301 } 302 } else { 303 throw new Error("JSXGraph: Can't create circumcircle sector with parent types '" + 304 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'."); 305 } 306 307 return el; 308 }; 309 310 JXG.JSXGraph.registerElement('circumcirclesector', JXG.createCircumcircleSector); 311 312 313 /** 314 * @class The angle element is used to denote an angle defined by three points. Visually it is just a {@link Sector} 315 * element with a radius not defined by the parent elements but by an attribute <tt>radius</tt>. As opposed to the sector, 316 * an angle has two angle points and no radius point. 317 * Sector is displayed if type=="sector". 318 * If type=="square", instead of a sector a parallelogram is displayed. 319 * In case of type=="auto", a square is displayed if the angle is near orthogonal. 320 * If no name is provided the angle label is automatically set to a lower greek letter. 321 * @pseudo 322 * @name Angle 323 * @augments Sector 324 * @constructor 325 * @type Sector 326 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 327 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 An angle is always drawn counterclockwise from <tt>p1</tt> to 328 * <tt>p3</tt> around <tt>p2</tt>. 329 * @example 330 * // Create an arc out of three free points 331 * var p1 = board.create('point', [5.0, 3.0]), 332 * p2 = board.create('point', [1.0, 0.5]), 333 * p3 = board.create('point', [1.5, 5.0]), 334 * 335 * a = board.create('angle', [p1, p2, p3]); 336 * </pre><div id="a34151f9-bb26-480a-8d6e-9b8cbf789ae5" style="width: 300px; height: 300px;"></div> 337 * <script type="text/javascript"> 338 * (function () { 339 * var board = JXG.JSXGraph.initBoard('a34151f9-bb26-480a-8d6e-9b8cbf789ae5', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 340 * p1 = board.create('point', [5.0, 3.0]), 341 * p2 = board.create('point', [1.0, 0.5]), 342 * p3 = board.create('point', [1.5, 5.0]), 343 * 344 * a = board.create('angle', [p1, p2, p3]); 345 * })(); 346 * </script><pre> 347 */ 348 JXG.createAngle = function(board, parents, attributes) { 349 var el, p, q, text, attr, attrsub, 350 possibleNames = ['α', 'β', 'γ', 'δ', 'ε', 'ζ', '&eta', 'θ', 351 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 352 'ς', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω'], 353 i = 0, j, x, pre, post, found, dot; 354 355 // Test if three points are given 356 if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) && (JXG.isPoint(parents[2]))) { 357 attr = JXG.copyAttributes(attributes, board.options, 'angle'); 358 // If empty, create a new name 359 text = attr.name; 360 if (typeof text =='undefined' || text == '') { 361 while(i < possibleNames.length) { 362 j=i; 363 x = possibleNames[i]; 364 for(el in board.objects) { 365 if(board.objects[el].type == JXG.OBJECT_TYPE_ANGLE) { 366 if(board.objects[el].name == x) { 367 i++; 368 break; 369 } 370 } 371 } 372 if(i==j) { 373 text = x; 374 i = possibleNames.length+1; 375 } 376 } 377 if (i == possibleNames.length) { 378 pre = 'α_{'; 379 post = '}'; 380 found = false; 381 j=0; 382 while(!found) { 383 for(el in board.objects) { 384 if(board.objects[el].type == JXG.OBJECT_TYPE_ANGLE) { 385 if(board.objects[el].name == (pre+j+post)) { 386 found = true; 387 break; 388 } 389 } 390 } 391 if(found) { 392 found= false; 393 } 394 else { 395 found = true; 396 text = (pre+j+post); 397 } 398 } 399 } 400 attr.name = text; 401 } 402 403 attrsub = JXG.copyAttributes(attributes, board.options, 'angle', 'radiuspoint'); 404 p = board.create('point', [ 405 function(){ 406 var A = parents[0], S = parents[1], 407 r = JXG.evaluate(attr.radius), 408 d = S.Dist(A); 409 return [S.X()+(A.X()-S.X())*r/d, S.Y()+(A.Y()-S.Y())*r/d]; 410 }], attrsub); 411 412 p.dump = false; 413 414 attrsub = JXG.copyAttributes(attributes, board.options, 'angle', 'pointsquare'); 415 // Second helper point for square 416 q = board.create('point', [ 417 function(){ 418 var A = parents[2], S = parents[1], 419 r = JXG.evaluate(attr.radius), 420 d = S.Dist(A); 421 return [S.X()+(A.X()-S.X())*r/d, S.Y()+(A.Y()-S.Y())*r/d]; 422 }], attrsub); 423 424 q.dump = false; 425 426 // Sector is just a curve with its own updateDataArray method 427 el = board.create('sector', [parents[1], p, parents[2]], attr); 428 429 el.elType = 'angle'; 430 el.parents = [parents[0].id, parents[1].id, parents[2].id]; 431 el.subs = { 432 point: p, 433 pointsquare: q 434 }; 435 436 el.updateDataArraySquare = function() { 437 var S = parents[1], 438 v, l1, l2, r; 439 440 v = JXG.Math.crossProduct(q.coords.usrCoords, S.coords.usrCoords); 441 l1 = [-p.X()*v[1]-p.Y()*v[2], p.Z()*v[1], p.Z()*v[2]]; 442 v = JXG.Math.crossProduct(p.coords.usrCoords, S.coords.usrCoords); 443 l2 = [-q.X()*v[1]-q.Y()*v[2], q.Z()*v[1], q.Z()*v[2]]; 444 r = JXG.Math.crossProduct(l1,l2); 445 r[1] /= r[0]; 446 r[2] /= r[0]; 447 448 this.dataX = [S.X(), p.X(), r[1], q.X(), S.X()]; 449 this.dataY = [S.Y(), p.Y(), r[2], q.Y(), S.Y()]; 450 }; 451 452 el.updateDataArraySector = el.updateDataArray; 453 el.updateDataArray = function() { 454 var rad = JXG.Math.Geometry.rad(parents[0], parents[1], parents[2]); 455 456 if (this.visProp.type=='square') { 457 this.updateDataArraySquare(); 458 } else if (this.visProp.type=='sector') { 459 this.updateDataArraySector(); 460 if (Math.abs(rad-Math.PI*0.5)<0.0025) { 461 if (this.dot.visProp.visible === false) { 462 this.dot.setProperty({visible: true}); 463 } 464 } else { 465 if (this.dot.visProp.visible) { 466 this.dot.setProperty({visible: false}); 467 } 468 } 469 } else { 470 if (Math.abs(rad-Math.PI*0.5)<0.0025) { 471 this.updateDataArraySquare(); 472 } else { 473 this.updateDataArraySector(); 474 } 475 } 476 }; 477 478 /** 479 * The point defining the radius of the angle element. 480 * @type JXG.Point 481 * @name radiuspoint 482 * @memberOf Angle.prototype 483 */ 484 el.radiuspoint = p; 485 486 /** 487 * The point defining the radius of the angle element. Alias for {@link Angle.prototype#radiuspoint}. 488 * @type JXG.Point 489 * @name point 490 * @memberOf Angle.prototype 491 */ 492 el.point = p; 493 494 /** 495 * Helper point for angles of type 'square'. 496 * @type JXG.Point 497 * @name pointsquare 498 * @memberOf Angle.prototype 499 */ 500 el.pointsquare = q; 501 502 dot = JXG.copyAttributes(attributes, board.options, 'angle', 'dot'); 503 /** 504 * Indicates a right angle. Invisible by default, use <tt>dot.visible: true</tt> to show. 505 * Though this dot indicates a right angle, it can be visible even if the angle is not a right 506 * one. 507 * @type JXG.Point 508 * @name dot 509 * @memberOf Angle.prototype 510 */ 511 el.dot = board.create('point', [function () { 512 if (JXG.exists(el.dot) && el.dot.visProp.visible === false) { 513 return [0, 0]; 514 } 515 516 var c = p.coords.usrCoords, 517 transform = board.create('transform', [-parents[1].X(), -parents[1].Y()], {type: 'translate'}); 518 519 transform.melt(board.create('transform', [0.5, 0.5], {type: 'scale'})); 520 transform.melt(board.create('transform', [JXG.Math.Geometry.rad(parents[0], parents[1], parents[2])/2, 0, 0], {type:'rotate'})); 521 transform.melt(board.create('transform', [parents[1].X(), parents[1].Y()], {type: 'translate'})); 522 transform.update(); 523 524 return JXG.Math.matVecMult(transform.matrix, c); 525 }], dot); 526 527 el.dot.dump = false; 528 el.subs.dot = el.dot; 529 530 for (i = 0; i < 3; i++) { 531 JXG.getRef(board,parents[i]).addChild(p); 532 JXG.getRef(board,parents[i]).addChild(el.dot); 533 } 534 535 el.type = JXG.OBJECT_TYPE_ANGLE; 536 JXG.getRef(board,parents[0]).addChild(el); 537 538 // documented in GeometryElement 539 el.getLabelAnchor = function() { 540 var angle = JXG.Math.Geometry.rad(this.point2, this.point1, this.point3), 541 dx = 13/(this.board.unitX), 542 dy = 13/(this.board.unitY), 543 p2c = this.point2.coords.usrCoords, 544 pmc = this.point1.coords.usrCoords, 545 bxminusax = p2c[1] - pmc[1], 546 byminusay = p2c[2] - pmc[2], 547 coords, vecx, vecy, len; 548 549 if(this.label.content != null) { 550 this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0],this.board); 551 } 552 553 coords = new JXG.Coords(JXG.COORDS_BY_USER, 554 [pmc[1]+ Math.cos(angle*0.5*1.125)*bxminusax - Math.sin(angle*0.5*1.125)*byminusay, 555 pmc[2]+ Math.sin(angle*0.5*1.125)*bxminusax + Math.cos(angle*0.5*1.125)*byminusay], 556 this.board); 557 558 vecx = coords.usrCoords[1] - pmc[1]; 559 vecy = coords.usrCoords[2] - pmc[2]; 560 561 len = Math.sqrt(vecx*vecx+vecy*vecy); 562 vecx = vecx*(len+dx)/len; 563 vecy = vecy*(len+dy)/len; 564 565 return new JXG.Coords(JXG.COORDS_BY_USER, [pmc[1]+vecx,pmc[2]+vecy],this.board); 566 }; 567 568 el.Value = function () { 569 return JXG.Math.Geometry.rad(this.point2, this.point1, this.point3); 570 }; 571 572 el.methodMap = JXG.deepCopy(el.methodMap, { 573 Value: 'Value' 574 }); 575 576 } else { 577 throw new Error("JSXGraph: Can't create angle with parent types '" + 578 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'."); 579 } 580 581 return el; 582 }; 583 584 JXG.JSXGraph.registerElement('angle', JXG.createAngle); 585 586