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