1 /* 2 Copyright 2008-2013 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', 'base/point', 'base/curve', 52 'base/transformation', 'element/composition' 53 ], function (JXG, Geometry, Mat, Statistics, Coords, Const, Type, Point, Curve, Transform, Compositions) { 54 55 "use strict"; 56 57 /** 58 * @class A circular sector is a subarea of the area enclosed by a circle. It is enclosed by two radii and an arc. 59 * @pseudo 60 * @name Sector 61 * @augments JXG.Curve 62 * @constructor 63 * @type JXG.Curve 64 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 65 * 66 * First possiblity of input parameters are: 67 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A sector is defined by three points: The sector's center <tt>p1</tt>, 68 * a second point <tt>p2</tt> defining the radius and a third point <tt>p3</tt> defining the angle of the sector. The 69 * Sector is always drawn counter clockwise from <tt>p2</tt> to <tt>p3</tt> 70 * 71 * Second possibility of input parameters are: 72 * @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. 73 * 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). 74 * The last parameter is the radius of the sector. 75 * 76 * 77 * @example 78 * // Create a sector out of three free points 79 * var p1 = board.create('point', [1.5, 5.0]), 80 * p2 = board.create('point', [1.0, 0.5]), 81 * p3 = board.create('point', [5.0, 3.0]), 82 * 83 * a = board.create('sector', [p1, p2, p3]); 84 * </pre><div id="49f59123-f013-4681-bfd9-338b89893156" style="width: 300px; height: 300px;"></div> 85 * <script type="text/javascript"> 86 * (function () { 87 * var board = JXG.JSXGraph.initBoard('49f59123-f013-4681-bfd9-338b89893156', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 88 * p1 = board.create('point', [1.5, 5.0]), 89 * p2 = board.create('point', [1.0, 0.5]), 90 * p3 = board.create('point', [5.0, 3.0]), 91 * 92 * a = board.create('sector', [p1, p2, p3]); 93 * })(); 94 * </script><pre> 95 * 96 * @example 97 * // Create a sector out of two lines, two directions and a radius 98 * var p1 = board.create('point', [-1, 4]), 99 * p2 = board.create('point', [4, 1]), 100 * q1 = board.create('point', [-2, -3]), 101 * q2 = board.create('point', [4,3]), 102 * 103 * li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}), 104 * li2 = board.create('line', [q1,q2], {lastArrow:true}), 105 * 106 * sec1 = board.create('sector', [li1, li2, [5.5, 0], [4, 3], 3]), 107 * sec2 = board.create('sector', [li1, li2, 1, -1, 4]); 108 * 109 * </pre><div id="bb9e2809-9895-4ff1-adfa-c9c71d50aa53" style="width: 300px; height: 300px;"></div> 110 * <script type="text/javascript"> 111 * (function () { 112 * var board = JXG.JSXGraph.initBoard('bb9e2809-9895-4ff1-adfa-c9c71d50aa53', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 113 * p1 = board.create('point', [-1, 4]), 114 * p2 = board.create('point', [4, 1]), 115 * q1 = board.create('point', [-2, -3]), 116 * q2 = board.create('point', [4,3]), 117 * 118 * li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}), 119 * li2 = board.create('line', [q1,q2], {lastArrow:true}), 120 * 121 * sec1 = board.create('sector', [li1, li2, [5.5, 0], [4, 3], 3]), 122 * sec2 = board.create('sector', [li1, li2, 1, -1, 4]); 123 * })(); 124 * </script><pre> 125 */ 126 JXG.createSector = function (board, parents, attributes) { 127 var el, i, attr, 128 type = 'invalid', 129 s, v, 130 points = ['center', 'radiuspoint', 'anglepoint']; 131 132 // Three points? 133 if (Type.isPoint(parents[0]) && Type.isPoint(parents[1]) && Type.isPoint(parents[2])) { 134 type = '3points'; 135 } else if (parents[0].elementClass === Const.OBJECT_CLASS_LINE && 136 parents[1].elementClass === Const.OBJECT_CLASS_LINE && 137 (Type.isArray(parents[2]) || Type.isNumber(parents[2])) && 138 (Type.isArray(parents[3]) || Type.isNumber(parents[3])) && 139 (Type.isNumber(parents[4]) || Type.isFunction(parents[4]))) { 140 type = '2lines'; 141 } 142 143 if (type === 'invalid') { 144 /** 145 * Second try for 3 point sector 146 */ 147 try { 148 149 for (i = 0; i < parents.length; i++) { 150 if (!Type.isPoint(parents[i])) { 151 attr = Type.copyAttributes(attributes, board.options, 'sector', points[i]); 152 parents[i] = board.create('point', parents[i], attr); 153 } 154 } 155 156 type = '3points'; 157 158 } catch (e) { 159 throw new Error("JSXGraph: Can't create Sector with parent types '" + 160 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + 161 (typeof parents[2]) + "'."); 162 } 163 } 164 165 attr = Type.copyAttributes(attributes, board.options, 'sector'); 166 el = board.create('curve', [[0], [0]], attr); 167 el.type = Const.OBJECT_TYPE_SECTOR; 168 el.elType = 'sector'; 169 170 if (type === '2lines') { 171 el.Radius = function () { 172 return Type.evaluate(parents[4]); 173 }; 174 175 el.line1 = board.select(parents[0]); 176 el.line2 = board.select(parents[1]); 177 178 el.line1.addChild(el); 179 el.line2.addChild(el); 180 el.parents = [parents[0].id, parents[1].id]; 181 182 el.point1 = {visProp: {}}; 183 el.point2 = {visProp: {}}; 184 el.point3 = {visProp: {}}; 185 186 /* Intersection point */ 187 s = Geometry.meetLineLine(el.line1.stdform, el.line2.stdform, 0, board); 188 189 if (Type.isArray(parents[2])) { 190 /* project p1 to l1 */ 191 if (parents[2].length === 2) { 192 parents[2] = [1].concat(parents[2]); 193 } 194 v = [0, el.line1.stdform[1], el.line1.stdform[2]]; 195 v = Mat.crossProduct(v, parents[2]); 196 v = Geometry.meetLineLine(v, el.line1.stdform, 0, board); 197 v = Statistics.subtract(v.usrCoords, s.usrCoords); 198 el.direction1 = (Mat.innerProduct(v, [0, el.line1.stdform[2], -el.line1.stdform[1]], 3) >= 0) ? +1 : -1; 199 } else { 200 el.direction1 = (parents[2] >= 0) ? 1 : -1; 201 } 202 203 if (Type.isArray(parents[3])) { 204 /* project p2 to l2 */ 205 if (parents[3].length === 2) { 206 parents[3] = [1].concat(parents[3]); 207 } 208 v = [0, el.line2.stdform[1], el.line2.stdform[2]]; 209 v = Mat.crossProduct(v, parents[3]); 210 v = Geometry.meetLineLine(v, el.line2.stdform, 0, board); 211 v = Statistics.subtract(v.usrCoords, s.usrCoords); 212 el.direction2 = (Mat.innerProduct(v, [0, el.line2.stdform[2], -el.line2.stdform[1]], 3) >= 0) ? +1 : -1; 213 } else { 214 el.direction2 = (parents[3] >= 0) ? 1 : -1; 215 } 216 217 el.updateDataArray = function () { 218 var r, l1, l2, A, B, C, ar; 219 220 l1 = this.line1; 221 l2 = this.line2; 222 223 // Intersection point of the lines 224 B = Mat.crossProduct(l1.stdform, l2.stdform); 225 B[1] /= B[0]; 226 B[2] /= B[0]; 227 B[0] /= B[0]; 228 229 // First point 230 r = this.direction1 * this.Radius(); 231 A = Statistics.add(B, [0, r * l1.stdform[2], -r * l1.stdform[1]]); 232 233 // Second point 234 r = this.direction2 * this.Radius(); 235 C = Statistics.add(B, [0, r * l2.stdform[2], -r * l2.stdform[1]]); 236 237 this.point2.coords = new Coords(Const.COORDS_BY_USER, A, el.board); 238 this.point1.coords = new Coords(Const.COORDS_BY_USER, B, el.board); 239 this.point3.coords = new Coords(Const.COORDS_BY_USER, C, el.board); 240 241 if (Math.abs(A[0]) < Mat.eps || Math.abs(B[0]) < Mat.eps || Math.abs(C[0]) < Mat.eps) { 242 this.dataX = [NaN]; 243 this.dataY = [NaN]; 244 return; 245 } 246 247 ar = Geometry.bezierArc(A, B, C, true, 1); 248 249 this.dataX = ar[0]; 250 this.dataY = ar[1]; 251 252 this.bezierDegree = 3; 253 }; 254 255 el.methodMap = JXG.deepCopy(el.methodMap, { 256 radius: 'getRadius', 257 getRadius: 'getRadius' 258 }); 259 260 el.prepareUpdate().update(); 261 262 // end '2lines' 263 264 } else if (type === '3points') { 265 266 /** 267 * Midpoint of the sector. 268 * @memberOf Sector.prototype 269 * @name point1 270 * @type JXG.Point 271 */ 272 el.point1 = board.select(parents[0]); 273 274 /** 275 * This point together with {@link Sector#point1} defines the radius.. 276 * @memberOf Sector.prototype 277 * @name point2 278 * @type JXG.Point 279 */ 280 el.point2 = board.select(parents[1]); 281 282 /** 283 * Defines the sector's angle. 284 * @memberOf Sector.prototype 285 * @name point3 286 * @type JXG.Point 287 */ 288 el.point3 = board.select(parents[2]); 289 290 /* Add arc as child to defining points */ 291 el.point1.addChild(el); 292 el.point2.addChild(el); 293 el.point3.addChild(el); 294 295 // useDirection is necessary for circumCircleSectors 296 el.useDirection = attributes.usedirection; 297 el.parents = [parents[0].id, parents[1].id, parents[2].id]; 298 299 /** 300 * Defines the sectors orientation in case of circumCircleSectors. 301 * @memberOf Sector.prototype 302 * @name point4 303 * @type JXG.Point 304 */ 305 if (Type.exists(parents[3])) { 306 el.point4 = board.select(parents[3]); 307 el.point4.addChild(el); 308 // el.parents.push(parents[3].id); 309 } 310 311 el.methodMap = JXG.deepCopy(el.methodMap, { 312 center: 'center', 313 radiuspoint: 'radiuspoint', 314 anglepoint: 'anglepoint', 315 radius: 'getRadius', 316 getRadius: 'getRadius' 317 }); 318 319 /** 320 * documented in JXG.Curve 321 * @ignore 322 */ 323 el.updateDataArray = function () { 324 var ar, det, p0c, p1c, p2c, 325 A = this.point2, 326 B = this.point1, 327 C = this.point3; 328 329 if (!A.isReal || !B.isReal || !C.isReal) { 330 this.dataX = [NaN]; 331 this.dataY = [NaN]; 332 return; 333 } 334 335 // This is true for circumCircleSectors. In that case there is 336 // a fourth parent element: [midpoint, point1, point3, point2] 337 if (this.useDirection && Type.exists(this.point4)) { 338 p0c = this.point2.coords.usrCoords; 339 p1c = this.point4.coords.usrCoords; 340 p2c = this.point3.coords.usrCoords; 341 det = (p0c[1] - p2c[1]) * (p0c[2] - p1c[2]) - (p0c[2] - p2c[2]) * (p0c[1] - p1c[1]); 342 343 if (det >= 0.0) { 344 C = this.point2; 345 A = this.point3; 346 } 347 } 348 349 A = A.coords.usrCoords; 350 B = B.coords.usrCoords; 351 C = C.coords.usrCoords; 352 353 ar = Geometry.bezierArc(A, B, C, true, 1); 354 355 this.dataX = ar[0]; 356 this.dataY = ar[1]; 357 this.bezierDegree = 3; 358 }; 359 360 /** 361 * Returns the radius of the sector. 362 * @memberOf Sector.prototype 363 * @name Radius 364 * @function 365 * @returns {Number} The distance between {@link Sector#point1} and {@link Sector#point2}. 366 */ 367 el.Radius = function () { 368 return this.point2.Dist(this.point1); 369 }; 370 371 } // end '3points' 372 373 el.center = el.point1; 374 el.radiuspoint = el.point2; 375 el.anglepoint = el.point3; 376 377 // Default hasPoint method. Documented in geometry element 378 el.hasPointCurve = function (x, y) { 379 var angle, alpha, beta, 380 prec = this.board.options.precision.hasPoint / (this.board.unitX), 381 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board), 382 r = this.Radius(), 383 dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint), 384 has = (Math.abs(dist - r) < prec); 385 386 if (has) { 387 angle = Geometry.rad(this.point2, this.center, checkPoint.usrCoords.slice(1)); 388 alpha = 0; 389 beta = Geometry.rad(this.point2, this.center, this.point3); 390 391 if (angle < alpha || angle > beta) { 392 has = false; 393 } 394 } 395 396 return has; 397 }; 398 399 /** 400 * Checks whether (x,y) is within the area defined by the sector. 401 * @memberOf Sector.prototype 402 * @name hasPointSector 403 * @function 404 * @param {Number} x Coordinate in x direction, screen coordinates. 405 * @param {Number} y Coordinate in y direction, screen coordinates. 406 * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise. 407 */ 408 el.hasPointSector = function (x, y) { 409 var angle, 410 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board), 411 r = this.Radius(), 412 dist = this.point1.coords.distance(Const.COORDS_BY_USER, checkPoint), 413 has = (dist < r); 414 415 if (has) { 416 angle = Geometry.rad(this.point2, this.point1, checkPoint.usrCoords.slice(1)); 417 418 if (angle > Geometry.rad(this.point2, this.point1, this.point3)) { 419 has = false; 420 } 421 } 422 return has; 423 }; 424 425 el.hasPoint = function (x, y) { 426 if (this.visProp.highlightonsector) { 427 return this.hasPointSector(x, y); 428 } 429 430 return this.hasPointCurve(x, y); 431 }; 432 433 // documented in GeometryElement 434 el.getTextAnchor = function () { 435 return this.point1.coords; 436 }; 437 438 // documented in GeometryElement 439 // this method is very similar to arc.getLabelAnchor() 440 // there are some additions in the arc version though, mainly concerning 441 // "major" and "minor" arcs. but maybe these methods can be merged. 442 el.getLabelAnchor = function () { 443 var coords, vecx, vecy, len, 444 angle = Geometry.rad(this.point2, this.point1, this.point3), 445 dx = 13 / this.board.unitX, 446 dy = 13 / this.board.unitY, 447 p2c = this.point2.coords.usrCoords, 448 pmc = this.point1.coords.usrCoords, 449 bxminusax = p2c[1] - pmc[1], 450 byminusay = p2c[2] - pmc[2]; 451 452 if (Type.exists(this.label)) { 453 this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board); 454 } 455 456 coords = new Coords(Const.COORDS_BY_USER, [ 457 pmc[1] + Math.cos(angle * 0.5) * bxminusax - Math.sin(angle * 0.5) * byminusay, 458 pmc[2] + Math.sin(angle * 0.5) * bxminusax + Math.cos(angle * 0.5) * byminusay 459 ], this.board); 460 461 vecx = coords.usrCoords[1] - pmc[1]; 462 vecy = coords.usrCoords[2] - pmc[2]; 463 464 len = Math.sqrt(vecx * vecx + vecy * vecy); 465 vecx = vecx * (len + dx) / len; 466 vecy = vecy * (len + dy) / len; 467 468 return new Coords(Const.COORDS_BY_USER, [pmc[1] + vecx, pmc[2] + vecy], this.board); 469 }; 470 471 /** 472 * Overwrite the Radius method of the sector. 473 * Used in {@link GeometryElement#setAttribute}. 474 * @param {Number, Function} value New radius. 475 */ 476 el.setRadius = function (value) { 477 el.Radius = function () { 478 return Type.evaluate(value); 479 }; 480 }; 481 482 /** 483 * deprecated 484 * @ignore 485 */ 486 el.getRadius = function () { 487 return this.Radius(); 488 }; 489 490 el.prepareUpdate().update(); 491 492 return el; 493 }; 494 495 JXG.registerElement('sector', JXG.createSector); 496 497 498 /** 499 * @class A circumcircle sector is different from a {@link Sector} mostly in the way the parent elements are interpreted. 500 * At first, the circum centre is determined from the three given points. Then the sector is drawn from <tt>p1</tt> through 501 * <tt>p2</tt> to <tt>p3</tt>. 502 * @pseudo 503 * @name CircumcircleSector 504 * @augments Sector 505 * @constructor 506 * @type Sector 507 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 508 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A circumcircle sector is defined by the circumcircle which is determined 509 * by these three given points. The circumcircle sector is always drawn from <tt>p1</tt> through <tt>p2</tt> to <tt>p3</tt>. 510 * @example 511 * // Create an arc out of three free points 512 * var p1 = board.create('point', [1.5, 5.0]), 513 * p2 = board.create('point', [1.0, 0.5]), 514 * p3 = board.create('point', [5.0, 3.0]), 515 * 516 * a = board.create('circumcirclesector', [p1, p2, p3]); 517 * </pre><div id="695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04" style="width: 300px; height: 300px;"></div> 518 * <script type="text/javascript"> 519 * (function () { 520 * var board = JXG.JSXGraph.initBoard('695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 521 * p1 = board.create('point', [1.5, 5.0]), 522 * p2 = board.create('point', [1.0, 0.5]), 523 * p3 = board.create('point', [5.0, 3.0]), 524 * 525 * a = board.create('circumcirclesector', [p1, p2, p3]); 526 * })(); 527 * </script><pre> 528 */ 529 JXG.createCircumcircleSector = function (board, parents, attributes) { 530 var el, mp, attr; 531 532 if ((Type.isPoint(parents[0])) && (Type.isPoint(parents[1])) && (Type.isPoint(parents[2]))) { 533 attr = Type.copyAttributes(attributes, board.options, 'circumcirclesector', 'center'); 534 mp = board.create('circumcenter', [parents[0], parents[1], parents[2]], attr); 535 536 mp.dump = false; 537 538 attr = Type.copyAttributes(attributes, board.options, 'circumcirclesector'); 539 el = board.create('sector', [mp, parents[0], parents[2], parents[1]], attr); 540 541 el.elType = 'circumcirclesector'; 542 el.parents = [parents[0].id, parents[1].id, parents[2].id]; 543 544 /** 545 * Center of the circumcirclesector 546 * @memberOf CircumcircleSector.prototype 547 * @name center 548 * @type Circumcenter 549 */ 550 el.center = mp; 551 el.subs = { 552 center: mp 553 }; 554 } else { 555 throw new Error("JSXGraph: Can't create circumcircle sector with parent types '" + 556 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'."); 557 } 558 559 return el; 560 }; 561 562 JXG.registerElement('circumcirclesector', JXG.createCircumcircleSector); 563 564 565 /** 566 * @class The angle element is used to denote an angle defined by three points. Visually it is just a {@link Sector} 567 * element with a radius not defined by the parent elements but by an attribute <tt>radius</tt>. As opposed to the sector, 568 * an angle has two angle points and no radius point. 569 * Sector is displayed if type=="sector". 570 * If type=="square", instead of a sector a parallelogram is displayed. 571 * In case of type=="auto", a square is displayed if the angle is near orthogonal. 572 * If no name is provided the angle label is automatically set to a lower greek letter. 573 * @pseudo 574 * @name Angle 575 * @augments Sector 576 * @constructor 577 * @type Sector 578 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 579 * First possiblity of input parameters are: 580 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 An angle is always drawn counterclockwise from <tt>p1</tt> to 581 * <tt>p3</tt> around <tt>p2</tt>. 582 * 583 * Second possibility of input parameters are: 584 * @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. 585 * The two legs which define the angle are given by two coordinates arrays which are project initially two the two lines or by two directions (+/- 1). 586 * 587 * @example 588 * // Create an angle out of three free points 589 * var p1 = board.create('point', [5.0, 3.0]), 590 * p2 = board.create('point', [1.0, 0.5]), 591 * p3 = board.create('point', [1.5, 5.0]), 592 * 593 * a = board.create('angle', [p1, p2, p3]); 594 * </pre><div id="a34151f9-bb26-480a-8d6e-9b8cbf789ae5" style="width: 300px; height: 300px;"></div> 595 * <script type="text/javascript"> 596 * (function () { 597 * var board = JXG.JSXGraph.initBoard('a34151f9-bb26-480a-8d6e-9b8cbf789ae5', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 598 * p1 = board.create('point', [5.0, 3.0]), 599 * p2 = board.create('point', [1.0, 0.5]), 600 * p3 = board.create('point', [1.5, 5.0]), 601 * 602 * a = board.create('angle', [p1, p2, p3]); 603 * })(); 604 * </script><pre> 605 * 606 * @example 607 * // Create an angle out of two lines and two directions 608 * var p1 = board.create('point', [-1, 4]), 609 * p2 = board.create('point', [4, 1]), 610 * q1 = board.create('point', [-2, -3]), 611 * q2 = board.create('point', [4,3]), 612 * 613 * li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}), 614 * li2 = board.create('line', [q1,q2], {lastArrow:true}), 615 * 616 * a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }), 617 * a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 }); 618 * 619 * </pre><div id="3a667ddd-63dc-4594-b5f1-afac969b371f" style="width: 300px; height: 300px;"></div> 620 * <script type="text/javascript"> 621 * (function () { 622 * var board = JXG.JSXGraph.initBoard('3a667ddd-63dc-4594-b5f1-afac969b371f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 623 * p1 = board.create('point', [-1, 4]), 624 * p2 = board.create('point', [4, 1]), 625 * q1 = board.create('point', [-2, -3]), 626 * q2 = board.create('point', [4,3]), 627 * 628 * li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}), 629 * li2 = board.create('line', [q1,q2], {lastArrow:true}), 630 * 631 * a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }), 632 * a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 }); 633 * })(); 634 * </script><pre> 635 */ 636 JXG.createAngle = function (board, parents, attributes) { 637 var el, radius, text, attr, attrsub, 638 i, dot, 639 type = 'invalid'; 640 641 // Three points? 642 if (Type.isPoint(parents[0]) && Type.isPoint(parents[1]) && Type.isPoint(parents[2])) { 643 type = '3points'; 644 } else if (parents[0].elementClass === Const.OBJECT_CLASS_LINE && 645 parents[1].elementClass === Const.OBJECT_CLASS_LINE && 646 (Type.isArray(parents[2]) || Type.isNumber(parents[2])) && 647 (Type.isArray(parents[3]) || Type.isNumber(parents[3]))) { 648 type = '2lines'; 649 } 650 651 if (type === 'invalid') { 652 throw new Error("JSXGraph: Can't create angle with parent types '" + 653 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'."); 654 655 } 656 657 attr = Type.copyAttributes(attributes, board.options, 'angle'); 658 659 // If empty, create a new name 660 text = attr.name; 661 if (!Type.exists(text) || text === '') { 662 text = board.generateName({type: Const.OBJECT_TYPE_ANGLE}); 663 attr.name = text; 664 } 665 666 if (Type.exists(attr.radius)) { 667 radius = attr.radius; 668 } else { 669 radius = 0; 670 } 671 672 if (type === '2lines') { 673 el = board.create('sector', [parents[0], parents[1], parents[2], parents[3], radius], attr); 674 675 el.updateDataArraySector = el.updateDataArray; 676 677 // Todo 678 el.setAngle = function (val) {}; 679 el.free = function (val) {}; 680 681 } else { 682 el = board.create('sector', [parents[1], parents[0], parents[2]], attr); 683 684 /** 685 * The point defining the radius of the angle element. Alias for {@link Angle.prototype#radiuspoint}. 686 * @type JXG.Point 687 * @name point 688 * @memberOf Angle.prototype 689 */ 690 el.point = el.point2 = el.radiuspoint = parents[0]; 691 692 /** 693 * Helper point for angles of type 'square'. 694 * @type JXG.Point 695 * @name pointsquare 696 * @memberOf Angle.prototype 697 */ 698 el.pointsquare = el.point3 = el.anglepoint = parents[2]; 699 700 el.Radius = function () { 701 return Type.evaluate(radius); 702 }; 703 704 el.updateDataArraySector = function () { 705 var A = this.point2, 706 B = this.point1, 707 C = this.point3, 708 r = this.Radius(), 709 d = B.Dist(A), 710 ar; 711 712 A = A.coords.usrCoords; 713 B = B.coords.usrCoords; 714 C = C.coords.usrCoords; 715 716 A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d]; 717 C = [1, B[1] + (C[1] - B[1]) * r / d, B[2] + (C[2] - B[2]) * r / d]; 718 719 ar = Geometry.bezierArc(A, B, C, true, 1); 720 721 this.dataX = ar[0]; 722 this.dataY = ar[1]; 723 this.bezierDegree = 3; 724 }; 725 726 /** 727 * Set an angle to a prescribed value given in radians. This is only possible if the third point of the angle, i.e. 728 * the anglepoint is a free point. 729 * @name setAngle 730 * @function 731 * @param {Number|Function} val Number or Function which returns the size of the angle in Radians 732 * @returns {Object} Pointer to the angle element.. 733 * @memberOf Angle.prototype 734 */ 735 el.setAngle = function (val) { 736 var t, 737 p = this.anglepoint, 738 q = this.radiuspoint; 739 740 if (p.draggable()) { 741 t = this.board.create('transform', [val, this.center], {type: 'rotate'}); 742 p.addTransform(q, t); 743 p.isDraggable = false; 744 p.parents = [q]; 745 } 746 return this; 747 }; 748 749 /** 750 * Frees an angle from a prescribed value. This is only relevant if the angle size has been set by 751 * setAngle() previously. The anglepoint is set to a free point. 752 * @name free 753 * @function 754 * @returns {Object} Pointer to the angle element.. 755 * @memberOf Angle.prototype 756 */ 757 el.free = function () { 758 var p = this.anglepoint; 759 if (p.transformations.length > 0) { 760 p.transformations.pop(); 761 p.isDraggable = true; 762 p.parents = []; 763 } 764 return this; 765 }; 766 767 } // end '3points' 768 769 el.elType = 'angle'; 770 el.type = Const.OBJECT_TYPE_ANGLE; 771 el.parents = [parents[0].id, parents[1].id, parents[2].id]; 772 el.subs = {}; 773 774 el.updateDataArraySquare = function () { 775 var A, B, C, 776 r = this.Radius(), 777 d1, d2, 778 v, l1, l2; 779 780 781 if (type === '2lines') { 782 // This is necessary to update this.point1, this.point2, this.point3. 783 this.updateDataArraySector(); 784 } 785 786 A = this.point2; 787 B = this.point1; 788 C = this.point3; 789 790 A = A.coords.usrCoords; 791 B = B.coords.usrCoords; 792 C = C.coords.usrCoords; 793 794 d1 = Geometry.distance(A, B, 3); 795 d2 = Geometry.distance(C, B, 3); 796 797 // In case of type=='2lines' this is redundant, because r == d1 == d2 798 A = [1, B[1] + (A[1] - B[1]) * r / d1, B[2] + (A[2] - B[2]) * r / d1]; 799 C = [1, B[1] + (C[1] - B[1]) * r / d2, B[2] + (C[2] - B[2]) * r / d2]; 800 801 v = Mat.crossProduct(C, B); 802 l1 = [-A[1] * v[1] - A[2] * v[2], A[0] * v[1], A[0] * v[2]]; 803 v = Mat.crossProduct(A, B); 804 l2 = [-C[1] * v[1] - C[2] * v[2], C[0] * v[1], C[0] * v[2]]; 805 806 v = Mat.crossProduct(l1, l2); 807 v[1] /= v[0]; 808 v[2] /= v[0]; 809 810 this.dataX = [B[1], A[1], v[1], C[1], B[1]]; 811 this.dataY = [B[2], A[2], v[2], C[2], B[2]]; 812 813 this.bezierDegree = 1; 814 }; 815 816 el.updateDataArrayNone = function () { 817 this.dataX = [NaN]; 818 this.dataY = [NaN]; 819 this.bezierDegree = 1; 820 }; 821 822 el.updateDataArray = function () { 823 var type = this.visProp.type, 824 deg = Geometry.trueAngle(this.point2, this.point1, this.point3); 825 826 if (Math.abs(deg - 90) < this.visProp.orthosensitivity) { 827 type = this.visProp.orthotype; 828 } 829 830 if (type === 'none') { 831 this.updateDataArrayNone(); 832 } else if (type === 'square') { 833 this.updateDataArraySquare(); 834 } else if (type === 'sector') { 835 this.updateDataArraySector(); 836 } else if (type === 'sectordot') { 837 this.updateDataArraySector(); 838 if (!this.dot.visProp.visible) { 839 this.dot.setAttribute({visible: true}); 840 } 841 } 842 843 if (!this.visProp.visible || (type !== 'sectordot' && this.dot.visProp.visible)) { 844 this.dot.setAttribute({visible: false}); 845 } 846 }; 847 848 /** 849 * Indicates a right angle. Invisible by default, use <tt>dot.visible: true</tt> to show. 850 * Though this dot indicates a right angle, it can be visible even if the angle is not a right 851 * one. 852 * @type JXG.Point 853 * @name dot 854 * @memberOf Angle.prototype 855 */ 856 attrsub = Type.copyAttributes(attributes, board.options, 'angle', 'dot'); 857 el.dot = board.create('point', [function () { 858 var A, B, r, d, a2, co, si, mat; 859 860 if (Type.exists(el.dot) && !el.dot.visProp.visible) { 861 return [0, 0]; 862 } 863 864 A = el.point2.coords.usrCoords; 865 B = el.point1.coords.usrCoords; 866 r = el.Radius(); 867 d = Geometry.distance(A, B, 3); 868 a2 = Geometry.rad(el.point2, el.point1, el.point3) * 0.5; 869 co = Math.cos(a2); 870 si = Math.sin(a2); 871 872 A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d]; 873 874 mat = [ 875 [1, 0, 0], 876 [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5], 877 [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5, co * 0.5] 878 ]; 879 return Mat.matVecMult(mat, A); 880 }], attrsub); 881 882 el.dot.dump = false; 883 el.subs.dot = el.dot; 884 885 if (type === '2lines') { 886 for (i = 0; i < 2; i++) { 887 board.select(parents[i]).addChild(el.dot); 888 } 889 } else { 890 for (i = 0; i < 3; i++) { 891 board.select(parents[i]).addChild(el.dot); 892 } 893 } 894 895 // documented in GeometryElement 896 el.getLabelAnchor = function () { 897 var vec, dx = 12, dy = 12, 898 A, B, r, d, a2, co, si, mat; 899 900 if (Type.exists(this.label)) { 901 this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board); 902 } 903 904 if (Type.exists(this.label.visProp.fontSize)) { 905 dx = this.label.visProp.fontSize; 906 dy = this.label.visProp.fontSize; 907 } 908 dx /= this.board.unitX; 909 dy /= this.board.unitY; 910 911 A = el.point2.coords.usrCoords; 912 B = el.point1.coords.usrCoords; 913 r = el.Radius(); 914 d = Geometry.distance(A, B, 3); 915 a2 = Geometry.rad(el.point2, el.point1, el.point3) * 0.5; 916 co = Math.cos(a2); 917 si = Math.sin(a2); 918 919 A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d]; 920 921 mat = [ 922 [1, 0, 0], 923 [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5], 924 [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5, co * 0.5] 925 ]; 926 vec = Mat.matVecMult(mat, A); 927 vec[1] /= vec[0]; 928 vec[2] /= vec[0]; 929 vec[0] /= vec[0]; 930 931 d = Geometry.distance(vec, B, 3); 932 vec = [vec[0], B[1] + (vec[1] - B[1]) * (r + dx) / d, B[2] + (vec[2] - B[2]) * (r + dx) / d]; 933 934 return new Coords(Const.COORDS_BY_USER, vec, this.board); 935 }; 936 937 el.Value = function () { 938 return Geometry.rad(this.point2, this.point1, this.point3); 939 }; 940 941 el.methodMap = Type.deepCopy(el.methodMap, { 942 Value: 'Value', 943 setAngle: 'setAngle', 944 free: 'free' 945 }); 946 947 return el; 948 }; 949 950 JXG.registerElement('angle', JXG.createAngle); 951 952 return { 953 createSector: JXG.createSector, 954 createCircumcircleSector: JXG.createCircumcircleSector, 955 createAngle: JXG.createAngle 956 }; 957 }); 958