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 import JXG from "../jxg.js";
 36 import Geometry from "../math/geometry.js";
 37 import Mat from "../math/math.js";
 38 import Statistics from "../math/statistics.js";
 39 import Coords from "../base/coords.js";
 40 import Const from "../base/constants.js";
 41 import Type from "../utils/type.js";
 42 
 43 /**
 44  * @class A circular sector is a subarea of the area enclosed by a circle. It is enclosed by two radii and an arc.
 45  * @pseudo
 46  * @name Sector
 47  * @augments JXG.Curve
 48  * @constructor
 49  * @type JXG.Curve
 50  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
 51  *
 52  * First possibility of input parameters are:
 53  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 A sector is defined by three points: The sector's center <tt>p1</tt>,
 54  * a second point <tt>p2</tt> defining the radius and a third point <tt>p3</tt> defining the angle of the sector. The
 55  * Sector is always drawn counter clockwise from <tt>p2</tt> to <tt>p3</tt>.
 56  * <p>
 57  * In this case, the sector will have an arc as sub-object.
 58  * <p>
 59  * Second possibility of input parameters are:
 60  * @param {JXG.Line_JXG.Line_array,number_array,number_number,function} line, line2, coords1 or direction1, coords2 or direction2, radius The sector is defined by two lines.
 61  * The two legs which define the sector are given by two coordinates arrays which are project initially two the two lines or by two directions (+/- 1).
 62  * The last parameter is the radius of the sector.
 63  * <p>In this case, the sector will <b>not</b> have an arc as sub-object.
 64  *
 65  * @example
 66  * // Create a sector out of three free points
 67  * var p1 = board.create('point', [1.5, 5.0]),
 68  *     p2 = board.create('point', [1.0, 0.5]),
 69  *     p3 = board.create('point', [5.0, 3.0]),
 70  *
 71  *     a = board.create('sector', [p1, p2, p3]);
 72  * </pre><div class="jxgbox" id="JXG49f59123-f013-4681-bfd9-338b89893156" style="width: 300px; height: 300px;"></div>
 73  * <script type="text/javascript">
 74  * (function () {
 75  *   var board = JXG.JSXGraph.initBoard('JXG49f59123-f013-4681-bfd9-338b89893156', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
 76  *     p1 = board.create('point', [1.5, 5.0]),
 77  *     p2 = board.create('point', [1.0, 0.5]),
 78  *     p3 = board.create('point', [5.0, 3.0]),
 79  *
 80  *     a = board.create('sector', [p1, p2, p3]);
 81  * })();
 82  * </script><pre>
 83  *
 84  * @example
 85  * // Create a sector out of two lines, two directions and a radius
 86  * var p1 = board.create('point', [-1, 4]),
 87  *  p2 = board.create('point', [4, 1]),
 88  *  q1 = board.create('point', [-2, -3]),
 89  *  q2 = board.create('point', [4,3]),
 90  *
 91  *  li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
 92  *  li2 = board.create('line', [q1,q2], {lastArrow:true}),
 93  *
 94  *  sec1 = board.create('sector', [li1, li2, [5.5, 0], [4, 3], 3]),
 95  *  sec2 = board.create('sector', [li1, li2, 1, -1, 4]);
 96  *
 97  * </pre><div class="jxgbox" id="JXGbb9e2809-9895-4ff1-adfa-c9c71d50aa53" style="width: 300px; height: 300px;"></div>
 98  * <script type="text/javascript">
 99  * (function () {
100  *   var board = JXG.JSXGraph.initBoard('JXGbb9e2809-9895-4ff1-adfa-c9c71d50aa53', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
101  *     p1 = board.create('point', [-1, 4]),
102  *     p2 = board.create('point', [4, 1]),
103  *     q1 = board.create('point', [-2, -3]),
104  *     q2 = board.create('point', [4,3]),
105  *
106  *     li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
107  *     li2 = board.create('line', [q1,q2], {lastArrow:true}),
108  *
109  *     sec1 = board.create('sector', [li1, li2, [5.5, 0], [4, 3], 3]),
110  *     sec2 = board.create('sector', [li1, li2, 1, -1, 4]);
111  * })();
112  * </script><pre>
113  *
114  * @example
115  * var t = board.create('transform', [2, 1.5], {type: 'scale'});
116  * var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], {
117  *                 anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true},
118  *                 fillColor: 'yellow', strokeColor: 'black'});
119  * var s2 = board.create('curve', [s1, t], {fillColor: 'yellow', strokeColor: 'black'});
120  *
121  * </pre><div id="JXG2e70ee14-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
122  * <script type="text/javascript">
123  *     (function() {
124  *         var board = JXG.JSXGraph.initBoard('JXG2e70ee14-6339-11e8-9fb9-901b0e1b8723',
125  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
126  *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
127  *     var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], {
128  *                     anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true},
129  *                     fillColor: 'yellow', strokeColor: 'black'});
130  *     var s2 = board.create('curve', [s1, t], {fillColor: 'yellow', strokeColor: 'black'});
131  *
132  *     })();
133  *
134  * </script><pre>
135  *
136  * @example
137  * var A = board.create('point', [3, -2]),
138  *     B = board.create('point', [-2, -2]),
139  *     C = board.create('point', [0, 4]);
140  *
141  * var angle = board.create('sector', [B, A, C], {
142  *         strokeWidth: 0,
143  *         arc: {
144  *         	visible: true,
145  *         	strokeWidth: 3,
146  *           lastArrow: {size: 4},
147  *           firstArrow: {size: 4}
148  *         }
149  *       });
150  * //angle.arc.setAttribute({firstArrow: false});
151  * angle.arc.setAttribute({lastArrow: false});
152  *
153  * </pre><div id="JXGca37b99e-1510-49fa-ac9e-efd60e956104" class="jxgbox" style="width: 300px; height: 300px;"></div>
154  * <script type="text/javascript">
155  *     (function() {
156  *         var board = JXG.JSXGraph.initBoard('JXGca37b99e-1510-49fa-ac9e-efd60e956104',
157  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
158  *     var A = board.create('point', [3, -2]),
159  *         B = board.create('point', [-2, -2]),
160  *         C = board.create('point', [0, 4]);
161  *
162  *     var angle = board.create('sector', [B, A, C], {
163  *             strokeWidth: 0,
164  *             arc: {
165  *             	visible: true,
166  *             	strokeWidth: 3,
167  *               lastArrow: {size: 4},
168  *               firstArrow: {size: 4}
169  *             }
170  *           });
171  *     //angle.arc.setAttribute({firstArrow: false});
172  *     angle.arc.setAttribute({lastArrow: false});
173  *
174  *     })();
175  *
176  * </script><pre>
177  *
178  *
179  */
180 JXG.createSector = function (board, parents, attributes) {
181     var el,
182         attr,
183         i,
184         type = "invalid",
185         s,
186         v,
187         attrPoints = ["center", "radiusPoint", "anglePoint"],
188         points;
189 
190     // Three points?
191     if (
192         parents[0].elementClass === Const.OBJECT_CLASS_LINE &&
193         parents[1].elementClass === Const.OBJECT_CLASS_LINE &&
194         (Type.isArray(parents[2]) || Type.isNumber(parents[2])) &&
195         (Type.isArray(parents[3]) || Type.isNumber(parents[3])) &&
196         (Type.isNumber(parents[4]) || Type.isFunction(parents[4]) || Type.isString(parents[4]))
197     ) {
198         type = "2lines";
199     } else {
200         points = Type.providePoints(board, parents, attributes, "sector", attrPoints);
201         if (points === false) {
202             throw new Error(
203                 "JSXGraph: Can't create Sector with parent types '" +
204                     typeof parents[0] +
205                     "' and '" +
206                     typeof parents[1] +
207                     "' and '" +
208                     typeof parents[2] +
209                     "'."
210             );
211         }
212         type = "3points";
213     }
214 
215     attr = Type.copyAttributes(attributes, board.options, "sector");
216     el = board.create("curve", [[0], [0]], attr);
217     el.type = Const.OBJECT_TYPE_SECTOR;
218     el.elType = "sector";
219 
220     /**
221      * Sets radius if the attribute `radius` has value 'auto'.
222      * Sets a radius between 20 and 50 points, depending on the distance
223      * between the center and the radius point.
224      * This function is used in {@link Angle}.
225      *
226      * @name autoRadius
227      * @memberof Sector.prototype
228      * @function
229      * @returns {Number} returns a radius value in user coordinates.
230      * @private
231      */
232     el.autoRadius = function () {
233         var r1 = 20 / el.board.unitX, // 20px
234             r2 = Infinity,
235             r3 = 50 / el.board.unitX; // 50px
236 
237         if (Type.isPoint(el.center)) {
238             // This does not work for 2-lines sectors / angles
239             r2 = el.center.Dist(el.point2) * 0.3333;
240         }
241 
242         return Math.max(r1, Math.min(r2, r3));
243     };
244 
245     if (type === "2lines") {
246         /**
247          * @ignore
248          */
249         el.Radius = function () {
250             var r = Type.evaluate(parents[4]);
251             if (r === "auto") {
252                 return this.autoRadius();
253             }
254             return r;
255         };
256 
257         el.line1 = board.select(parents[0]);
258         el.line2 = board.select(parents[1]);
259 
260         el.line1.addChild(el);
261         el.line2.addChild(el);
262         el.setParents(parents);
263 
264         el.point1 = { visProp: {} };
265         el.point2 = { visProp: {} };
266         el.point3 = { visProp: {} };
267 
268         /* Intersection point */
269         s = Geometry.meetLineLine(el.line1.stdform, el.line2.stdform, 0, board);
270 
271         if (Type.isArray(parents[2])) {
272             /* project p1 to l1 */
273             if (parents[2].length === 2) {
274                 parents[2] = [1].concat(parents[2]);
275             }
276             /*
277                 v = [0, el.line1.stdform[1], el.line1.stdform[2]];
278                 v = Mat.crossProduct(v, parents[2]);
279                 v = Geometry.meetLineLine(v, el.line1.stdform, 0, board);
280                 */
281             v = Geometry.projectPointToLine(
282                 { coords: { usrCoords: parents[2] } },
283                 el.line1,
284                 board
285             );
286             v = Statistics.subtract(v.usrCoords, s.usrCoords);
287             el.direction1 =
288                 Mat.innerProduct(v, [0, el.line1.stdform[2], -el.line1.stdform[1]], 3) >= 0
289                     ? +1
290                     : -1;
291         } else {
292             el.direction1 = parents[2] >= 0 ? 1 : -1;
293         }
294 
295         if (Type.isArray(parents[3])) {
296             /* project p2 to l2 */
297             if (parents[3].length === 2) {
298                 parents[3] = [1].concat(parents[3]);
299             }
300             /*
301                 v = [0, el.line2.stdform[1], el.line2.stdform[2]];
302                 v = Mat.crossProduct(v, parents[3]);
303                 v = Geometry.meetLineLine(v, el.line2.stdform, 0, board);
304                 */
305             v = Geometry.projectPointToLine(
306                 { coords: { usrCoords: parents[3] } },
307                 el.line2,
308                 board
309             );
310             v = Statistics.subtract(v.usrCoords, s.usrCoords);
311             el.direction2 =
312                 Mat.innerProduct(v, [0, el.line2.stdform[2], -el.line2.stdform[1]], 3) >= 0
313                     ? +1
314                     : -1;
315         } else {
316             el.direction2 = parents[3] >= 0 ? 1 : -1;
317         }
318 
319         el.methodMap = JXG.deepCopy(el.methodMap, {
320             arc: "arc",
321             center: "center",
322             line1: "line1",
323             line2: "line2"
324         });
325 
326         /**
327          * @class
328          * @ignore
329          */
330         el.updateDataArray = function () {
331             var r,
332                 l1,
333                 l2,
334                 A = [0, 0, 0],
335                 B = [0, 0, 0],
336                 C = [0, 0, 0],
337                 ar;
338 
339             l1 = this.line1;
340             l2 = this.line2;
341 
342             // Intersection point of the lines
343             B = Mat.crossProduct(l1.stdform, l2.stdform);
344 
345             if (Math.abs(B[0]) > Mat.eps * Mat.eps) {
346                 B[1] /= B[0];
347                 B[2] /= B[0];
348                 B[0] /= B[0];
349             }
350             // First point
351             r = this.direction1 * this.Radius();
352             A = Statistics.add(B, [0, r * l1.stdform[2], -r * l1.stdform[1]]);
353 
354             // Second point
355             r = this.direction2 * this.Radius();
356             C = Statistics.add(B, [0, r * l2.stdform[2], -r * l2.stdform[1]]);
357 
358             this.point2.coords = new Coords(Const.COORDS_BY_USER, A, el.board);
359             this.point1.coords = new Coords(Const.COORDS_BY_USER, B, el.board);
360             this.point3.coords = new Coords(Const.COORDS_BY_USER, C, el.board);
361 
362             if (
363                 Math.abs(A[0]) < Mat.eps ||
364                 Math.abs(B[0]) < Mat.eps ||
365                 Math.abs(C[0]) < Mat.eps
366             ) {
367                 this.dataX = [NaN];
368                 this.dataY = [NaN];
369                 return;
370             }
371 
372             ar = Geometry.bezierArc(A, B, C, true, 1);
373 
374             this.dataX = ar[0];
375             this.dataY = ar[1];
376 
377             this.bezierDegree = 3;
378         };
379 
380         // Arc does not work yet, since point1, point2 and point3 are
381         // virtual points.
382         //
383         // attr = Type.copyAttributes(attributes, board.options, "arc");
384         // attr = Type.copyAttributes(attr, board.options, "sector", "arc");
385         // attr.withLabel = false;
386         // attr.name += "_arc";
387         // // el.arc = board.create("arc", [el.point1, el.point2, el.point3], attr);
388         // // The arc's radius is always the radius of sector.
389         // // This is important for angles.
390         // el.updateDataArray();
391         // el.arc = board.create("arc", [
392         //     function() {
393         //         return el.point1.coords.usrCoords;
394         //     }, // Center
395         //     function() {
396         //         var d = el.point2.coords.distance(Const.COORDS_BY_USER, el.point1.coords);
397         //         if (d === 0) {
398         //             return [el.point1.coords.usrCoords[1], el.point1.coords.usrCoords[2]];
399         //         }
400         //         return [
401         //             el.point1.coords.usrCoords[1] + el.Radius() * (el.point2.coords.usrCoords[1] - el.point1.coords.usrCoords[1]) / d,
402         //             el.point1.coords.usrCoords[2] + el.Radius() * (el.point2.coords.usrCoords[2] - el.point1.coords.usrCoords[2]) / d
403         //         ];
404         //     },
405         //     function() {
406         //         return el.point3.coords.usrCoords;
407         //     }, // Center
408         // ], attr);
409         // el.addChild(el.arc);
410 
411         // end '2lines'
412     } else if (type === "3points") {
413         /**
414          * Midpoint of the sector.
415          * @memberOf Sector.prototype
416          * @name point1
417          * @type JXG.Point
418          */
419         el.point1 = points[0];
420 
421         /**
422          * This point together with {@link Sector#point1} defines the radius.
423          * @memberOf Sector.prototype
424          * @name point2
425          * @type JXG.Point
426          */
427         el.point2 = points[1];
428 
429         /**
430          * Defines the sector's angle.
431          * @memberOf Sector.prototype
432          * @name point3
433          * @type JXG.Point
434          */
435         el.point3 = points[2];
436 
437         /* Add arc as child to defining points */
438         for (i = 0; i < 3; i++) {
439             if (Type.exists(points[i]._is_new)) {
440                 el.addChild(points[i]);
441                 delete points[i]._is_new;
442             } else {
443                 points[i].addChild(el);
444             }
445         }
446 
447         // useDirection is necessary for circumCircleSectors
448         el.useDirection = attributes.usedirection;
449         el.setParents(points);
450 
451         /**
452          * Defines the sectors orientation in case of circumCircleSectors.
453          * @memberOf Sector.prototype
454          * @name point4
455          * @type JXG.Point
456          */
457         if (Type.exists(points[3])) {
458             el.point4 = points[3];
459             el.point4.addChild(el);
460         }
461 
462         el.methodMap = JXG.deepCopy(el.methodMap, {
463             arc: "arc",
464             center: "center",
465             radiuspoint: "radiuspoint",
466             anglepoint: "anglepoint"
467         });
468 
469         /**
470          * @class
471          * @ignore
472          */
473         el.updateDataArray = function () {
474             var ar,
475                 det,
476                 p0c,
477                 p1c,
478                 p2c,
479                 A = this.point2,
480                 B = this.point1,
481                 C = this.point3,
482                 phi,
483                 sgn = 1,
484                 vp_s = Type.evaluate(this.visProp.selection);
485 
486             if (!A.isReal || !B.isReal || !C.isReal) {
487                 this.dataX = [NaN];
488                 this.dataY = [NaN];
489                 return;
490             }
491 
492             phi = Geometry.rad(A, B, C);
493             if ((vp_s === "minor" && phi > Math.PI) || (vp_s === "major" && phi < Math.PI)) {
494                 sgn = -1;
495             }
496 
497             // This is true for circumCircleSectors. In that case there is
498             // a fourth parent element: [midpoint, point1, point3, point2]
499             if (this.useDirection && Type.exists(this.point4)) {
500                 p0c = this.point2.coords.usrCoords;
501                 p1c = this.point4.coords.usrCoords;
502                 p2c = this.point3.coords.usrCoords;
503                 det =
504                     (p0c[1] - p2c[1]) * (p0c[2] - p1c[2]) -
505                     (p0c[2] - p2c[2]) * (p0c[1] - p1c[1]);
506 
507                 if (det >= 0.0) {
508                     C = this.point2;
509                     A = this.point3;
510                 }
511             }
512 
513             A = A.coords.usrCoords;
514             B = B.coords.usrCoords;
515             C = C.coords.usrCoords;
516 
517             ar = Geometry.bezierArc(A, B, C, true, sgn);
518 
519             this.dataX = ar[0];
520             this.dataY = ar[1];
521             this.bezierDegree = 3;
522         };
523 
524         /**
525          * Returns the radius of the sector.
526          * @memberOf Sector.prototype
527          * @name Radius
528          * @function
529          * @returns {Number} The distance between {@link Sector#point1} and {@link Sector#point2}.
530          */
531         el.Radius = function () {
532             return this.point2.Dist(this.point1);
533         };
534     } // end '3points'
535 
536     el.center = el.point1;
537     el.radiuspoint = el.point2;
538     el.anglepoint = el.point3;
539 
540     attr = Type.copyAttributes(attributes, board.options, "arc");
541     attr = Type.copyAttributes(attr, board.options, "sector", "arc");
542     attr.withLabel = false;
543     // Minor or major arc:
544     attr.selection = el.visProp.selection;
545     attr.name += "_arc";
546 
547     if (type === "2lines") {
548         el.updateDataArray();
549         el.arc = board.create("arc", [
550             function() {
551                 return el.point1.coords.usrCoords;
552             }, // Center
553             function() {
554                 var d = el.point2.coords.distance(Const.COORDS_BY_USER, el.point1.coords);
555                 if (d === 0) {
556                     return [el.point1.coords.usrCoords[1], el.point1.coords.usrCoords[2]];
557                 }
558                 return [
559                     el.point1.coords.usrCoords[1] + el.Radius() * (el.point2.coords.usrCoords[1] - el.point1.coords.usrCoords[1]) / d,
560                     el.point1.coords.usrCoords[2] + el.Radius() * (el.point2.coords.usrCoords[2] - el.point1.coords.usrCoords[2]) / d
561                 ];
562             },
563             function() {
564                 return el.point3.coords.usrCoords;
565             } // Center
566         ], attr);
567     } else {
568         // The arc's radius is always the radius of sector.
569         // This is important for angles.
570         el.arc = board.create("arc", [
571             el.point1, // Center
572             function() {
573                 var d = el.point2.Dist(el.point1);
574                 if (d === 0) {
575                     return [el.point1.X(), el.point1.Y()];
576                 }
577                 return [
578                     el.point1.X() + el.Radius() * (el.point2.X() - el.point1.X()) / d,
579                     el.point1.Y() + el.Radius() * (el.point2.Y() - el.point1.Y()) / d
580                 ];
581             },
582             el.point3
583         ], attr);
584     }
585     el.addChild(el.arc);
586 
587     // Default hasPoint method. Documented in geometry element
588     el.hasPointCurve = function (x, y) {
589         var angle,
590             alpha,
591             beta,
592             prec,
593             type,
594             checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
595             r = this.Radius(),
596             dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint),
597             has,
598             vp_s = Type.evaluate(this.visProp.selection);
599 
600         if (Type.isObject(Type.evaluate(this.visProp.precision))) {
601             type = this.board._inputDevice;
602             prec = Type.evaluate(this.visProp.precision[type]);
603         } else {
604             // 'inherit'
605             prec = this.board.options.precision.hasPoint;
606         }
607         prec /= Math.min(Math.abs(this.board.unitX), Math.abs(this.board.unitY));
608         has = Math.abs(dist - r) < prec;
609         if (has) {
610             angle = Geometry.rad(this.point2, this.center, checkPoint.usrCoords.slice(1));
611             alpha = 0;
612             beta = Geometry.rad(this.point2, this.center, this.point3);
613 
614             if ((vp_s === "minor" && beta > Math.PI) || (vp_s === "major" && beta < Math.PI)) {
615                 alpha = beta;
616                 beta = 2 * Math.PI;
617             }
618 
619             if (angle < alpha || angle > beta) {
620                 has = false;
621             }
622         }
623 
624         return has;
625     };
626 
627     /**
628      * Checks whether (x,y) is within the area defined by the sector.
629      * @memberOf Sector.prototype
630      * @name hasPointSector
631      * @function
632      * @param {Number} x Coordinate in x direction, screen coordinates.
633      * @param {Number} y Coordinate in y direction, screen coordinates.
634      * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise.
635      */
636     el.hasPointSector = function (x, y) {
637         var angle,
638             checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
639             r = this.Radius(),
640             dist = this.point1.coords.distance(Const.COORDS_BY_USER, checkPoint),
641             alpha,
642             beta,
643             has = dist < r,
644             vp_s = Type.evaluate(this.visProp.selection);
645 
646         if (has) {
647             angle = Geometry.rad(this.radiuspoint, this.center, checkPoint.usrCoords.slice(1));
648             alpha = 0.0;
649             beta = Geometry.rad(this.radiuspoint, this.center, this.anglepoint);
650 
651             if ((vp_s === "minor" && beta > Math.PI) || (vp_s === "major" && beta < Math.PI)) {
652                 alpha = beta;
653                 beta = 2 * Math.PI;
654             }
655             //if (angle > Geometry.rad(this.point2, this.point1, this.point3)) {
656             if (angle < alpha || angle > beta) {
657                 has = false;
658             }
659         }
660         return has;
661     };
662 
663     el.hasPoint = function (x, y) {
664         if (
665             Type.evaluate(this.visProp.highlightonsector) ||
666             Type.evaluate(this.visProp.hasinnerpoints)
667         ) {
668             return this.hasPointSector(x, y);
669         }
670 
671         return this.hasPointCurve(x, y);
672     };
673 
674     // documented in GeometryElement
675     el.getTextAnchor = function () {
676         return this.point1.coords;
677     };
678 
679     // documented in GeometryElement
680     // this method is very similar to arc.getLabelAnchor()
681     // there are some additions in the arc version though, mainly concerning
682     // "major" and "minor" arcs. but maybe these methods can be merged.
683     /**
684      * @class
685      * @ignore
686      */
687     el.getLabelAnchor = function () {
688         var coords,
689             vec,
690             vecx,
691             vecy,
692             len,
693             angle = Geometry.rad(this.point2, this.point1, this.point3),
694             dx = 13 / this.board.unitX,
695             dy = 13 / this.board.unitY,
696             p2c = this.point2.coords.usrCoords,
697             pmc = this.point1.coords.usrCoords,
698             bxminusax = p2c[1] - pmc[1],
699             byminusay = p2c[2] - pmc[2],
700             vp_s = Type.evaluate(this.visProp.selection),
701             l_vp = this.label ? this.label.visProp : this.visProp.label;
702 
703         // If this is uncommented, the angle label can not be dragged
704         //if (Type.exists(this.label)) {
705         //    this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
706         //}
707 
708         if ((vp_s === "minor" && angle > Math.PI) || (vp_s === "major" && angle < Math.PI)) {
709             angle = -(2 * Math.PI - angle);
710         }
711 
712         coords = new Coords(
713             Const.COORDS_BY_USER,
714             [
715                 pmc[1] + Math.cos(angle * 0.5) * bxminusax - Math.sin(angle * 0.5) * byminusay,
716                 pmc[2] + Math.sin(angle * 0.5) * bxminusax + Math.cos(angle * 0.5) * byminusay
717             ],
718             this.board
719         );
720 
721         vecx = coords.usrCoords[1] - pmc[1];
722         vecy = coords.usrCoords[2] - pmc[2];
723 
724         len = Mat.hypot(vecx, vecy);
725         vecx = (vecx * (len + dx)) / len;
726         vecy = (vecy * (len + dy)) / len;
727         vec = [pmc[1] + vecx, pmc[2] + vecy];
728 
729         l_vp.position = Geometry.calcLabelQuadrant(Geometry.rad([1, 0], [0, 0], vec));
730 
731         return new Coords(Const.COORDS_BY_USER, vec, this.board);
732     };
733 
734     /**
735      * Overwrite the Radius method of the sector.
736      * Used in {@link GeometryElement#setAttribute}.
737      * @memberOf Sector.prototype
738      * @name setRadius
739      * @param {Number|Function} value New radius.
740      * @function
741      */
742     el.setRadius = function (val) {
743         var res,
744             e = Type.evaluate(val);
745 
746         if (val === 'auto' || e === 'auto') {
747             res = 'auto';
748         } else if (Type.isNumber(val)) {
749             res = 'number';
750         } else if (Type.isFunction(val) && !Type.isString(e)) {
751             res = 'function';
752         } else {
753             res = 'undefined';
754         }
755         if (res !== 'undefined') {
756             this.visProp.radius = val;
757         }
758 
759         /**
760          * @ignore
761          */
762         el.Radius = function () {
763             var r = Type.evaluate(val);
764             if (r === "auto") {
765                 return this.autoRadius();
766             }
767             return r;
768         };
769     };
770 
771     /**
772      * @deprecated
773      * @ignore
774      */
775     el.getRadius = function () {
776         JXG.deprecated("Sector.getRadius()", "Sector.Radius()");
777         return this.Radius();
778     };
779 
780     /**
781      * Length of the sector's arc or the angle in various units, see {@link Arc#Value}.
782      * @memberOf Sector.prototype
783      * @name Value
784      * @function
785      * @param {String} unit
786      * @returns {Number} The arc length or the angle value in various units.
787      * @see Arc#Value
788      */
789     el.Value = function(unit) {
790         return this.arc.Value(unit);
791     };
792 
793     /**
794      * Arc length.
795      * @memberOf Sector.prototype
796      * @name L
797      * @returns {Number} Length of the sector's arc.
798      * @function
799      * @see Arc#L
800      */
801     el.L = function() {
802         return this.arc.L();
803     };
804 
805     /**
806      * Area of the sector.
807      * @memberOf Sector.prototype
808      * @name Area
809      * @function
810      * @returns {Number} The area of the sector.
811      */
812     el.Area = function () {
813         var r = this.Radius();
814 
815         return 0.5 * r * r * this.Value('radians');
816     };
817 
818     /**
819      * Sector perimeter, i.e. arc length plus 2 * radius.
820      * @memberOf Sector.prototype
821      * @name Perimeter
822      * @function
823      * @returns {Number} Perimeter of sector.
824      */
825     el.Perimeter = function () {
826         return this.L() + 2 * this.Radius();
827     };
828 
829     if (type === "3points") {
830         /**
831          * Moves the sector by the difference of two coordinates.
832          * @memberOf Sector.prototype
833          * @name setPositionDirectly
834          * @function
835          * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
836          * @param {Array} coords coordinates in screen/user units
837          * @param {Array} oldcoords previous coordinates in screen/user units
838          * @returns {JXG.Curve} this element
839          * @private
840          */
841         el.setPositionDirectly = function (method, coords, oldcoords) {
842             var dc, t,
843                 c = new Coords(method, coords, this.board),
844                 oldc = new Coords(method, oldcoords, this.board);
845 
846             if (!el.point1.draggable() || !el.point2.draggable() || !el.point3.draggable()) {
847                 return this;
848             }
849 
850             dc = Statistics.subtract(c.usrCoords, oldc.usrCoords);
851             t = this.board.create("transform", dc.slice(1), { type: "translate" });
852             t.applyOnce([el.point1, el.point2, el.point3]);
853 
854             return this;
855         };
856     }
857 
858     el.methodMap = JXG.deepCopy(el.methodMap, {
859         radius: "Radius",
860         Radius: "Radius",
861         getRadius: "Radius",
862         setRadius: "setRadius",
863         Value: "Value",
864         L: "L",
865         Area: "Area",
866         Perimeter: "Perimeter"
867     });
868 
869     return el;
870 };
871 
872 JXG.registerElement("sector", JXG.createSector);
873 
874 /**
875  * @class A circumcircle sector is different from a {@link Sector} mostly in the way the parent elements are interpreted.
876  * At first, the circum center is determined from the three given points. Then the sector is drawn from <tt>p1</tt> through
877  * <tt>p2</tt> to <tt>p3</tt>.
878  * @pseudo
879  * @name CircumcircleSector
880  * @augments Sector
881  * @constructor
882  * @type Sector
883  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
884  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A circumcircle sector is defined by the circumcircle which is determined
885  * by these three given points. The circumcircle sector is always drawn from <tt>p1</tt> through <tt>p2</tt> to <tt>p3</tt>.
886  * @example
887  * // Create an arc out of three free points
888  * var p1 = board.create('point', [1.5, 5.0]),
889  *     p2 = board.create('point', [1.0, 0.5]),
890  *     p3 = board.create('point', [5.0, 3.0]),
891  *
892  *     a = board.create('circumcirclesector', [p1, p2, p3]);
893  * </pre><div class="jxgbox" id="JXG695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04" style="width: 300px; height: 300px;"></div>
894  * <script type="text/javascript">
895  * (function () {
896  *   var board = JXG.JSXGraph.initBoard('JXG695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
897  *     p1 = board.create('point', [1.5, 5.0]),
898  *     p2 = board.create('point', [1.0, 0.5]),
899  *     p3 = board.create('point', [5.0, 3.0]),
900  *
901  *     a = board.create('circumcirclesector', [p1, p2, p3]);
902  * })();
903  * </script><pre>
904  */
905 JXG.createCircumcircleSector = function (board, parents, attributes) {
906     var el, mp, attr, points;
907 
908     points = Type.providePoints(board, parents, attributes, "point");
909     if (points === false) {
910         throw new Error(
911             "JSXGraph: Can't create circumcircle sector with parent types '" +
912                 typeof parents[0] +
913                 "' and '" +
914                 typeof parents[1] +
915                 "' and '" +
916                 typeof parents[2] +
917                 "'."
918         );
919     }
920 
921     mp = board.create("circumcenter", points.slice(0, 3), attr);
922     mp.dump = false;
923 
924     attr = Type.copyAttributes(attributes, board.options, "circumcirclesector");
925     el = board.create("sector", [mp, points[0], points[2], points[1]], attr);
926 
927     el.elType = "circumcirclesector";
928     el.setParents(points);
929 
930     /**
931      * Center of the circumcirclesector
932      * @memberOf CircumcircleSector.prototype
933      * @name center
934      * @type Circumcenter
935      */
936     el.center = mp;
937     el.subs = {
938         center: mp
939     };
940 
941     return el;
942 };
943 
944 JXG.registerElement("circumcirclesector", JXG.createCircumcircleSector);
945 
946 /**
947  * @class A minor sector is a sector of a circle having measure less than or equal to
948  * 180 degrees (pi radians). It is defined by a center, one point that
949  * defines the radius, and a third point that defines the angle of the sector.
950  * @pseudo
951  * @name MinorSector
952  * @augments Curve
953  * @constructor
954  * @type JXG.Curve
955  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
956  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor sector is a sector of a circle around p1 having measure less than or equal to
957  * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
958  * @example
959  * // Create sector out of three free points
960  * var p1 = board.create('point', [2.0, 2.0]);
961  * var p2 = board.create('point', [1.0, 0.5]);
962  * var p3 = board.create('point', [3.5, 1.0]);
963  *
964  * var a = board.create('minorsector', [p1, p2, p3]);
965  * </pre><div class="jxgbox" id="JXGaf27ddcc-265f-428f-90dd-d31ace945800" style="width: 300px; height: 300px;"></div>
966  * <script type="text/javascript">
967  * (function () {
968  *   var board = JXG.JSXGraph.initBoard('JXGaf27ddcc-265f-428f-90dd-d31ace945800', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
969  *       p1 = board.create('point', [2.0, 2.0]),
970  *       p2 = board.create('point', [1.0, 0.5]),
971  *       p3 = board.create('point', [3.5, 1.0]),
972  *
973  *       a = board.create('minorsector', [p1, p2, p3]);
974  * })();
975  * </script><pre>
976  *
977  * @example
978  * var A = board.create('point', [3, -2]),
979  *     B = board.create('point', [-2, -2]),
980  *     C = board.create('point', [0, 4]);
981  *
982  * var angle = board.create('minorsector', [B, A, C], {
983  *         strokeWidth: 0,
984  *         arc: {
985  *         	visible: true,
986  *         	strokeWidth: 3,
987  *           lastArrow: {size: 4},
988  *           firstArrow: {size: 4}
989  *         }
990  *       });
991  * //angle.arc.setAttribute({firstArrow: false});
992  * angle.arc.setAttribute({lastArrow: false});
993  *
994  *
995  * </pre><div id="JXGdddf3c8f-4b0c-4268-8171-8fcd30e71f60" class="jxgbox" style="width: 300px; height: 300px;"></div>
996  * <script type="text/javascript">
997  *     (function() {
998  *         var board = JXG.JSXGraph.initBoard('JXGdddf3c8f-4b0c-4268-8171-8fcd30e71f60',
999  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1000  *     var A = board.create('point', [3, -2]),
1001  *         B = board.create('point', [-2, -2]),
1002  *         C = board.create('point', [0, 4]);
1003  *
1004  *     var angle = board.create('minorsector', [B, A, C], {
1005  *             strokeWidth: 0,
1006  *             arc: {
1007  *             	visible: true,
1008  *             	strokeWidth: 3,
1009  *               lastArrow: {size: 4},
1010  *               firstArrow: {size: 4}
1011  *             }
1012  *           });
1013  *     //angle.arc.setAttribute({firstArrow: false});
1014  *     angle.arc.setAttribute({lastArrow: false});
1015  *
1016  *
1017  *     })();
1018  *
1019  * </script><pre>
1020  *
1021  */
1022 JXG.createMinorSector = function (board, parents, attributes) {
1023     attributes.selection = "minor";
1024     return JXG.createSector(board, parents, attributes);
1025 };
1026 
1027 JXG.registerElement("minorsector", JXG.createMinorSector);
1028 
1029 /**
1030  * @class A major sector is a sector of a circle having measure greater than or equal to
1031  * 180 degrees (pi radians). It is defined by a center, one point that
1032  * defines the radius, and a third point that defines the angle of the sector.
1033  * @pseudo
1034  * @name MajorSector
1035  * @augments Curve
1036  * @constructor
1037  * @type JXG.Curve
1038  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
1039  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Major sector is a sector of a circle around p1 having measure greater than or equal to
1040  * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
1041  * @example
1042  * // Create an arc out of three free points
1043  * var p1 = board.create('point', [2.0, 2.0]);
1044  * var p2 = board.create('point', [1.0, 0.5]);
1045  * var p3 = board.create('point', [3.5, 1.0]);
1046  *
1047  * var a = board.create('majorsector', [p1, p2, p3]);
1048  * </pre><div class="jxgbox" id="JXG83c6561f-7561-4047-b98d-036248a00932" style="width: 300px; height: 300px;"></div>
1049  * <script type="text/javascript">
1050  * (function () {
1051  *   var board = JXG.JSXGraph.initBoard('JXG83c6561f-7561-4047-b98d-036248a00932', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
1052  *       p1 = board.create('point', [2.0, 2.0]),
1053  *       p2 = board.create('point', [1.0, 0.5]),
1054  *       p3 = board.create('point', [3.5, 1.0]),
1055  *
1056  *       a = board.create('majorsector', [p1, p2, p3]);
1057  * })();
1058  * </script><pre>
1059  */
1060 JXG.createMajorSector = function (board, parents, attributes) {
1061     attributes.selection = "major";
1062     return JXG.createSector(board, parents, attributes);
1063 };
1064 
1065 JXG.registerElement("majorsector", JXG.createMajorSector);
1066 
1067 /**
1068  * @class The angle element is used to denote an angle defined by three points. Visually it is just a {@link Sector}
1069  * element with a radius not defined by the parent elements but by an attribute <tt>radius</tt>. As opposed to the sector,
1070  * an angle has two angle points and no radius point.
1071  * Sector is displayed if type=="sector".
1072  * If type=="square", instead of a sector a parallelogram is displayed.
1073  * In case of type=="auto", a square is displayed if the angle is near orthogonal.
1074  * <p>
1075  * If no name is provided the angle label is automatically set to a lower greek letter. If no label should be displayed use
1076  * the attribute <tt>withLabel:false</tt>.
1077  *
1078  * @pseudo
1079  * @name Angle
1080  * @augments Sector
1081  * @constructor
1082  * @type Sector
1083  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
1084  * First possibility of input parameters are:
1085  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 An angle is always drawn counterclockwise from <tt>p1</tt> to
1086  * <tt>p3</tt> around <tt>p2</tt>.
1087  *
1088  * Second possibility of input parameters are:
1089  * @param {JXG.Line_JXG.Line_array|number_array|number} line, line2, coords1 or direction1, coords2 or direction2, radius The angle is defined by two lines.
1090  * The two legs which define the angle are given by two coordinate arrays.
1091  * The points given by these coordinate arrays are projected initially (i.e. only once) onto the two lines.
1092  * The other possibility is to supply directions (+/- 1).
1093  *
1094  * @example
1095  * // Create an angle out of three free points
1096  * var p1 = board.create('point', [5.0, 3.0]),
1097  *     p2 = board.create('point', [1.0, 0.5]),
1098  *     p3 = board.create('point', [1.5, 5.0]),
1099  *
1100  *     a = board.create('angle', [p1, p2, p3]),
1101  *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
1102  * </pre><div class="jxgbox" id="JXGa34151f9-bb26-480a-8d6e-9b8cbf789ae5" style="width: 300px; height: 300px;"></div>
1103  * <script type="text/javascript">
1104  * (function () {
1105  *   var board = JXG.JSXGraph.initBoard('JXGa34151f9-bb26-480a-8d6e-9b8cbf789ae5', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
1106  *     p1 = board.create('point', [5.0, 3.0]),
1107  *     p2 = board.create('point', [1.0, 0.5]),
1108  *     p3 = board.create('point', [1.5, 5.0]),
1109  *
1110  *     a = board.create('angle', [p1, p2, p3]),
1111  *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
1112  * })();
1113  * </script><pre>
1114  *
1115  * @example
1116  * // Create an angle out of two lines and two directions
1117  * var p1 = board.create('point', [-1, 4]),
1118  *  p2 = board.create('point', [4, 1]),
1119  *  q1 = board.create('point', [-2, -3]),
1120  *  q2 = board.create('point', [4,3]),
1121  *
1122  *  li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
1123  *  li2 = board.create('line', [q1,q2], {lastArrow:true}),
1124  *
1125  *  a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }),
1126  *  a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 });
1127  *
1128  *
1129  * </pre><div class="jxgbox" id="JXG3a667ddd-63dc-4594-b5f1-afac969b371f" style="width: 300px; height: 300px;"></div>
1130  * <script type="text/javascript">
1131  * (function () {
1132  *   var board = JXG.JSXGraph.initBoard('JXG3a667ddd-63dc-4594-b5f1-afac969b371f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
1133  *     p1 = board.create('point', [-1, 4]),
1134  *     p2 = board.create('point', [4, 1]),
1135  *     q1 = board.create('point', [-2, -3]),
1136  *     q2 = board.create('point', [4,3]),
1137  *
1138  *     li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
1139  *     li2 = board.create('line', [q1,q2], {lastArrow:true}),
1140  *
1141  *     a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }),
1142  *     a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 });
1143  * })();
1144  * </script><pre>
1145  *
1146  *
1147  * @example
1148  * // Display the angle value instead of the name
1149  * var p1 = board.create('point', [0,2]);
1150  * var p2 = board.create('point', [0,0]);
1151  * var p3 = board.create('point', [-2,0.2]);
1152  *
1153  * var a = board.create('angle', [p1, p2, p3], {
1154  * 	 radius: 1,
1155  *   name: function() {
1156  *   	return JXG.Math.Geometry.trueAngle(p1, p2, p3).toFixed(1) + '°';
1157  *   }});
1158  *
1159  * </pre><div id="JXGc813f601-8dd3-4030-9892-25c6d8671512" class="jxgbox" style="width: 300px; height: 300px;"></div>
1160  * <script type="text/javascript">
1161  *     (function() {
1162  *         var board = JXG.JSXGraph.initBoard('JXGc813f601-8dd3-4030-9892-25c6d8671512',
1163  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1164  *
1165  *     var p1 = board.create('point', [0,2]);
1166  *     var p2 = board.create('point', [0,0]);
1167  *     var p3 = board.create('point', [-2,0.2]);
1168  *
1169  *     var a = board.create('angle', [p1, p2, p3], {
1170  *     	radius: 1,
1171  *       name: function() {
1172  *       	return JXG.Math.Geometry.trueAngle(p1, p2, p3).toFixed(1) + '°';
1173  *       }});
1174  *
1175  *     })();
1176  *
1177  * </script><pre>
1178  *
1179  *
1180  * @example
1181  * // Apply a transformation to an angle.
1182  * var t = board.create('transform', [2, 1.5], {type: 'scale'});
1183  * var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]);
1184  * var an2 = board.create('curve', [an1, t]);
1185  *
1186  * </pre><div id="JXG4c8d9ed8-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
1187  * <script type="text/javascript">
1188  *     (function() {
1189  *         var board = JXG.JSXGraph.initBoard('JXG4c8d9ed8-6339-11e8-9fb9-901b0e1b8723',
1190  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1191  *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
1192  *     var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]);
1193  *     var an2 = board.create('curve', [an1, t]);
1194  *
1195  *     })();
1196  *
1197  * </script><pre>
1198  *
1199  */
1200 JXG.createAngle = function (board, parents, attributes) {
1201     var el,
1202         radius, attr, attrsub,
1203         i, points,
1204         type = "invalid";
1205 
1206     // Two lines or three points?
1207     if (
1208         parents[0].elementClass === Const.OBJECT_CLASS_LINE &&
1209         parents[1].elementClass === Const.OBJECT_CLASS_LINE &&
1210         (Type.isArray(parents[2]) || Type.isNumber(parents[2])) &&
1211         (Type.isArray(parents[3]) || Type.isNumber(parents[3]))
1212     ) {
1213         type = "2lines";
1214     } else {
1215         attr = {
1216             name: ''
1217         };
1218         points = Type.providePoints(board, parents, attr, "point");
1219         if (points === false) {
1220             throw new Error(
1221                 "JSXGraph: Can't create angle with parent types '" +
1222                     typeof parents[0] +
1223                     "' and '" +
1224                     typeof parents[1] +
1225                     "' and '" +
1226                     typeof parents[2] +
1227                     "'."
1228             );
1229         }
1230         type = "3points";
1231     }
1232 
1233     attr = Type.copyAttributes(attributes, board.options, "angle");
1234 
1235     //  If empty, create a new name
1236     if (!Type.exists(attr.name) /*|| attr.name === ""*/) {
1237         attr.name = board.generateName({ type: Const.OBJECT_TYPE_ANGLE });
1238     }
1239 
1240     if (Type.exists(attr.radius)) {
1241         radius = attr.radius;
1242     } else {
1243         radius = 0;
1244     }
1245 
1246     board.suspendUpdate(); // Necessary for immediate availability of radius.
1247     if (type === "2lines") {
1248         parents.push(radius);
1249         el = board.create("sector", parents, attr);
1250         /**
1251          * @class
1252          * @ignore
1253          */
1254         el.updateDataArraySector = el.updateDataArray;
1255 
1256         // TODO
1257         /**
1258          * @class
1259          * @ignore
1260          */
1261         el.setAngle = function (val) {};
1262         /**
1263          * @class
1264          * @ignore
1265          */
1266         el.free = function (val) {};
1267     } else {
1268         el = board.create("sector", [points[1], points[0], points[2]], attr);
1269         el.arc.visProp.priv = true;
1270 
1271         /**
1272          * The point defining the radius of the angle element.
1273          * Alias for {@link Sector#radiuspoint}.
1274          * @type JXG.Point
1275          * @name point
1276          * @memberOf Angle.prototype
1277          *
1278          */
1279         el.point = el.point2 = el.radiuspoint = points[0];
1280 
1281         /**
1282          * Helper point for angles of type 'square'.
1283          * @type JXG.Point
1284          * @name pointsquare
1285          * @memberOf Angle.prototype
1286          */
1287         el.pointsquare = el.point3 = el.anglepoint = points[2];
1288 
1289         /**
1290          * @ignore
1291          */
1292         el.Radius = function () {
1293             // Set the angle radius, also @see @link Sector#autoRadius
1294             var r = Type.evaluate(radius);
1295             if (r === "auto") {
1296                 return el.autoRadius();
1297             }
1298             return r;
1299         };
1300 
1301         /**
1302          * @class
1303          * @ignore
1304          */
1305         el.updateDataArraySector = function () {
1306             var A = this.point2,
1307                 B = this.point1,
1308                 C = this.point3,
1309                 r = this.Radius(),
1310                 d = B.Dist(A),
1311                 ar,
1312                 phi,
1313                 sgn = 1,
1314                 vp_s = Type.evaluate(this.visProp.selection);
1315 
1316             phi = Geometry.rad(A, B, C);
1317             if ((vp_s === "minor" && phi > Math.PI) || (vp_s === "major" && phi < Math.PI)) {
1318                 sgn = -1;
1319             }
1320 
1321             A = A.coords.usrCoords;
1322             B = B.coords.usrCoords;
1323             C = C.coords.usrCoords;
1324 
1325             A = [1, B[1] + ((A[1] - B[1]) * r) / d, B[2] + ((A[2] - B[2]) * r) / d];
1326             C = [1, B[1] + ((C[1] - B[1]) * r) / d, B[2] + ((C[2] - B[2]) * r) / d];
1327 
1328             ar = Geometry.bezierArc(A, B, C, true, sgn);
1329 
1330             this.dataX = ar[0];
1331             this.dataY = ar[1];
1332             this.bezierDegree = 3;
1333         };
1334 
1335         /**
1336          * Set an angle to a prescribed value given in radians.
1337          * This is only possible if the third point of the angle, i.e.
1338          * the anglepoint is a free point.
1339          * Removing the constraint again is done by calling "angle.free()".
1340          *
1341          * Changing the angle requires to call the method "free()":
1342          *
1343          * <pre>
1344          * angle.setAngle(Math.PI / 6);
1345          * // ...
1346          * angle.free().setAngle(Math.PI / 4);
1347          * </pre>
1348          *
1349          * @name setAngle
1350          * @memberof Angle.prototype
1351          * @function
1352          * @param {Number|Function} val Number or Function which returns the size of the angle in Radians
1353          * @returns {Object} Pointer to the angle element..
1354          * @see Angle#free
1355          *
1356          * @example
1357          * var p1, p2, p3, c, a, s;
1358          *
1359          * p1 = board.create('point',[0,0]);
1360          * p2 = board.create('point',[5,0]);
1361          * p3 = board.create('point',[0,5]);
1362          *
1363          * c1 = board.create('circle',[p1, p2]);
1364          *
1365          * a = board.create('angle',[p2, p1, p3], {radius:3});
1366          *
1367          * a.setAngle(function() {
1368          *     return Math.PI / 3;
1369          * });
1370          * board.update();
1371          *
1372          * </pre><div id="JXG987c-394f-11e6-af4a-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
1373          * <script type="text/javascript">
1374          *     (function() {
1375          *         var board = JXG.JSXGraph.initBoard('JXG987c-394f-11e6-af4a-901b0e1b8723',
1376          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1377          *     var p1, p2, p3, c, a, s;
1378          *
1379          *     p1 = board.create('point',[0,0]);
1380          *     p2 = board.create('point',[5,0]);
1381          *     p3 = board.create('point',[0,5]);
1382          *
1383          *     c1 = board.create('circle',[p1, p2]);
1384          *
1385          *     a = board.create('angle',[p2, p1, p3], {radius: 3});
1386          *
1387          *     a.setAngle(function() {
1388          *         return Math.PI / 3;
1389          *     });
1390          *     board.update();
1391          *
1392          *     })();
1393          *
1394          * </script><pre>
1395          *
1396          * @example
1397          * var p1, p2, p3, c, a, s;
1398          *
1399          * p1 = board.create('point',[0,0]);
1400          * p2 = board.create('point',[5,0]);
1401          * p3 = board.create('point',[0,5]);
1402          *
1403          * c1 = board.create('circle',[p1, p2]);
1404          *
1405          * a = board.create('angle',[p2, p1, p3], {radius:3});
1406          * s = board.create('slider',[[-2,1], [2,1], [0, Math.PI*0.5, 2*Math.PI]]);
1407          *
1408          * a.setAngle(function() {
1409          *     return s.Value();
1410          * });
1411          * board.update();
1412          *
1413          * </pre><div id="JXG99957b1c-394f-11e6-af4a-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
1414          * <script type="text/javascript">
1415          *     (function() {
1416          *         var board = JXG.JSXGraph.initBoard('JXG99957b1c-394f-11e6-af4a-901b0e1b8723',
1417          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1418          *     var p1, p2, p3, c, a, s;
1419          *
1420          *     p1 = board.create('point',[0,0]);
1421          *     p2 = board.create('point',[5,0]);
1422          *     p3 = board.create('point',[0,5]);
1423          *
1424          *     c1 = board.create('circle',[p1, p2]);
1425          *
1426          *     a = board.create('angle',[p2, p1, p3], {radius: 3});
1427          *     s = board.create('slider',[[-2,1], [2,1], [0, Math.PI*0.5, 2*Math.PI]]);
1428          *
1429          *     a.setAngle(function() {
1430          *         return s.Value();
1431          *     });
1432          *     board.update();
1433          *
1434          *     })();
1435          *
1436          * </script><pre>
1437          *
1438          */
1439         el.setAngle = function (val) {
1440             var t1, t2,
1441                 val2,
1442                 p = this.anglepoint,
1443                 q = this.radiuspoint;
1444 
1445             if (p.draggable()) {
1446                 t1 = this.board.create("transform", [val, this.center], {
1447                     type: "rotate"
1448                 });
1449                 p.addTransform(q, t1);
1450                 // Immediately apply the transformation.
1451                 // This prevents that jumping elements can be watched.
1452                 t1.update();
1453                 p.moveTo(Mat.matVecMult(t1.matrix, q.coords.usrCoords));
1454 
1455                 if (Type.isFunction(val)) {
1456                     /**
1457                      * @ignore
1458                      */
1459                     val2 = function () {
1460                         return Math.PI * 2 - val();
1461                     };
1462                 } else {
1463                     /**
1464                      * @ignore
1465                      */
1466                     val2 = function () {
1467                         return Math.PI * 2 - val;
1468                     };
1469                 }
1470                 t2 = this.board.create("transform", [val2, this.center], {
1471                     type: "rotate"
1472                 });
1473                 p.coords.on("update", function () {
1474                     t2.update();
1475                     q.moveTo(Mat.matVecMult(t2.matrix, p.coords.usrCoords));
1476                 });
1477 
1478                 p.setParents(q);
1479 
1480                 this.hasFixedAngle = true;
1481             }
1482             return this;
1483         };
1484 
1485         /**
1486          * Frees an angle from a prescribed value. This is only relevant if the angle size has been set by
1487          * "setAngle()" previously. The anglepoint is set to a free point.
1488          * @name free
1489          * @function
1490          * @memberof Angle.prototype
1491          * @returns {Object} Pointer to the angle element..
1492          * @see Angle#setAngle
1493          */
1494         el.free = function () {
1495             var p = this.anglepoint;
1496 
1497             if (p.transformations.length > 0) {
1498                 p.transformations.pop();
1499                 p.isDraggable = true;
1500                 p.parents = [];
1501 
1502                 p.coords.off("update");
1503             }
1504 
1505             this.hasFixedAngle = false;
1506 
1507             return this;
1508         };
1509 
1510         el.setParents(points); // Important: This overwrites the parents order in underlying sector
1511     } // end '3points'
1512 
1513     // GEONExT compatible labels.
1514     if (Type.exists(el.visProp.text)) {
1515         el.label.setText(Type.evaluate(el.visProp.text));
1516     }
1517 
1518     el.elType = "angle";
1519     el.type = Const.OBJECT_TYPE_ANGLE;
1520     el.subs = {};
1521 
1522     /**
1523      * @class
1524      * @ignore
1525      */
1526     el.updateDataArraySquare = function () {
1527         var A, B, C,
1528             d1, d2, v, l1, l2,
1529             r = this.Radius();
1530 
1531         if (type === "2lines") {
1532             // This is necessary to update this.point1, this.point2, this.point3.
1533             this.updateDataArraySector();
1534         }
1535 
1536         A = this.point2;
1537         B = this.point1;
1538         C = this.point3;
1539 
1540         A = A.coords.usrCoords;
1541         B = B.coords.usrCoords;
1542         C = C.coords.usrCoords;
1543 
1544         d1 = Geometry.distance(A, B, 3);
1545         d2 = Geometry.distance(C, B, 3);
1546 
1547         // In case of type=='2lines' this is redundant, because r == d1 == d2
1548         A = [1, B[1] + ((A[1] - B[1]) * r) / d1, B[2] + ((A[2] - B[2]) * r) / d1];
1549         C = [1, B[1] + ((C[1] - B[1]) * r) / d2, B[2] + ((C[2] - B[2]) * r) / d2];
1550 
1551         v = Mat.crossProduct(C, B);
1552         l1 = [-A[1] * v[1] - A[2] * v[2], A[0] * v[1], A[0] * v[2]];
1553         v = Mat.crossProduct(A, B);
1554         l2 = [-C[1] * v[1] - C[2] * v[2], C[0] * v[1], C[0] * v[2]];
1555 
1556         v = Mat.crossProduct(l1, l2);
1557         v[1] /= v[0];
1558         v[2] /= v[0];
1559 
1560         this.dataX = [B[1], A[1], v[1], C[1], B[1]];
1561         this.dataY = [B[2], A[2], v[2], C[2], B[2]];
1562 
1563         this.bezierDegree = 1;
1564     };
1565 
1566     /**
1567      * @class
1568      * @ignore
1569      */
1570     el.updateDataArrayNone = function () {
1571         this.dataX = [NaN];
1572         this.dataY = [NaN];
1573         this.bezierDegree = 1;
1574     };
1575 
1576     /**
1577      * @class
1578      * @ignore
1579      */
1580     el.updateDataArray = function () {
1581         var type = Type.evaluate(this.visProp.type),
1582             deg = Geometry.trueAngle(this.point2, this.point1, this.point3),
1583             vp_s = Type.evaluate(this.visProp.selection);
1584 
1585         if ((vp_s === "minor" && deg > 180.0) || (vp_s === "major" && deg < 180.0)) {
1586             deg = 360.0 - deg;
1587         }
1588 
1589         if (Math.abs(deg - 90.0) < Type.evaluate(this.visProp.orthosensitivity) + Mat.eps) {
1590             type = Type.evaluate(this.visProp.orthotype);
1591         }
1592 
1593         if (type === "none") {
1594             this.updateDataArrayNone();
1595         } else if (type === "square") {
1596             this.updateDataArraySquare();
1597         } else if (type === "sector") {
1598             this.updateDataArraySector();
1599         } else if (type === "sectordot") {
1600             this.updateDataArraySector();
1601             if (!this.dot.visProp.visible) {
1602                 this.dot.setAttribute({ visible: true });
1603             }
1604         }
1605 
1606         if (!this.visProp.visible || (type !== "sectordot" && this.dot.visProp.visible)) {
1607             this.dot.setAttribute({ visible: false });
1608         }
1609     };
1610 
1611     attrsub = Type.copyAttributes(attributes, board.options, "angle", "dot");
1612     /**
1613      * Indicates a right angle. Invisible by default, use <tt>dot.visible: true</tt> to show.
1614      * Though this dot indicates a right angle, it can be visible even if the angle is not a right
1615      * one.
1616      * @type JXG.Point
1617      * @name dot
1618      * @memberOf Angle.prototype
1619      */
1620     el.dot = board.create(
1621         "point",
1622         [
1623             function () {
1624                 var A, B, r, d, a2, co, si, mat, vp_s;
1625 
1626                 if (Type.exists(el.dot) && !el.dot.visProp.visible) {
1627                     return [0, 0];
1628                 }
1629 
1630                 A = el.point2.coords.usrCoords;
1631                 B = el.point1.coords.usrCoords;
1632                 r = el.Radius();
1633                 d = Geometry.distance(A, B, 3);
1634                 a2 = Geometry.rad(el.point2, el.point1, el.point3);
1635 
1636                 vp_s = Type.evaluate(el.visProp.selection);
1637                 if ((vp_s === "minor" && a2 > Math.PI) || (vp_s === "major" && a2 < Math.PI)) {
1638                     a2 = -(2 * Math.PI - a2);
1639                 }
1640                 a2 *= 0.5;
1641 
1642                 co = Math.cos(a2);
1643                 si = Math.sin(a2);
1644 
1645                 A = [1, B[1] + ((A[1] - B[1]) * r) / d, B[2] + ((A[2] - B[2]) * r) / d];
1646 
1647                 mat = [
1648                     [1, 0, 0],
1649                     [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5],
1650                     [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5, co * 0.5]
1651                 ];
1652                 return Mat.matVecMult(mat, A);
1653             }
1654         ],
1655         attrsub
1656     );
1657 
1658     el.dot.dump = false;
1659     el.subs.dot = el.dot;
1660 
1661     if (type === "2lines") {
1662         for (i = 0; i < 2; i++) {
1663             board.select(parents[i]).addChild(el.dot);
1664         }
1665     } else {
1666         for (i = 0; i < 3; i++) {
1667             board.select(points[i]).addChild(el.dot);
1668         }
1669     }
1670     board.unsuspendUpdate();
1671 
1672     /**
1673      * Returns the value of the angle.
1674      * @memberOf Angle.prototype
1675      * @name Value
1676      * @function
1677      * @param {String} [unit='length'] Unit of the returned values. Possible units are
1678      * <ul>
1679      * <li> 'radians' (default): angle value in radians
1680      * <li> 'degrees': angle value in degrees
1681      * <li> 'semicircle': angle value in radians as a multiple of π, e.g. if the angle is 1.5π, 1.5 will be returned.
1682      * <li> 'circle': angle value in radians as a multiple of 2π
1683      * <li> 'length': length of the arc line of the angle
1684      * </ul>
1685      * It is sufficient to supply the first three characters of the unit, e.g. 'len'.
1686      * @returns {Number} angle value in various units.
1687      * @see Sector#L
1688      * @see Arc#Value
1689      * @example
1690      * var A, B, C, ang,
1691      *     r = 0.5;
1692      * A = board.create("point", [3, 0]);
1693      * B = board.create("point", [0, 0]);
1694      * C = board.create("point", [2, 2]);
1695      * ang = board.create("angle", [A, B, C], {radius: r});
1696      *
1697      * console.log(ang.Value());
1698      * // Output Math.PI * 0.25
1699      *
1700      * console.log(ang.Value('radian'));
1701      * // Output Math.PI * 0.25
1702      *
1703      * console.log(ang.Value('degree');
1704      * // Output 45
1705      *
1706      * console.log(ang.Value('semicircle'));
1707      * // Output 0.25
1708      *
1709      * console.log(ang.Value('circle'));
1710      * // Output 0.125
1711      *
1712      * console.log(ang.Value('length'));
1713      * // Output r * Math.PI * 0.25
1714      *
1715      * console.log(ang.L());
1716      * // Output r * Math.PI * 0.25
1717      *
1718      */
1719     el.Value = function(unit) {
1720         unit = unit || 'radians';
1721         if (unit === '') {
1722             unit = 'radians';
1723         }
1724         return el.arc.Value(unit);
1725     };
1726 
1727     // documented in GeometryElement
1728     /**
1729      * @class
1730      * @ignore
1731      */
1732     el.getLabelAnchor = function () {
1733         var vec,
1734             dx = 12,
1735             A, B, r, d, a2, co, si, mat,
1736             vp_s = Type.evaluate(el.visProp.selection),
1737             l_vp = this.label ? this.label.visProp : this.visProp.label;
1738 
1739         // If this is uncommented, the angle label can not be dragged
1740         //if (Type.exists(this.label)) {
1741         //    this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
1742         //}
1743 
1744         if (Type.exists(this.label) && Type.exists(this.label.visProp.fontsize)) {
1745             dx = Type.evaluate(this.label.visProp.fontsize);
1746         }
1747         dx /= this.board.unitX;
1748 
1749         A = el.point2.coords.usrCoords;
1750         B = el.point1.coords.usrCoords;
1751         r = el.Radius();
1752         d = Geometry.distance(A, B, 3);
1753         a2 = Geometry.rad(el.point2, el.point1, el.point3);
1754         if ((vp_s === "minor" && a2 > Math.PI) || (vp_s === "major" && a2 < Math.PI)) {
1755             a2 = -(2 * Math.PI - a2);
1756         }
1757         a2 *= 0.5;
1758         co = Math.cos(a2);
1759         si = Math.sin(a2);
1760 
1761         A = [1, B[1] + ((A[1] - B[1]) * r) / d, B[2] + ((A[2] - B[2]) * r) / d];
1762 
1763         mat = [
1764             [1, 0, 0],
1765             [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5],
1766             [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5, co * 0.5]
1767         ];
1768         vec = Mat.matVecMult(mat, A);
1769         vec[1] /= vec[0];
1770         vec[2] /= vec[0];
1771         vec[0] /= vec[0];
1772 
1773         d = Geometry.distance(vec, B, 3);
1774         vec = [
1775             vec[0],
1776             B[1] + ((vec[1] - B[1]) * (r + dx)) / d,
1777             B[2] + ((vec[2] - B[2]) * (r + dx)) / d
1778         ];
1779 
1780         l_vp.position = Geometry.calcLabelQuadrant(Geometry.rad([1, 0], [0, 0], vec));
1781 
1782         return new Coords(Const.COORDS_BY_USER, vec, this.board);
1783     };
1784 
1785     el.methodMap = Type.deepCopy(el.methodMap, {
1786         setAngle: "setAngle",
1787         Value: "Value",
1788         free: "free"
1789     });
1790 
1791     return el;
1792 };
1793 
1794 JXG.registerElement("angle", JXG.createAngle);
1795 
1796 /**
1797  * @class A non-reflex angle is the acute or obtuse instance of an angle.
1798  * It is defined by a center, one point that
1799  * defines the radius, and a third point that defines the angle of the sector.
1800  * @pseudo
1801  * @name NonReflexAngle
1802  * @augments Angle
1803  * @constructor
1804  * @type Sector
1805  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
1806  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor sector is a sector of a circle around p1 having measure less than or equal to
1807  * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
1808  * @example
1809  * // Create a non-reflex angle out of three free points
1810  * var p1 = board.create('point', [5.0, 3.0]),
1811  *     p2 = board.create('point', [1.0, 0.5]),
1812  *     p3 = board.create('point', [1.5, 5.0]),
1813  *
1814  *     a = board.create('nonreflexangle', [p1, p2, p3], {radius: 2}),
1815  *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
1816  * </pre><div class="jxgbox" id="JXGd0ab6d6b-63a7-48b2-8749-b02bb5e744f9" style="width: 300px; height: 300px;"></div>
1817  * <script type="text/javascript">
1818  * (function () {
1819  *   var board = JXG.JSXGraph.initBoard('JXGd0ab6d6b-63a7-48b2-8749-b02bb5e744f9', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
1820  *     p1 = board.create('point', [5.0, 3.0]),
1821  *     p2 = board.create('point', [1.0, 0.5]),
1822  *     p3 = board.create('point', [1.5, 5.0]),
1823  *
1824  *     a = board.create('nonreflexangle', [p1, p2, p3], {radius: 2}),
1825  *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
1826  * })();
1827  * </script><pre>
1828  */
1829 JXG.createNonreflexAngle = function (board, parents, attributes) {
1830     var el;
1831 
1832     attributes.selection = "minor";
1833     attributes = Type.copyAttributes(attributes, board.options, 'nonreflexangle');
1834     el = JXG.createAngle(board, parents, attributes);
1835 
1836     // Documented in createAngle
1837     el.Value = function (unit) {
1838         var rad = Geometry.rad(this.point2, this.point1, this.point3);
1839         unit = unit || 'radians';
1840         if (unit === '') {
1841             unit = 'radians';
1842         }
1843         rad = (rad < Math.PI) ? rad : 2.0 * Math.PI - rad;
1844 
1845         return this.arc.Value(unit, rad);
1846     };
1847     return el;
1848 };
1849 
1850 JXG.registerElement("nonreflexangle", JXG.createNonreflexAngle);
1851 
1852 /**
1853  * @class A reflex angle is the neither acute nor obtuse instance of an angle.
1854  * It is defined by a center, one point that
1855  * defines the radius, and a third point that defines the angle of the sector.
1856  * @pseudo
1857  * @name ReflexAngle
1858  * @augments Angle
1859  * @constructor
1860  * @type Sector
1861  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
1862  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor sector is a sector of a circle around p1 having measure less than or equal to
1863  * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
1864  * @example
1865  * // Create a non-reflex angle out of three free points
1866  * var p1 = board.create('point', [5.0, 3.0]),
1867  *     p2 = board.create('point', [1.0, 0.5]),
1868  *     p3 = board.create('point', [1.5, 5.0]),
1869  *
1870  *     a = board.create('reflexangle', [p1, p2, p3], {radius: 2}),
1871  *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
1872  * </pre><div class="jxgbox" id="JXGf2a577f2-553d-4f9f-a895-2d6d4b8c60e8" style="width: 300px; height: 300px;"></div>
1873  * <script type="text/javascript">
1874  * (function () {
1875  * var board = JXG.JSXGraph.initBoard('JXGf2a577f2-553d-4f9f-a895-2d6d4b8c60e8', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
1876  *     p1 = board.create('point', [5.0, 3.0]),
1877  *     p2 = board.create('point', [1.0, 0.5]),
1878  *     p3 = board.create('point', [1.5, 5.0]),
1879  *
1880  *     a = board.create('reflexangle', [p1, p2, p3], {radius: 2}),
1881  *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
1882  * })();
1883  * </script><pre>
1884  */
1885 JXG.createReflexAngle = function (board, parents, attributes) {
1886     var el;
1887 
1888     attributes.selection = "major";
1889     attributes = Type.copyAttributes(attributes, board.options, 'reflexangle');
1890     el = JXG.createAngle(board, parents, attributes);
1891 
1892     // Documented in createAngle
1893     el.Value = function (unit) {
1894         var rad = Geometry.rad(this.point2, this.point1, this.point3);
1895         unit = unit || 'radians';
1896         if (unit === '') {
1897             unit = 'radians';
1898         }
1899         rad = (rad >= Math.PI) ? rad : 2.0 * Math.PI - rad;
1900 
1901         return this.arc.Value(unit, rad);
1902     };
1903 
1904     return el;
1905 };
1906 
1907 JXG.registerElement("reflexangle", JXG.createReflexAngle);
1908