      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