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