1 /*
  2     Copyright 2008-2021
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  base/element
 39  base/constants
 40  base/coords
 41  parser/geonext
 42  math/geometry
 43  math/statistics
 44  utils/type
 45   elements:
 46    transform
 47    point
 48  */
 49 
 50 /**
 51  * @fileoverview The geometry object Circle is defined in this file. Circle stores all
 52  * style and functional properties that are required to draw and move a circle on
 53  * a board.
 54  */
 55 
 56 define([
 57     'jxg', 'base/element', 'base/coords', 'base/constants', 'element/conic', 'parser/geonext', 'utils/type'
 58 ], function (JXG, GeometryElement, Coords, Const, Conic, GeonextParser, Type) {
 59 
 60     "use strict";
 61 
 62     /**
 63      * A circle consists of all points with a given distance from one point. This point is called center, the distance is called radius.
 64      * A circle can be constructed by providing a center and a point on the circle or a center and a radius (given as a number, function,
 65      * line, or circle).
 66      * @class Creates a new circle object. Do not use this constructor to create a circle. Use {@link JXG.Board#create} with
 67      * type {@link Circle} instead.
 68      * @constructor
 69      * @augments JXG.GeometryElement
 70      * @param {JXG.Board} board The board the new circle is drawn on.
 71      * @param {String} method Can be
 72      * <ul><li> <b>'twoPoints'</b> which means the circle is defined by its center and a point on the circle.</li>
 73      * <li><b>'pointRadius'</b> which means the circle is defined by its center and its radius in user units</li>
 74      * <li><b>'pointLine'</b> which means the circle is defined by its center and its radius given by the distance from the startpoint and the endpoint of the line</li>
 75      * <li><b>'pointCircle'</b> which means the circle is defined by its center and its radius given by the radius of another circle</li></ul>
 76      * The parameters p1, p2 and radius must be set according to this method parameter.
 77      * @param {JXG.Point} par1 center of the circle.
 78      * @param {JXG.Point|JXG.Line|JXG.Circle} par2 Can be
 79      * <ul><li>a point on the circle if method is 'twoPoints'</li>
 80      * <li>a line if the method is 'pointLine'</li>
 81      * <li>a circle if the method is 'pointCircle'</li></ul>
 82      * @param {Object} attributes
 83      * @see JXG.Board#generateName
 84      */
 85     JXG.Circle = function (board, method, par1, par2, attributes) {
 86         // Call the constructor of GeometryElement
 87         this.constructor(board, attributes, Const.OBJECT_TYPE_CIRCLE, Const.OBJECT_CLASS_CIRCLE);
 88 
 89         /**
 90          * Stores the given method.
 91          * Can be
 92          * <ul><li><b>'twoPoints'</b> which means the circle is defined by its center and a point on the circle.</li>
 93          * <li><b>'pointRadius'</b> which means the circle is defined by its center and its radius given in user units or as term.</li>
 94          * <li><b>'pointLine'</b> which means the circle is defined by its center and its radius given by the distance from the startpoint and the endpoint of the line.</li>
 95          * <li><b>'pointCircle'</b> which means the circle is defined by its center and its radius given by the radius of another circle.</li></ul>
 96          * @type String
 97          * @see #center
 98          * @see #point2
 99          * @see #radius
100          * @see #line
101          * @see #circle
102          */
103         this.method = method;
104 
105         // this is kept so existing code won't ne broken
106         this.midpoint = this.board.select(par1);
107 
108         /**
109          * The circles center. Do not set this parameter directly as it will break JSXGraph's update system.
110          * @type JXG.Point
111          */
112         this.center = this.board.select(par1);
113 
114         /** Point on the circle only set if method equals 'twoPoints'. Do not set this parameter directly as it will break JSXGraph's update system.
115          * @type JXG.Point
116          * @see #method
117          */
118         this.point2 = null;
119 
120         /** Radius of the circle
121          * only set if method equals 'pointRadius'
122          * @type Number
123          * @default null
124          * @see #method
125          */
126         this.radius = 0;
127 
128         /** Line defining the radius of the circle given by the distance from the startpoint and the endpoint of the line
129          * only set if method equals 'pointLine'. Do not set this parameter directly as it will break JSXGraph's update system.
130          * @type JXG.Line
131          * @default null
132          * @see #method
133          */
134         this.line = null;
135 
136         /** Circle defining the radius of the circle given by the radius of the other circle
137          * only set if method equals 'pointLine'. Do not set this parameter directly as it will break JSXGraph's update system.
138          * @type JXG.Circle
139          * @default null
140          * @see #method
141          */
142         this.circle = null;
143 
144         this.points = [];
145 
146         if (method === 'twoPoints') {
147             this.point2 = board.select(par2);
148             this.radius = this.Radius();
149         } else if (method === 'pointRadius') {
150             this.gxtterm = par2;
151             // Converts GEONExT syntax into JavaScript syntax and generally ensures that the radius is a function
152             this.updateRadius = Type.createFunction(par2, this.board, null, true);
153             // First evaluation of the radius function
154             this.updateRadius();
155         } else if (method === 'pointLine') {
156             // dann ist p2 die Id eines Objekts vom Typ Line!
157             this.line = board.select(par2);
158             this.radius = this.line.point1.coords.distance(Const.COORDS_BY_USER, this.line.point2.coords);
159         } else if (method === 'pointCircle') {
160             // dann ist p2 die Id eines Objekts vom Typ Circle!
161             this.circle = board.select(par2);
162             this.radius = this.circle.Radius();
163         }
164 
165         // create Label
166         this.id = this.board.setId(this, 'C');
167         this.board.renderer.drawEllipse(this);
168         this.board.finalizeAdding(this);
169 
170         this.createGradient();
171         this.elType = 'circle';
172         this.createLabel();
173 
174         if (Type.exists(this.center._is_new)) {
175             this.addChild(this.center);
176             delete this.center._is_new;
177         } else {
178             this.center.addChild(this);
179         }
180 
181         if (method === 'pointRadius') {
182             this.notifyParents(par2);
183         } else if (method === 'pointLine') {
184             this.line.addChild(this);
185         } else if (method === 'pointCircle') {
186             this.circle.addChild(this);
187         } else if (method === 'twoPoints') {
188             if (Type.exists(this.point2._is_new)) {
189                 this.addChild(this.point2);
190                 delete this.point2._is_new;
191             } else {
192                 this.point2.addChild(this);
193             }
194         }
195 
196         this.methodMap = Type.deepCopy(this.methodMap, {
197             setRadius: 'setRadius',
198             getRadius: 'getRadius',
199             Area: 'Area',
200             area: 'Area',
201             radius: 'Radius',
202             center: 'center',
203             line: 'line',
204             point2: 'point2'
205         });
206     };
207 
208     JXG.Circle.prototype = new GeometryElement();
209 
210     JXG.extend(JXG.Circle.prototype, /** @lends JXG.Circle.prototype */ {
211         /**
212          * Checks whether (x,y) is near the circle line or inside of the ellipse
213          * (in case JXG.Options.conic#hasInnerPoints is true).
214          * @param {Number} x Coordinate in x direction, screen coordinates.
215          * @param {Number} y Coordinate in y direction, screen coordinates.
216          * @returns {Boolean} True if (x,y) is near the circle, False otherwise.
217          * @private
218          */
219         hasPoint: function (x, y) {
220             var prec, type,
221                 mp = this.center.coords.usrCoords,
222                 p = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
223                 r = this.Radius(),
224                 dx, dy, dist;
225 
226 
227             if (Type.isObject(Type.evaluate(this.visProp.precision))) {
228                 type = this.board._inputDevice;
229                 prec = Type.evaluate(this.visProp.precision[type]);
230             } else {
231                 // 'inherit'
232                 prec = this.board.options.precision.hasPoint;
233             }
234             dx = mp[1] - p.usrCoords[1];
235             dy = mp[2] - p.usrCoords[2];
236             dist = Math.sqrt(dx * dx + dy * dy);
237             // We have to use usrCoords, since Radius is available in usrCoords only.
238             prec += Type.evaluate(this.visProp.strokewidth) * 0.5;
239             prec /= Math.sqrt(this.board.unitX * this.board.unitY);
240 
241             if (Type.evaluate(this.visProp.hasinnerpoints)) {
242                 return (dist < r + prec);
243             }
244 
245             return (Math.abs(dist - r) < prec);
246         },
247 
248         /**
249          * Used to generate a polynomial for a point p that lies on this circle.
250          * @param {JXG.Point} p The point for which the polynomial is generated.
251          * @returns {Array} An array containing the generated polynomial.
252          * @private
253          */
254         generatePolynomial: function (p) {
255             /*
256              * We have four methods to construct a circle:
257              *   (a) Two points
258              *   (b) center and radius
259              *   (c) center and radius given by length of a segment
260              *   (d) center and radius given by another circle
261              *
262              * In case (b) we have to distinguish two cases:
263              *  (i)  radius is given as a number
264              *  (ii) radius is given as a function
265              * In the latter case there's no guarantee the radius depends on other geometry elements
266              * in a polynomial way so this case has to be omitted.
267              *
268              * Another tricky case is case (d):
269              * The radius depends on another circle so we have to cycle through the ancestors of each circle
270              * until we reach one that's radius does not depend on another circles radius.
271              *
272              *
273              * All cases (a) to (d) vary only in calculation of the radius. So the basic formulae for
274              * a glider G (g1,g2) on a circle with center M (m1,m2) and radius r is just:
275              *
276              *     (g1-m1)^2 + (g2-m2)^2 - r^2 = 0
277              *
278              * So the easiest case is (b) with a fixed radius given as a number. The other two cases (a)
279              * and (c) are quite the same: Euclidean distance between two points A (a1,a2) and B (b1,b2),
280              * squared:
281              *
282              *     r^2 = (a1-b1)^2 + (a2-b2)^2
283              *
284              * For case (d) we have to cycle recursively through all defining circles and finally return the
285              * formulae for calculating r^2. For that we use JXG.Circle.symbolic.generateRadiusSquared().
286              */
287             var m1 = this.center.symbolic.x,
288                 m2 = this.center.symbolic.y,
289                 g1 = p.symbolic.x,
290                 g2 = p.symbolic.y,
291                 rsq = this.generateRadiusSquared();
292 
293             /* No radius can be calculated (Case b.ii) */
294             if (rsq === '') {
295                 return [];
296             }
297 
298             return ['((' + g1 + ')-(' + m1 + '))^2 + ((' + g2 + ')-(' + m2 + '))^2 - (' + rsq + ')'];
299         },
300 
301         /**
302          * Generate symbolic radius calculation for loci determination with Groebner-Basis algorithm.
303          * @returns {String} String containing symbolic calculation of the circle's radius or an empty string
304          * if the radius can't be expressed in a polynomial equation.
305          * @private
306          */
307         generateRadiusSquared: function () {
308             /*
309              * Four cases:
310              *
311              *   (a) Two points
312              *   (b) center and radius
313              *   (c) center and radius given by length of a segment
314              *   (d) center and radius given by another circle
315              */
316             var m1, m2, p1, p2, q1, q2,
317                 rsq = '';
318 
319             if (this.method === "twoPoints") {
320                 m1 = this.center.symbolic.x;
321                 m2 = this.center.symbolic.y;
322                 p1 = this.point2.symbolic.x;
323                 p2 = this.point2.symbolic.y;
324 
325                 rsq = '((' + p1 + ')-(' + m1 + '))^2 + ((' + p2 + ')-(' + m2 + '))^2';
326             } else if (this.method === "pointRadius") {
327                 if (Type.isNumber(this.radius)) {
328                     rsq = (this.radius * this.radius).toString();
329                 }
330             } else if (this.method === "pointLine") {
331                 p1 = this.line.point1.symbolic.x;
332                 p2 = this.line.point1.symbolic.y;
333 
334                 q1 = this.line.point2.symbolic.x;
335                 q2 = this.line.point2.symbolic.y;
336 
337                 rsq = '((' + p1 + ')-(' + q1 + '))^2 + ((' + p2 + ')-(' + q2 + '))^2';
338             } else if (this.method === "pointCircle") {
339                 rsq = this.circle.Radius();
340             }
341 
342             return rsq;
343         },
344 
345         /**
346          * Uses the boards renderer to update the circle.
347          */
348         update: function () {
349             var x, y, z, r, c, i;
350 
351             if (this.needsUpdate) {
352                 if (Type.evaluate(this.visProp.trace)) {
353                     this.cloneToBackground(true);
354                 }
355 
356                 if (this.method === 'pointLine') {
357                     this.radius = this.line.point1.coords.distance(Const.COORDS_BY_USER, this.line.point2.coords);
358                 } else if (this.method === 'pointCircle') {
359                     this.radius = this.circle.Radius();
360                 } else if (this.method === 'pointRadius') {
361                     this.radius = this.updateRadius();
362                 }
363 
364                 this.updateStdform();
365                 this.updateQuadraticform();
366 
367                 // Approximate the circle by 4 Bezier segments
368                 // This will be used for intersections of type curve / circle.
369                 // See https://spencermortensen.com/articles/bezier-circle/
370                 z = this.center.coords.usrCoords[0];
371                 x = this.center.coords.usrCoords[1] / z;
372                 y = this.center.coords.usrCoords[2] / z;
373                 z /= z;
374                 r = this.Radius();
375                 c = 0.551915024494;
376 
377                 this.numberPoints = 13;
378                 this.dataX = [x + r, x + r, x + r * c, x, x - r * c, x - r, x - r, x - r, x - r * c, x, x + r * c, x + r, x + r];
379                 this.dataY = [y, y + r * c, y + r, y + r, y + r, y + r * c, y, y - r * c, y - r, y - r, y - r, y - r * c, y];
380                 this.bezierDegree = 3;
381                 for (i = 0; i < this.numberPoints; i++) {
382                     this.points[i] = new Coords(Const.COORDS_BY_USER, [this.dataX[i], this.dataY[i]], this.board);
383                 }
384             }
385 
386             return this;
387         },
388 
389         /**
390          * Updates this circle's {@link JXG.Circle#quadraticform}.
391          * @private
392          */
393         updateQuadraticform: function () {
394             var m = this.center,
395                 mX = m.X(),
396                 mY = m.Y(),
397                 r = this.Radius();
398 
399             this.quadraticform = [
400                 [mX * mX + mY * mY - r * r, -mX, -mY],
401                 [-mX, 1, 0],
402                 [-mY, 0, 1]
403             ];
404         },
405 
406         /**
407          * Updates the stdform derived from the position of the center and the circle's radius.
408          * @private
409          */
410         updateStdform: function () {
411             this.stdform[3] = 0.5;
412             this.stdform[4] = this.Radius();
413             this.stdform[1] = -this.center.coords.usrCoords[1];
414             this.stdform[2] = -this.center.coords.usrCoords[2];
415             if (!isFinite(this.stdform[4])) {
416                 this.stdform[0] = Type.exists(this.point2) ? -(
417                     this.stdform[1] * this.point2.coords.usrCoords[1] +
418                     this.stdform[2] * this.point2.coords.usrCoords[2]
419                 ) : 0;
420             }
421             this.normalize();
422         },
423 
424         /**
425          * Uses the boards renderer to update the circle.
426          * @private
427          */
428         updateRenderer: function () {
429             // var wasReal;
430 
431             if (!this.needsUpdate) {
432                 return this;
433             }
434 
435             if (this.visPropCalc.visible) {
436                 // wasReal = this.isReal;
437                 this.isReal = (!isNaN(this.center.coords.usrCoords[1] + this.center.coords.usrCoords[2] + this.Radius())) && this.center.isReal;
438 
439                 if (//wasReal &&
440                     !this.isReal) {
441                     this.updateVisibility(false);
442                 }
443             }
444 
445             // Update the position
446             if (this.visPropCalc.visible) {
447                 this.board.renderer.updateEllipse(this);
448             }
449 
450             // Update the label if visible.
451             if (this.hasLabel && this.visPropCalc.visible && this.label &&
452                 this.label.visPropCalc.visible && this.isReal) {
453 
454                 this.label.update();
455                 this.board.renderer.updateText(this.label);
456             }
457 
458             // Update rendNode display
459             this.setDisplayRendNode();
460             // if (this.visPropCalc.visible !== this.visPropOld.visible) {
461             //     this.board.renderer.display(this, this.visPropCalc.visible);
462             //     this.visPropOld.visible = this.visPropCalc.visible;
463             //
464             //     if (this.hasLabel) {
465             //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
466             //     }
467             // }
468 
469             this.needsUpdate = false;
470             return this;
471         },
472 
473         /**
474          * Finds dependencies in a given term and resolves them by adding the elements referenced in this
475          * string to the circle's list of ancestors.
476          * @param {String} contentStr
477          * @private
478          */
479         notifyParents: function (contentStr) {
480             if (Type.isString(contentStr)) {
481                 GeonextParser.findDependencies(this, contentStr, this.board);
482             }
483         },
484 
485         /**
486          * Set a new radius, then update the board.
487          * @param {String|Number|function} r A string, function or number describing the new radius.
488          * @returns {JXG.Circle} Reference to this circle
489          */
490         setRadius: function (r) {
491             this.updateRadius = Type.createFunction(r, this.board, null, true);
492             this.board.update();
493 
494             return this;
495         },
496 
497         /**
498          * Calculates the radius of the circle.
499          * @param {String|Number|function} [value] Set new radius
500          * @returns {Number} The radius of the circle
501          */
502         Radius: function (value) {
503             if (Type.exists(value)) {
504                 this.setRadius(value);
505                 return this.Radius();
506             }
507 
508             if (this.method === 'twoPoints') {
509                 if (Type.cmpArrays(this.point2.coords.usrCoords, [0, 0, 0]) ||
510                         Type.cmpArrays(this.center.coords.usrCoords, [0, 0, 0])) {
511 
512                     return NaN;
513                 }
514 
515                 return this.center.Dist(this.point2);
516             }
517 
518             if (this.method === 'pointLine' || this.method === 'pointCircle') {
519                 return this.radius;
520             }
521 
522             if (this.method === 'pointRadius') {
523                 return this.updateRadius();
524             }
525 
526             return NaN;
527         },
528 
529         /**
530          * Use {@link JXG.Circle#Radius}.
531          * @deprecated
532          */
533         getRadius: function () {
534             JXG.deprecated('Circle.getRadius()', 'Circle.Radius()');
535             return this.Radius();
536         },
537 
538         // documented in geometry element
539         getTextAnchor: function () {
540             return this.center.coords;
541         },
542 
543         // documented in geometry element
544         getLabelAnchor: function () {
545             var x, y,
546                 r = this.Radius(),
547                 c = this.center.coords.usrCoords,
548                 SQRTH = 7.07106781186547524401E-1;      // sqrt(2)/2
549 
550             switch (Type.evaluate(this.visProp.label.position)) {
551             case 'lft':
552                 x = c[1] - r;
553                 y = c[2];
554                 break;
555             case 'llft':
556                 x = c[1] - SQRTH * r;
557                 y = c[2] - SQRTH * r;
558                 break;
559             case 'rt':
560                 x = c[1] + r;
561                 y = c[2];
562                 break;
563             case 'lrt':
564                 x = c[1] + SQRTH * r;
565                 y = c[2] - SQRTH * r;
566                 break;
567             case 'urt':
568                 x = c[1] + SQRTH * r;
569                 y = c[2] + SQRTH * r;
570                 break;
571             case 'top':
572                 x = c[1];
573                 y = c[2] + r;
574                 break;
575             case 'bot':
576                 x = c[1];
577                 y = c[2] - r;
578                 break;
579             default:
580                 // includes case 'ulft'
581                 x = c[1] - SQRTH * r;
582                 y = c[2] + SQRTH * r;
583                 break;
584             }
585 
586             return new Coords(Const.COORDS_BY_USER, [x, y], this.board);
587         },
588 
589         // documented in geometry element
590         cloneToBackground: function () {
591             var er,
592                 r = this.Radius(),
593                 copy = {
594                     id: this.id + 'T' + this.numTraces,
595                     elementClass: Const.OBJECT_CLASS_CIRCLE,
596                     center: {
597                         coords: this.center.coords
598                     },
599                     Radius: function () {
600                         return r;
601                     },
602                     getRadius: function () {
603                         return r;
604                     },
605                     board: this.board,
606                     visProp: Type.deepCopy(this.visProp, this.visProp.traceattributes, true)
607                 };
608 
609             copy.visProp.layer = this.board.options.layer.trace;
610 
611             this.numTraces++;
612             Type.clearVisPropOld(copy);
613             copy.visPropCalc = {
614                 visible: Type.evaluate(copy.visProp.visible)
615             };
616 
617             er = this.board.renderer.enhancedRendering;
618             this.board.renderer.enhancedRendering = true;
619             this.board.renderer.drawEllipse(copy);
620             this.board.renderer.enhancedRendering = er;
621             this.traces[copy.id] = copy.rendNode;
622 
623             return this;
624         },
625 
626         /**
627          * Add transformations to this circle.
628          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s.
629          * @returns {JXG.Circle} Reference to this circle object.
630          */
631         addTransform: function (transform) {
632             var i,
633                 list = Type.isArray(transform) ? transform : [transform],
634                 len = list.length;
635 
636             for (i = 0; i < len; i++) {
637                 this.center.transformations.push(list[i]);
638 
639                 if (this.method === 'twoPoints') {
640                     this.point2.transformations.push(list[i]);
641                 }
642             }
643 
644             return this;
645         },
646 
647         // see element.js
648         snapToGrid: function () {
649             var forceIt = Type.evaluate(this.visProp.snaptogrid);
650 
651             this.center.handleSnapToGrid(forceIt, true);
652             if (this.method === 'twoPoints') {
653                 this.point2.handleSnapToGrid(forceIt, true);
654             }
655 
656             return this;
657         },
658 
659         // see element.js
660         snapToPoints: function () {
661             var forceIt = Type.evaluate(this.visProp.snaptopoints);
662 
663             this.center.handleSnapToPoints(forceIt);
664             if (this.method === 'twoPoints') {
665                 this.point2.handleSnapToPoints(forceIt);
666             }
667 
668             return this;
669         },
670 
671         /**
672          * Treats the circle as parametric curve and calculates its X coordinate.
673          * @param {Number} t Number between 0 and 1.
674          * @returns {Number} <tt>X(t)= radius*cos(t)+centerX</tt>.
675          */
676         X: function (t) {
677             return this.Radius() * Math.cos(t * 2 * Math.PI) + this.center.coords.usrCoords[1];
678         },
679 
680         /**
681          * Treats the circle as parametric curve and calculates its Y coordinate.
682          * @param {Number} t Number between 0 and 1.
683          * @returns {Number} <tt>X(t)= radius*sin(t)+centerY</tt>.
684          */
685         Y: function (t) {
686             return this.Radius() * Math.sin(t * 2 * Math.PI) + this.center.coords.usrCoords[2];
687         },
688 
689         /**
690          * Treat the circle as parametric curve and calculates its Z coordinate.
691          * @param {Number} t ignored
692          * @returns {Number} 1.0
693          */
694         Z: function (t) {
695             return 1.0;
696         },
697 
698         /**
699          * Returns 0.
700          * @private
701          */
702         minX: function () {
703             return 0.0;
704         },
705 
706         /**
707          * Returns 1.
708          * @private
709          */
710         maxX: function () {
711             return 1.0;
712         },
713 
714         /**
715          * Circle area
716          * @returns {Number} area of the circle.
717          */
718         Area: function () {
719             var r = this.Radius();
720 
721             return r * r * Math.PI;
722         },
723 
724         /**
725          * Get bounding box of the circle.
726          * @returns {Array} [x1, y1, x2, y2]
727          */
728         bounds: function () {
729             var uc = this.center.coords.usrCoords,
730                 r = this.Radius();
731 
732             return [uc[1] - r, uc[2] + r, uc[1] + r, uc[2] - r];
733         },
734 
735         /**
736          * Get data to construct this element. Data consists of the parent elements
737          * and static data like radius.
738          * @returns {Array} data necessary to construct this element
739          */
740         getParents: function() {
741             if (this.parents.length === 1) {  // i.e. this.method === 'pointRadius'
742                 return this.parents.concat(this.radius);
743             }
744             return this.parents;
745         }
746     });
747 
748     /**
749      * @class This element is used to provide a constructor for a circle.
750      * @pseudo
751      * @description  A circle consists of all points with a given distance from one point. This point is called center, the distance is called radius.
752      * 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,
753      * line, or circle).
754      * @name Circle
755      * @augments JXG.Circle
756      * @constructor
757      * @type JXG.Circle
758      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
759      * @param {JXG.Point_number,JXG.Point,JXG.Line,JXG.Circle} center,radius The center must be given as a {@link JXG.Point}, see {@link JXG.providePoints}, but the radius can be given
760      * 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
761      * line will determine the radius), or another {@link JXG.Circle}.
762      * @example
763      * // Create a circle providing two points
764      * var p1 = board.create('point', [2.0, 2.0]),
765      *     p2 = board.create('point', [2.0, 0.0]),
766      *     c1 = board.create('circle', [p1, p2]);
767      *
768      * // Create another circle using the above circle
769      * var p3 = board.create('point', [3.0, 2.0]),
770      *     c2 = board.create('circle', [p3, c1]);
771      * </pre><div class="jxgbox" id="JXG5f304d31-ef20-4a8e-9c0e-ea1a2b6c79e0" style="width: 400px; height: 400px;"></div>
772      * <script type="text/javascript">
773      * (function() {
774      *   var cex1_board = JXG.JSXGraph.initBoard('JXG5f304d31-ef20-4a8e-9c0e-ea1a2b6c79e0', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
775      *       cex1_p1 = cex1_board.create('point', [2.0, 2.0]),
776      *       cex1_p2 = cex1_board.create('point', [2.0, 0.0]),
777      *       cex1_c1 = cex1_board.create('circle', [cex1_p1, cex1_p2]),
778      *       cex1_p3 = cex1_board.create('point', [3.0, 2.0]),
779      *       cex1_c2 = cex1_board.create('circle', [cex1_p3, cex1_c1]);
780      * })();
781      * </script><pre>
782      * @example
783      * // Create a circle providing two points
784      * var p1 = board.create('point', [2.0, 2.0]),
785      *     c1 = board.create('circle', [p1, 3]);
786      *
787      * // Create another circle using the above circle
788      * var c2 = board.create('circle', [function() { return [p1.X(), p1.Y() + 1];}, function() { return c1.Radius(); }]);
789      * </pre><div class="jxgbox" id="JXG54165f60-93b9-441d-8979-ac5d0f193020" style="width: 400px; height: 400px;"></div>
790      * <script type="text/javascript">
791      * (function() {
792      * var board = JXG.JSXGraph.initBoard('JXG54165f60-93b9-441d-8979-ac5d0f193020', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
793      * var p1 = board.create('point', [2.0, 2.0]);
794      * var c1 = board.create('circle', [p1, 3]);
795      *
796      * // Create another circle using the above circle
797      * var c2 = board.create('circle', [function() { return [p1.X(), p1.Y() + 1];}, function() { return c1.Radius(); }]);
798      * })();
799      * </script><pre>
800      * @example
801      * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
802      * var reflect = board.create('transform', [li], {type: 'reflect'});
803      *
804      * var c1 = board.create('circle', [[-2,-2], [-2, -1]], {center: {visible:true}});
805      * var c2 = board.create('circle', [c1, reflect]);
806      *      * </pre><div id="JXGa2a5a870-5dbb-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
807      * <script type="text/javascript">
808      *     (function() {
809      *         var board = JXG.JSXGraph.initBoard('JXGa2a5a870-5dbb-11e8-9fb9-901b0e1b8723',
810      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
811      *             var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
812      *             var reflect = board.create('transform', [li], {type: 'reflect'});
813      *
814      *             var c1 = board.create('circle', [[-2,-2], [-2, -1]], {center: {visible:true}});
815      *             var c2 = board.create('circle', [c1, reflect]);
816      *     })();
817      *
818      * </script><pre>
819      *
820      * @example
821      * var t = board.create('transform', [2, 1.5], {type: 'scale'});
822      * var c1 = board.create('circle', [[1.3, 1.3], [0, 1.3]], {strokeColor: 'black', center: {visible:true}});
823      * var c2 = board.create('circle', [c1, t], {strokeColor: 'black'});
824      *
825      * </pre><div id="JXG0686a222-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
826      * <script type="text/javascript">
827      *     (function() {
828      *         var board = JXG.JSXGraph.initBoard('JXG0686a222-6339-11e8-9fb9-901b0e1b8723',
829      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
830      *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
831      *     var c1 = board.create('circle', [[1.3, 1.3], [0, 1.3]], {strokeColor: 'black', center: {visible:true}});
832      *     var c2 = board.create('circle', [c1, t], {strokeColor: 'black'});
833      *
834      *     })();
835      *
836      * </script><pre>
837      *
838      */
839     JXG.createCircle = function (board, parents, attributes) {
840         var el, p, i, attr, obj,
841             isDraggable = true,
842             point_style = ['center', 'point2'];
843 
844         p = [];
845         obj = board.select(parents[0]);
846         if (Type.isObject(obj) && obj.elementClass === Const.OBJECT_CLASS_CIRCLE &&
847             Type.isTransformationOrArray(parents[1])) {
848 
849             attr = Type.copyAttributes(attributes, board.options, 'circle');
850             // if (!Type.exists(attr.type) || attr.type.toLowerCase() !== 'euclidean') {
851             //     // Create a circle element from a circle and a Euclidean transformation
852             //     el = JXG.createCircle(board, [obj.center, function() { return obj.Radius(); }], attr);
853             // } else {
854                 // Create a conic element from a circle and a projective transformation
855                 el = Conic.createEllipse(board, [obj.center, obj.center, function() { return 2 * obj.Radius(); }], attr);
856             // }
857             el.addTransform(parents[1]);
858             return el;
859 
860         }
861         // Circle defined by points
862         for (i = 0; i < parents.length; i++) {
863             if (Type.isPointType(board, parents[i])) {
864                 p = p.concat(Type.providePoints(board, [parents[i]], attributes, 'circle', [point_style[i]]));
865                 if (p[p.length - 1] === false) {
866                     throw new Error('JSXGraph: Can\'t create circle from this type. Please provide a point type.');
867                 }
868             } else {
869                 p.push(parents[i]);
870             }
871         }
872 
873         attr = Type.copyAttributes(attributes, board.options, 'circle');
874 
875         if (p.length === 2 && Type.isPoint(p[0]) && Type.isPoint(p[1])) {
876             // Point/Point
877             el = new JXG.Circle(board, 'twoPoints', p[0], p[1], attr);
878         } else if ((Type.isNumber(p[0]) || Type.isFunction(p[0]) || Type.isString(p[0])) &&
879                     Type.isPoint(p[1])) {
880             // Number/Point
881             el = new JXG.Circle(board, 'pointRadius', p[1], p[0], attr);
882         } else if ((Type.isNumber(p[1]) || Type.isFunction(p[1]) || Type.isString(p[1])) &&
883                 Type.isPoint(p[0])) {
884             // Point/Number
885             el = new JXG.Circle(board, 'pointRadius', p[0], p[1], attr);
886         } else if ((p[0].elementClass === Const.OBJECT_CLASS_CIRCLE) && Type.isPoint(p[1])) {
887             // Circle/Point
888             el = new JXG.Circle(board, 'pointCircle', p[1], p[0], attr);
889         } else if ((p[1].elementClass === Const.OBJECT_CLASS_CIRCLE) && Type.isPoint(p[0])) {
890             // Point/Circle
891             el = new JXG.Circle(board, 'pointCircle', p[0], p[1], attr);
892         } else if ((p[0].elementClass === Const.OBJECT_CLASS_LINE) && Type.isPoint(p[1])) {
893             // Line/Point
894             el = new JXG.Circle(board, 'pointLine', p[1], p[0], attr);
895         } else if ((p[1].elementClass === Const.OBJECT_CLASS_LINE) && Type.isPoint(p[0])) {
896             // Point/Line
897             el = new JXG.Circle(board, 'pointLine', p[0], p[1], attr);
898         } else if (parents.length === 3 && Type.isPoint(p[0]) && Type.isPoint(p[1]) && Type.isPoint(p[2])) {
899             // Circle through three points
900             // Check if circumcircle element is available
901             if (JXG.elements.circumcircle) {
902                 el = JXG.elements.circumcircle(board, p, attr);
903             } else {
904                 throw new Error('JSXGraph: Can\'t create circle with three points. Please include the circumcircle element (element/composition).');
905             }
906 
907         } else {
908             throw new Error("JSXGraph: Can't create circle with parent types '" +
909                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
910                 "\nPossible parent types: [point,point], [point,number], [point,function], [point,circle], [point,point,point], [circle,transformation]");
911         }
912 
913         el.isDraggable = isDraggable;
914         el.setParents(p);
915         el.elType = 'circle';
916         for (i = 0; i < p.length; i++) {
917             if (Type.isPoint(p[i])) {
918                 el.inherits.push(p[i]);
919             }
920         }
921         return el;
922     };
923 
924     JXG.registerElement('circle', JXG.createCircle);
925 
926     return {
927         Circle: JXG.Circle,
928         createCircle: JXG.createCircle
929     };
930 });
931