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