1 /* 2 Copyright 2008-2018 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/circle 42 utils/type 43 base/constants 44 elements: 45 curve 46 midpoint 47 circumcenter 48 */ 49 50 /** 51 * @fileoverview In this file the geometry object Arc is defined. Arc stores all 52 * style and functional properties that are required to draw an arc on a board. 53 */ 54 55 define([ 56 'jxg', 'math/geometry', 'math/math', 'base/coords', 'base/circle', 'utils/type', 'base/constants', 57 'base/curve', 'element/composition' 58 ], function (JXG, Geometry, Mat, Coords, Circle, Type, Const, Curve, Compositions) { 59 60 "use strict"; 61 62 /** 63 * @class An arc is a segment of the circumference of a circle. It is defined by a center, one point that 64 * defines the radius, and a third point that defines the angle of the arc. 65 * @pseudo 66 * @name Arc 67 * @augments Curve 68 * @constructor 69 * @type JXG.Curve 70 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 71 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The result will be an arc of a circle around p1 through p2. The arc is drawn 72 * counter-clockwise from p2 to p3. 73 * @example 74 * // Create an arc out of three free points 75 * var p1 = board.create('point', [2.0, 2.0]); 76 * var p2 = board.create('point', [1.0, 0.5]); 77 * var p3 = board.create('point', [3.5, 1.0]); 78 * 79 * var a = board.create('arc', [p1, p2, p3]); 80 * </pre><div class="jxgbox" id="114ef584-4a5e-4686-8392-c97501befb5b" style="width: 300px; height: 300px;"></div> 81 * <script type="text/javascript"> 82 * (function () { 83 * var board = JXG.JSXGraph.initBoard('114ef584-4a5e-4686-8392-c97501befb5b', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 84 * p1 = board.create('point', [2.0, 2.0]), 85 * p2 = board.create('point', [1.0, 0.5]), 86 * p3 = board.create('point', [3.5, 1.0]), 87 * 88 * a = board.create('arc', [p1, p2, p3]); 89 * })(); 90 * </script><pre> 91 */ 92 JXG.createArc = function (board, parents, attributes) { 93 var el, attr, i, points; 94 95 // This method is used to create circumcirclearcs, too. If a circumcirclearc is created we get a fourth 96 // point, that's why we need to check that case, too. 97 points = Type.providePoints(board, parents, attributes, 'point'); 98 if (points === false || points.length < 3) { 99 throw new Error("JSXGraph: Can't create Arc with parent types '" + 100 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + 101 (typeof parents[2]) + "'." + 102 "\nPossible parent types: [point,point,point]"); 103 } 104 105 attr = Type.copyAttributes(attributes, board.options, 'arc'); 106 el = board.create('curve', [[0], [0]], attr); 107 108 el.elType = 'arc'; 109 el.setParents(points); 110 111 /** 112 * documented in JXG.GeometryElement 113 * @ignore 114 */ 115 el.type = Const.OBJECT_TYPE_ARC; 116 117 /** 118 * Center of the arc. 119 * @memberOf Arc.prototype 120 * @name center 121 * @type JXG.Point 122 */ 123 el.center = points[0]; 124 125 /** 126 * Point defining the arc's radius. 127 * @memberOf Arc.prototype 128 * @name radiuspoint 129 * @type JXG.Point 130 */ 131 el.radiuspoint = points[1]; 132 el.point2 = el.radiuspoint; 133 134 /** 135 * The point defining the arc's angle. 136 * @memberOf Arc.prototype 137 * @name anglepoint 138 * @type JXG.Point 139 */ 140 el.anglepoint = points[2]; 141 el.point3 = el.anglepoint; 142 143 // Add arc as child to defining points 144 el.center.addChild(el); 145 el.radiuspoint.addChild(el); 146 el.anglepoint.addChild(el); 147 148 // should be documented in options 149 el.useDirection = attr.usedirection; 150 151 // documented in JXG.Curve 152 el.updateDataArray = function () { 153 var ar, phi, v, det, p0c, p1c, p2c, 154 sgn = 1, 155 A = this.radiuspoint, 156 B = this.center, 157 C = this.anglepoint, 158 ev_s = Type.evaluate(this.visProp.selection); 159 160 phi = Geometry.rad(A, B, C); 161 if ((ev_s === 'minor' && phi > Math.PI) || 162 (ev_s === 'major' && phi < Math.PI)) { 163 sgn = -1; 164 } 165 166 // This is true for circumCircleArcs. In that case there is 167 // a fourth parent element: [center, point1, point3, point2] 168 if (this.useDirection) { 169 p0c = points[1].coords.usrCoords; 170 p1c = points[3].coords.usrCoords; 171 p2c = points[2].coords.usrCoords; 172 det = (p0c[1] - p2c[1]) * (p0c[2] - p1c[2]) - (p0c[2] - p2c[2]) * (p0c[1] - p1c[1]); 173 174 if (det < 0) { 175 this.radiuspoint = points[1]; 176 this.anglepoint = points[2]; 177 } else { 178 this.radiuspoint = points[2]; 179 this.anglepoint = points[1]; 180 } 181 } 182 183 A = A.coords.usrCoords; 184 B = B.coords.usrCoords; 185 C = C.coords.usrCoords; 186 187 ar = Geometry.bezierArc(A, B, C, false, sgn); 188 189 this.dataX = ar[0]; 190 this.dataY = ar[1]; 191 192 this.bezierDegree = 3; 193 194 this.updateStdform(); 195 this.updateQuadraticform(); 196 }; 197 198 /** 199 * Determines the arc's current radius. I.e. the distance between {@link Arc#center} and {@link Arc#radiuspoint}. 200 * @memberOf Arc.prototype 201 * @name Radius 202 * @function 203 * @returns {Number} The arc's radius 204 */ 205 el.Radius = function () { 206 return this.radiuspoint.Dist(this.center); 207 }; 208 209 /** 210 * @deprecated Use {@link Arc#Radius} 211 * @memberOf Arc.prototype 212 * @name getRadius 213 * @function 214 * @returns {Number} 215 */ 216 el.getRadius = function () { 217 JXG.deprecated('Arc.getRadius()', 'Arc.Radius()'); 218 return this.Radius(); 219 }; 220 221 /** 222 * Returns the length of the arc. 223 * @memberOf Arc.prototype 224 * @name Value 225 * @function 226 * @returns {Number} The arc length 227 */ 228 el.Value = function () { 229 return this.Radius() * Geometry.rad(this.radiuspoint, this.center, this.anglepoint); 230 }; 231 232 // documented in geometry element 233 el.hasPoint = function (x, y) { 234 var dist, checkPoint, 235 has, angle, alpha, beta, 236 invMat, c, 237 prec, 238 r = this.Radius(), 239 ev_s = Type.evaluate(this.visProp.selection); 240 241 prec = this.board.options.precision.hasPoint / Math.min(this.board.unitX, this.board.unitY); 242 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 243 244 if (this.transformations.length > 0) { 245 // Transform the mouse/touch coordinates 246 // back to the original position of the curve. 247 this.updateTransformMatrix(); 248 invMat = Mat.inverse(this.transformMat); 249 c = Mat.matVecMult(invMat, checkPoint.usrCoords); 250 checkPoint = new Coords(Const.COORDS_BY_USER, c, this.board); 251 } 252 253 dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint); 254 has = (Math.abs(dist - r) < prec); 255 256 /** 257 * At that point we know that the user has touched the circle line. 258 */ 259 if (has) { 260 angle = Geometry.rad(this.radiuspoint, this.center, checkPoint.usrCoords.slice(1)); 261 alpha = 0.0; 262 beta = Geometry.rad(this.radiuspoint, this.center, this.anglepoint); 263 264 if ((ev_s === 'minor' && beta > Math.PI) || 265 (ev_s === 'major' && beta < Math.PI)) { 266 alpha = beta; 267 beta = 2 * Math.PI; 268 } 269 if (angle < alpha || angle > beta) { 270 has = false; 271 } 272 } 273 274 return has; 275 }; 276 277 /** 278 * Checks whether (x,y) is within the sector defined by the arc. 279 * @memberOf Arc.prototype 280 * @name hasPointSector 281 * @function 282 * @param {Number} x Coordinate in x direction, screen coordinates. 283 * @param {Number} y Coordinate in y direction, screen coordinates. 284 * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise. 285 */ 286 el.hasPointSector = function (x, y) { 287 var angle, alpha, beta, 288 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board), 289 r = this.Radius(), 290 dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint), 291 has = (dist < r), 292 ev_s = Type.evaluate(this.visProp.selection); 293 294 if (has) { 295 angle = Geometry.rad(this.radiuspoint, this.center, checkPoint.usrCoords.slice(1)); 296 alpha = 0; 297 beta = Geometry.rad(this.radiuspoint, this.center, this.anglepoint); 298 299 if ((ev_s === 'minor' && beta > Math.PI) || 300 (ev_s === 'major' && beta < Math.PI)) { 301 alpha = beta; 302 beta = 2 * Math.PI; 303 } 304 if (angle < alpha || angle > beta) { 305 has = false; 306 } 307 } 308 309 return has; 310 }; 311 312 // documented in geometry element 313 el.getTextAnchor = function () { 314 return this.center.coords; 315 }; 316 317 // documented in geometry element 318 el.getLabelAnchor = function () { 319 var coords, vec, vecx, vecy, len, 320 angle = Geometry.rad(this.radiuspoint, this.center, this.anglepoint), 321 dx = 10 / this.board.unitX, 322 dy = 10 / this.board.unitY, 323 p2c = this.point2.coords.usrCoords, 324 pmc = this.center.coords.usrCoords, 325 bxminusax = p2c[1] - pmc[1], 326 byminusay = p2c[2] - pmc[2], 327 ev_s = Type.evaluate(this.visProp.selection), 328 l_vp = this.label ? this.label.visProp : this.visProp.label; 329 330 // If this is uncommented, the angle label can not be dragged 331 //if (Type.exists(this.label)) { 332 // this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board); 333 //} 334 335 if ((ev_s === 'minor' && angle > Math.PI) || 336 (ev_s === 'major' && angle < Math.PI)) { 337 angle = -(2 * Math.PI - angle); 338 } 339 340 coords = new Coords(Const.COORDS_BY_USER, [ 341 pmc[1] + Math.cos(angle * 0.5) * bxminusax - Math.sin(angle * 0.5) * byminusay, 342 pmc[2] + Math.sin(angle * 0.5) * bxminusax + Math.cos(angle * 0.5) * byminusay 343 ], this.board); 344 345 vecx = coords.usrCoords[1] - pmc[1]; 346 vecy = coords.usrCoords[2] - pmc[2]; 347 348 len = Math.sqrt(vecx * vecx + vecy * vecy); 349 vecx = vecx * (len + dx) / len; 350 vecy = vecy * (len + dy) / len; 351 vec = [pmc[1] + vecx, pmc[2] + vecy]; 352 353 l_vp.position = Geometry.calcLabelQuadrant(Geometry.rad([1,0],[0,0],vec)); 354 355 return new Coords(Const.COORDS_BY_USER, vec, this.board); 356 }; 357 358 // documentation in jxg.circle 359 el.updateQuadraticform = Circle.Circle.prototype.updateQuadraticform; 360 361 // documentation in jxg.circle 362 el.updateStdform = Circle.Circle.prototype.updateStdform; 363 364 el.methodMap = JXG.deepCopy(el.methodMap, { 365 getRadius: 'getRadius', 366 radius: 'Radius', 367 center: 'center', 368 radiuspoint: 'radiuspoint', 369 anglepoint: 'anglepoint', 370 Value: 'Value' 371 }); 372 373 el.prepareUpdate().update(); 374 return el; 375 }; 376 377 JXG.registerElement('arc', JXG.createArc); 378 379 /** 380 * @class A semicircle is a special arc defined by two points. The arc hits both points. 381 * @pseudo 382 * @name Semicircle 383 * @augments Arc 384 * @constructor 385 * @type Arc 386 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 387 * @param {JXG.Point_JXG.Point} p1,p2 The result will be a composition of an arc drawn clockwise from <tt>p1</tt> and 388 * <tt>p2</tt> and the midpoint of <tt>p1</tt> and <tt>p2</tt>. 389 * @example 390 * // Create an arc out of three free points 391 * var p1 = board.create('point', [4.5, 2.0]); 392 * var p2 = board.create('point', [1.0, 0.5]); 393 * 394 * var a = board.create('semicircle', [p1, p2]); 395 * </pre><div class="jxgbox" id="5385d349-75d7-4078-b732-9ae808db1b0e" style="width: 300px; height: 300px;"></div> 396 * <script type="text/javascript"> 397 * (function () { 398 * var board = JXG.JSXGraph.initBoard('5385d349-75d7-4078-b732-9ae808db1b0e', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 399 * p1 = board.create('point', [4.5, 2.0]), 400 * p2 = board.create('point', [1.0, 0.5]), 401 * 402 * sc = board.create('semicircle', [p1, p2]); 403 * })(); 404 * </script><pre> 405 */ 406 JXG.createSemicircle = function (board, parents, attributes) { 407 var el, mp, attr, points; 408 409 // we need 2 points 410 points = Type.providePoints(board, parents, attributes, 'point'); 411 if (points === false || points.length !== 2) { 412 throw new Error("JSXGraph: Can't create Semicircle with parent types '" + 413 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 414 "\nPossible parent types: [point,point]"); 415 } 416 417 attr = Type.copyAttributes(attributes, board.options, 'semicircle', 'midpoint'); 418 mp = board.create('midpoint', points, attr); 419 mp.dump = false; 420 421 attr = Type.copyAttributes(attributes, board.options, 'semicircle'); 422 el = board.create('arc', [mp, points[1], points[0]], attr); 423 el.elType = 'semicircle'; 424 el.setParents([points[0].id, points[1].id]); 425 el.subs = { 426 midpoint: mp 427 }; 428 el.inherits.push(mp); 429 430 /** 431 * The midpoint of the two defining points. 432 * @memberOf Semicircle.prototype 433 * @name midpoint 434 * @type Midpoint 435 */ 436 el.midpoint = el.center = mp; 437 438 return el; 439 }; 440 441 JXG.registerElement('semicircle', JXG.createSemicircle); 442 443 /** 444 * @class A circumcircle arc is an {@link Arc} defined by three points. All three points lie on the arc. 445 * @pseudo 446 * @name CircumcircleArc 447 * @augments Arc 448 * @constructor 449 * @type Arc 450 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 451 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The result will be a composition of an arc of the circumcircle of 452 * <tt>p1</tt>, <tt>p2</tt>, and <tt>p3</tt> and the midpoint of the circumcircle of the three points. The arc is drawn 453 * counter-clockwise from <tt>p1</tt> over <tt>p2</tt> to <tt>p3</tt>. 454 * @example 455 * // Create a circum circle arc out of three free points 456 * var p1 = board.create('point', [2.0, 2.0]); 457 * var p2 = board.create('point', [1.0, 0.5]); 458 * var p3 = board.create('point', [3.5, 1.0]); 459 * 460 * var a = board.create('arc', [p1, p2, p3]); 461 * </pre><div class="jxgbox" id="87125fd4-823a-41c1-88ef-d1a1369504e3" style="width: 300px; height: 300px;"></div> 462 * <script type="text/javascript"> 463 * (function () { 464 * var board = JXG.JSXGraph.initBoard('87125fd4-823a-41c1-88ef-d1a1369504e3', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 465 * p1 = board.create('point', [2.0, 2.0]), 466 * p2 = board.create('point', [1.0, 0.5]), 467 * p3 = board.create('point', [3.5, 1.0]), 468 * 469 * cca = board.create('circumcirclearc', [p1, p2, p3]); 470 * })(); 471 * </script><pre> 472 */ 473 JXG.createCircumcircleArc = function (board, parents, attributes) { 474 var el, mp, attr, points; 475 476 // We need three points 477 points = Type.providePoints(board, parents, attributes, 'point'); 478 if (points === false || points.length !== 3) { 479 throw new Error("JSXGraph: create Circumcircle Arc with parent types '" + 480 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." + 481 "\nPossible parent types: [point,point,point]"); 482 } 483 484 attr = Type.copyAttributes(attributes, board.options, 'circumcirclearc', 'center'); 485 mp = board.create('circumcenter', points, attr); 486 mp.dump = false; 487 488 attr = Type.copyAttributes(attributes, board.options, 'circumcirclearc'); 489 attr.usedirection = true; 490 el = board.create('arc', [mp, points[0], points[2], points[1]], attr); 491 492 el.elType = 'circumcirclearc'; 493 el.setParents([points[0].id, points[1].id, points[2].id]); 494 el.subs = { 495 center: mp 496 }; 497 el.inherits.push(mp); 498 499 /** 500 * The midpoint of the circumcircle of the three points defining the circumcircle arc. 501 * @memberOf CircumcircleArc.prototype 502 * @name center 503 * @type Circumcenter 504 */ 505 el.center = mp; 506 507 return el; 508 }; 509 510 JXG.registerElement('circumcirclearc', JXG.createCircumcircleArc); 511 512 /** 513 * @class A minor arc is a segment of the circumference of a circle having measure less than or equal to 514 * 180 degrees (pi radians). It is defined by a center, one point that 515 * defines the radius, and a third point that defines the angle of the arc. 516 * @pseudo 517 * @name MinorArc 518 * @augments Curve 519 * @constructor 520 * @type JXG.Curve 521 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 522 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor arc is an arc of a circle around p1 having measure less than or equal to 523 * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3. 524 * @example 525 * // Create an arc out of three free points 526 * var p1 = board.create('point', [2.0, 2.0]); 527 * var p2 = board.create('point', [1.0, 0.5]); 528 * var p3 = board.create('point', [3.5, 1.0]); 529 * 530 * var a = board.create('arc', [p1, p2, p3]); 531 * </pre><div class="jxgbox" id="64ba7ca2-8728-45f3-96e5-3c7a4414de2f" style="width: 300px; height: 300px;"></div> 532 * <script type="text/javascript"> 533 * (function () { 534 * var board = JXG.JSXGraph.initBoard('64ba7ca2-8728-45f3-96e5-3c7a4414de2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 535 * p1 = board.create('point', [2.0, 2.0]), 536 * p2 = board.create('point', [1.0, 0.5]), 537 * p3 = board.create('point', [3.5, 1.0]), 538 * 539 * a = board.create('minorarc', [p1, p2, p3]); 540 * })(); 541 * </script><pre> 542 */ 543 544 JXG.createMinorArc = function (board, parents, attributes) { 545 attributes.selection = 'minor'; 546 return JXG.createArc(board, parents, attributes); 547 }; 548 549 JXG.registerElement('minorarc', JXG.createMinorArc); 550 551 /** 552 * @class A major arc is a segment of the circumference of a circle having measure greater than or equal to 553 * 180 degrees (pi radians). It is defined by a center, one point that 554 * defines the radius, and a third point that defines the angle of the arc. 555 * @pseudo 556 * @name MajorArc 557 * @augments Curve 558 * @constructor 559 * @type JXG.Curve 560 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 561 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Major arc is an arc of a circle around p1 having measure greater than or equal to 562 * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3. 563 * @example 564 * // Create an arc out of three free points 565 * var p1 = board.create('point', [2.0, 2.0]); 566 * var p2 = board.create('point', [1.0, 0.5]); 567 * var p3 = board.create('point', [3.5, 1.0]); 568 * 569 * var a = board.create('minorarc', [p1, p2, p3]); 570 * </pre><div class="jxgbox" id="17a10d38-5629-40a4-b150-f41806edee9f" style="width: 300px; height: 300px;"></div> 571 * <script type="text/javascript"> 572 * (function () { 573 * var board = JXG.JSXGraph.initBoard('17a10d38-5629-40a4-b150-f41806edee9f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 574 * p1 = board.create('point', [2.0, 2.0]), 575 * p2 = board.create('point', [1.0, 0.5]), 576 * p3 = board.create('point', [3.5, 1.0]), 577 * 578 * a = board.create('majorarc', [p1, p2, p3]); 579 * })(); 580 * </script><pre> 581 */ 582 JXG.createMajorArc = function (board, parents, attributes) { 583 attributes.selection = 'major'; 584 return JXG.createArc(board, parents, attributes); 585 }; 586 587 JXG.registerElement('majorarc', JXG.createMajorArc); 588 589 return { 590 createArc: JXG.createArc, 591 createSemicircle: JXG.createSemicircle, 592 createCircumcircleArc: JXG.createCircumcircleArc, 593 createMinorArc: JXG.createMinorArc, 594 createMajorArc: JXG.createMajorArc 595 }; 596 }); 597