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