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