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