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