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