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