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