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