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