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