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