1 /* 2 Copyright 2008-2024 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 <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview The geometry object Circle is defined in this file. Circle stores all 37 * style and functional properties that are required to draw and move a circle on 38 * a board. 39 */ 40 41 import JXG from "../jxg.js"; 42 import GeometryElement from "./element.js"; 43 import Coords from "./coords.js"; 44 import Const from "./constants.js"; 45 import Mat from "../math/math.js"; 46 import GeonextParser from "../parser/geonext.js"; 47 import Type from "../utils/type.js"; 48 49 /** 50 * A circle consists of all points with a given distance from one point. This point is called center, the distance is called radius. 51 * A circle can be constructed by providing a center and a point on the circle or a center and a radius (given as a number, function, 52 * line, or circle). 53 * @class Creates a new circle object. Do not use this constructor to create a circle. Use {@link JXG.Board#create} with 54 * type {@link Circle} instead. 55 * @constructor 56 * @augments JXG.GeometryElement 57 * @param {JXG.Board} board The board the new circle is drawn on. 58 * @param {String} method Can be 59 * <ul><li> <b>'twoPoints'</b> which means the circle is defined by its center and a point on the circle.</li> 60 * <li><b>'pointRadius'</b> which means the circle is defined by its center and its radius in user units</li> 61 * <li><b>'pointLine'</b> which means the circle is defined by its center and its radius given by the distance from the startpoint and the endpoint of the line</li> 62 * <li><b>'pointCircle'</b> which means the circle is defined by its center and its radius given by the radius of another circle</li></ul> 63 * The parameters p1, p2 and radius must be set according to this method parameter. 64 * @param {JXG.Point} par1 center of the circle. 65 * @param {JXG.Point|JXG.Line|JXG.Circle} par2 Can be 66 * <ul><li>a point on the circle if method is 'twoPoints'</li> 67 * <li>a line if the method is 'pointLine'</li> 68 * <li>a circle if the method is 'pointCircle'</li></ul> 69 * @param {Object} attributes 70 * @see JXG.Board#generateName 71 */ 72 JXG.Circle = function (board, method, par1, par2, attributes) { 73 // Call the constructor of GeometryElement 74 this.constructor(board, attributes, Const.OBJECT_TYPE_CIRCLE, Const.OBJECT_CLASS_CIRCLE); 75 76 /** 77 * Stores the given method. 78 * Can be 79 * <ul><li><b>'twoPoints'</b> which means the circle is defined by its center and a point on the circle.</li> 80 * <li><b>'pointRadius'</b> which means the circle is defined by its center and its radius given in user units or as term.</li> 81 * <li><b>'pointLine'</b> which means the circle is defined by its center and its radius given by the distance from the startpoint and the endpoint of the line.</li> 82 * <li><b>'pointCircle'</b> which means the circle is defined by its center and its radius given by the radius of another circle.</li></ul> 83 * @type String 84 * @see #center 85 * @see #point2 86 * @see #radius 87 * @see #line 88 * @see #circle 89 */ 90 this.method = method; 91 92 // this is kept so existing code won't ne broken 93 this.midpoint = this.board.select(par1); 94 95 /** 96 * The circles center. Do not set this parameter directly as it will break JSXGraph's update system. 97 * @type JXG.Point 98 */ 99 this.center = this.board.select(par1); 100 101 /** Point on the circle only set if method equals 'twoPoints'. Do not set this parameter directly as it will break JSXGraph's update system. 102 * @type JXG.Point 103 * @see #method 104 */ 105 this.point2 = null; 106 107 /** Radius of the circle 108 * only set if method equals 'pointRadius' 109 * @type Number 110 * @default null 111 * @see #method 112 */ 113 this.radius = 0; 114 115 /** Line defining the radius of the circle given by the distance from the startpoint and the endpoint of the line 116 * only set if method equals 'pointLine'. Do not set this parameter directly as it will break JSXGraph's update system. 117 * @type JXG.Line 118 * @default null 119 * @see #method 120 */ 121 this.line = null; 122 123 /** Circle defining the radius of the circle given by the radius of the other circle 124 * only set if method equals 'pointLine'. Do not set this parameter directly as it will break JSXGraph's update system. 125 * @type JXG.Circle 126 * @default null 127 * @see #method 128 */ 129 this.circle = null; 130 131 this.points = []; 132 133 if (method === "twoPoints") { 134 this.point2 = board.select(par2); 135 this.radius = this.Radius(); 136 } else if (method === "pointRadius") { 137 this.gxtterm = par2; 138 // Converts JessieCode syntax into JavaScript syntax and generally ensures that the radius is a function 139 this.updateRadius = Type.createFunction(par2, this.board); 140 // First evaluation of the radius function 141 this.updateRadius(); 142 this.addParentsFromJCFunctions([this.updateRadius]); 143 } else if (method === "pointLine") { 144 // dann ist p2 die Id eines Objekts vom Typ Line! 145 this.line = board.select(par2); 146 this.radius = this.line.point1.coords.distance( 147 Const.COORDS_BY_USER, 148 this.line.point2.coords 149 ); 150 } else if (method === "pointCircle") { 151 // dann ist p2 die Id eines Objekts vom Typ Circle! 152 this.circle = board.select(par2); 153 this.radius = this.circle.Radius(); 154 } 155 156 // create Label 157 this.id = this.board.setId(this, "C"); 158 this.board.renderer.drawEllipse(this); 159 this.board.finalizeAdding(this); 160 161 this.createGradient(); 162 this.elType = "circle"; 163 this.createLabel(); 164 165 if (Type.exists(this.center._is_new)) { 166 this.addChild(this.center); 167 delete this.center._is_new; 168 } else { 169 this.center.addChild(this); 170 } 171 172 if (method === "pointRadius") { 173 this.notifyParents(par2); 174 } else if (method === "pointLine") { 175 this.line.addChild(this); 176 } else if (method === "pointCircle") { 177 this.circle.addChild(this); 178 } else if (method === "twoPoints") { 179 if (Type.exists(this.point2._is_new)) { 180 this.addChild(this.point2); 181 delete this.point2._is_new; 182 } else { 183 this.point2.addChild(this); 184 } 185 } 186 187 this.methodMap = Type.deepCopy(this.methodMap, { 188 setRadius: "setRadius", 189 getRadius: "getRadius", 190 Area: "Area", 191 area: "Area", 192 Perimeter: "Perimeter", 193 Circumference: "Perimeter", 194 radius: "Radius", 195 Radius: "Radius", 196 Diameter: "Diameter", 197 center: "center", 198 line: "line", 199 point2: "point2" 200 }); 201 }; 202 203 JXG.Circle.prototype = new GeometryElement(); 204 205 JXG.extend( 206 JXG.Circle.prototype, 207 /** @lends JXG.Circle.prototype */ { 208 /** 209 * Checks whether (x,y) is near the circle line or inside of the ellipse 210 * (in case JXG.Options.conic#hasInnerPoints is true). 211 * @param {Number} x Coordinate in x direction, screen coordinates. 212 * @param {Number} y Coordinate in y direction, screen coordinates. 213 * @returns {Boolean} True if (x,y) is near the circle, False otherwise. 214 * @private 215 */ 216 hasPoint: function (x, y) { 217 var prec, type, 218 mp = this.center.coords.usrCoords, 219 p = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board), 220 r = this.Radius(), 221 dx, dy, dist; 222 223 if (Type.isObject(Type.evaluate(this.visProp.precision))) { 224 type = this.board._inputDevice; 225 prec = Type.evaluate(this.visProp.precision[type]); 226 } else { 227 // 'inherit' 228 prec = this.board.options.precision.hasPoint; 229 } 230 dx = mp[1] - p.usrCoords[1]; 231 dy = mp[2] - p.usrCoords[2]; 232 dist = Mat.hypot(dx, dy); 233 234 // We have to use usrCoords, since Radius is available in usrCoords only. 235 prec += Type.evaluate(this.visProp.strokewidth) * 0.5; 236 prec /= Math.sqrt(Math.abs(this.board.unitX * this.board.unitY)); 237 238 if (Type.evaluate(this.visProp.hasinnerpoints)) { 239 return dist < r + prec; 240 } 241 242 return Math.abs(dist - r) < prec; 243 }, 244 245 // /** 246 // * Used to generate a polynomial for a point p that lies on this circle. 247 // * @param {JXG.Point} p The point for which the polynomial is generated. 248 // * @returns {Array} An array containing the generated polynomial. 249 // * @private 250 // */ 251 generatePolynomial: function (p) { 252 /* 253 * We have four methods to construct a circle: 254 * (a) Two points 255 * (b) center and radius 256 * (c) center and radius given by length of a segment 257 * (d) center and radius given by another circle 258 * 259 * In case (b) we have to distinguish two cases: 260 * (i) radius is given as a number 261 * (ii) radius is given as a function 262 * In the latter case there's no guarantee the radius depends on other geometry elements 263 * in a polynomial way so this case has to be omitted. 264 * 265 * Another tricky case is case (d): 266 * The radius depends on another circle so we have to cycle through the ancestors of each circle 267 * until we reach one that's radius does not depend on another circles radius. 268 * 269 * 270 * All cases (a) to (d) vary only in calculation of the radius. So the basic formulae for 271 * a glider G (g1,g2) on a circle with center M (m1,m2) and radius r is just: 272 * 273 * (g1-m1)^2 + (g2-m2)^2 - r^2 = 0 274 * 275 * So the easiest case is (b) with a fixed radius given as a number. The other two cases (a) 276 * and (c) are quite the same: Euclidean distance between two points A (a1,a2) and B (b1,b2), 277 * squared: 278 * 279 * r^2 = (a1-b1)^2 + (a2-b2)^2 280 * 281 * For case (d) we have to cycle recursively through all defining circles and finally return the 282 * formulae for calculating r^2. For that we use JXG.Circle.symbolic.generateRadiusSquared(). 283 */ 284 var m1 = this.center.symbolic.x, 285 m2 = this.center.symbolic.y, 286 g1 = p.symbolic.x, 287 g2 = p.symbolic.y, 288 rsq = this.generateRadiusSquared(); 289 290 /* No radius can be calculated (Case b.ii) */ 291 if (rsq === "") { 292 return []; 293 } 294 295 return [ 296 "((" + g1 + ")-(" + m1 + "))^2 + ((" + g2 + ")-(" + m2 + "))^2 - (" + rsq + ")" 297 ]; 298 }, 299 300 /** 301 * Generate symbolic radius calculation for loci determination with Groebner-Basis algorithm. 302 * @returns {String} String containing symbolic calculation of the circle's radius or an empty string 303 * if the radius can't be expressed in a polynomial equation. 304 * @private 305 */ 306 generateRadiusSquared: function () { 307 /* 308 * Four cases: 309 * 310 * (a) Two points 311 * (b) center and radius 312 * (c) center and radius given by length of a segment 313 * (d) center and radius given by another circle 314 */ 315 var m1, 316 m2, 317 p1, 318 p2, 319 q1, 320 q2, 321 rsq = ""; 322 323 if (this.method === "twoPoints") { 324 m1 = this.center.symbolic.x; 325 m2 = this.center.symbolic.y; 326 p1 = this.point2.symbolic.x; 327 p2 = this.point2.symbolic.y; 328 329 rsq = "((" + p1 + ")-(" + m1 + "))^2 + ((" + p2 + ")-(" + m2 + "))^2"; 330 } else if (this.method === "pointRadius") { 331 if (Type.isNumber(this.radius)) { 332 rsq = (this.radius * this.radius).toString(); 333 } 334 } else if (this.method === "pointLine") { 335 p1 = this.line.point1.symbolic.x; 336 p2 = this.line.point1.symbolic.y; 337 338 q1 = this.line.point2.symbolic.x; 339 q2 = this.line.point2.symbolic.y; 340 341 rsq = "((" + p1 + ")-(" + q1 + "))^2 + ((" + p2 + ")-(" + q2 + "))^2"; 342 } else if (this.method === "pointCircle") { 343 rsq = this.circle.Radius(); 344 } 345 346 return rsq; 347 }, 348 349 /** 350 * Uses the boards renderer to update the circle. 351 */ 352 update: function () { 353 var x, y, z, r, c, i; 354 355 if (this.needsUpdate) { 356 if (Type.evaluate(this.visProp.trace)) { 357 this.cloneToBackground(true); 358 } 359 360 if (this.method === "pointLine") { 361 this.radius = this.line.point1.coords.distance( 362 Const.COORDS_BY_USER, 363 this.line.point2.coords 364 ); 365 } else if (this.method === "pointCircle") { 366 this.radius = this.circle.Radius(); 367 } else if (this.method === "pointRadius") { 368 this.radius = this.updateRadius(); 369 } 370 this.radius = Math.abs(this.radius); 371 372 this.updateStdform(); 373 this.updateQuadraticform(); 374 375 // Approximate the circle by 4 Bezier segments 376 // This will be used for intersections of type curve / circle. 377 // See https://spencermortensen.com/articles/bezier-circle/ 378 z = this.center.coords.usrCoords[0]; 379 x = this.center.coords.usrCoords[1] / z; 380 y = this.center.coords.usrCoords[2] / z; 381 z /= z; 382 r = this.Radius(); 383 c = 0.551915024494; 384 385 this.numberPoints = 13; 386 this.dataX = [ 387 x + r, x + r, x + r * c, x, x - r * c, x - r, x - r, x - r, x - r * c, x, x + r * c, x + r, x + r 388 ]; 389 this.dataY = [ 390 y, y + r * c, y + r, y + r, y + r, y + r * c, y, y - r * c, y - r, y - r, y - r, y - r * c, y 391 ]; 392 this.bezierDegree = 3; 393 for (i = 0; i < this.numberPoints; i++) { 394 this.points[i] = new Coords( 395 Const.COORDS_BY_USER, 396 [this.dataX[i], this.dataY[i]], 397 this.board 398 ); 399 } 400 } 401 402 return this; 403 }, 404 405 /** 406 * Updates this circle's {@link JXG.Circle#quadraticform}. 407 * @private 408 */ 409 updateQuadraticform: function () { 410 var m = this.center, 411 mX = m.X(), 412 mY = m.Y(), 413 r = this.Radius(); 414 415 this.quadraticform = [ 416 [mX * mX + mY * mY - r * r, -mX, -mY], 417 [-mX, 1, 0], 418 [-mY, 0, 1] 419 ]; 420 }, 421 422 /** 423 * Updates the stdform derived from the position of the center and the circle's radius. 424 * @private 425 */ 426 updateStdform: function () { 427 this.stdform[3] = 0.5; 428 this.stdform[4] = this.Radius(); 429 this.stdform[1] = -this.center.coords.usrCoords[1]; 430 this.stdform[2] = -this.center.coords.usrCoords[2]; 431 if (!isFinite(this.stdform[4])) { 432 this.stdform[0] = Type.exists(this.point2) 433 ? -( 434 this.stdform[1] * this.point2.coords.usrCoords[1] + 435 this.stdform[2] * this.point2.coords.usrCoords[2] 436 ) 437 : 0; 438 } 439 this.normalize(); 440 }, 441 442 /** 443 * Uses the boards renderer to update the circle. 444 * @private 445 */ 446 updateRenderer: function () { 447 // var wasReal; 448 449 if (!this.needsUpdate) { 450 return this; 451 } 452 453 if (this.visPropCalc.visible) { 454 // wasReal = this.isReal; 455 this.isReal = 456 !isNaN( 457 this.center.coords.usrCoords[1] + 458 this.center.coords.usrCoords[2] + 459 this.Radius() 460 ) && this.center.isReal; 461 462 if ( 463 //wasReal && 464 !this.isReal 465 ) { 466 this.updateVisibility(false); 467 } 468 } 469 470 // Update the position 471 if (this.visPropCalc.visible) { 472 this.board.renderer.updateEllipse(this); 473 } 474 475 // Update the label if visible. 476 if ( 477 this.hasLabel && 478 this.visPropCalc.visible && 479 this.label && 480 this.label.visPropCalc.visible && 481 this.isReal 482 ) { 483 this.label.update(); 484 this.board.renderer.updateText(this.label); 485 } 486 487 // Update rendNode display 488 this.setDisplayRendNode(); 489 // if (this.visPropCalc.visible !== this.visPropOld.visible) { 490 // this.board.renderer.display(this, this.visPropCalc.visible); 491 // this.visPropOld.visible = this.visPropCalc.visible; 492 // 493 // if (this.hasLabel) { 494 // this.board.renderer.display(this.label, this.label.visPropCalc.visible); 495 // } 496 // } 497 498 this.needsUpdate = false; 499 return this; 500 }, 501 502 /** 503 * Finds dependencies in a given term and resolves them by adding the elements referenced in this 504 * string to the circle's list of ancestors. 505 * @param {String} contentStr 506 * @private 507 */ 508 notifyParents: function (contentStr) { 509 if (Type.isString(contentStr)) { 510 GeonextParser.findDependencies(this, contentStr, this.board); 511 } 512 }, 513 514 /** 515 * Set a new radius, then update the board. 516 * @param {String|Number|function} r A string, function or number describing the new radius. 517 * @returns {JXG.Circle} Reference to this circle 518 */ 519 setRadius: function (r) { 520 this.updateRadius = Type.createFunction(r, this.board); 521 this.addParentsFromJCFunctions([this.updateRadius]); 522 this.board.update(); 523 524 return this; 525 }, 526 527 /** 528 * Calculates the radius of the circle. 529 * @param {String|Number|function} [value] Set new radius 530 * @returns {Number} The radius of the circle 531 */ 532 Radius: function (value) { 533 if (Type.exists(value)) { 534 this.setRadius(value); 535 return this.Radius(); 536 } 537 538 if (this.method === "twoPoints") { 539 if ( 540 Type.cmpArrays(this.point2.coords.usrCoords, [0, 0, 0]) || 541 Type.cmpArrays(this.center.coords.usrCoords, [0, 0, 0]) 542 ) { 543 return NaN; 544 } 545 546 return this.center.Dist(this.point2); 547 } 548 549 if (this.method === "pointLine" || this.method === "pointCircle") { 550 return this.radius; 551 } 552 553 if (this.method === "pointRadius") { 554 return (Type.evaluate(this.visProp.nonnegativeonly)) ? 555 Math.max(0.0, this.updateRadius()) : 556 Math.abs(this.updateRadius()); 557 } 558 559 return NaN; 560 }, 561 562 /** 563 * Calculates the diameter of the circle. 564 * @returns {Number} The Diameter of the circle 565 */ 566 Diameter: function () { 567 return 2 * this.Radius(); 568 }, 569 570 /** 571 * Use {@link JXG.Circle#Radius}. 572 * @deprecated 573 */ 574 getRadius: function () { 575 JXG.deprecated("Circle.getRadius()", "Circle.Radius()"); 576 return this.Radius(); 577 }, 578 579 // documented in geometry element 580 getTextAnchor: function () { 581 return this.center.coords; 582 }, 583 584 // documented in geometry element 585 getLabelAnchor: function () { 586 var x, y, pos, 587 xy, lbda, sgn, 588 dist = 1.5, 589 r = this.Radius(), 590 c = this.center.coords.usrCoords, 591 SQRTH = 7.071067811865e-1; // sqrt(2)/2 592 593 if (!Type.exists(this.label)) { 594 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board); 595 } 596 597 pos = Type.evaluate(this.label.visProp.position); 598 if (!Type.isString(pos)) { 599 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board); 600 } 601 602 if (pos.indexOf('right') < 0 && pos.indexOf('left') < 0) { 603 switch (Type.evaluate(this.visProp.label.position)) { 604 case "lft": 605 x = c[1] - r; 606 y = c[2]; 607 break; 608 case "llft": 609 x = c[1] - SQRTH * r; 610 y = c[2] - SQRTH * r; 611 break; 612 case "rt": 613 x = c[1] + r; 614 y = c[2]; 615 break; 616 case "lrt": 617 x = c[1] + SQRTH * r; 618 y = c[2] - SQRTH * r; 619 break; 620 case "urt": 621 x = c[1] + SQRTH * r; 622 y = c[2] + SQRTH * r; 623 break; 624 case "top": 625 x = c[1]; 626 y = c[2] + r; 627 break; 628 case "bot": 629 x = c[1]; 630 y = c[2] - r; 631 break; 632 default: 633 // includes case 'ulft' 634 x = c[1] - SQRTH * r; 635 y = c[2] + SQRTH * r; 636 break; 637 } 638 } else { 639 // New positioning 640 c = this.center.coords.scrCoords; 641 642 xy = Type.parsePosition(pos); 643 lbda = Type.parseNumber(xy.pos, 2 * Math.PI, 1); 644 if (xy.pos.indexOf('fr') < 0 && 645 xy.pos.indexOf('%') < 0) { 646 if (xy.pos.indexOf('px') >= 0) { 647 // 'px' or numbers are not supported 648 lbda = 0; 649 } else { 650 // Pure numbers are interpreted as degrees 651 lbda *= Math.PI / 180; 652 } 653 } 654 655 // Position left or right 656 sgn = 1; 657 if (xy.side === 'left') { 658 sgn = -1; 659 } 660 661 if (Type.exists(this.label)) { 662 dist = sgn * 0.5 * Type.evaluate(this.label.visProp.distance); 663 } 664 665 x = c[1] + (r * this.board.unitX + this.label.size[0] * dist) * Math.cos(lbda); 666 y = c[2] - (r * this.board.unitY + this.label.size[1] * dist) * Math.sin(lbda); 667 668 return new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 669 } 670 671 return new Coords(Const.COORDS_BY_USER, [x, y], this.board); 672 }, 673 674 // documented in geometry element 675 cloneToBackground: function () { 676 var er, 677 r = this.Radius(), 678 copy = { 679 id: this.id + "T" + this.numTraces, 680 elementClass: Const.OBJECT_CLASS_CIRCLE, 681 center: { 682 coords: this.center.coords 683 }, 684 Radius: function () { 685 return r; 686 }, 687 getRadius: function () { 688 return r; 689 }, 690 board: this.board, 691 visProp: Type.deepCopy(this.visProp, this.visProp.traceattributes, true) 692 }; 693 694 copy.visProp.layer = this.board.options.layer.trace; 695 696 this.numTraces++; 697 Type.clearVisPropOld(copy); 698 copy.visPropCalc = { 699 visible: Type.evaluate(copy.visProp.visible) 700 }; 701 702 er = this.board.renderer.enhancedRendering; 703 this.board.renderer.enhancedRendering = true; 704 this.board.renderer.drawEllipse(copy); 705 this.board.renderer.enhancedRendering = er; 706 this.traces[copy.id] = copy.rendNode; 707 708 return this; 709 }, 710 711 /** 712 * Add transformations to this circle. 713 * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s. 714 * @returns {JXG.Circle} Reference to this circle object. 715 */ 716 addTransform: function (transform) { 717 var i, 718 list = Type.isArray(transform) ? transform : [transform], 719 len = list.length; 720 721 for (i = 0; i < len; i++) { 722 this.center.transformations.push(list[i]); 723 724 if (this.method === "twoPoints") { 725 this.point2.transformations.push(list[i]); 726 } 727 } 728 729 return this; 730 }, 731 732 // see element.js 733 snapToGrid: function () { 734 var forceIt = Type.evaluate(this.visProp.snaptogrid); 735 736 this.center.handleSnapToGrid(forceIt, true); 737 if (this.method === "twoPoints") { 738 this.point2.handleSnapToGrid(forceIt, true); 739 } 740 741 return this; 742 }, 743 744 // see element.js 745 snapToPoints: function () { 746 var forceIt = Type.evaluate(this.visProp.snaptopoints); 747 748 this.center.handleSnapToPoints(forceIt); 749 if (this.method === "twoPoints") { 750 this.point2.handleSnapToPoints(forceIt); 751 } 752 753 return this; 754 }, 755 756 /** 757 * Treats the circle as parametric curve and calculates its X coordinate. 758 * @param {Number} t Number between 0 and 1. 759 * @returns {Number} <tt>X(t)= radius*cos(t)+centerX</tt>. 760 */ 761 X: function (t) { 762 return this.Radius() * Math.cos(t * 2 * Math.PI) + this.center.coords.usrCoords[1]; 763 }, 764 765 /** 766 * Treats the circle as parametric curve and calculates its Y coordinate. 767 * @param {Number} t Number between 0 and 1. 768 * @returns {Number} <tt>X(t)= radius*sin(t)+centerY</tt>. 769 */ 770 Y: function (t) { 771 return this.Radius() * Math.sin(t * 2 * Math.PI) + this.center.coords.usrCoords[2]; 772 }, 773 774 /** 775 * Treat the circle as parametric curve and calculates its Z coordinate. 776 * @param {Number} t ignored 777 * @returns {Number} 1.0 778 */ 779 Z: function (t) { 780 return 1.0; 781 }, 782 783 /** 784 * Returns 0. 785 * @private 786 */ 787 minX: function () { 788 return 0.0; 789 }, 790 791 /** 792 * Returns 1. 793 * @private 794 */ 795 maxX: function () { 796 return 1.0; 797 }, 798 799 /** 800 * Circle area 801 * @returns {Number} area of the circle. 802 */ 803 Area: function () { 804 var r = this.Radius(); 805 806 return r * r * Math.PI; 807 }, 808 809 /** 810 * Perimeter (circumference) of circle. 811 * @returns {Number} Perimeter of circle in user units. 812 */ 813 Perimeter: function () { 814 return 2 * this.Radius() * Math.PI; 815 }, 816 817 /** 818 * Get bounding box of the circle. 819 * @returns {Array} [x1, y1, x2, y2] 820 */ 821 bounds: function () { 822 var uc = this.center.coords.usrCoords, 823 r = this.Radius(); 824 825 return [uc[1] - r, uc[2] + r, uc[1] + r, uc[2] - r]; 826 }, 827 828 /** 829 * Get data to construct this element. Data consists of the parent elements 830 * and static data like radius. 831 * @returns {Array} data necessary to construct this element 832 */ 833 getParents: function () { 834 if (this.parents.length === 1) { 835 // i.e. this.method === 'pointRadius' 836 return this.parents.concat(this.radius); 837 } 838 return this.parents; 839 } 840 } 841 ); 842 843 /** 844 * @class This element is used to provide a constructor for a circle. 845 * @pseudo 846 * @description A circle consists of all points with a given distance from one point. This point is called center, the distance is called radius. 847 * A circle can be constructed by providing a center and a point on the circle or a center and a radius (given as a number, function, 848 * line, or circle). If the radius is a negative value, its absolute values is taken. 849 * @name Circle 850 * @augments JXG.Circle 851 * @constructor 852 * @type JXG.Circle 853 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 854 * @param {JXG.Point_number,JXG.Point,JXG.Line,JXG.Circle} center,radius The center must be given as a {@link JXG.Point}, 855 * see {@link JXG.providePoints}, but the radius can be given 856 * as a number (which will create a circle with a fixed radius), 857 * another {@link JXG.Point}, a {@link JXG.Line} (the distance of start and end point of the 858 * line will determine the radius), or another {@link JXG.Circle}. 859 * <p> 860 * If the radius is supplied as number or output of a function, its absolute value is taken. 861 * 862 * @example 863 * // Create a circle providing two points 864 * var p1 = board.create('point', [2.0, 2.0]), 865 * p2 = board.create('point', [2.0, 0.0]), 866 * c1 = board.create('circle', [p1, p2]); 867 * 868 * // Create another circle using the above circle 869 * var p3 = board.create('point', [3.0, 2.0]), 870 * c2 = board.create('circle', [p3, c1]); 871 * </pre><div class="jxgbox" id="JXG5f304d31-ef20-4a8e-9c0e-ea1a2b6c79e0" style="width: 400px; height: 400px;"></div> 872 * <script type="text/javascript"> 873 * (function() { 874 * var cex1_board = JXG.JSXGraph.initBoard('JXG5f304d31-ef20-4a8e-9c0e-ea1a2b6c79e0', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 875 * cex1_p1 = cex1_board.create('point', [2.0, 2.0]), 876 * cex1_p2 = cex1_board.create('point', [2.0, 0.0]), 877 * cex1_c1 = cex1_board.create('circle', [cex1_p1, cex1_p2]), 878 * cex1_p3 = cex1_board.create('point', [3.0, 2.0]), 879 * cex1_c2 = cex1_board.create('circle', [cex1_p3, cex1_c1]); 880 * })(); 881 * </script><pre> 882 * @example 883 * // Create a circle providing two points 884 * var p1 = board.create('point', [2.0, 2.0]), 885 * c1 = board.create('circle', [p1, 3]); 886 * 887 * // Create another circle using the above circle 888 * var c2 = board.create('circle', [function() { return [p1.X(), p1.Y() + 1];}, function() { return c1.Radius(); }]); 889 * </pre><div class="jxgbox" id="JXG54165f60-93b9-441d-8979-ac5d0f193020" style="width: 400px; height: 400px;"></div> 890 * <script type="text/javascript"> 891 * (function() { 892 * var board = JXG.JSXGraph.initBoard('JXG54165f60-93b9-441d-8979-ac5d0f193020', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 893 * var p1 = board.create('point', [2.0, 2.0]); 894 * var c1 = board.create('circle', [p1, 3]); 895 * 896 * // Create another circle using the above circle 897 * var c2 = board.create('circle', [function() { return [p1.X(), p1.Y() + 1];}, function() { return c1.Radius(); }]); 898 * })(); 899 * </script><pre> 900 * @example 901 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 902 * var reflect = board.create('transform', [li], {type: 'reflect'}); 903 * 904 * var c1 = board.create('circle', [[-2,-2], [-2, -1]], {center: {visible:true}}); 905 * var c2 = board.create('circle', [c1, reflect]); 906 * * </pre><div id="JXGa2a5a870-5dbb-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 907 * <script type="text/javascript"> 908 * (function() { 909 * var board = JXG.JSXGraph.initBoard('JXGa2a5a870-5dbb-11e8-9fb9-901b0e1b8723', 910 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 911 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 912 * var reflect = board.create('transform', [li], {type: 'reflect'}); 913 * 914 * var c1 = board.create('circle', [[-2,-2], [-2, -1]], {center: {visible:true}}); 915 * var c2 = board.create('circle', [c1, reflect]); 916 * })(); 917 * 918 * </script><pre> 919 * 920 * @example 921 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 922 * var c1 = board.create('circle', [[1.3, 1.3], [0, 1.3]], {strokeColor: 'black', center: {visible:true}}); 923 * var c2 = board.create('circle', [c1, t], {strokeColor: 'black'}); 924 * 925 * </pre><div id="JXG0686a222-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 926 * <script type="text/javascript"> 927 * (function() { 928 * var board = JXG.JSXGraph.initBoard('JXG0686a222-6339-11e8-9fb9-901b0e1b8723', 929 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 930 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 931 * var c1 = board.create('circle', [[1.3, 1.3], [0, 1.3]], {strokeColor: 'black', center: {visible:true}}); 932 * var c2 = board.create('circle', [c1, t], {strokeColor: 'black'}); 933 * 934 * })(); 935 * 936 * </script><pre> 937 * 938 */ 939 JXG.createCircle = function (board, parents, attributes) { 940 var el, 941 p, 942 i, 943 attr, 944 obj, 945 isDraggable = true, 946 point_style = ["center", "point2"]; 947 948 p = []; 949 obj = board.select(parents[0]); 950 if ( 951 Type.isObject(obj) && 952 obj.elementClass === Const.OBJECT_CLASS_CIRCLE && 953 Type.isTransformationOrArray(parents[1]) 954 ) { 955 attr = Type.copyAttributes(attributes, board.options, "circle"); 956 // if (!Type.exists(attr.type) || attr.type.toLowerCase() !== 'euclidean') { 957 // // Create a circle element from a circle and a Euclidean transformation 958 // el = JXG.createCircle(board, [obj.center, function() { return obj.Radius(); }], attr); 959 // } else { 960 // Create a conic element from a circle and a projective transformation 961 el = JXG.createEllipse( 962 board, 963 [ 964 obj.center, 965 obj.center, 966 function () { 967 return 2 * obj.Radius(); 968 } 969 ], 970 attr 971 ); 972 // } 973 el.addTransform(parents[1]); 974 return el; 975 } 976 // Circle defined by points 977 for (i = 0; i < parents.length; i++) { 978 if (Type.isPointType(board, parents[i])) { 979 if (parents.length < 3) { 980 p.push( 981 Type.providePoints(board, [parents[i]], attributes, "circle", [point_style[i]])[0] 982 ); 983 } else { 984 p.push( 985 Type.providePoints(board, [parents[i]], attributes, "point")[0] 986 ); 987 } 988 if (p[p.length - 1] === false) { 989 throw new Error( 990 "JSXGraph: Can't create circle from this type. Please provide a point type." 991 ); 992 } 993 } else { 994 p.push(parents[i]); 995 } 996 } 997 998 attr = Type.copyAttributes(attributes, board.options, "circle"); 999 1000 if (p.length === 2 && Type.isPoint(p[0]) && Type.isPoint(p[1])) { 1001 // Point/Point 1002 el = new JXG.Circle(board, "twoPoints", p[0], p[1], attr); 1003 } else if ( 1004 (Type.isNumber(p[0]) || Type.isFunction(p[0]) || Type.isString(p[0])) && 1005 Type.isPoint(p[1]) 1006 ) { 1007 // Number/Point 1008 el = new JXG.Circle(board, "pointRadius", p[1], p[0], attr); 1009 } else if ( 1010 (Type.isNumber(p[1]) || Type.isFunction(p[1]) || Type.isString(p[1])) && 1011 Type.isPoint(p[0]) 1012 ) { 1013 // Point/Number 1014 el = new JXG.Circle(board, "pointRadius", p[0], p[1], attr); 1015 } else if (p[0].elementClass === Const.OBJECT_CLASS_CIRCLE && Type.isPoint(p[1])) { 1016 // Circle/Point 1017 el = new JXG.Circle(board, "pointCircle", p[1], p[0], attr); 1018 } else if (p[1].elementClass === Const.OBJECT_CLASS_CIRCLE && Type.isPoint(p[0])) { 1019 // Point/Circle 1020 el = new JXG.Circle(board, "pointCircle", p[0], p[1], attr); 1021 } else if (p[0].elementClass === Const.OBJECT_CLASS_LINE && Type.isPoint(p[1])) { 1022 // Line/Point 1023 el = new JXG.Circle(board, "pointLine", p[1], p[0], attr); 1024 } else if (p[1].elementClass === Const.OBJECT_CLASS_LINE && Type.isPoint(p[0])) { 1025 // Point/Line 1026 el = new JXG.Circle(board, "pointLine", p[0], p[1], attr); 1027 } else if ( 1028 parents.length === 3 && 1029 Type.isPoint(p[0]) && 1030 Type.isPoint(p[1]) && 1031 Type.isPoint(p[2]) 1032 ) { 1033 // Circle through three points 1034 // Check if circumcircle element is available 1035 if (JXG.elements.circumcircle) { 1036 el = JXG.elements.circumcircle(board, p, attr); 1037 } else { 1038 throw new Error( 1039 "JSXGraph: Can't create circle with three points. Please include the circumcircle element (element/composition)." 1040 ); 1041 } 1042 } else { 1043 throw new Error( 1044 "JSXGraph: Can't create circle with parent types '" + 1045 typeof parents[0] + 1046 "' and '" + 1047 typeof parents[1] + 1048 "'." + 1049 "\nPossible parent types: [point,point], [point,number], [point,function], [point,circle], [point,point,point], [circle,transformation]" 1050 ); 1051 } 1052 1053 el.isDraggable = isDraggable; 1054 el.setParents(p); 1055 el.elType = "circle"; 1056 for (i = 0; i < p.length; i++) { 1057 if (Type.isPoint(p[i])) { 1058 el.inherits.push(p[i]); 1059 } 1060 } 1061 return el; 1062 }; 1063 1064 JXG.registerElement("circle", JXG.createCircle); 1065 1066 export default JXG.Circle; 1067 // export default { 1068 // Circle: JXG.Circle, 1069 // createCircle: JXG.createCircle 1070 // }; 1071