      1 /*
      2     Copyright 2008-2018
      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', 'parser/geonext', 'utils/type'
     58 ], function (JXG, GeometryElement, Coords, Const, 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         if (method === 'twoPoints') {
    145             this.point2 = board.select(par2);
    146             this.radius = this.Radius();
    147         } else if (method === 'pointRadius') {
    148             this.gxtterm = par2;
    149             // Converts GEONExT syntax into JavaScript syntax and generally ensures that the radius is a function
    150             this.updateRadius = Type.createFunction(par2, this.board, null, true);
    151             // First evaluation of the radius function
    152             this.updateRadius();
    153         } else if (method === 'pointLine') {
    154             // dann ist p2 die Id eines Objekts vom Typ Line!
    155             this.line = board.select(par2);
    156             this.radius = this.line.point1.coords.distance(Const.COORDS_BY_USER, this.line.point2.coords);
    157         } else if (method === 'pointCircle') {
    158             // dann ist p2 die Id eines Objekts vom Typ Circle!
    159             this.circle = board.select(par2);
    160             this.radius = this.circle.Radius();
    161         }
    162
    163         // create Label
    164         this.id = this.board.setId(this, 'C');
    165         this.board.renderer.drawEllipse(this);
    166         this.board.finalizeAdding(this);
    167
    168         this.createGradient();
    169         this.elType = 'circle';
    170         this.createLabel();
    171
    172         this.center.addChild(this);
    173
    174         if (method === 'pointRadius') {
    175             this.notifyParents(par2);
    176         } else if (method === 'pointLine') {
    177             this.line.addChild(this);
    178         } else if (method === 'pointCircle') {
    179             this.circle.addChild(this);
    180         } else if (method === 'twoPoints') {
    181             this.point2.addChild(this);
    182         }
    183
    184         this.methodMap = Type.deepCopy(this.methodMap, {
    185             setRadius: 'setRadius',
    186             getRadius: 'getRadius',
    187             Area: 'Area',
    188             area: 'Area',
    189             radius: 'Radius',
    190             center: 'center',
    191             line: 'line',
    192             point2: 'point2'
    193         });
    194     };
    195
    196     JXG.Circle.prototype = new GeometryElement();
    197
    198     JXG.extend(JXG.Circle.prototype, /** @lends JXG.Circle.prototype */ {
    199         /**
    200          * Checks whether (x,y) is near the circle line or inside of the ellipse
    201          * (in case JXG.Options.conic#hasInnerPoints is true).
    202          * @param {Number} x Coordinate in x direction, screen coordinates.
    203          * @param {Number} y Coordinate in y direction, screen coordinates.
    204          * @returns {Boolean} True if (x,y) is near the circle, False otherwise.
    205          * @private
    206          */
    207         hasPoint: function (x, y) {
    208             var prec = this.board.options.precision.hasPoint,
    209                 mp = this.center.coords.usrCoords,
    210                 p = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
    211                 r = this.Radius(),
    212                 dx, dy, dist;
    213
    214                 dx = mp[1] - p.usrCoords[1];
    215                 dy = mp[2] - p.usrCoords[2];
    216                 dist = Math.sqrt(dx * dx + dy * dy);
    217
    218                 // We have to use usrCoords, since Radius is available in usrCoords only.
    219                 prec += Type.evaluate(this.visProp.strokewidth) * 0.5;
    220                 prec /= Math.sqrt(this.board.unitX * this.board.unitY);
    221
    222             if (Type.evaluate(this.visProp.hasinnerpoints)) {
    223                 return (dist < r + prec);
    224             }
    225
    226             return (Math.abs(dist - r) < prec);
    227         },
    228
    229         /**
    230          * Used to generate a polynomial for a point p that lies on this circle.
    231          * @param {JXG.Point} p The point for which the polynomial is generated.
    232          * @returns {Array} An array containing the generated polynomial.
    233          * @private
    234          */
    235         generatePolynomial: function (p) {
    236             /*
    237              * We have four methods to construct a circle:
    238              *   (a) Two points
    239              *   (b) center and radius
    240              *   (c) center and radius given by length of a segment
    241              *   (d) center and radius given by another circle
    242              *
    243              * In case (b) we have to distinguish two cases:
    244              *  (i)  radius is given as a number
    245              *  (ii) radius is given as a function
    246              * In the latter case there's no guarantee the radius depends on other geometry elements
    247              * in a polynomial way so this case has to be omitted.
    248              *
    249              * Another tricky case is case (d):
    250              * The radius depends on another circle so we have to cycle through the ancestors of each circle
    251              * until we reach one that's radius does not depend on another circles radius.
    252              *
    253              *
    254              * All cases (a) to (d) vary only in calculation of the radius. So the basic formulae for
    255              * a glider G (g1,g2) on a circle with center M (m1,m2) and radius r is just:
    256              *
    257              *     (g1-m1)^2 + (g2-m2)^2 - r^2 = 0
    258              *
    259              * So the easiest case is (b) with a fixed radius given as a number. The other two cases (a)
    260              * and (c) are quite the same: Euclidean distance between two points A (a1,a2) and B (b1,b2),
    261              * squared:
    262              *
    263              *     r^2 = (a1-b1)^2 + (a2-b2)^2
    264              *
    265              * For case (d) we have to cycle recursively through all defining circles and finally return the
    266              * formulae for calculating r^2. For that we use JXG.Circle.symbolic.generateRadiusSquared().
    267              */
    268             var m1 = this.center.symbolic.x,
    269                 m2 = this.center.symbolic.y,
    270                 g1 = p.symbolic.x,
    271                 g2 = p.symbolic.y,
    272                 rsq = this.generateRadiusSquared();
    273
    274             /* No radius can be calculated (Case b.ii) */
    275             if (rsq === '') {
    276                 return [];
    277             }
    278
    279             return ['((' + g1 + ')-(' + m1 + '))^2 + ((' + g2 + ')-(' + m2 + '))^2 - (' + rsq + ')'];
    280         },
    281
    282         /**
    283          * Generate symbolic radius calculation for loci determination with Groebner-Basis algorithm.
    284          * @returns {String} String containing symbolic calculation of the circle's radius or an empty string
    285          * if the radius can't be expressed in a polynomial equation.
    286          * @private
    287          */
    288         generateRadiusSquared: function () {
    289             /*
    290              * Four cases:
    291              *
    292              *   (a) Two points
    293              *   (b) center and radius
    294              *   (c) center and radius given by length of a segment
    295              *   (d) center and radius given by another circle
    296              */
    297             var m1, m2, p1, p2, q1, q2,
    298                 rsq = '';
    299
    300             if (this.method === "twoPoints") {
    301                 m1 = this.center.symbolic.x;
    302                 m2 = this.center.symbolic.y;
    303                 p1 = this.point2.symbolic.x;
    304                 p2 = this.point2.symbolic.y;
    305
    306                 rsq = '((' + p1 + ')-(' + m1 + '))^2 + ((' + p2 + ')-(' + m2 + '))^2';
    307             } else if (this.method === "pointRadius") {
    308                 if (Type.isNumber(this.radius)) {
    309                     rsq = (this.radius * this.radius).toString();
    310                 }
    311             } else if (this.method === "pointLine") {
    312                 p1 = this.line.point1.symbolic.x;
    313                 p2 = this.line.point1.symbolic.y;
    314
    315                 q1 = this.line.point2.symbolic.x;
    316                 q2 = this.line.point2.symbolic.y;
    317
    318                 rsq = '((' + p1 + ')-(' + q1 + '))^2 + ((' + p2 + ')-(' + q2 + '))^2';
    319             } else if (this.method === "pointCircle") {
    320                 rsq = this.circle.Radius();
    321             }
    322
    323             return rsq;
    324         },
    325
    326         /**
    327          * Uses the boards renderer to update the circle.
    328          */
    329         update: function () {
    330             if (this.needsUpdate) {
    331                 if (Type.evaluate(this.visProp.trace)) {
    332                     this.cloneToBackground(true);
    333                 }
    334
    335                 if (this.method === 'pointLine') {
    336                     this.radius = this.line.point1.coords.distance(Const.COORDS_BY_USER, this.line.point2.coords);
    337                 } else if (this.method === 'pointCircle') {
    338                     this.radius = this.circle.Radius();
    339                 } else if (this.method === 'pointRadius') {
    340                     this.radius = this.updateRadius();
    341                 }
    342
    343                 this.updateStdform();
    344                 this.updateQuadraticform();
    345             }
    346
    347             return this;
    348         },
    349
    350         /**
    351          * Updates this circle's {@link JXG.Circle#quadraticform}.
    352          * @private
    353          */
    354         updateQuadraticform: function () {
    355             var m = this.center,
    356                 mX = m.X(),
    357                 mY = m.Y(),
    358                 r = this.Radius();
    359
    360             this.quadraticform = [
    361                 [mX * mX + mY * mY - r * r, -mX, -mY],
    362                 [-mX, 1, 0],
    363                 [-mY, 0, 1]
    364             ];
    365         },
    366
    367         /**
    368          * Updates the stdform derived from the position of the center and the circle's radius.
    369          * @private
    370          */
    371         updateStdform: function () {
    372             this.stdform[3] = 0.5;
    373             this.stdform[4] = this.Radius();
    374             this.stdform[1] = -this.center.coords.usrCoords[1];
    375             this.stdform[2] = -this.center.coords.usrCoords[2];
    376             if (!isFinite(this.stdform[4])) {
    377                 this.stdform[0] = Type.exists(this.point2) ? -(
    378                     this.stdform[1] * this.point2.coords.usrCoords[1] +
    379                     this.stdform[2] * this.point2.coords.usrCoords[2]
    380                 ) : 0;
    381             }
    382             this.normalize();
    383         },
    384
    385         /**
    386          * Uses the boards renderer to update the circle.
    387          * @private
    388          */
    389         updateRenderer: function () {
    390             // var wasReal;
    391
    392             if (!this.needsUpdate) {
    393                 return this;
    394             }
    395
    396             if (this.visPropCalc.visible) {
    397                 // wasReal = this.isReal;
    398                 this.isReal = (!isNaN(this.center.coords.usrCoords[1] + this.center.coords.usrCoords[2] + this.Radius())) && this.center.isReal;
    399
    400                 if (//wasReal &&
    401                     !this.isReal) {
    402                     this.updateVisibility(false);
    403                 }
    404             }
    405
    406             // Update the position
    407             if (this.visPropCalc.visible) {
    408                 this.board.renderer.updateEllipse(this);
    409             }
    410
    411             // Update the label if visible.
    412             if (this.hasLabel && this.visPropCalc.visible && this.label &&
    413                 this.label.visPropCalc.visible && this.isReal) {
    414
    415                 this.label.update();
    416                 this.board.renderer.updateText(this.label);
    417             }
    418
    419             // Update rendNode display
    420             this.setDisplayRendNode();
    421             // if (this.visPropCalc.visible !== this.visPropOld.visible) {
    422             //     this.board.renderer.display(this, this.visPropCalc.visible);
    423             //     this.visPropOld.visible = this.visPropCalc.visible;
    424             //
    425             //     if (this.hasLabel) {
    426             //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
    427             //     }
    428             // }
    429
    430             this.needsUpdate = false;
    431             return this;
    432         },
    433
    434         /**
    435          * Finds dependencies in a given term and resolves them by adding the elements referenced in this
    436          * string to the circle's list of ancestors.
    437          * @param {String} contentStr
    438          * @private
    439          */
    440         notifyParents: function (contentStr) {
    441             if (Type.isString(contentStr)) {
    442                 GeonextParser.findDependencies(this, contentStr, this.board);
    443             }
    444         },
    445
    446         /**
    447          * Set a new radius, then update the board.
    448          * @param {String|Number|function} r A string, function or number describing the new radius.
    449          * @returns {JXG.Circle} Reference to this circle
    450          */
    451         setRadius: function (r) {
    452             this.updateRadius = Type.createFunction(r, this.board, null, true);
    453             this.board.update();
    454
    455             return this;
    456         },
    457
    458         /**
    459          * Calculates the radius of the circle.
    460          * @param {String|Number|function} [value] Set new radius
    461          * @returns {Number} The radius of the circle
    462          */
    463         Radius: function (value) {
    464             if (Type.exists(value)) {
    465                 this.setRadius(value);
    466                 return this.Radius();
    467             }
    468
    469             if (this.method === 'twoPoints') {
    470                 if (Type.cmpArrays(this.point2.coords.usrCoords, [0, 0, 0]) ||
    471                         Type.cmpArrays(this.center.coords.usrCoords, [0, 0, 0])) {
    472
    473                     return NaN;
    474                 }
    475
    476                 return this.center.Dist(this.point2);
    477             }
    478
    479             if (this.method === 'pointLine' || this.method === 'pointCircle') {
    480                 return this.radius;
    481             }
    482
    483             if (this.method === 'pointRadius') {
    484                 return this.updateRadius();
    485             }
    486
    487             return NaN;
    488         },
    489
    490         /**
    491          * Use {@link JXG.Circle#Radius}.
    492          * @deprecated
    493          */
    494         getRadius: function () {
    495             JXG.deprecated('Circle.getRadius()', 'Circle.Radius()');
    496             return this.Radius();
    497         },
    498
    499         // documented in geometry element
    500         getTextAnchor: function () {
    501             return this.center.coords;
    502         },
    503
    504         // documented in geometry element
    505         getLabelAnchor: function () {
    506             var x, y,
    507                 r = this.Radius(),
    508                 c = this.center.coords.usrCoords;
    509
    510             switch (Type.evaluate(this.visProp.label.position)) {
    511             case 'lft':
    512                 x = c[1] - r;
    513                 y = c[2];
    514                 break;
    515             case 'llft':
    516                 x = c[1] - Math.sqrt(0.5) * r;
    517                 y = c[2] - Math.sqrt(0.5) * r;
    518                 break;
    519             case 'rt':
    520                 x = c[1] + r;
    521                 y = c[2];
    522                 break;
    523             case 'lrt':
    524                 x = c[1] + Math.sqrt(0.5) * r;
    525                 y = c[2] - Math.sqrt(0.5) * r;
    526                 break;
    527             case 'urt':
    528                 x = c[1] + Math.sqrt(0.5) * r;
    529                 y = c[2] + Math.sqrt(0.5) * r;
    530                 break;
    531             case 'top':
    532                 x = c[1];
    533                 y = c[2] + r;
    534                 break;
    535             case 'bot':
    536                 x = c[1];
    537                 y = c[2] - r;
    538                 break;
    539             default:
    540                 // includes case 'ulft'
    541                 x = c[1] - Math.sqrt(0.5) * r;
    542                 y = c[2] + Math.sqrt(0.5) * r;
    543                 break;
    544             }
    545
    546             return new Coords(Const.COORDS_BY_USER, [x, y], this.board);
    547         },
    548
    549
    550         // documented in geometry element
    551         cloneToBackground: function () {
    552             var er,
    553                 r = this.Radius(),
    554                 copy = {
    555                     id: this.id + 'T' + this.numTraces,
    556                     elementClass: Const.OBJECT_CLASS_CIRCLE,
    557                     center: {
    558                         coords: this.center.coords
    559                     },
    560                     Radius: function () {
    561                         return r;
    562                     },
    563                     getRadius: function () {
    564                         return r;
    565                     },
    566                     board: this.board,
    567                     visProp: Type.deepCopy(this.visProp, this.visProp.traceattributes, true)
    568                 };
    569
    570             copy.visProp.layer = this.board.options.layer.trace;
    571
    572             this.numTraces++;
    573             Type.clearVisPropOld(copy);
    574
    575             er = this.board.renderer.enhancedRendering;
    576             this.board.renderer.enhancedRendering = true;
    577             this.board.renderer.drawEllipse(copy);
    578             this.board.renderer.enhancedRendering = er;
    579             this.traces[copy.id] = copy.rendNode;
    580
    581             return this;
    582         },
    583
    584         /**
    585          * Add transformations to this circle.
    586          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s.
    587          * @returns {JXG.Circle} Reference to this circle object.
    588          */
    589         addTransform: function (transform) {
    590             var i,
    591                 list = Type.isArray(transform) ? transform : [transform],
    592                 len = list.length;
    593
    594             for (i = 0; i < len; i++) {
    595                 this.center.transformations.push(list[i]);
    596
    597                 if (this.method === 'twoPoints') {
    598                     this.point2.transformations.push(list[i]);
    599                 }
    600             }
    601
    602             return this;
    603         },
    604
    605         // see element.js
    606         snapToGrid: function () {
    607             var forceIt = Type.evaluate(this.visProp.snaptogrid);
    608
    609             this.center.handleSnapToGrid(forceIt, true);
    610             if (this.method === 'twoPoints') {
    611                 this.point2.handleSnapToGrid(forceIt, true);
    612             }
    613
    614             return this;
    615         },
    616
    617         // see element.js
    618         snapToPoints: function () {
    619             var forceIt = Type.evaluate(this.visProp.snaptopoints);
    620
    621             this.center.handleSnapToPoints(forceIt);
    622             if (this.method === 'twoPoints') {
    623                 this.point2.handleSnapToPoints(forceIt);
    624             }
    625
    626             return this;
    627         },
    628
    629         /**
    630          * Treats the circle as parametric curve and calculates its X coordinate.
    631          * @param {Number} t Number between 0 and 1.
    632          * @returns {Number} <tt>X(t)= radius*cos(t)+centerX</tt>.
    633          */
    634         X: function (t) {
    635             return this.Radius() * Math.cos(t * 2 * Math.PI) + this.center.coords.usrCoords[1];
    636         },
    637
    638         /**
    639          * Treats the circle as parametric curve and calculates its Y coordinate.
    640          * @param {Number} t Number between 0 and 1.
    641          * @returns {Number} <tt>X(t)= radius*sin(t)+centerY</tt>.
    642          */
    643         Y: function (t) {
    644             return this.Radius() * Math.sin(t * 2 * Math.PI) + this.center.coords.usrCoords[2];
    645         },
    646
    647         /**
    648          * Treat the circle as parametric curve and calculates its Z coordinate.
    649          * @param {Number} t ignored
    650          * @returns {Number} 1.0
    651          */
    652         Z: function (t) {
    653             return 1.0;
    654         },
    655
    656         /**
    657          * Returns 0.
    658          * @private
    659          */
    660         minX: function () {
    661             return 0.0;
    662         },
    663
    664         /**
    665          * Returns 1.
    666          * @private
    667          */
    668         maxX: function () {
    669             return 1.0;
    670         },
    671
    672         /**
    673          * Circle area
    674          * @returns {Number} area of the circle.
    675          */
    676         Area: function () {
    677             var r = this.Radius();
    678
    679             return r * r * Math.PI;
    680         },
    681
    682         /**
    683          * Get bounding box of the circle.
    684          * @returns {Array} [x1, y1, x2, y2]
    685          */
    686         bounds: function () {
    687             var uc = this.center.coords.usrCoords,
    688                 r = this.Radius();
    689
    690             return [uc[1] - r, uc[2] + r, uc[1] + r, uc[2] - r];
    691         },
    692
    693         /**
    694          * Get data to construct this element. Data consists of the parent elements
    695          * and static data like radius.
    696          * @returns {Array} data necessary to construct this element
    697          */
    698         getParents: function() {
    699             if (this.parents.length === 1) {  // i.e. this.method === 'pointRadius'
    700                 return this.parents.concat(this.radius);
    701             }
    702             return this.parents;
    703         }
    704     });
    705
    706     /**
    707      * @class This element is used to provide a constructor for a circle.
    708      * @pseudo
    709      * @description  A circle consists of all points with a given distance from one point. This point is called center, the distance is called radius.
    710      * 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,
    711      * line, or circle).
    712      * @name Circle
    713      * @augments JXG.Circle
    714      * @constructor
    715      * @type JXG.Circle
    716      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    717      * @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
    718      * 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
    719      * line will determine the radius), or another {@link JXG.Circle}.
    720      * @example
    721      * // Create a circle providing two points
    722      * var p1 = board.create('point', [2.0, 2.0]),
    723      *     p2 = board.create('point', [2.0, 0.0]),
    724      *     c1 = board.create('circle', [p1, p2]);
    725      *
    726      * // Create another circle using the above circle
    727      * var p3 = board.create('point', [3.0, 2.0]),
    728      *     c2 = board.create('circle', [p3, c1]);
    729      * </pre><div class="jxgbox" id="5f304d31-ef20-4a8e-9c0e-ea1a2b6c79e0" style="width: 400px; height: 400px;"></div>
    730      * <script type="text/javascript">
    731      * (function() {
    732      *   var cex1_board = JXG.JSXGraph.initBoard('5f304d31-ef20-4a8e-9c0e-ea1a2b6c79e0', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
    733      *       cex1_p1 = cex1_board.create('point', [2.0, 2.0]),
    734      *       cex1_p2 = cex1_board.create('point', [2.0, 0.0]),
    735      *       cex1_c1 = cex1_board.create('circle', [cex1_p1, cex1_p2]),
    736      *       cex1_p3 = cex1_board.create('point', [3.0, 2.0]),
    737      *       cex1_c2 = cex1_board.create('circle', [cex1_p3, cex1_c1]);
    738      * })();
    739      * </script><pre>
    740      * @example
    741      * // Create a circle providing two points
    742      * var p1 = board.create('point', [2.0, 2.0]),
    743      *     c1 = board.create('circle', [p1, 3]);
    744      *
    745      * // Create another circle using the above circle
    746      * var c2 = board.create('circle', [function() { return [p1.X(), p1.Y() + 1];}, function() { return c1.Radius(); }]);
    747      * </pre><div class="jxgbox" id="54165f60-93b9-441d-8979-ac5d0f193020" style="width: 400px; height: 400px;"></div>
    748      * <script type="text/javascript">
    749      * (function() {
    750      * var board = JXG.JSXGraph.initBoard('54165f60-93b9-441d-8979-ac5d0f193020', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
    751      * var p1 = board.create('point', [2.0, 2.0]);
    752      * var c1 = board.create('circle', [p1, 3]);
    753      *
    754      * // Create another circle using the above circle
    755      * var c2 = board.create('circle', [function() { return [p1.X(), p1.Y() + 1];}, function() { return c1.Radius(); }]);
    756      * })();
    757      * </script><pre>
    758      */
    759     JXG.createCircle = function (board, parents, attributes) {
    760         var el, p, i, attr,
    761             isDraggable = true;
    762
    763         p = [];
    764         for (i = 0; i < parents.length; i++) {
    765             if (Type.isPointType(board, parents[i])) {
    766                 p = p.concat(Type.providePoints(board, [parents[i]], attributes, 'circle', ['center']));
    767                 if (p[p.length - 1] === false) {
    768                     throw new Error('JSXGraph: Can\'t create circle from this type. Please provide a point type.');
    769                 }
    770             } else {
    771                 p.push(parents[i]);
    772             }
    773         }
    774
    775         attr = Type.copyAttributes(attributes, board.options, 'circle');
    776
    777         if (p.length === 2 && Type.isPoint(p[0]) && Type.isPoint(p[1])) {
    778             // Point/Point
    779             el = new JXG.Circle(board, 'twoPoints', p[0], p[1], attr);
    780         } else if ((Type.isNumber(p[0]) || Type.isFunction(p[0]) || Type.isString(p[0])) && Type.isPoint(p[1])) {
    781             // Number/Point
    782             el = new JXG.Circle(board, 'pointRadius', p[1], p[0], attr);
    783         } else if ((Type.isNumber(p[1]) || Type.isFunction(p[1]) || Type.isString(p[1])) && Type.isPoint(p[0])) {
    784             // Point/Number
    785             el = new JXG.Circle(board, 'pointRadius', p[0], p[1], attr);
    786         } else if ((p[0].elementClass === Const.OBJECT_CLASS_CIRCLE) && Type.isPoint(p[1])) {
    787             // Circle/Point
    788             el = new JXG.Circle(board, 'pointCircle', p[1], p[0], attr);
    789         } else if ((p[1].elementClass === Const.OBJECT_CLASS_CIRCLE) && Type.isPoint(p[0])) {
    790             // Point/Circle
    791             el = new JXG.Circle(board, 'pointCircle', p[0], p[1], attr);
    792         } else if ((p[0].elementClass === Const.OBJECT_CLASS_LINE) && Type.isPoint(p[1])) {
    793             // Line/Point
    794             el = new JXG.Circle(board, 'pointLine', p[1], p[0], attr);
    795         } else if ((p[1].elementClass === Const.OBJECT_CLASS_LINE) && Type.isPoint(p[0])) {
    796             // Point/Line
    797             el = new JXG.Circle(board, 'pointLine', p[0], p[1], attr);
    798         } else if (parents.length === 3 && Type.isPoint(p[0]) && Type.isPoint(p[1]) && Type.isPoint(p[2])) {
    799             // Circle through three points
    800             // Check if circumcircle element is available
    801             if (JXG.elements.circumcircle) {
    802                 el = JXG.elements.circumcircle(board, p, attr);
    803             } else {
    804                 throw new Error('JSXGraph: Can\'t create circle with three points. Please include the circumcircle element (element/composition).');
    805             }
    806         } else {
    807             throw new Error("JSXGraph: Can't create circle with parent types '" +
    808                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    809                 "\nPossible parent types: [point,point], [point,number], [point,function], [point,circle], [point,point,point]");
    810         }
    811
    812         el.isDraggable = isDraggable;
    813         el.setParents(p);
    814         el.elType = 'circle';
    815         for (i = 0; i < p.length; i++) {
    816             if (Type.isPoint(p[i])) {
    817                 el.inherits.push(p[i]);
    818             }
    819         }
    820         return el;
    821     };
    822
    823     JXG.registerElement('circle', JXG.createCircle);
    824
    825     return {
    826         Circle: JXG.Circle,
    827         createCircle: JXG.createCircle
    828     };
    829 });
    830