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