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