      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