1 /*
  2     Copyright 2008-2024
  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.js";
 42 import GeometryElement from "./element.js";
 43 import Coords from "./coords.js";
 44 import Const from "./constants.js";
 45 import Mat from "../math/math.js";
 46 import GeonextParser from "../parser/geonext.js";
 47 import Type from "../utils/type.js";
 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 JessieCode syntax into JavaScript syntax and generally ensures that the radius is a function
139         this.updateRadius = Type.createFunction(par2, this.board);
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         Perimeter: "Perimeter",
193         Circumference: "Perimeter",
194         radius: "Radius",
195         Radius: "Radius",
196         Diameter: "Diameter",
197         center: "center",
198         line: "line",
199         point2: "point2"
200     });
201 };
202 
203 JXG.Circle.prototype = new GeometryElement();
204 
205 JXG.extend(
206     JXG.Circle.prototype,
207     /** @lends JXG.Circle.prototype */ {
208         /**
209          * Checks whether (x,y) is near the circle line or inside of the ellipse
210          * (in case JXG.Options.conic#hasInnerPoints is true).
211          * @param {Number} x Coordinate in x direction, screen coordinates.
212          * @param {Number} y Coordinate in y direction, screen coordinates.
213          * @returns {Boolean} True if (x,y) is near the circle, False otherwise.
214          * @private
215          */
216         hasPoint: function (x, y) {
217             var prec, type,
218                 mp = this.center.coords.usrCoords,
219                 p = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
220                 r = this.Radius(),
221                 dx, dy, dist;
222 
223             if (Type.isObject(Type.evaluate(this.visProp.precision))) {
224                 type = this.board._inputDevice;
225                 prec = Type.evaluate(this.visProp.precision[type]);
226             } else {
227                 // 'inherit'
228                 prec = this.board.options.precision.hasPoint;
229             }
230             dx = mp[1] - p.usrCoords[1];
231             dy = mp[2] - p.usrCoords[2];
232             dist = Mat.hypot(dx, dy);
233 
234             // We have to use usrCoords, since Radius is available in usrCoords only.
235             prec += Type.evaluate(this.visProp.strokewidth) * 0.5;
236             prec /= Math.sqrt(Math.abs(this.board.unitX * this.board.unitY));
237 
238             if (Type.evaluate(this.visProp.hasinnerpoints)) {
239                 return dist < r + prec;
240             }
241 
242             return Math.abs(dist - r) < prec;
243         },
244 
245         // /**
246         //  * Used to generate a polynomial for a point p that lies on this circle.
247         //  * @param {JXG.Point} p The point for which the polynomial is generated.
248         //  * @returns {Array} An array containing the generated polynomial.
249         //  * @private
250         //  */
251         generatePolynomial: function (p) {
252             /*
253              * We have four methods to construct a circle:
254              *   (a) Two points
255              *   (b) center and radius
256              *   (c) center and radius given by length of a segment
257              *   (d) center and radius given by another circle
258              *
259              * In case (b) we have to distinguish two cases:
260              *  (i)  radius is given as a number
261              *  (ii) radius is given as a function
262              * In the latter case there's no guarantee the radius depends on other geometry elements
263              * in a polynomial way so this case has to be omitted.
264              *
265              * Another tricky case is case (d):
266              * The radius depends on another circle so we have to cycle through the ancestors of each circle
267              * until we reach one that's radius does not depend on another circles radius.
268              *
269              *
270              * All cases (a) to (d) vary only in calculation of the radius. So the basic formulae for
271              * a glider G (g1,g2) on a circle with center M (m1,m2) and radius r is just:
272              *
273              *     (g1-m1)^2 + (g2-m2)^2 - r^2 = 0
274              *
275              * So the easiest case is (b) with a fixed radius given as a number. The other two cases (a)
276              * and (c) are quite the same: Euclidean distance between two points A (a1,a2) and B (b1,b2),
277              * squared:
278              *
279              *     r^2 = (a1-b1)^2 + (a2-b2)^2
280              *
281              * For case (d) we have to cycle recursively through all defining circles and finally return the
282              * formulae for calculating r^2. For that we use JXG.Circle.symbolic.generateRadiusSquared().
283              */
284             var m1 = this.center.symbolic.x,
285                 m2 = this.center.symbolic.y,
286                 g1 = p.symbolic.x,
287                 g2 = p.symbolic.y,
288                 rsq = this.generateRadiusSquared();
289 
290             /* No radius can be calculated (Case b.ii) */
291             if (rsq === "") {
292                 return [];
293             }
294 
295             return [
296                 "((" + g1 + ")-(" + m1 + "))^2 + ((" + g2 + ")-(" + m2 + "))^2 - (" + rsq + ")"
297             ];
298         },
299 
300         /**
301          * Generate symbolic radius calculation for loci determination with Groebner-Basis algorithm.
302          * @returns {String} String containing symbolic calculation of the circle's radius or an empty string
303          * if the radius can't be expressed in a polynomial equation.
304          * @private
305          */
306         generateRadiusSquared: function () {
307             /*
308              * Four cases:
309              *
310              *   (a) Two points
311              *   (b) center and radius
312              *   (c) center and radius given by length of a segment
313              *   (d) center and radius given by another circle
314              */
315             var m1,
316                 m2,
317                 p1,
318                 p2,
319                 q1,
320                 q2,
321                 rsq = "";
322 
323             if (this.method === "twoPoints") {
324                 m1 = this.center.symbolic.x;
325                 m2 = this.center.symbolic.y;
326                 p1 = this.point2.symbolic.x;
327                 p2 = this.point2.symbolic.y;
328 
329                 rsq = "((" + p1 + ")-(" + m1 + "))^2 + ((" + p2 + ")-(" + m2 + "))^2";
330             } else if (this.method === "pointRadius") {
331                 if (Type.isNumber(this.radius)) {
332                     rsq = (this.radius * this.radius).toString();
333                 }
334             } else if (this.method === "pointLine") {
335                 p1 = this.line.point1.symbolic.x;
336                 p2 = this.line.point1.symbolic.y;
337 
338                 q1 = this.line.point2.symbolic.x;
339                 q2 = this.line.point2.symbolic.y;
340 
341                 rsq = "((" + p1 + ")-(" + q1 + "))^2 + ((" + p2 + ")-(" + q2 + "))^2";
342             } else if (this.method === "pointCircle") {
343                 rsq = this.circle.Radius();
344             }
345 
346             return rsq;
347         },
348 
349         /**
350          * Uses the boards renderer to update the circle.
351          */
352         update: function () {
353             var x, y, z, r, c, i;
354 
355             if (this.needsUpdate) {
356                 if (Type.evaluate(this.visProp.trace)) {
357                     this.cloneToBackground(true);
358                 }
359 
360                 if (this.method === "pointLine") {
361                     this.radius = this.line.point1.coords.distance(
362                         Const.COORDS_BY_USER,
363                         this.line.point2.coords
364                     );
365                 } else if (this.method === "pointCircle") {
366                     this.radius = this.circle.Radius();
367                 } else if (this.method === "pointRadius") {
368                     this.radius = this.updateRadius();
369                 }
370                 this.radius = Math.abs(this.radius);
371 
372                 this.updateStdform();
373                 this.updateQuadraticform();
374 
375                 // Approximate the circle by 4 Bezier segments
376                 // This will be used for intersections of type curve / circle.
377                 // See https://spencermortensen.com/articles/bezier-circle/
378                 z = this.center.coords.usrCoords[0];
379                 x = this.center.coords.usrCoords[1] / z;
380                 y = this.center.coords.usrCoords[2] / z;
381                 z /= z;
382                 r = this.Radius();
383                 c = 0.551915024494;
384 
385                 this.numberPoints = 13;
386                 this.dataX = [
387                     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
388                 ];
389                 this.dataY = [
390                     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
391                 ];
392                 this.bezierDegree = 3;
393                 for (i = 0; i < this.numberPoints; i++) {
394                     this.points[i] = new Coords(
395                         Const.COORDS_BY_USER,
396                         [this.dataX[i], this.dataY[i]],
397                         this.board
398                     );
399                 }
400             }
401 
402             return this;
403         },
404 
405         /**
406          * Updates this circle's {@link JXG.Circle#quadraticform}.
407          * @private
408          */
409         updateQuadraticform: function () {
410             var m = this.center,
411                 mX = m.X(),
412                 mY = m.Y(),
413                 r = this.Radius();
414 
415             this.quadraticform = [
416                 [mX * mX + mY * mY - r * r, -mX, -mY],
417                 [-mX, 1, 0],
418                 [-mY, 0, 1]
419             ];
420         },
421 
422         /**
423          * Updates the stdform derived from the position of the center and the circle's radius.
424          * @private
425          */
426         updateStdform: function () {
427             this.stdform[3] = 0.5;
428             this.stdform[4] = this.Radius();
429             this.stdform[1] = -this.center.coords.usrCoords[1];
430             this.stdform[2] = -this.center.coords.usrCoords[2];
431             if (!isFinite(this.stdform[4])) {
432                 this.stdform[0] = Type.exists(this.point2)
433                     ? -(
434                           this.stdform[1] * this.point2.coords.usrCoords[1] +
435                           this.stdform[2] * this.point2.coords.usrCoords[2]
436                       )
437                     : 0;
438             }
439             this.normalize();
440         },
441 
442         /**
443          * Uses the boards renderer to update the circle.
444          * @private
445          */
446         updateRenderer: function () {
447             // var wasReal;
448 
449             if (!this.needsUpdate) {
450                 return this;
451             }
452 
453             if (this.visPropCalc.visible) {
454                 // wasReal = this.isReal;
455                 this.isReal =
456                     !isNaN(
457                         this.center.coords.usrCoords[1] +
458                             this.center.coords.usrCoords[2] +
459                             this.Radius()
460                     ) && this.center.isReal;
461 
462                 if (
463                     //wasReal &&
464                     !this.isReal
465                 ) {
466                     this.updateVisibility(false);
467                 }
468             }
469 
470             // Update the position
471             if (this.visPropCalc.visible) {
472                 this.board.renderer.updateEllipse(this);
473             }
474 
475             // Update the label if visible.
476             if (
477                 this.hasLabel &&
478                 this.visPropCalc.visible &&
479                 this.label &&
480                 this.label.visPropCalc.visible &&
481                 this.isReal
482             ) {
483                 this.label.update();
484                 this.board.renderer.updateText(this.label);
485             }
486 
487             // Update rendNode display
488             this.setDisplayRendNode();
489             // if (this.visPropCalc.visible !== this.visPropOld.visible) {
490             //     this.board.renderer.display(this, this.visPropCalc.visible);
491             //     this.visPropOld.visible = this.visPropCalc.visible;
492             //
493             //     if (this.hasLabel) {
494             //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
495             //     }
496             // }
497 
498             this.needsUpdate = false;
499             return this;
500         },
501 
502         /**
503          * Finds dependencies in a given term and resolves them by adding the elements referenced in this
504          * string to the circle's list of ancestors.
505          * @param {String} contentStr
506          * @private
507          */
508         notifyParents: function (contentStr) {
509             if (Type.isString(contentStr)) {
510                 GeonextParser.findDependencies(this, contentStr, this.board);
511             }
512         },
513 
514         /**
515          * Set a new radius, then update the board.
516          * @param {String|Number|function} r A string, function or number describing the new radius.
517          * @returns {JXG.Circle} Reference to this circle
518          */
519         setRadius: function (r) {
520             this.updateRadius = Type.createFunction(r, this.board);
521             this.addParentsFromJCFunctions([this.updateRadius]);
522             this.board.update();
523 
524             return this;
525         },
526 
527         /**
528          * Calculates the radius of the circle.
529          * @param {String|Number|function} [value] Set new radius
530          * @returns {Number} The radius of the circle
531          */
532         Radius: function (value) {
533             if (Type.exists(value)) {
534                 this.setRadius(value);
535                 return this.Radius();
536             }
537 
538             if (this.method === "twoPoints") {
539                 if (
540                     Type.cmpArrays(this.point2.coords.usrCoords, [0, 0, 0]) ||
541                     Type.cmpArrays(this.center.coords.usrCoords, [0, 0, 0])
542                 ) {
543                     return NaN;
544                 }
545 
546                 return this.center.Dist(this.point2);
547             }
548 
549             if (this.method === "pointLine" || this.method === "pointCircle") {
550                 return this.radius;
551             }
552 
553             if (this.method === "pointRadius") {
554                 return (Type.evaluate(this.visProp.nonnegativeonly)) ?
555                     Math.max(0.0, this.updateRadius()) :
556                     Math.abs(this.updateRadius());
557             }
558 
559             return NaN;
560         },
561 
562         /**
563          * Calculates the diameter of the circle.
564          * @returns {Number} The Diameter of the circle
565          */
566         Diameter: function () {
567             return 2 * this.Radius();
568         },
569 
570         /**
571          * Use {@link JXG.Circle#Radius}.
572          * @deprecated
573          */
574         getRadius: function () {
575             JXG.deprecated("Circle.getRadius()", "Circle.Radius()");
576             return this.Radius();
577         },
578 
579         // documented in geometry element
580         getTextAnchor: function () {
581             return this.center.coords;
582         },
583 
584         // documented in geometry element
585         getLabelAnchor: function () {
586             var x, y, pos,
587                 xy, lbda, sgn,
588                 dist = 1.5,
589                 r = this.Radius(),
590                 c = this.center.coords.usrCoords,
591                 SQRTH = 7.071067811865e-1; // sqrt(2)/2
592 
593             if (!Type.exists(this.label)) {
594                 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board);
595             }
596 
597             pos = Type.evaluate(this.label.visProp.position);
598             if (!Type.isString(pos)) {
599                 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board);
600             }
601 
602             if (pos.indexOf('right') < 0 && pos.indexOf('left') < 0) {
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             } else {
639                 // New positioning
640                 c = this.center.coords.scrCoords;
641 
642                 xy = Type.parsePosition(pos);
643                 lbda = Type.parseNumber(xy.pos, 2 * Math.PI, 1);
644                 if (xy.pos.indexOf('fr') < 0 &&
645                     xy.pos.indexOf('%') < 0) {
646                     if (xy.pos.indexOf('px') >= 0) {
647                         // 'px' or numbers are not supported
648                         lbda = 0;
649                     } else {
650                         // Pure numbers are interpreted as degrees
651                         lbda *= Math.PI / 180;
652                     }
653                 }
654 
655                 // Position left or right
656                 sgn = 1;
657                 if (xy.side === 'left') {
658                     sgn = -1;
659                 }
660 
661                 if (Type.exists(this.label)) {
662                     dist = sgn * 0.5 * Type.evaluate(this.label.visProp.distance);
663                 }
664 
665                 x = c[1] + (r * this.board.unitX + this.label.size[0] * dist) * Math.cos(lbda);
666                 y = c[2] - (r * this.board.unitY + this.label.size[1] * dist) * Math.sin(lbda);
667 
668                 return new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
669             }
670 
671             return new Coords(Const.COORDS_BY_USER, [x, y], this.board);
672         },
673 
674         // documented in geometry element
675         cloneToBackground: function () {
676             var er,
677                 r = this.Radius(),
678                 copy = {
679                     id: this.id + "T" + this.numTraces,
680                     elementClass: Const.OBJECT_CLASS_CIRCLE,
681                     center: {
682                         coords: this.center.coords
683                     },
684                     Radius: function () {
685                         return r;
686                     },
687                     getRadius: function () {
688                         return r;
689                     },
690                     board: this.board,
691                     visProp: Type.deepCopy(this.visProp, this.visProp.traceattributes, true)
692                 };
693 
694             copy.visProp.layer = this.board.options.layer.trace;
695 
696             this.numTraces++;
697             Type.clearVisPropOld(copy);
698             copy.visPropCalc = {
699                 visible: Type.evaluate(copy.visProp.visible)
700             };
701 
702             er = this.board.renderer.enhancedRendering;
703             this.board.renderer.enhancedRendering = true;
704             this.board.renderer.drawEllipse(copy);
705             this.board.renderer.enhancedRendering = er;
706             this.traces[copy.id] = copy.rendNode;
707 
708             return this;
709         },
710 
711         /**
712          * Add transformations to this circle.
713          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s.
714          * @returns {JXG.Circle} Reference to this circle object.
715          */
716         addTransform: function (transform) {
717             var i,
718                 list = Type.isArray(transform) ? transform : [transform],
719                 len = list.length;
720 
721             for (i = 0; i < len; i++) {
722                 this.center.transformations.push(list[i]);
723 
724                 if (this.method === "twoPoints") {
725                     this.point2.transformations.push(list[i]);
726                 }
727             }
728 
729             return this;
730         },
731 
732         // see element.js
733         snapToGrid: function () {
734             var forceIt = Type.evaluate(this.visProp.snaptogrid);
735 
736             this.center.handleSnapToGrid(forceIt, true);
737             if (this.method === "twoPoints") {
738                 this.point2.handleSnapToGrid(forceIt, true);
739             }
740 
741             return this;
742         },
743 
744         // see element.js
745         snapToPoints: function () {
746             var forceIt = Type.evaluate(this.visProp.snaptopoints);
747 
748             this.center.handleSnapToPoints(forceIt);
749             if (this.method === "twoPoints") {
750                 this.point2.handleSnapToPoints(forceIt);
751             }
752 
753             return this;
754         },
755 
756         /**
757          * Treats the circle as parametric curve and calculates its X coordinate.
758          * @param {Number} t Number between 0 and 1.
759          * @returns {Number} <tt>X(t)= radius*cos(t)+centerX</tt>.
760          */
761         X: function (t) {
762             return this.Radius() * Math.cos(t * 2 * Math.PI) + this.center.coords.usrCoords[1];
763         },
764 
765         /**
766          * Treats the circle as parametric curve and calculates its Y coordinate.
767          * @param {Number} t Number between 0 and 1.
768          * @returns {Number} <tt>X(t)= radius*sin(t)+centerY</tt>.
769          */
770         Y: function (t) {
771             return this.Radius() * Math.sin(t * 2 * Math.PI) + this.center.coords.usrCoords[2];
772         },
773 
774         /**
775          * Treat the circle as parametric curve and calculates its Z coordinate.
776          * @param {Number} t ignored
777          * @returns {Number} 1.0
778          */
779         Z: function (t) {
780             return 1.0;
781         },
782 
783         /**
784          * Returns 0.
785          * @private
786          */
787         minX: function () {
788             return 0.0;
789         },
790 
791         /**
792          * Returns 1.
793          * @private
794          */
795         maxX: function () {
796             return 1.0;
797         },
798 
799         /**
800          * Circle area
801          * @returns {Number} area of the circle.
802          */
803         Area: function () {
804             var r = this.Radius();
805 
806             return r * r * Math.PI;
807         },
808 
809         /**
810          * Perimeter (circumference) of circle.
811          * @returns {Number} Perimeter of circle in user units.
812          */
813         Perimeter: function () {
814             return 2 * this.Radius() * Math.PI;
815         },
816 
817         /**
818          * Get bounding box of the circle.
819          * @returns {Array} [x1, y1, x2, y2]
820          */
821         bounds: function () {
822             var uc = this.center.coords.usrCoords,
823                 r = this.Radius();
824 
825             return [uc[1] - r, uc[2] + r, uc[1] + r, uc[2] - r];
826         },
827 
828         /**
829          * Get data to construct this element. Data consists of the parent elements
830          * and static data like radius.
831          * @returns {Array} data necessary to construct this element
832          */
833         getParents: function () {
834             if (this.parents.length === 1) {
835                 // i.e. this.method === 'pointRadius'
836                 return this.parents.concat(this.radius);
837             }
838             return this.parents;
839         }
840     }
841 );
842 
843 /**
844  * @class This element is used to provide a constructor for a circle.
845  * @pseudo
846  * @description  A circle consists of all points with a given distance from one point. This point is called center, the distance is called radius.
847  * 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,
848  * line, or circle). If the radius is a negative value, its absolute values is taken.
849  * @name Circle
850  * @augments JXG.Circle
851  * @constructor
852  * @type JXG.Circle
853  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
854  * @param {JXG.Point_number,JXG.Point,JXG.Line,JXG.Circle} center,radius The center must be given as a {@link JXG.Point},
855  * see {@link JXG.providePoints}, but the radius can be given
856  * as a number (which will create a circle with a fixed radius),
857  * another {@link JXG.Point}, a {@link JXG.Line} (the distance of start and end point of the
858  * line will determine the radius), or another {@link JXG.Circle}.
859  * <p>
860  * If the radius is supplied as number or output of a function, its absolute value is taken.
861  *
862  * @example
863  * // Create a circle providing two points
864  * var p1 = board.create('point', [2.0, 2.0]),
865  *     p2 = board.create('point', [2.0, 0.0]),
866  *     c1 = board.create('circle', [p1, p2]);
867  *
868  * // Create another circle using the above circle
869  * var p3 = board.create('point', [3.0, 2.0]),
870  *     c2 = board.create('circle', [p3, c1]);
871  * </pre><div class="jxgbox" id="JXG5f304d31-ef20-4a8e-9c0e-ea1a2b6c79e0" style="width: 400px; height: 400px;"></div>
872  * <script type="text/javascript">
873  * (function() {
874  *   var cex1_board = JXG.JSXGraph.initBoard('JXG5f304d31-ef20-4a8e-9c0e-ea1a2b6c79e0', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
875  *       cex1_p1 = cex1_board.create('point', [2.0, 2.0]),
876  *       cex1_p2 = cex1_board.create('point', [2.0, 0.0]),
877  *       cex1_c1 = cex1_board.create('circle', [cex1_p1, cex1_p2]),
878  *       cex1_p3 = cex1_board.create('point', [3.0, 2.0]),
879  *       cex1_c2 = cex1_board.create('circle', [cex1_p3, cex1_c1]);
880  * })();
881  * </script><pre>
882  * @example
883  * // Create a circle providing two points
884  * var p1 = board.create('point', [2.0, 2.0]),
885  *     c1 = board.create('circle', [p1, 3]);
886  *
887  * // Create another circle using the above circle
888  * var c2 = board.create('circle', [function() { return [p1.X(), p1.Y() + 1];}, function() { return c1.Radius(); }]);
889  * </pre><div class="jxgbox" id="JXG54165f60-93b9-441d-8979-ac5d0f193020" style="width: 400px; height: 400px;"></div>
890  * <script type="text/javascript">
891  * (function() {
892  * var board = JXG.JSXGraph.initBoard('JXG54165f60-93b9-441d-8979-ac5d0f193020', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
893  * var p1 = board.create('point', [2.0, 2.0]);
894  * var c1 = board.create('circle', [p1, 3]);
895  *
896  * // Create another circle using the above circle
897  * var c2 = board.create('circle', [function() { return [p1.X(), p1.Y() + 1];}, function() { return c1.Radius(); }]);
898  * })();
899  * </script><pre>
900  * @example
901  * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
902  * var reflect = board.create('transform', [li], {type: 'reflect'});
903  *
904  * var c1 = board.create('circle', [[-2,-2], [-2, -1]], {center: {visible:true}});
905  * var c2 = board.create('circle', [c1, reflect]);
906  *      * </pre><div id="JXGa2a5a870-5dbb-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
907  * <script type="text/javascript">
908  *     (function() {
909  *         var board = JXG.JSXGraph.initBoard('JXGa2a5a870-5dbb-11e8-9fb9-901b0e1b8723',
910  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
911  *             var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
912  *             var reflect = board.create('transform', [li], {type: 'reflect'});
913  *
914  *             var c1 = board.create('circle', [[-2,-2], [-2, -1]], {center: {visible:true}});
915  *             var c2 = board.create('circle', [c1, reflect]);
916  *     })();
917  *
918  * </script><pre>
919  *
920  * @example
921  * var t = board.create('transform', [2, 1.5], {type: 'scale'});
922  * var c1 = board.create('circle', [[1.3, 1.3], [0, 1.3]], {strokeColor: 'black', center: {visible:true}});
923  * var c2 = board.create('circle', [c1, t], {strokeColor: 'black'});
924  *
925  * </pre><div id="JXG0686a222-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
926  * <script type="text/javascript">
927  *     (function() {
928  *         var board = JXG.JSXGraph.initBoard('JXG0686a222-6339-11e8-9fb9-901b0e1b8723',
929  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
930  *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
931  *     var c1 = board.create('circle', [[1.3, 1.3], [0, 1.3]], {strokeColor: 'black', center: {visible:true}});
932  *     var c2 = board.create('circle', [c1, t], {strokeColor: 'black'});
933  *
934  *     })();
935  *
936  * </script><pre>
937  *
938  */
939 JXG.createCircle = function (board, parents, attributes) {
940     var el,
941         p,
942         i,
943         attr,
944         obj,
945         isDraggable = true,
946         point_style = ["center", "point2"];
947 
948     p = [];
949     obj = board.select(parents[0]);
950     if (
951         Type.isObject(obj) &&
952         obj.elementClass === Const.OBJECT_CLASS_CIRCLE &&
953         Type.isTransformationOrArray(parents[1])
954     ) {
955         attr = Type.copyAttributes(attributes, board.options, "circle");
956         // if (!Type.exists(attr.type) || attr.type.toLowerCase() !== 'euclidean') {
957         //     // Create a circle element from a circle and a Euclidean transformation
958         //     el = JXG.createCircle(board, [obj.center, function() { return obj.Radius(); }], attr);
959         // } else {
960         // Create a conic element from a circle and a projective transformation
961         el = JXG.createEllipse(
962             board,
963             [
964                 obj.center,
965                 obj.center,
966                 function () {
967                     return 2 * obj.Radius();
968                 }
969             ],
970             attr
971         );
972         // }
973         el.addTransform(parents[1]);
974         return el;
975     }
976     // Circle defined by points
977     for (i = 0; i < parents.length; i++) {
978         if (Type.isPointType(board, parents[i])) {
979             if (parents.length < 3) {
980                 p.push(
981                     Type.providePoints(board, [parents[i]], attributes, "circle", [point_style[i]])[0]
982                 );
983             } else {
984                 p.push(
985                     Type.providePoints(board, [parents[i]], attributes, "point")[0]
986                 );
987             }
988             if (p[p.length - 1] === false) {
989                 throw new Error(
990                     "JSXGraph: Can't create circle from this type. Please provide a point type."
991                 );
992             }
993         } else {
994             p.push(parents[i]);
995         }
996     }
997 
998     attr = Type.copyAttributes(attributes, board.options, "circle");
999 
1000     if (p.length === 2 && Type.isPoint(p[0]) && Type.isPoint(p[1])) {
1001         // Point/Point
1002         el = new JXG.Circle(board, "twoPoints", p[0], p[1], attr);
1003     } else if (
1004         (Type.isNumber(p[0]) || Type.isFunction(p[0]) || Type.isString(p[0])) &&
1005         Type.isPoint(p[1])
1006     ) {
1007         // Number/Point
1008         el = new JXG.Circle(board, "pointRadius", p[1], p[0], attr);
1009     } else if (
1010         (Type.isNumber(p[1]) || Type.isFunction(p[1]) || Type.isString(p[1])) &&
1011         Type.isPoint(p[0])
1012     ) {
1013         // Point/Number
1014         el = new JXG.Circle(board, "pointRadius", p[0], p[1], attr);
1015     } else if (p[0].elementClass === Const.OBJECT_CLASS_CIRCLE && Type.isPoint(p[1])) {
1016         // Circle/Point
1017         el = new JXG.Circle(board, "pointCircle", p[1], p[0], attr);
1018     } else if (p[1].elementClass === Const.OBJECT_CLASS_CIRCLE && Type.isPoint(p[0])) {
1019         // Point/Circle
1020         el = new JXG.Circle(board, "pointCircle", p[0], p[1], attr);
1021     } else if (p[0].elementClass === Const.OBJECT_CLASS_LINE && Type.isPoint(p[1])) {
1022         // Line/Point
1023         el = new JXG.Circle(board, "pointLine", p[1], p[0], attr);
1024     } else if (p[1].elementClass === Const.OBJECT_CLASS_LINE && Type.isPoint(p[0])) {
1025         // Point/Line
1026         el = new JXG.Circle(board, "pointLine", p[0], p[1], attr);
1027     } else if (
1028         parents.length === 3 &&
1029         Type.isPoint(p[0]) &&
1030         Type.isPoint(p[1]) &&
1031         Type.isPoint(p[2])
1032     ) {
1033         // Circle through three points
1034         // Check if circumcircle element is available
1035         if (JXG.elements.circumcircle) {
1036             el = JXG.elements.circumcircle(board, p, attr);
1037         } else {
1038             throw new Error(
1039                 "JSXGraph: Can't create circle with three points. Please include the circumcircle element (element/composition)."
1040             );
1041         }
1042     } else {
1043         throw new Error(
1044             "JSXGraph: Can't create circle with parent types '" +
1045                 typeof parents[0] +
1046                 "' and '" +
1047                 typeof parents[1] +
1048                 "'." +
1049                 "\nPossible parent types: [point,point], [point,number], [point,function], [point,circle], [point,point,point], [circle,transformation]"
1050         );
1051     }
1052 
1053     el.isDraggable = isDraggable;
1054     el.setParents(p);
1055     el.elType = "circle";
1056     for (i = 0; i < p.length; i++) {
1057         if (Type.isPoint(p[i])) {
1058             el.inherits.push(p[i]);
1059         }
1060     }
1061     return el;
1062 };
1063 
1064 JXG.registerElement("circle", JXG.createCircle);
1065 
1066 export default JXG.Circle;
1067 // export default {
1068 //     Circle: JXG.Circle,
1069 //     createCircle: JXG.createCircle
1070 // };
1071