1 /* 2 Copyright 2008-2023 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 dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 import JXG from "../jxg"; 36 import Geometry from "../math/geometry"; 37 import Mat from "../math/math"; 38 import Statistics from "../math/statistics"; 39 import Coords from "../base/coords"; 40 import Const from "../base/constants"; 41 import Type from "../utils/type"; 42 43 /** 44 * @class A circular sector is a subarea of the area enclosed by a circle. It is enclosed by two radii and an arc. 45 * @pseudo 46 * @name Sector 47 * @augments JXG.Curve 48 * @constructor 49 * @type JXG.Curve 50 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 51 * 52 * First possibility of input parameters are: 53 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 A sector is defined by three points: The sector's center <tt>p1</tt>, 54 * a second point <tt>p2</tt> defining the radius and a third point <tt>p3</tt> defining the angle of the sector. The 55 * Sector is always drawn counter clockwise from <tt>p2</tt> to <tt>p3</tt>. 56 * <p> 57 * In this case, the sector will have an arc as sub-object. 58 * <p> 59 * Second possibility of input parameters are: 60 * @param {JXG.Line_JXG.Line_array,number_array,number_number,function} line, line2, coords1 or direction1, coords2 or direction2, radius The sector is defined by two lines. 61 * The two legs which define the sector are given by two coordinates arrays which are project initially two the two lines or by two directions (+/- 1). 62 * The last parameter is the radius of the sector. 63 * <p>In this case, the sector will <b>not</b> have an arc as sub-object. 64 * 65 * @example 66 * // Create a sector out of three free points 67 * var p1 = board.create('point', [1.5, 5.0]), 68 * p2 = board.create('point', [1.0, 0.5]), 69 * p3 = board.create('point', [5.0, 3.0]), 70 * 71 * a = board.create('sector', [p1, p2, p3]); 72 * </pre><div class="jxgbox" id="JXG49f59123-f013-4681-bfd9-338b89893156" style="width: 300px; height: 300px;"></div> 73 * <script type="text/javascript"> 74 * (function () { 75 * var board = JXG.JSXGraph.initBoard('JXG49f59123-f013-4681-bfd9-338b89893156', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 76 * p1 = board.create('point', [1.5, 5.0]), 77 * p2 = board.create('point', [1.0, 0.5]), 78 * p3 = board.create('point', [5.0, 3.0]), 79 * 80 * a = board.create('sector', [p1, p2, p3]); 81 * })(); 82 * </script><pre> 83 * 84 * @example 85 * // Create a sector out of two lines, two directions and a radius 86 * var p1 = board.create('point', [-1, 4]), 87 * p2 = board.create('point', [4, 1]), 88 * q1 = board.create('point', [-2, -3]), 89 * q2 = board.create('point', [4,3]), 90 * 91 * li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}), 92 * li2 = board.create('line', [q1,q2], {lastArrow:true}), 93 * 94 * sec1 = board.create('sector', [li1, li2, [5.5, 0], [4, 3], 3]), 95 * sec2 = board.create('sector', [li1, li2, 1, -1, 4]); 96 * 97 * </pre><div class="jxgbox" id="JXGbb9e2809-9895-4ff1-adfa-c9c71d50aa53" style="width: 300px; height: 300px;"></div> 98 * <script type="text/javascript"> 99 * (function () { 100 * var board = JXG.JSXGraph.initBoard('JXGbb9e2809-9895-4ff1-adfa-c9c71d50aa53', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 101 * p1 = board.create('point', [-1, 4]), 102 * p2 = board.create('point', [4, 1]), 103 * q1 = board.create('point', [-2, -3]), 104 * q2 = board.create('point', [4,3]), 105 * 106 * li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}), 107 * li2 = board.create('line', [q1,q2], {lastArrow:true}), 108 * 109 * sec1 = board.create('sector', [li1, li2, [5.5, 0], [4, 3], 3]), 110 * sec2 = board.create('sector', [li1, li2, 1, -1, 4]); 111 * })(); 112 * </script><pre> 113 * 114 * @example 115 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 116 * var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], { 117 * anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true}, 118 * fillColor: 'yellow', strokeColor: 'black'}); 119 * var s2 = board.create('curve', [s1, t], {fillColor: 'yellow', strokeColor: 'black'}); 120 * 121 * </pre><div id="JXG2e70ee14-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 122 * <script type="text/javascript"> 123 * (function() { 124 * var board = JXG.JSXGraph.initBoard('JXG2e70ee14-6339-11e8-9fb9-901b0e1b8723', 125 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 126 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 127 * var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], { 128 * anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true}, 129 * fillColor: 'yellow', strokeColor: 'black'}); 130 * var s2 = board.create('curve', [s1, t], {fillColor: 'yellow', strokeColor: 'black'}); 131 * 132 * })(); 133 * 134 * </script><pre> 135 * 136 * @example 137 * var A = board.create('point', [3, -2]), 138 * B = board.create('point', [-2, -2]), 139 * C = board.create('point', [0, 4]); 140 * 141 * var angle = board.create('sector', [B, A, C], { 142 * strokeWidth: 0, 143 * arc: { 144 * visible: true, 145 * strokeWidth: 3, 146 * lastArrow: {size: 4}, 147 * firstArrow: {size: 4} 148 * } 149 * }); 150 * //angle.arc.setAttribute({firstArrow: false}); 151 * angle.arc.setAttribute({lastArrow: false}); 152 * 153 * </pre><div id="JXGca37b99e-1510-49fa-ac9e-efd60e956104" class="jxgbox" style="width: 300px; height: 300px;"></div> 154 * <script type="text/javascript"> 155 * (function() { 156 * var board = JXG.JSXGraph.initBoard('JXGca37b99e-1510-49fa-ac9e-efd60e956104', 157 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 158 * var A = board.create('point', [3, -2]), 159 * B = board.create('point', [-2, -2]), 160 * C = board.create('point', [0, 4]); 161 * 162 * var angle = board.create('sector', [B, A, C], { 163 * strokeWidth: 0, 164 * arc: { 165 * visible: true, 166 * strokeWidth: 3, 167 * lastArrow: {size: 4}, 168 * firstArrow: {size: 4} 169 * } 170 * }); 171 * //angle.arc.setAttribute({firstArrow: false}); 172 * angle.arc.setAttribute({lastArrow: false}); 173 * 174 * })(); 175 * 176 * </script><pre> 177 * 178 * 179 */ 180 JXG.createSector = function (board, parents, attributes) { 181 var el, 182 attr, 183 i, 184 type = "invalid", 185 s, 186 v, 187 attrPoints = ["center", "radiusPoint", "anglePoint"], 188 points; 189 190 // Three points? 191 if ( 192 parents[0].elementClass === Const.OBJECT_CLASS_LINE && 193 parents[1].elementClass === Const.OBJECT_CLASS_LINE && 194 (Type.isArray(parents[2]) || Type.isNumber(parents[2])) && 195 (Type.isArray(parents[3]) || Type.isNumber(parents[3])) && 196 (Type.isNumber(parents[4]) || Type.isFunction(parents[4]) || Type.isString(parents[4])) 197 ) { 198 type = "2lines"; 199 } else { 200 points = Type.providePoints(board, parents, attributes, "sector", attrPoints); 201 if (points === false) { 202 throw new Error( 203 "JSXGraph: Can't create Sector with parent types '" + 204 typeof parents[0] + 205 "' and '" + 206 typeof parents[1] + 207 "' and '" + 208 typeof parents[2] + 209 "'." 210 ); 211 } 212 type = "3points"; 213 } 214 215 attr = Type.copyAttributes(attributes, board.options, "sector"); 216 el = board.create("curve", [[0], [0]], attr); 217 el.type = Const.OBJECT_TYPE_SECTOR; 218 el.elType = "sector"; 219 220 /** 221 * Sets radius if the attribute `radius` has value 'auto'. 222 * Sets a radius between 20 and 50 points, depending on the distance 223 * between the center and the radius point. 224 * This function is used in {@link Angle}. 225 * 226 * @name autoRadius 227 * @memberof Sector.prototype 228 * @function 229 * @returns {Number} returns a radius value in user coordinates. 230 * @private 231 */ 232 el.autoRadius = function () { 233 var r1 = 20 / el.board.unitX, // 20px 234 r2 = Infinity, 235 r3 = 50 / el.board.unitX; // 50px 236 237 if (Type.isPoint(el.center)) { 238 // This does not work for 2-lines sectors / angles 239 r2 = el.center.Dist(el.point2) * 0.3333; 240 } 241 242 return Math.max(r1, Math.min(r2, r3)); 243 }; 244 245 if (type === "2lines") { 246 /** 247 * @ignore 248 */ 249 el.Radius = function () { 250 var r = Type.evaluate(parents[4]); 251 if (r === "auto") { 252 return this.autoRadius(); 253 } 254 return r; 255 }; 256 257 el.line1 = board.select(parents[0]); 258 el.line2 = board.select(parents[1]); 259 260 el.line1.addChild(el); 261 el.line2.addChild(el); 262 el.setParents(parents); 263 264 el.point1 = { visProp: {} }; 265 el.point2 = { visProp: {} }; 266 el.point3 = { visProp: {} }; 267 268 /* Intersection point */ 269 s = Geometry.meetLineLine(el.line1.stdform, el.line2.stdform, 0, board); 270 271 if (Type.isArray(parents[2])) { 272 /* project p1 to l1 */ 273 if (parents[2].length === 2) { 274 parents[2] = [1].concat(parents[2]); 275 } 276 /* 277 v = [0, el.line1.stdform[1], el.line1.stdform[2]]; 278 v = Mat.crossProduct(v, parents[2]); 279 v = Geometry.meetLineLine(v, el.line1.stdform, 0, board); 280 */ 281 v = Geometry.projectPointToLine( 282 { coords: { usrCoords: parents[2] } }, 283 el.line1, 284 board 285 ); 286 v = Statistics.subtract(v.usrCoords, s.usrCoords); 287 el.direction1 = 288 Mat.innerProduct(v, [0, el.line1.stdform[2], -el.line1.stdform[1]], 3) >= 0 289 ? +1 290 : -1; 291 } else { 292 el.direction1 = parents[2] >= 0 ? 1 : -1; 293 } 294 295 if (Type.isArray(parents[3])) { 296 /* project p2 to l2 */ 297 if (parents[3].length === 2) { 298 parents[3] = [1].concat(parents[3]); 299 } 300 /* 301 v = [0, el.line2.stdform[1], el.line2.stdform[2]]; 302 v = Mat.crossProduct(v, parents[3]); 303 v = Geometry.meetLineLine(v, el.line2.stdform, 0, board); 304 */ 305 v = Geometry.projectPointToLine( 306 { coords: { usrCoords: parents[3] } }, 307 el.line2, 308 board 309 ); 310 v = Statistics.subtract(v.usrCoords, s.usrCoords); 311 el.direction2 = 312 Mat.innerProduct(v, [0, el.line2.stdform[2], -el.line2.stdform[1]], 3) >= 0 313 ? +1 314 : -1; 315 } else { 316 el.direction2 = parents[3] >= 0 ? 1 : -1; 317 } 318 319 /** 320 * @class 321 * @ignore 322 */ 323 el.updateDataArray = function () { 324 var r, 325 l1, 326 l2, 327 A = [0, 0, 0], 328 B = [0, 0, 0], 329 C = [0, 0, 0], 330 ar; 331 332 l1 = this.line1; 333 l2 = this.line2; 334 335 // Intersection point of the lines 336 B = Mat.crossProduct(l1.stdform, l2.stdform); 337 338 if (Math.abs(B[0]) > Mat.eps * Mat.eps) { 339 B[1] /= B[0]; 340 B[2] /= B[0]; 341 B[0] /= B[0]; 342 } 343 // First point 344 r = this.direction1 * this.Radius(); 345 A = Statistics.add(B, [0, r * l1.stdform[2], -r * l1.stdform[1]]); 346 347 // Second point 348 r = this.direction2 * this.Radius(); 349 C = Statistics.add(B, [0, r * l2.stdform[2], -r * l2.stdform[1]]); 350 351 this.point2.coords = new Coords(Const.COORDS_BY_USER, A, el.board); 352 this.point1.coords = new Coords(Const.COORDS_BY_USER, B, el.board); 353 this.point3.coords = new Coords(Const.COORDS_BY_USER, C, el.board); 354 355 if ( 356 Math.abs(A[0]) < Mat.eps || 357 Math.abs(B[0]) < Mat.eps || 358 Math.abs(C[0]) < Mat.eps 359 ) { 360 this.dataX = [NaN]; 361 this.dataY = [NaN]; 362 return; 363 } 364 365 ar = Geometry.bezierArc(A, B, C, true, 1); 366 367 this.dataX = ar[0]; 368 this.dataY = ar[1]; 369 370 this.bezierDegree = 3; 371 }; 372 373 // Arc does not work yet, since point1, point2 and point3 are 374 // virtual points. 375 // 376 // attr = Type.copyAttributes(attributes, board.options, "arc"); 377 // attr = Type.copyAttributes(attr, board.options, "sector", "arc"); 378 // attr.withLabel = false; 379 // attr.name += "_arc"; 380 // // el.arc = board.create("arc", [el.point1, el.point2, el.point3], attr); 381 // // The arc's radius is always the radius of sector. 382 // // This is important for angles. 383 // el.updateDataArray(); 384 // el.arc = board.create("arc", [ 385 // function() { 386 // return el.point1.coords.usrCoords; 387 // }, // Center 388 // function() { 389 // var d = el.point2.coords.distance(Const.COORDS_BY_USER, el.point1.coords); 390 // if (d === 0) { 391 // return [el.point1.coords.usrCoords[1], el.point1.coords.usrCoords[2]]; 392 // } 393 // return [ 394 // el.point1.coords.usrCoords[1] + el.Radius() * (el.point2.coords.usrCoords[1] - el.point1.coords.usrCoords[1]) / d, 395 // el.point1.coords.usrCoords[2] + el.Radius() * (el.point2.coords.usrCoords[2] - el.point1.coords.usrCoords[2]) / d 396 // ]; 397 // }, 398 // function() { 399 // return el.point3.coords.usrCoords; 400 // }, // Center 401 // ], attr); 402 // el.addChild(el.arc); 403 404 // end '2lines' 405 } else if (type === "3points") { 406 /** 407 * Midpoint of the sector. 408 * @memberOf Sector.prototype 409 * @name point1 410 * @type JXG.Point 411 */ 412 el.point1 = points[0]; 413 414 /** 415 * This point together with {@link Sector#point1} defines the radius. 416 * @memberOf Sector.prototype 417 * @name point2 418 * @type JXG.Point 419 */ 420 el.point2 = points[1]; 421 422 /** 423 * Defines the sector's angle. 424 * @memberOf Sector.prototype 425 * @name point3 426 * @type JXG.Point 427 */ 428 el.point3 = points[2]; 429 430 /* Add arc as child to defining points */ 431 for (i = 0; i < 3; i++) { 432 if (Type.exists(points[i]._is_new)) { 433 el.addChild(points[i]); 434 delete points[i]._is_new; 435 } else { 436 points[i].addChild(el); 437 } 438 } 439 440 // useDirection is necessary for circumCircleSectors 441 el.useDirection = attributes.usedirection; 442 el.setParents(points); 443 444 /** 445 * Defines the sectors orientation in case of circumCircleSectors. 446 * @memberOf Sector.prototype 447 * @name point4 448 * @type JXG.Point 449 */ 450 if (Type.exists(points[3])) { 451 el.point4 = points[3]; 452 el.point4.addChild(el); 453 } 454 455 el.methodMap = JXG.deepCopy(el.methodMap, { 456 arc: "arc", 457 center: "center", 458 radiuspoint: "radiuspoint", 459 anglepoint: "anglepoint" 460 }); 461 462 /** 463 * @class 464 * @ignore 465 */ 466 el.updateDataArray = function () { 467 var ar, 468 det, 469 p0c, 470 p1c, 471 p2c, 472 A = this.point2, 473 B = this.point1, 474 C = this.point3, 475 phi, 476 sgn = 1, 477 vp_s = Type.evaluate(this.visProp.selection); 478 479 if (!A.isReal || !B.isReal || !C.isReal) { 480 this.dataX = [NaN]; 481 this.dataY = [NaN]; 482 return; 483 } 484 485 phi = Geometry.rad(A, B, C); 486 if ((vp_s === "minor" && phi > Math.PI) || (vp_s === "major" && phi < Math.PI)) { 487 sgn = -1; 488 } 489 490 // This is true for circumCircleSectors. In that case there is 491 // a fourth parent element: [midpoint, point1, point3, point2] 492 if (this.useDirection && Type.exists(this.point4)) { 493 p0c = this.point2.coords.usrCoords; 494 p1c = this.point4.coords.usrCoords; 495 p2c = this.point3.coords.usrCoords; 496 det = 497 (p0c[1] - p2c[1]) * (p0c[2] - p1c[2]) - 498 (p0c[2] - p2c[2]) * (p0c[1] - p1c[1]); 499 500 if (det >= 0.0) { 501 C = this.point2; 502 A = this.point3; 503 } 504 } 505 506 A = A.coords.usrCoords; 507 B = B.coords.usrCoords; 508 C = C.coords.usrCoords; 509 510 ar = Geometry.bezierArc(A, B, C, true, sgn); 511 512 this.dataX = ar[0]; 513 this.dataY = ar[1]; 514 this.bezierDegree = 3; 515 }; 516 517 /** 518 * Returns the radius of the sector. 519 * @memberOf Sector.prototype 520 * @name Radius 521 * @function 522 * @returns {Number} The distance between {@link Sector#point1} and {@link Sector#point2}. 523 */ 524 el.Radius = function () { 525 return this.point2.Dist(this.point1); 526 }; 527 } // end '3points' 528 529 el.center = el.point1; 530 el.radiuspoint = el.point2; 531 el.anglepoint = el.point3; 532 533 attr = Type.copyAttributes(attributes, board.options, "arc"); 534 attr = Type.copyAttributes(attr, board.options, "sector", "arc"); 535 attr.withLabel = false; 536 attr.name += "_arc"; 537 538 if (type === "2lines") { 539 el.updateDataArray(); 540 el.arc = board.create("arc", [ 541 function() { 542 return el.point1.coords.usrCoords; 543 }, // Center 544 function() { 545 var d = el.point2.coords.distance(Const.COORDS_BY_USER, el.point1.coords); 546 if (d === 0) { 547 return [el.point1.coords.usrCoords[1], el.point1.coords.usrCoords[2]]; 548 } 549 return [ 550 el.point1.coords.usrCoords[1] + el.Radius() * (el.point2.coords.usrCoords[1] - el.point1.coords.usrCoords[1]) / d, 551 el.point1.coords.usrCoords[2] + el.Radius() * (el.point2.coords.usrCoords[2] - el.point1.coords.usrCoords[2]) / d 552 ]; 553 }, 554 function() { 555 return el.point3.coords.usrCoords; 556 } // Center 557 ], attr); 558 } else { 559 // The arc's radius is always the radius of sector. 560 // This is important for angles. 561 el.arc = board.create("arc", [ 562 el.point1, // Center 563 function() { 564 var d = el.point2.Dist(el.point1); 565 if (d === 0) { 566 return [el.point1.X(), el.point1.Y()]; 567 } 568 return [ 569 el.point1.X() + el.Radius() * (el.point2.X() - el.point1.X()) / d, 570 el.point1.Y() + el.Radius() * (el.point2.Y() - el.point1.Y()) / d 571 ]; 572 }, 573 el.point3 574 ], attr); 575 } 576 el.addChild(el.arc); 577 578 // Default hasPoint method. Documented in geometry element 579 el.hasPointCurve = function (x, y) { 580 var angle, 581 alpha, 582 beta, 583 prec, 584 type, 585 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board), 586 r = this.Radius(), 587 dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint), 588 has, 589 vp_s = Type.evaluate(this.visProp.selection); 590 591 if (Type.isObject(Type.evaluate(this.visProp.precision))) { 592 type = this.board._inputDevice; 593 prec = Type.evaluate(this.visProp.precision[type]); 594 } else { 595 // 'inherit' 596 prec = this.board.options.precision.hasPoint; 597 } 598 prec /= Math.min(Math.abs(this.board.unitX), Math.abs(this.board.unitY)); 599 has = Math.abs(dist - r) < prec; 600 if (has) { 601 angle = Geometry.rad(this.point2, this.center, checkPoint.usrCoords.slice(1)); 602 alpha = 0; 603 beta = Geometry.rad(this.point2, this.center, this.point3); 604 605 if ((vp_s === "minor" && beta > Math.PI) || (vp_s === "major" && beta < Math.PI)) { 606 alpha = beta; 607 beta = 2 * Math.PI; 608 } 609 610 if (angle < alpha || angle > beta) { 611 has = false; 612 } 613 } 614 615 return has; 616 }; 617 618 /** 619 * Checks whether (x,y) is within the area defined by the sector. 620 * @memberOf Sector.prototype 621 * @name hasPointSector 622 * @function 623 * @param {Number} x Coordinate in x direction, screen coordinates. 624 * @param {Number} y Coordinate in y direction, screen coordinates. 625 * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise. 626 */ 627 el.hasPointSector = function (x, y) { 628 var angle, 629 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board), 630 r = this.Radius(), 631 dist = this.point1.coords.distance(Const.COORDS_BY_USER, checkPoint), 632 alpha, 633 beta, 634 has = dist < r, 635 vp_s = Type.evaluate(this.visProp.selection); 636 637 if (has) { 638 angle = Geometry.rad(this.radiuspoint, this.center, checkPoint.usrCoords.slice(1)); 639 alpha = 0.0; 640 beta = Geometry.rad(this.radiuspoint, this.center, this.anglepoint); 641 642 if ((vp_s === "minor" && beta > Math.PI) || (vp_s === "major" && beta < Math.PI)) { 643 alpha = beta; 644 beta = 2 * Math.PI; 645 } 646 //if (angle > Geometry.rad(this.point2, this.point1, this.point3)) { 647 if (angle < alpha || angle > beta) { 648 has = false; 649 } 650 } 651 return has; 652 }; 653 654 el.hasPoint = function (x, y) { 655 if ( 656 Type.evaluate(this.visProp.highlightonsector) || 657 Type.evaluate(this.visProp.hasinnerpoints) 658 ) { 659 return this.hasPointSector(x, y); 660 } 661 662 return this.hasPointCurve(x, y); 663 }; 664 665 // documented in GeometryElement 666 el.getTextAnchor = function () { 667 return this.point1.coords; 668 }; 669 670 // documented in GeometryElement 671 // this method is very similar to arc.getLabelAnchor() 672 // there are some additions in the arc version though, mainly concerning 673 // "major" and "minor" arcs. but maybe these methods can be merged. 674 /** 675 * @class 676 * @ignore 677 */ 678 el.getLabelAnchor = function () { 679 var coords, 680 vec, 681 vecx, 682 vecy, 683 len, 684 angle = Geometry.rad(this.point2, this.point1, this.point3), 685 dx = 13 / this.board.unitX, 686 dy = 13 / this.board.unitY, 687 p2c = this.point2.coords.usrCoords, 688 pmc = this.point1.coords.usrCoords, 689 bxminusax = p2c[1] - pmc[1], 690 byminusay = p2c[2] - pmc[2], 691 vp_s = Type.evaluate(this.visProp.selection), 692 l_vp = this.label ? this.label.visProp : this.visProp.label; 693 694 // If this is uncommented, the angle label can not be dragged 695 //if (Type.exists(this.label)) { 696 // this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board); 697 //} 698 699 if ((vp_s === "minor" && angle > Math.PI) || (vp_s === "major" && angle < Math.PI)) { 700 angle = -(2 * Math.PI - angle); 701 } 702 703 coords = new Coords( 704 Const.COORDS_BY_USER, 705 [ 706 pmc[1] + Math.cos(angle * 0.5) * bxminusax - Math.sin(angle * 0.5) * byminusay, 707 pmc[2] + Math.sin(angle * 0.5) * bxminusax + Math.cos(angle * 0.5) * byminusay 708 ], 709 this.board 710 ); 711 712 vecx = coords.usrCoords[1] - pmc[1]; 713 vecy = coords.usrCoords[2] - pmc[2]; 714 715 len = Mat.hypot(vecx, vecy); 716 vecx = (vecx * (len + dx)) / len; 717 vecy = (vecy * (len + dy)) / len; 718 vec = [pmc[1] + vecx, pmc[2] + vecy]; 719 720 l_vp.position = Geometry.calcLabelQuadrant(Geometry.rad([1, 0], [0, 0], vec)); 721 722 return new Coords(Const.COORDS_BY_USER, vec, this.board); 723 }; 724 725 /** 726 * Overwrite the Radius method of the sector. 727 * Used in {@link GeometryElement#setAttribute}. 728 * @memberOf Sector.prototype 729 * @name setRadius 730 * @param {Number|Function} value New radius. 731 * @function 732 */ 733 el.setRadius = function (val) { 734 var res, 735 e = Type.evaluate(val); 736 737 if (val === 'auto' || e === 'auto') { 738 res = 'auto'; 739 } else if (Type.isNumber(val)) { 740 res = 'number'; 741 } else if (Type.isFunction(val) && !Type.isString(e)) { 742 res = 'function'; 743 } else { 744 res = 'undefined'; 745 } 746 if (res !== 'undefined') { 747 this.visProp.radius = val; 748 } 749 750 /** 751 * @ignore 752 */ 753 el.Radius = function () { 754 var r = Type.evaluate(val); 755 if (r === "auto") { 756 return this.autoRadius(); 757 } 758 return r; 759 }; 760 }; 761 762 /** 763 * @deprecated 764 * @ignore 765 */ 766 el.getRadius = function () { 767 JXG.deprecated("Sector.getRadius()", "Sector.Radius()"); 768 return this.Radius(); 769 }; 770 771 /** 772 * Length of the sector's arc or the angle in various units, see {@link Arc#Value}. 773 * @memberOf Sector.prototype 774 * @name Value 775 * @function 776 * @param {String} unit 777 * @returns {Number} The arc length or the angle value in various units. 778 * @see Arc#Value 779 */ 780 el.Value = function(unit) { 781 return this.arc.Value(unit); 782 }; 783 784 /** 785 * Arc length. 786 * @memberOf Sector.prototype 787 * @name L 788 * @returns {Number} Length of the sector's arc. 789 * @function 790 * @see Arc#L 791 */ 792 el.L = function() { 793 return this.arc.L(); 794 }; 795 796 /** 797 * Area of the sector. 798 * @memberOf Sector.prototype 799 * @name Area 800 * @function 801 * @returns {Number} The area of the sector. 802 */ 803 el.Area = function () { 804 var r = this.Radius(); 805 806 return 0.5 * r * r * this.Value('radians'); 807 }; 808 809 /** 810 * Sector perimeter, i.e. arc length plus 2 * radius. 811 * @memberOf Sector.prototype 812 * @name Perimeter 813 * @function 814 * @returns {Number} Perimeter of sector. 815 */ 816 el.Perimeter = function () { 817 return this.L() + 2 * this.Radius(); 818 }; 819 820 if (type === "3points") { 821 /** 822 * Moves the sector by the difference of two coordinates. 823 * @memberOf Sector.prototype 824 * @name setPositionDirectly 825 * @function 826 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 827 * @param {Array} coords coordinates in screen/user units 828 * @param {Array} oldcoords previous coordinates in screen/user units 829 * @returns {JXG.Curve} this element 830 * @private 831 */ 832 el.setPositionDirectly = function (method, coords, oldcoords) { 833 var dc, t, 834 c = new Coords(method, coords, this.board), 835 oldc = new Coords(method, oldcoords, this.board); 836 837 if (!el.point1.draggable() || !el.point2.draggable() || !el.point3.draggable()) { 838 return this; 839 } 840 841 dc = Statistics.subtract(c.usrCoords, oldc.usrCoords); 842 t = this.board.create("transform", dc.slice(1), { type: "translate" }); 843 t.applyOnce([el.point1, el.point2, el.point3]); 844 845 return this; 846 }; 847 } 848 849 el.methodMap = JXG.deepCopy(el.methodMap, { 850 radius: "Radius", 851 Radius: "Radius", 852 getRadius: "Radius", 853 setRadius: "setRadius", 854 Value: "Value", 855 L: "L", 856 Area: "Area", 857 Perimeter: "Perimeter" 858 }); 859 860 return el; 861 }; 862 863 JXG.registerElement("sector", JXG.createSector); 864 865 /** 866 * @class A circumcircle sector is different from a {@link Sector} mostly in the way the parent elements are interpreted. 867 * At first, the circum center is determined from the three given points. Then the sector is drawn from <tt>p1</tt> through 868 * <tt>p2</tt> to <tt>p3</tt>. 869 * @pseudo 870 * @name CircumcircleSector 871 * @augments Sector 872 * @constructor 873 * @type Sector 874 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 875 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A circumcircle sector is defined by the circumcircle which is determined 876 * by these three given points. The circumcircle sector is always drawn from <tt>p1</tt> through <tt>p2</tt> to <tt>p3</tt>. 877 * @example 878 * // Create an arc out of three free points 879 * var p1 = board.create('point', [1.5, 5.0]), 880 * p2 = board.create('point', [1.0, 0.5]), 881 * p3 = board.create('point', [5.0, 3.0]), 882 * 883 * a = board.create('circumcirclesector', [p1, p2, p3]); 884 * </pre><div class="jxgbox" id="JXG695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04" style="width: 300px; height: 300px;"></div> 885 * <script type="text/javascript"> 886 * (function () { 887 * var board = JXG.JSXGraph.initBoard('JXG695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 888 * p1 = board.create('point', [1.5, 5.0]), 889 * p2 = board.create('point', [1.0, 0.5]), 890 * p3 = board.create('point', [5.0, 3.0]), 891 * 892 * a = board.create('circumcirclesector', [p1, p2, p3]); 893 * })(); 894 * </script><pre> 895 */ 896 JXG.createCircumcircleSector = function (board, parents, attributes) { 897 var el, mp, attr, points; 898 899 points = Type.providePoints(board, parents, attributes, "point"); 900 if (points === false) { 901 throw new Error( 902 "JSXGraph: Can't create circumcircle sector with parent types '" + 903 typeof parents[0] + 904 "' and '" + 905 typeof parents[1] + 906 "' and '" + 907 typeof parents[2] + 908 "'." 909 ); 910 } 911 912 mp = board.create("circumcenter", points.slice(0, 3), attr); 913 mp.dump = false; 914 915 attr = Type.copyAttributes(attributes, board.options, "circumcirclesector"); 916 el = board.create("sector", [mp, points[0], points[2], points[1]], attr); 917 918 el.elType = "circumcirclesector"; 919 el.setParents(points); 920 921 /** 922 * Center of the circumcirclesector 923 * @memberOf CircumcircleSector.prototype 924 * @name center 925 * @type Circumcenter 926 */ 927 el.center = mp; 928 el.subs = { 929 center: mp 930 }; 931 932 return el; 933 }; 934 935 JXG.registerElement("circumcirclesector", JXG.createCircumcircleSector); 936 937 /** 938 * @class A minor sector is a sector of a circle having measure less than or equal to 939 * 180 degrees (pi radians). It is defined by a center, one point that 940 * defines the radius, and a third point that defines the angle of the sector. 941 * @pseudo 942 * @name MinorSector 943 * @augments Curve 944 * @constructor 945 * @type JXG.Curve 946 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 947 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor sector is a sector of a circle around p1 having measure less than or equal to 948 * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3. 949 * @example 950 * // Create sector out of three free points 951 * var p1 = board.create('point', [2.0, 2.0]); 952 * var p2 = board.create('point', [1.0, 0.5]); 953 * var p3 = board.create('point', [3.5, 1.0]); 954 * 955 * var a = board.create('minorsector', [p1, p2, p3]); 956 * </pre><div class="jxgbox" id="JXGaf27ddcc-265f-428f-90dd-d31ace945800" style="width: 300px; height: 300px;"></div> 957 * <script type="text/javascript"> 958 * (function () { 959 * var board = JXG.JSXGraph.initBoard('JXGaf27ddcc-265f-428f-90dd-d31ace945800', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 960 * p1 = board.create('point', [2.0, 2.0]), 961 * p2 = board.create('point', [1.0, 0.5]), 962 * p3 = board.create('point', [3.5, 1.0]), 963 * 964 * a = board.create('minorsector', [p1, p2, p3]); 965 * })(); 966 * </script><pre> 967 * 968 * @example 969 * var A = board.create('point', [3, -2]), 970 * B = board.create('point', [-2, -2]), 971 * C = board.create('point', [0, 4]); 972 * 973 * var angle = board.create('minorsector', [B, A, C], { 974 * strokeWidth: 0, 975 * arc: { 976 * visible: true, 977 * strokeWidth: 3, 978 * lastArrow: {size: 4}, 979 * firstArrow: {size: 4} 980 * } 981 * }); 982 * //angle.arc.setAttribute({firstArrow: false}); 983 * angle.arc.setAttribute({lastArrow: false}); 984 * 985 * 986 * </pre><div id="JXGdddf3c8f-4b0c-4268-8171-8fcd30e71f60" class="jxgbox" style="width: 300px; height: 300px;"></div> 987 * <script type="text/javascript"> 988 * (function() { 989 * var board = JXG.JSXGraph.initBoard('JXGdddf3c8f-4b0c-4268-8171-8fcd30e71f60', 990 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 991 * var A = board.create('point', [3, -2]), 992 * B = board.create('point', [-2, -2]), 993 * C = board.create('point', [0, 4]); 994 * 995 * var angle = board.create('minorsector', [B, A, C], { 996 * strokeWidth: 0, 997 * arc: { 998 * visible: true, 999 * strokeWidth: 3, 1000 * lastArrow: {size: 4}, 1001 * firstArrow: {size: 4} 1002 * } 1003 * }); 1004 * //angle.arc.setAttribute({firstArrow: false}); 1005 * angle.arc.setAttribute({lastArrow: false}); 1006 * 1007 * 1008 * })(); 1009 * 1010 * </script><pre> 1011 * 1012 */ 1013 JXG.createMinorSector = function (board, parents, attributes) { 1014 attributes.selection = "minor"; 1015 return JXG.createSector(board, parents, attributes); 1016 }; 1017 1018 JXG.registerElement("minorsector", JXG.createMinorSector); 1019 1020 /** 1021 * @class A major sector is a sector of a circle having measure greater than or equal to 1022 * 180 degrees (pi radians). It is defined by a center, one point that 1023 * defines the radius, and a third point that defines the angle of the sector. 1024 * @pseudo 1025 * @name MajorSector 1026 * @augments Curve 1027 * @constructor 1028 * @type JXG.Curve 1029 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1030 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Major sector is a sector of a circle around p1 having measure greater than or equal to 1031 * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3. 1032 * @example 1033 * // Create an arc out of three free points 1034 * var p1 = board.create('point', [2.0, 2.0]); 1035 * var p2 = board.create('point', [1.0, 0.5]); 1036 * var p3 = board.create('point', [3.5, 1.0]); 1037 * 1038 * var a = board.create('majorsector', [p1, p2, p3]); 1039 * </pre><div class="jxgbox" id="JXG83c6561f-7561-4047-b98d-036248a00932" style="width: 300px; height: 300px;"></div> 1040 * <script type="text/javascript"> 1041 * (function () { 1042 * var board = JXG.JSXGraph.initBoard('JXG83c6561f-7561-4047-b98d-036248a00932', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 1043 * p1 = board.create('point', [2.0, 2.0]), 1044 * p2 = board.create('point', [1.0, 0.5]), 1045 * p3 = board.create('point', [3.5, 1.0]), 1046 * 1047 * a = board.create('majorsector', [p1, p2, p3]); 1048 * })(); 1049 * </script><pre> 1050 */ 1051 JXG.createMajorSector = function (board, parents, attributes) { 1052 attributes.selection = "major"; 1053 return JXG.createSector(board, parents, attributes); 1054 }; 1055 1056 JXG.registerElement("majorsector", JXG.createMajorSector); 1057 1058 /** 1059 * @class The angle element is used to denote an angle defined by three points. Visually it is just a {@link Sector} 1060 * element with a radius not defined by the parent elements but by an attribute <tt>radius</tt>. As opposed to the sector, 1061 * an angle has two angle points and no radius point. 1062 * Sector is displayed if type=="sector". 1063 * If type=="square", instead of a sector a parallelogram is displayed. 1064 * In case of type=="auto", a square is displayed if the angle is near orthogonal. 1065 * If no name is provided the angle label is automatically set to a lower greek letter. 1066 * @pseudo 1067 * @name Angle 1068 * @augments Sector 1069 * @constructor 1070 * @type Sector 1071 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1072 * First possibility of input parameters are: 1073 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 An angle is always drawn counterclockwise from <tt>p1</tt> to 1074 * <tt>p3</tt> around <tt>p2</tt>. 1075 * 1076 * Second possibility of input parameters are: 1077 * @param {JXG.Line_JXG.Line_array|number_array|number} line, line2, coords1 or direction1, coords2 or direction2, radius The angle is defined by two lines. 1078 * The two legs which define the angle are given by two coordinate arrays. 1079 * The points given by these coordinate arrays are projected initially (i.e. only once) onto the two lines. 1080 * The other possibility is to supply directions (+/- 1). 1081 * 1082 * @example 1083 * // Create an angle out of three free points 1084 * var p1 = board.create('point', [5.0, 3.0]), 1085 * p2 = board.create('point', [1.0, 0.5]), 1086 * p3 = board.create('point', [1.5, 5.0]), 1087 * 1088 * a = board.create('angle', [p1, p2, p3]), 1089 * t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]); 1090 * </pre><div class="jxgbox" id="JXGa34151f9-bb26-480a-8d6e-9b8cbf789ae5" style="width: 300px; height: 300px;"></div> 1091 * <script type="text/javascript"> 1092 * (function () { 1093 * var board = JXG.JSXGraph.initBoard('JXGa34151f9-bb26-480a-8d6e-9b8cbf789ae5', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 1094 * p1 = board.create('point', [5.0, 3.0]), 1095 * p2 = board.create('point', [1.0, 0.5]), 1096 * p3 = board.create('point', [1.5, 5.0]), 1097 * 1098 * a = board.create('angle', [p1, p2, p3]), 1099 * t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]); 1100 * })(); 1101 * </script><pre> 1102 * 1103 * @example 1104 * // Create an angle out of two lines and two directions 1105 * var p1 = board.create('point', [-1, 4]), 1106 * p2 = board.create('point', [4, 1]), 1107 * q1 = board.create('point', [-2, -3]), 1108 * q2 = board.create('point', [4,3]), 1109 * 1110 * li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}), 1111 * li2 = board.create('line', [q1,q2], {lastArrow:true}), 1112 * 1113 * a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }), 1114 * a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 }); 1115 * 1116 * 1117 * </pre><div class="jxgbox" id="JXG3a667ddd-63dc-4594-b5f1-afac969b371f" style="width: 300px; height: 300px;"></div> 1118 * <script type="text/javascript"> 1119 * (function () { 1120 * var board = JXG.JSXGraph.initBoard('JXG3a667ddd-63dc-4594-b5f1-afac969b371f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 1121 * p1 = board.create('point', [-1, 4]), 1122 * p2 = board.create('point', [4, 1]), 1123 * q1 = board.create('point', [-2, -3]), 1124 * q2 = board.create('point', [4,3]), 1125 * 1126 * li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}), 1127 * li2 = board.create('line', [q1,q2], {lastArrow:true}), 1128 * 1129 * a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }), 1130 * a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 }); 1131 * })(); 1132 * </script><pre> 1133 * 1134 * 1135 * @example 1136 * // Display the angle value instead of the name 1137 * var p1 = board.create('point', [0,2]); 1138 * var p2 = board.create('point', [0,0]); 1139 * var p3 = board.create('point', [-2,0.2]); 1140 * 1141 * var a = board.create('angle', [p1, p2, p3], { 1142 * radius: 1, 1143 * name: function() { 1144 * return JXG.Math.Geometry.trueAngle(p1, p2, p3).toFixed(1) + '°'; 1145 * }}); 1146 * 1147 * </pre><div id="JXGc813f601-8dd3-4030-9892-25c6d8671512" class="jxgbox" style="width: 300px; height: 300px;"></div> 1148 * <script type="text/javascript"> 1149 * (function() { 1150 * var board = JXG.JSXGraph.initBoard('JXGc813f601-8dd3-4030-9892-25c6d8671512', 1151 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1152 * 1153 * var p1 = board.create('point', [0,2]); 1154 * var p2 = board.create('point', [0,0]); 1155 * var p3 = board.create('point', [-2,0.2]); 1156 * 1157 * var a = board.create('angle', [p1, p2, p3], { 1158 * radius: 1, 1159 * name: function() { 1160 * return JXG.Math.Geometry.trueAngle(p1, p2, p3).toFixed(1) + '°'; 1161 * }}); 1162 * 1163 * })(); 1164 * 1165 * </script><pre> 1166 * 1167 * 1168 * @example 1169 * // Apply a transformation to an angle. 1170 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 1171 * var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]); 1172 * var an2 = board.create('curve', [an1, t]); 1173 * 1174 * </pre><div id="JXG4c8d9ed8-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1175 * <script type="text/javascript"> 1176 * (function() { 1177 * var board = JXG.JSXGraph.initBoard('JXG4c8d9ed8-6339-11e8-9fb9-901b0e1b8723', 1178 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1179 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 1180 * var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]); 1181 * var an2 = board.create('curve', [an1, t]); 1182 * 1183 * })(); 1184 * 1185 * </script><pre> 1186 * 1187 */ 1188 JXG.createAngle = function (board, parents, attributes) { 1189 var el, 1190 radius, attr, attrsub, 1191 i, points, 1192 type = "invalid"; 1193 1194 // Two lines or three points? 1195 if ( 1196 parents[0].elementClass === Const.OBJECT_CLASS_LINE && 1197 parents[1].elementClass === Const.OBJECT_CLASS_LINE && 1198 (Type.isArray(parents[2]) || Type.isNumber(parents[2])) && 1199 (Type.isArray(parents[3]) || Type.isNumber(parents[3])) 1200 ) { 1201 type = "2lines"; 1202 } else { 1203 attr = { 1204 name: '' 1205 }; 1206 points = Type.providePoints(board, parents, attr, "point"); 1207 if (points === false) { 1208 throw new Error( 1209 "JSXGraph: Can't create angle with parent types '" + 1210 typeof parents[0] + 1211 "' and '" + 1212 typeof parents[1] + 1213 "' and '" + 1214 typeof parents[2] + 1215 "'." 1216 ); 1217 } 1218 type = "3points"; 1219 } 1220 1221 attr = Type.copyAttributes(attributes, board.options, "angle"); 1222 1223 // If empty, create a new name 1224 if (!Type.exists(attr.name) || attr.name === "") { 1225 attr.name = board.generateName({ type: Const.OBJECT_TYPE_ANGLE }); 1226 } 1227 1228 if (Type.exists(attr.radius)) { 1229 radius = attr.radius; 1230 } else { 1231 radius = 0; 1232 } 1233 1234 board.suspendUpdate(); // Necessary for immediate availability of radius. 1235 if (type === "2lines") { 1236 parents.push(radius); 1237 el = board.create("sector", parents, attr); 1238 /** 1239 * @class 1240 * @ignore 1241 */ 1242 el.updateDataArraySector = el.updateDataArray; 1243 1244 // TODO 1245 /** 1246 * @class 1247 * @ignore 1248 */ 1249 el.setAngle = function (val) {}; 1250 /** 1251 * @class 1252 * @ignore 1253 */ 1254 el.free = function (val) {}; 1255 } else { 1256 el = board.create("sector", [points[1], points[0], points[2]], attr); 1257 el.arc.visProp.priv = true; 1258 1259 /** 1260 * The point defining the radius of the angle element. 1261 * Alias for {@link Sector#radiuspoint}. 1262 * @type JXG.Point 1263 * @name point 1264 * @memberOf Angle.prototype 1265 * 1266 */ 1267 el.point = el.point2 = el.radiuspoint = points[0]; 1268 1269 /** 1270 * Helper point for angles of type 'square'. 1271 * @type JXG.Point 1272 * @name pointsquare 1273 * @memberOf Angle.prototype 1274 */ 1275 el.pointsquare = el.point3 = el.anglepoint = points[2]; 1276 1277 /** 1278 * @ignore 1279 */ 1280 el.Radius = function () { 1281 // Set the angle radius, also @see @link Sector#autoRadius 1282 var r = Type.evaluate(radius); 1283 if (r === "auto") { 1284 return el.autoRadius(); 1285 } 1286 return r; 1287 }; 1288 1289 /** 1290 * @class 1291 * @ignore 1292 */ 1293 el.updateDataArraySector = function () { 1294 var A = this.point2, 1295 B = this.point1, 1296 C = this.point3, 1297 r = this.Radius(), 1298 d = B.Dist(A), 1299 ar, 1300 phi, 1301 sgn = 1, 1302 vp_s = Type.evaluate(this.visProp.selection); 1303 1304 phi = Geometry.rad(A, B, C); 1305 if ((vp_s === "minor" && phi > Math.PI) || (vp_s === "major" && phi < Math.PI)) { 1306 sgn = -1; 1307 } 1308 1309 A = A.coords.usrCoords; 1310 B = B.coords.usrCoords; 1311 C = C.coords.usrCoords; 1312 1313 A = [1, B[1] + ((A[1] - B[1]) * r) / d, B[2] + ((A[2] - B[2]) * r) / d]; 1314 C = [1, B[1] + ((C[1] - B[1]) * r) / d, B[2] + ((C[2] - B[2]) * r) / d]; 1315 1316 ar = Geometry.bezierArc(A, B, C, true, sgn); 1317 1318 this.dataX = ar[0]; 1319 this.dataY = ar[1]; 1320 this.bezierDegree = 3; 1321 }; 1322 1323 /** 1324 * Set an angle to a prescribed value given in radians. 1325 * This is only possible if the third point of the angle, i.e. 1326 * the anglepoint is a free point. 1327 * Removing the constraint again is done by calling "angle.free()". 1328 * 1329 * Changing the angle requires to call the method "free()": 1330 * 1331 * <pre> 1332 * angle.setAngle(Math.PI / 6); 1333 * // ... 1334 * angle.free().setAngle(Math.PI / 4); 1335 * </pre> 1336 * 1337 * @name setAngle 1338 * @memberof Angle.prototype 1339 * @function 1340 * @param {Number|Function} val Number or Function which returns the size of the angle in Radians 1341 * @returns {Object} Pointer to the angle element.. 1342 * @see Angle#free 1343 * 1344 * @example 1345 * var p1, p2, p3, c, a, s; 1346 * 1347 * p1 = board.create('point',[0,0]); 1348 * p2 = board.create('point',[5,0]); 1349 * p3 = board.create('point',[0,5]); 1350 * 1351 * c1 = board.create('circle',[p1, p2]); 1352 * 1353 * a = board.create('angle',[p2, p1, p3], {radius:3}); 1354 * 1355 * a.setAngle(function() { 1356 * return Math.PI / 3; 1357 * }); 1358 * board.update(); 1359 * 1360 * </pre><div id="JXG987c-394f-11e6-af4a-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1361 * <script type="text/javascript"> 1362 * (function() { 1363 * var board = JXG.JSXGraph.initBoard('JXG987c-394f-11e6-af4a-901b0e1b8723', 1364 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1365 * var p1, p2, p3, c, a, s; 1366 * 1367 * p1 = board.create('point',[0,0]); 1368 * p2 = board.create('point',[5,0]); 1369 * p3 = board.create('point',[0,5]); 1370 * 1371 * c1 = board.create('circle',[p1, p2]); 1372 * 1373 * a = board.create('angle',[p2, p1, p3], {radius: 3}); 1374 * 1375 * a.setAngle(function() { 1376 * return Math.PI / 3; 1377 * }); 1378 * board.update(); 1379 * 1380 * })(); 1381 * 1382 * </script><pre> 1383 * 1384 * @example 1385 * var p1, p2, p3, c, a, s; 1386 * 1387 * p1 = board.create('point',[0,0]); 1388 * p2 = board.create('point',[5,0]); 1389 * p3 = board.create('point',[0,5]); 1390 * 1391 * c1 = board.create('circle',[p1, p2]); 1392 * 1393 * a = board.create('angle',[p2, p1, p3], {radius:3}); 1394 * s = board.create('slider',[[-2,1], [2,1], [0, Math.PI*0.5, 2*Math.PI]]); 1395 * 1396 * a.setAngle(function() { 1397 * return s.Value(); 1398 * }); 1399 * board.update(); 1400 * 1401 * </pre><div id="JXG99957b1c-394f-11e6-af4a-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1402 * <script type="text/javascript"> 1403 * (function() { 1404 * var board = JXG.JSXGraph.initBoard('JXG99957b1c-394f-11e6-af4a-901b0e1b8723', 1405 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1406 * var p1, p2, p3, c, a, s; 1407 * 1408 * p1 = board.create('point',[0,0]); 1409 * p2 = board.create('point',[5,0]); 1410 * p3 = board.create('point',[0,5]); 1411 * 1412 * c1 = board.create('circle',[p1, p2]); 1413 * 1414 * a = board.create('angle',[p2, p1, p3], {radius: 3}); 1415 * s = board.create('slider',[[-2,1], [2,1], [0, Math.PI*0.5, 2*Math.PI]]); 1416 * 1417 * a.setAngle(function() { 1418 * return s.Value(); 1419 * }); 1420 * board.update(); 1421 * 1422 * })(); 1423 * 1424 * </script><pre> 1425 * 1426 */ 1427 el.setAngle = function (val) { 1428 var t1, t2, 1429 val2, 1430 p = this.anglepoint, 1431 q = this.radiuspoint; 1432 1433 if (p.draggable()) { 1434 t1 = this.board.create("transform", [val, this.center], { 1435 type: "rotate" 1436 }); 1437 p.addTransform(q, t1); 1438 // Immediately apply the transformation. 1439 // This prevents that jumping elements can be watched. 1440 t1.update(); 1441 p.moveTo(Mat.matVecMult(t1.matrix, q.coords.usrCoords)); 1442 1443 if (Type.isFunction(val)) { 1444 /** 1445 * @ignore 1446 */ 1447 val2 = function () { 1448 return Math.PI * 2 - val(); 1449 }; 1450 } else { 1451 /** 1452 * @ignore 1453 */ 1454 val2 = function () { 1455 return Math.PI * 2 - val; 1456 }; 1457 } 1458 t2 = this.board.create("transform", [val2, this.center], { 1459 type: "rotate" 1460 }); 1461 p.coords.on("update", function () { 1462 t2.update(); 1463 q.moveTo(Mat.matVecMult(t2.matrix, p.coords.usrCoords)); 1464 }); 1465 1466 p.setParents(q); 1467 1468 this.hasFixedAngle = true; 1469 } 1470 return this; 1471 }; 1472 1473 /** 1474 * Frees an angle from a prescribed value. This is only relevant if the angle size has been set by 1475 * "setAngle()" previously. The anglepoint is set to a free point. 1476 * @name free 1477 * @function 1478 * @memberof Angle.prototype 1479 * @returns {Object} Pointer to the angle element.. 1480 * @see Angle#setAngle 1481 */ 1482 el.free = function () { 1483 var p = this.anglepoint; 1484 1485 if (p.transformations.length > 0) { 1486 p.transformations.pop(); 1487 p.isDraggable = true; 1488 p.parents = []; 1489 1490 p.coords.off("update"); 1491 } 1492 1493 this.hasFixedAngle = false; 1494 1495 return this; 1496 }; 1497 1498 el.setParents(points); // Important: This overwrites the parents order in underlying sector 1499 } // end '3points' 1500 1501 // GEONExT compatible labels. 1502 if (Type.exists(el.visProp.text)) { 1503 el.label.setText(Type.evaluate(el.visProp.text)); 1504 } 1505 1506 el.elType = "angle"; 1507 el.type = Const.OBJECT_TYPE_ANGLE; 1508 el.subs = {}; 1509 1510 /** 1511 * @class 1512 * @ignore 1513 */ 1514 el.updateDataArraySquare = function () { 1515 var A, B, C, 1516 d1, d2, v, l1, l2, 1517 r = this.Radius(); 1518 1519 if (type === "2lines") { 1520 // This is necessary to update this.point1, this.point2, this.point3. 1521 this.updateDataArraySector(); 1522 } 1523 1524 A = this.point2; 1525 B = this.point1; 1526 C = this.point3; 1527 1528 A = A.coords.usrCoords; 1529 B = B.coords.usrCoords; 1530 C = C.coords.usrCoords; 1531 1532 d1 = Geometry.distance(A, B, 3); 1533 d2 = Geometry.distance(C, B, 3); 1534 1535 // In case of type=='2lines' this is redundant, because r == d1 == d2 1536 A = [1, B[1] + ((A[1] - B[1]) * r) / d1, B[2] + ((A[2] - B[2]) * r) / d1]; 1537 C = [1, B[1] + ((C[1] - B[1]) * r) / d2, B[2] + ((C[2] - B[2]) * r) / d2]; 1538 1539 v = Mat.crossProduct(C, B); 1540 l1 = [-A[1] * v[1] - A[2] * v[2], A[0] * v[1], A[0] * v[2]]; 1541 v = Mat.crossProduct(A, B); 1542 l2 = [-C[1] * v[1] - C[2] * v[2], C[0] * v[1], C[0] * v[2]]; 1543 1544 v = Mat.crossProduct(l1, l2); 1545 v[1] /= v[0]; 1546 v[2] /= v[0]; 1547 1548 this.dataX = [B[1], A[1], v[1], C[1], B[1]]; 1549 this.dataY = [B[2], A[2], v[2], C[2], B[2]]; 1550 1551 this.bezierDegree = 1; 1552 }; 1553 1554 /** 1555 * @class 1556 * @ignore 1557 */ 1558 el.updateDataArrayNone = function () { 1559 this.dataX = [NaN]; 1560 this.dataY = [NaN]; 1561 this.bezierDegree = 1; 1562 }; 1563 1564 /** 1565 * @class 1566 * @ignore 1567 */ 1568 el.updateDataArray = function () { 1569 var type = Type.evaluate(this.visProp.type), 1570 deg = Geometry.trueAngle(this.point2, this.point1, this.point3), 1571 vp_s = Type.evaluate(this.visProp.selection); 1572 1573 if ((vp_s === "minor" && deg > 180.0) || (vp_s === "major" && deg < 180.0)) { 1574 deg = 360.0 - deg; 1575 } 1576 1577 if (Math.abs(deg - 90.0) < Type.evaluate(this.visProp.orthosensitivity) + Mat.eps) { 1578 type = Type.evaluate(this.visProp.orthotype); 1579 } 1580 1581 if (type === "none") { 1582 this.updateDataArrayNone(); 1583 } else if (type === "square") { 1584 this.updateDataArraySquare(); 1585 } else if (type === "sector") { 1586 this.updateDataArraySector(); 1587 } else if (type === "sectordot") { 1588 this.updateDataArraySector(); 1589 if (!this.dot.visProp.visible) { 1590 this.dot.setAttribute({ visible: true }); 1591 } 1592 } 1593 1594 if (!this.visProp.visible || (type !== "sectordot" && this.dot.visProp.visible)) { 1595 this.dot.setAttribute({ visible: false }); 1596 } 1597 }; 1598 1599 attrsub = Type.copyAttributes(attributes, board.options, "angle", "dot"); 1600 /** 1601 * Indicates a right angle. Invisible by default, use <tt>dot.visible: true</tt> to show. 1602 * Though this dot indicates a right angle, it can be visible even if the angle is not a right 1603 * one. 1604 * @type JXG.Point 1605 * @name dot 1606 * @memberOf Angle.prototype 1607 */ 1608 el.dot = board.create( 1609 "point", 1610 [ 1611 function () { 1612 var A, B, r, d, a2, co, si, mat, vp_s; 1613 1614 if (Type.exists(el.dot) && !el.dot.visProp.visible) { 1615 return [0, 0]; 1616 } 1617 1618 A = el.point2.coords.usrCoords; 1619 B = el.point1.coords.usrCoords; 1620 r = el.Radius(); 1621 d = Geometry.distance(A, B, 3); 1622 a2 = Geometry.rad(el.point2, el.point1, el.point3); 1623 1624 vp_s = Type.evaluate(el.visProp.selection); 1625 if ((vp_s === "minor" && a2 > Math.PI) || (vp_s === "major" && a2 < Math.PI)) { 1626 a2 = -(2 * Math.PI - a2); 1627 } 1628 a2 *= 0.5; 1629 1630 co = Math.cos(a2); 1631 si = Math.sin(a2); 1632 1633 A = [1, B[1] + ((A[1] - B[1]) * r) / d, B[2] + ((A[2] - B[2]) * r) / d]; 1634 1635 mat = [ 1636 [1, 0, 0], 1637 [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5], 1638 [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5, co * 0.5] 1639 ]; 1640 return Mat.matVecMult(mat, A); 1641 } 1642 ], 1643 attrsub 1644 ); 1645 1646 el.dot.dump = false; 1647 el.subs.dot = el.dot; 1648 1649 if (type === "2lines") { 1650 for (i = 0; i < 2; i++) { 1651 board.select(parents[i]).addChild(el.dot); 1652 } 1653 } else { 1654 for (i = 0; i < 3; i++) { 1655 board.select(points[i]).addChild(el.dot); 1656 } 1657 } 1658 board.unsuspendUpdate(); 1659 1660 /** 1661 * Returns the value of the angle. 1662 * @memberOf Angle.prototype 1663 * @name Value 1664 * @function 1665 * @param {String} [unit='length'] Unit of the returned values. Possible units are 1666 * <ul> 1667 * <li> 'radians' (default): angle value in radians 1668 * <li> 'degrees': angle value in degrees 1669 * <li> 'semicircle': angle value in radians as a multiple of π, e.g. if the angle is 1.5π, 1.5 will be returned. 1670 * <li> 'circle': angle value in radians as a multiple of 2π 1671 * <li> 'length': length of the arc line of the angle 1672 * </ul> 1673 * It is sufficient to supply the first three characters of the unit, e.g. 'len'. 1674 * @returns {Number} angle value in various units. 1675 * @see Sector#L 1676 * @see Arc#Value 1677 * @example 1678 * var A, B, C, ang, 1679 * r = 0.5; 1680 * A = board.create("point", [3, 0]); 1681 * B = board.create("point", [0, 0]); 1682 * C = board.create("point", [2, 2]); 1683 * ang = board.create("angle", [A, B, C], {radius: r}); 1684 * 1685 * console.log(ang.Value()); 1686 * // Output Math.PI * 0.25 1687 * 1688 * console.log(ang.Value('radian')); 1689 * // Output Math.PI * 0.25 1690 * 1691 * console.log(ang.Value('degree'); 1692 * // Output 45 1693 * 1694 * console.log(ang.Value('semicircle')); 1695 * // Output 0.25 1696 * 1697 * console.log(ang.Value('circle')); 1698 * // Output 0.125 1699 * 1700 * console.log(ang.Value('length')); 1701 * // Output r * Math.PI * 0.25 1702 * 1703 * console.log(ang.L()); 1704 * // Output r * Math.PI * 0.25 1705 * 1706 */ 1707 el.Value = function(unit) { 1708 unit = unit || 'radians'; 1709 if (unit === '') { 1710 unit = 'radians'; 1711 } 1712 return el.arc.Value(unit); 1713 }; 1714 1715 // documented in GeometryElement 1716 /** 1717 * @class 1718 * @ignore 1719 */ 1720 el.getLabelAnchor = function () { 1721 var vec, 1722 dx = 12, 1723 A, B, r, d, a2, co, si, mat, 1724 vp_s = Type.evaluate(el.visProp.selection), 1725 l_vp = this.label ? this.label.visProp : this.visProp.label; 1726 1727 // If this is uncommented, the angle label can not be dragged 1728 //if (Type.exists(this.label)) { 1729 // this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board); 1730 //} 1731 1732 if (Type.exists(this.label) && Type.exists(this.label.visProp.fontsize)) { 1733 dx = Type.evaluate(this.label.visProp.fontsize); 1734 } 1735 dx /= this.board.unitX; 1736 1737 A = el.point2.coords.usrCoords; 1738 B = el.point1.coords.usrCoords; 1739 r = el.Radius(); 1740 d = Geometry.distance(A, B, 3); 1741 a2 = Geometry.rad(el.point2, el.point1, el.point3); 1742 if ((vp_s === "minor" && a2 > Math.PI) || (vp_s === "major" && a2 < Math.PI)) { 1743 a2 = -(2 * Math.PI - a2); 1744 } 1745 a2 *= 0.5; 1746 co = Math.cos(a2); 1747 si = Math.sin(a2); 1748 1749 A = [1, B[1] + ((A[1] - B[1]) * r) / d, B[2] + ((A[2] - B[2]) * r) / d]; 1750 1751 mat = [ 1752 [1, 0, 0], 1753 [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5], 1754 [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5, co * 0.5] 1755 ]; 1756 vec = Mat.matVecMult(mat, A); 1757 vec[1] /= vec[0]; 1758 vec[2] /= vec[0]; 1759 vec[0] /= vec[0]; 1760 1761 d = Geometry.distance(vec, B, 3); 1762 vec = [ 1763 vec[0], 1764 B[1] + ((vec[1] - B[1]) * (r + dx)) / d, 1765 B[2] + ((vec[2] - B[2]) * (r + dx)) / d 1766 ]; 1767 1768 l_vp.position = Geometry.calcLabelQuadrant(Geometry.rad([1, 0], [0, 0], vec)); 1769 1770 return new Coords(Const.COORDS_BY_USER, vec, this.board); 1771 }; 1772 1773 el.methodMap = Type.deepCopy(el.methodMap, { 1774 setAngle: "setAngle", 1775 Value: "Value", 1776 free: "free" 1777 }); 1778 1779 return el; 1780 }; 1781 1782 JXG.registerElement("angle", JXG.createAngle); 1783 1784 /** 1785 * @class A non-reflex angle is the acute or obtuse instance of an angle. 1786 * It is defined by a center, one point that 1787 * defines the radius, and a third point that defines the angle of the sector. 1788 * @pseudo 1789 * @name NonReflexAngle 1790 * @augments Angle 1791 * @constructor 1792 * @type Sector 1793 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1794 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor sector is a sector of a circle around p1 having measure less than or equal to 1795 * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3. 1796 * @example 1797 * // Create a non-reflex angle out of three free points 1798 * var p1 = board.create('point', [5.0, 3.0]), 1799 * p2 = board.create('point', [1.0, 0.5]), 1800 * p3 = board.create('point', [1.5, 5.0]), 1801 * 1802 * a = board.create('nonreflexangle', [p1, p2, p3], {radius: 2}), 1803 * t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]); 1804 * </pre><div class="jxgbox" id="JXGd0ab6d6b-63a7-48b2-8749-b02bb5e744f9" style="width: 300px; height: 300px;"></div> 1805 * <script type="text/javascript"> 1806 * (function () { 1807 * var board = JXG.JSXGraph.initBoard('JXGd0ab6d6b-63a7-48b2-8749-b02bb5e744f9', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 1808 * p1 = board.create('point', [5.0, 3.0]), 1809 * p2 = board.create('point', [1.0, 0.5]), 1810 * p3 = board.create('point', [1.5, 5.0]), 1811 * 1812 * a = board.create('nonreflexangle', [p1, p2, p3], {radius: 2}), 1813 * t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]); 1814 * })(); 1815 * </script><pre> 1816 */ 1817 JXG.createNonreflexAngle = function (board, parents, attributes) { 1818 var el; 1819 1820 attributes.selection = "minor"; 1821 attributes = Type.copyAttributes(attributes, board.options, 'nonreflexangle'); 1822 el = JXG.createAngle(board, parents, attributes); 1823 1824 // Documented in createAngle 1825 el.Value = function (unit) { 1826 var rad = Geometry.rad(this.point2, this.point1, this.point3); 1827 unit = unit || 'radians'; 1828 if (unit === '') { 1829 unit = 'radians'; 1830 } 1831 rad = (rad < Math.PI) ? rad : 2.0 * Math.PI - rad; 1832 1833 return this.arc.Value(unit, rad); 1834 }; 1835 return el; 1836 }; 1837 1838 JXG.registerElement("nonreflexangle", JXG.createNonreflexAngle); 1839 1840 /** 1841 * @class A reflex angle is the neither acute nor obtuse instance of an angle. 1842 * It is defined by a center, one point that 1843 * defines the radius, and a third point that defines the angle of the sector. 1844 * @pseudo 1845 * @name ReflexAngle 1846 * @augments Angle 1847 * @constructor 1848 * @type Sector 1849 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1850 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor sector is a sector of a circle around p1 having measure less than or equal to 1851 * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3. 1852 * @example 1853 * // Create a non-reflex angle out of three free points 1854 * var p1 = board.create('point', [5.0, 3.0]), 1855 * p2 = board.create('point', [1.0, 0.5]), 1856 * p3 = board.create('point', [1.5, 5.0]), 1857 * 1858 * a = board.create('reflexangle', [p1, p2, p3], {radius: 2}), 1859 * t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]); 1860 * </pre><div class="jxgbox" id="JXGf2a577f2-553d-4f9f-a895-2d6d4b8c60e8" style="width: 300px; height: 300px;"></div> 1861 * <script type="text/javascript"> 1862 * (function () { 1863 * var board = JXG.JSXGraph.initBoard('JXGf2a577f2-553d-4f9f-a895-2d6d4b8c60e8', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 1864 * p1 = board.create('point', [5.0, 3.0]), 1865 * p2 = board.create('point', [1.0, 0.5]), 1866 * p3 = board.create('point', [1.5, 5.0]), 1867 * 1868 * a = board.create('reflexangle', [p1, p2, p3], {radius: 2}), 1869 * t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]); 1870 * })(); 1871 * </script><pre> 1872 */ 1873 JXG.createReflexAngle = function (board, parents, attributes) { 1874 var el; 1875 1876 attributes.selection = "major"; 1877 attributes = Type.copyAttributes(attributes, board.options, 'reflexangle'); 1878 el = JXG.createAngle(board, parents, attributes); 1879 1880 // Documented in createAngle 1881 el.Value = function (unit) { 1882 var rad = Geometry.rad(this.point2, this.point1, this.point3); 1883 unit = unit || 'radians'; 1884 if (unit === '') { 1885 unit = 'radians'; 1886 } 1887 rad = (rad >= Math.PI) ? rad : 2.0 * Math.PI - rad; 1888 1889 return this.arc.Value(unit, rad); 1890 }; 1891 1892 return el; 1893 }; 1894 1895 JXG.registerElement("reflexangle", JXG.createReflexAngle); 1896