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 // Second try for 3 point sector 145 try { 146 147 for (i = 0; i < parents.length; i++) { 148 if (!Type.isPoint(parents[i])) { 149 attr = Type.copyAttributes(attributes, board.options, 'sector', points[i]); 150 parents[i] = board.create('point', parents[i], attr); 151 } 152 } 153 154 type = '3points'; 155 156 } catch (e) { 157 throw new Error("JSXGraph: Can't create Sector with parent types '" + 158 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + 159 (typeof parents[2]) + "'."); 160 } 161 } 162 163 attr = Type.copyAttributes(attributes, board.options, 'sector'); 164 el = board.create('curve', [[0], [0]], attr); 165 el.type = Const.OBJECT_TYPE_SECTOR; 166 el.elType = 'sector'; 167 168 if (type === '2lines') { 169 el.Radius = function () { 170 return Type.evaluate(parents[4]); 171 }; 172 173 el.line1 = board.select(parents[0]); 174 el.line2 = board.select(parents[1]); 175 176 el.line1.addChild(el); 177 el.line2.addChild(el); 178 el.parents = [parents[0].id, parents[1].id]; 179 180 el.point1 = {visProp: {}}; 181 el.point2 = {visProp: {}}; 182 el.point3 = {visProp: {}}; 183 184 /* Intersection point */ 185 s = Geometry.meetLineLine(el.line1.stdform, el.line2.stdform, 0, board); 186 187 if (Type.isArray(parents[2])) { 188 /* project p1 to l1 */ 189 if (parents[2].length === 2) { 190 parents[2] = [1].concat(parents[2]); 191 } 192 v = [0, el.line1.stdform[1], el.line1.stdform[2]]; 193 v = Mat.crossProduct(v, parents[2]); 194 v = Geometry.meetLineLine(v, el.line1.stdform, 0, board); 195 v = Statistics.subtract(v.usrCoords, s.usrCoords); 196 el.direction1 = (Mat.innerProduct(v, [0, el.line1.stdform[2], -el.line1.stdform[1]], 3) >= 0) ? +1 : -1; 197 } else { 198 el.direction1 = (parents[2] >= 0) ? 1 : -1; 199 } 200 201 if (Type.isArray(parents[3])) { 202 /* project p2 to l2 */ 203 if (parents[3].length === 2) { 204 parents[3] = [1].concat(parents[3]); 205 } 206 v = [0, el.line2.stdform[1], el.line2.stdform[2]]; 207 v = Mat.crossProduct(v, parents[3]); 208 v = Geometry.meetLineLine(v, el.line2.stdform, 0, board); 209 v = Statistics.subtract(v.usrCoords, s.usrCoords); 210 el.direction2 = (Mat.innerProduct(v, [0, el.line2.stdform[2], -el.line2.stdform[1]], 3) >= 0) ? +1 : -1; 211 } else { 212 el.direction2 = (parents[3] >= 0) ? 1 : -1; 213 } 214 215 el.updateDataArray = function () { 216 var r, l1, l2, A, B, C, ar; 217 218 l1 = this.line1; 219 l2 = this.line2; 220 221 // Intersection point of the lines 222 B = Mat.crossProduct(l1.stdform, l2.stdform); 223 B[1] /= B[0]; 224 B[2] /= B[0]; 225 B[0] /= B[0]; 226 227 // First point 228 r = this.direction1 * this.Radius(); 229 A = Statistics.add(B, [0, r * l1.stdform[2], -r * l1.stdform[1]]); 230 231 // Second point 232 r = this.direction2 * this.Radius(); 233 C = Statistics.add(B, [0, r * l2.stdform[2], -r * l2.stdform[1]]); 234 235 this.point2.coords = new Coords(Const.COORDS_BY_USER, A, el.board); 236 this.point1.coords = new Coords(Const.COORDS_BY_USER, B, el.board); 237 this.point3.coords = new Coords(Const.COORDS_BY_USER, C, el.board); 238 239 if (Math.abs(A[0]) < Mat.eps || Math.abs(B[0]) < Mat.eps || Math.abs(C[0]) < Mat.eps) { 240 this.dataX = [NaN]; 241 this.dataY = [NaN]; 242 return; 243 } 244 245 ar = Geometry.bezierArc(A, B, C, true, 1); 246 247 this.dataX = ar[0]; 248 this.dataY = ar[1]; 249 250 this.bezierDegree = 3; 251 }; 252 253 el.methodMap = JXG.deepCopy(el.methodMap, { 254 radius: 'getRadius', 255 getRadius: 'getRadius', 256 setRadius: 'setRadius' 257 }); 258 259 el.prepareUpdate().update(); 260 261 // end '2lines' 262 263 } else if (type === '3points') { 264 265 /** 266 * Midpoint of the sector. 267 * @memberOf Sector.prototype 268 * @name point1 269 * @type JXG.Point 270 */ 271 el.point1 = board.select(parents[0]); 272 273 /** 274 * This point together with {@link Sector#point1} defines the radius.. 275 * @memberOf Sector.prototype 276 * @name point2 277 * @type JXG.Point 278 */ 279 el.point2 = board.select(parents[1]); 280 281 /** 282 * Defines the sector's angle. 283 * @memberOf Sector.prototype 284 * @name point3 285 * @type JXG.Point 286 */ 287 el.point3 = board.select(parents[2]); 288 289 /* Add arc as child to defining points */ 290 el.point1.addChild(el); 291 el.point2.addChild(el); 292 el.point3.addChild(el); 293 294 // useDirection is necessary for circumCircleSectors 295 el.useDirection = attributes.usedirection; 296 el.parents = [parents[0].id, parents[1].id, parents[2].id]; 297 298 /** 299 * Defines the sectors orientation in case of circumCircleSectors. 300 * @memberOf Sector.prototype 301 * @name point4 302 * @type JXG.Point 303 */ 304 if (Type.exists(parents[3])) { 305 el.point4 = board.select(parents[3]); 306 el.point4.addChild(el); 307 // el.parents.push(parents[3].id); 308 } 309 310 el.methodMap = JXG.deepCopy(el.methodMap, { 311 center: 'center', 312 radiuspoint: 'radiuspoint', 313 anglepoint: 'anglepoint', 314 radius: 'getRadius', 315 getRadius: 'getRadius', 316 setRadius: 'setRadius' 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 coordinate arrays. 586 * The points given by these coordinate arrays are projected initially (i.e. only once) onto the two lines. 587 * The other possibility is to supply directions (+/- 1). 588 * 589 * @example 590 * // Create an angle out of three free points 591 * var p1 = board.create('point', [5.0, 3.0]), 592 * p2 = board.create('point', [1.0, 0.5]), 593 * p3 = board.create('point', [1.5, 5.0]), 594 * 595 * a = board.create('angle', [p1, p2, p3]); 596 * </pre><div id="a34151f9-bb26-480a-8d6e-9b8cbf789ae5" style="width: 300px; height: 300px;"></div> 597 * <script type="text/javascript"> 598 * (function () { 599 * var board = JXG.JSXGraph.initBoard('a34151f9-bb26-480a-8d6e-9b8cbf789ae5', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 600 * p1 = board.create('point', [5.0, 3.0]), 601 * p2 = board.create('point', [1.0, 0.5]), 602 * p3 = board.create('point', [1.5, 5.0]), 603 * 604 * a = board.create('angle', [p1, p2, p3]); 605 * })(); 606 * </script><pre> 607 * 608 * @example 609 * // Create an angle out of two lines and two directions 610 * var p1 = board.create('point', [-1, 4]), 611 * p2 = board.create('point', [4, 1]), 612 * q1 = board.create('point', [-2, -3]), 613 * q2 = board.create('point', [4,3]), 614 * 615 * li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}), 616 * li2 = board.create('line', [q1,q2], {lastArrow:true}), 617 * 618 * a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }), 619 * a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 }); 620 * 621 * </pre><div id="3a667ddd-63dc-4594-b5f1-afac969b371f" style="width: 300px; height: 300px;"></div> 622 * <script type="text/javascript"> 623 * (function () { 624 * var board = JXG.JSXGraph.initBoard('3a667ddd-63dc-4594-b5f1-afac969b371f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 625 * p1 = board.create('point', [-1, 4]), 626 * p2 = board.create('point', [4, 1]), 627 * q1 = board.create('point', [-2, -3]), 628 * q2 = board.create('point', [4,3]), 629 * 630 * li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}), 631 * li2 = board.create('line', [q1,q2], {lastArrow:true}), 632 * 633 * a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }), 634 * a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 }); 635 * })(); 636 * </script><pre> 637 */ 638 JXG.createAngle = function (board, parents, attributes) { 639 var el, radius, text, attr, attrsub, 640 i, dot, 641 type = 'invalid'; 642 643 // Three points? 644 if (Type.isPoint(parents[0]) && Type.isPoint(parents[1]) && Type.isPoint(parents[2])) { 645 type = '3points'; 646 } else if (parents[0].elementClass === Const.OBJECT_CLASS_LINE && 647 parents[1].elementClass === Const.OBJECT_CLASS_LINE && 648 (Type.isArray(parents[2]) || Type.isNumber(parents[2])) && 649 (Type.isArray(parents[3]) || Type.isNumber(parents[3]))) { 650 type = '2lines'; 651 } 652 653 if (type === 'invalid') { 654 throw new Error("JSXGraph: Can't create angle with parent types '" + 655 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'."); 656 657 } 658 659 attr = Type.copyAttributes(attributes, board.options, 'angle'); 660 661 // If empty, create a new name 662 text = attr.name; 663 if (!Type.exists(text) || text === '') { 664 text = board.generateName({type: Const.OBJECT_TYPE_ANGLE}); 665 attr.name = text; 666 } 667 668 if (Type.exists(attr.radius)) { 669 radius = attr.radius; 670 } else { 671 radius = 0; 672 } 673 674 if (type === '2lines') { 675 el = board.create('sector', [parents[0], parents[1], parents[2], parents[3], radius], attr); 676 677 el.updateDataArraySector = el.updateDataArray; 678 679 // Todo 680 el.setAngle = function (val) {}; 681 el.free = function (val) {}; 682 683 } else { 684 el = board.create('sector', [parents[1], parents[0], parents[2]], attr); 685 686 /** 687 * The point defining the radius of the angle element. Alias for {@link Angle.prototype#radiuspoint}. 688 * @type JXG.Point 689 * @name point 690 * @memberOf Angle.prototype 691 */ 692 el.point = el.point2 = el.radiuspoint = parents[0]; 693 694 /** 695 * Helper point for angles of type 'square'. 696 * @type JXG.Point 697 * @name pointsquare 698 * @memberOf Angle.prototype 699 */ 700 el.pointsquare = el.point3 = el.anglepoint = parents[2]; 701 702 el.Radius = function () { 703 return Type.evaluate(radius); 704 }; 705 706 el.updateDataArraySector = function () { 707 var A = this.point2, 708 B = this.point1, 709 C = this.point3, 710 r = this.Radius(), 711 d = B.Dist(A), 712 ar; 713 714 A = A.coords.usrCoords; 715 B = B.coords.usrCoords; 716 C = C.coords.usrCoords; 717 718 A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d]; 719 C = [1, B[1] + (C[1] - B[1]) * r / d, B[2] + (C[2] - B[2]) * r / d]; 720 721 ar = Geometry.bezierArc(A, B, C, true, 1); 722 723 this.dataX = ar[0]; 724 this.dataY = ar[1]; 725 this.bezierDegree = 3; 726 }; 727 728 /** 729 * Set an angle to a prescribed value given in radians. This is only possible if the third point of the angle, i.e. 730 * the anglepoint is a free point. 731 * @name setAngle 732 * @function 733 * @param {Number|Function} val Number or Function which returns the size of the angle in Radians 734 * @returns {Object} Pointer to the angle element.. 735 * @memberOf Angle.prototype 736 */ 737 el.setAngle = function (val) { 738 var t, 739 p = this.anglepoint, 740 q = this.radiuspoint; 741 742 if (p.draggable()) { 743 t = this.board.create('transform', [val, this.center], {type: 'rotate'}); 744 p.addTransform(q, t); 745 p.isDraggable = false; 746 p.parents = [q]; 747 } 748 return this; 749 }; 750 751 /** 752 * Frees an angle from a prescribed value. This is only relevant if the angle size has been set by 753 * setAngle() previously. The anglepoint is set to a free point. 754 * @name free 755 * @function 756 * @returns {Object} Pointer to the angle element.. 757 * @memberOf Angle.prototype 758 */ 759 el.free = function () { 760 var p = this.anglepoint; 761 if (p.transformations.length > 0) { 762 p.transformations.pop(); 763 p.isDraggable = true; 764 p.parents = []; 765 } 766 return this; 767 }; 768 769 } // end '3points' 770 771 el.elType = 'angle'; 772 el.type = Const.OBJECT_TYPE_ANGLE; 773 el.parents = [parents[0].id, parents[1].id, parents[2].id]; 774 el.subs = {}; 775 776 el.updateDataArraySquare = function () { 777 var A, B, C, 778 r = this.Radius(), 779 d1, d2, 780 v, l1, l2; 781 782 783 if (type === '2lines') { 784 // This is necessary to update this.point1, this.point2, this.point3. 785 this.updateDataArraySector(); 786 } 787 788 A = this.point2; 789 B = this.point1; 790 C = this.point3; 791 792 A = A.coords.usrCoords; 793 B = B.coords.usrCoords; 794 C = C.coords.usrCoords; 795 796 d1 = Geometry.distance(A, B, 3); 797 d2 = Geometry.distance(C, B, 3); 798 799 // In case of type=='2lines' this is redundant, because r == d1 == d2 800 A = [1, B[1] + (A[1] - B[1]) * r / d1, B[2] + (A[2] - B[2]) * r / d1]; 801 C = [1, B[1] + (C[1] - B[1]) * r / d2, B[2] + (C[2] - B[2]) * r / d2]; 802 803 v = Mat.crossProduct(C, B); 804 l1 = [-A[1] * v[1] - A[2] * v[2], A[0] * v[1], A[0] * v[2]]; 805 v = Mat.crossProduct(A, B); 806 l2 = [-C[1] * v[1] - C[2] * v[2], C[0] * v[1], C[0] * v[2]]; 807 808 v = Mat.crossProduct(l1, l2); 809 v[1] /= v[0]; 810 v[2] /= v[0]; 811 812 this.dataX = [B[1], A[1], v[1], C[1], B[1]]; 813 this.dataY = [B[2], A[2], v[2], C[2], B[2]]; 814 815 this.bezierDegree = 1; 816 }; 817 818 el.updateDataArrayNone = function () { 819 this.dataX = [NaN]; 820 this.dataY = [NaN]; 821 this.bezierDegree = 1; 822 }; 823 824 el.updateDataArray = function () { 825 var type = this.visProp.type, 826 deg = Geometry.trueAngle(this.point2, this.point1, this.point3); 827 828 if (Math.abs(deg - 90) < this.visProp.orthosensitivity) { 829 type = this.visProp.orthotype; 830 } 831 832 if (type === 'none') { 833 this.updateDataArrayNone(); 834 } else if (type === 'square') { 835 this.updateDataArraySquare(); 836 } else if (type === 'sector') { 837 this.updateDataArraySector(); 838 } else if (type === 'sectordot') { 839 this.updateDataArraySector(); 840 if (!this.dot.visProp.visible) { 841 this.dot.setAttribute({visible: true}); 842 } 843 } 844 845 if (!this.visProp.visible || (type !== 'sectordot' && this.dot.visProp.visible)) { 846 this.dot.setAttribute({visible: false}); 847 } 848 }; 849 850 /** 851 * Indicates a right angle. Invisible by default, use <tt>dot.visible: true</tt> to show. 852 * Though this dot indicates a right angle, it can be visible even if the angle is not a right 853 * one. 854 * @type JXG.Point 855 * @name dot 856 * @memberOf Angle.prototype 857 */ 858 attrsub = Type.copyAttributes(attributes, board.options, 'angle', 'dot'); 859 el.dot = board.create('point', [function () { 860 var A, B, r, d, a2, co, si, mat; 861 862 if (Type.exists(el.dot) && !el.dot.visProp.visible) { 863 return [0, 0]; 864 } 865 866 A = el.point2.coords.usrCoords; 867 B = el.point1.coords.usrCoords; 868 r = el.Radius(); 869 d = Geometry.distance(A, B, 3); 870 a2 = Geometry.rad(el.point2, el.point1, el.point3) * 0.5; 871 co = Math.cos(a2); 872 si = Math.sin(a2); 873 874 A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d]; 875 876 mat = [ 877 [1, 0, 0], 878 [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5], 879 [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5, co * 0.5] 880 ]; 881 return Mat.matVecMult(mat, A); 882 }], attrsub); 883 884 el.dot.dump = false; 885 el.subs.dot = el.dot; 886 887 if (type === '2lines') { 888 for (i = 0; i < 2; i++) { 889 board.select(parents[i]).addChild(el.dot); 890 } 891 } else { 892 for (i = 0; i < 3; i++) { 893 board.select(parents[i]).addChild(el.dot); 894 } 895 } 896 897 // documented in GeometryElement 898 el.getLabelAnchor = function () { 899 var vec, dx = 12, dy = 12, 900 A, B, r, d, a2, co, si, mat; 901 902 if (Type.exists(this.label)) { 903 this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board); 904 } 905 906 if (Type.exists(this.label.visProp.fontSize)) { 907 dx = this.label.visProp.fontSize; 908 dy = this.label.visProp.fontSize; 909 } 910 dx /= this.board.unitX; 911 dy /= this.board.unitY; 912 913 A = el.point2.coords.usrCoords; 914 B = el.point1.coords.usrCoords; 915 r = el.Radius(); 916 d = Geometry.distance(A, B, 3); 917 a2 = Geometry.rad(el.point2, el.point1, el.point3) * 0.5; 918 co = Math.cos(a2); 919 si = Math.sin(a2); 920 921 A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d]; 922 923 mat = [ 924 [1, 0, 0], 925 [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5], 926 [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5, co * 0.5] 927 ]; 928 vec = Mat.matVecMult(mat, A); 929 vec[1] /= vec[0]; 930 vec[2] /= vec[0]; 931 vec[0] /= vec[0]; 932 933 d = Geometry.distance(vec, B, 3); 934 vec = [vec[0], B[1] + (vec[1] - B[1]) * (r + dx) / d, B[2] + (vec[2] - B[2]) * (r + dx) / d]; 935 936 return new Coords(Const.COORDS_BY_USER, vec, this.board); 937 }; 938 939 el.Value = function () { 940 return Geometry.rad(this.point2, this.point1, this.point3); 941 }; 942 943 el.methodMap = Type.deepCopy(el.methodMap, { 944 Value: 'Value', 945 setAngle: 'setAngle', 946 free: 'free' 947 }); 948 949 return el; 950 }; 951 952 JXG.registerElement('angle', JXG.createAngle); 953 954 return { 955 createSector: JXG.createSector, 956 createCircumcircleSector: JXG.createCircumcircleSector, 957 createAngle: JXG.createAngle 958 }; 959 }); 960