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