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