      1 /*
      2     Copyright 2008-2011
      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: you can redistribute it and/or modify
     13     it under the terms of the GNU Lesser General Public License as published by
     14     the Free Software Foundation, either version 3 of the License, or
     15     (at your option) any later version.
     16
     17     JSXGraph is distributed in the hope that it will be useful,
     18     but WITHOUT ANY WARRANTY; without even the implied warranty of
     19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     20     GNU Lesser General Public License for more details.
     21
     22     You should have received a copy of the GNU Lesser General Public License
     23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
     24
     25 */
     26
     27 /**
     28  * @class A circular sector is a subarea of the area enclosed by a circle. It is enclosed by two radii and an arc.
     29  * @pseudo
     30  * @name Sector
     31  * @augments JXG.Curve
     32  * @constructor
     33  * @type JXG.Curve
     34  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     35  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A sector is defined by three points: The sector's center <tt>p1</tt>,
     36  * a second point <tt>p2</tt> defining the radius and a third point <tt>p3</tt> defining the angle of the sector. The
     37  * Sector is always drawn counter clockwise from <tt>p2</tt> to <tt>p3</tt>
     38  * @example
     39  * // Create an arc out of three free points
     40  * var p1 = board.create('point', [1.5, 5.0]),
     41  *     p2 = board.create('point', [1.0, 0.5]),
     42  *     p3 = board.create('point', [5.0, 3.0]),
     43  *
     44  *     a = board.create('sector', [p1, p2, p3]);
     45  * </pre><div id="49f59123-f013-4681-bfd9-338b89893156" style="width: 300px; height: 300px;"></div>
     46  * <script type="text/javascript">
     47  * (function () {
     48  *   var board = JXG.JSXGraph.initBoard('49f59123-f013-4681-bfd9-338b89893156', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     49  *     p1 = board.create('point', [1.5, 5.0]),
     50  *     p2 = board.create('point', [1.0, 0.5]),
     51  *     p3 = board.create('point', [5.0, 3.0]),
     52  *
     53  *     a = board.create('sector', [p1, p2, p3]);
     54  * })();
     55  * </script><pre>
     56  */
     57 JXG.createSector = function(board, parents, attributes) {
     58     var el, attr;
     59
     60     // Alles 3 Punkte?
     61     if ( !(JXG.isPoint(parents[0]) && JXG.isPoint(parents[1]) && JXG.isPoint(parents[2]))) {
     62         throw new Error("JSXGraph: Can't create Sector with parent types '" +
     63                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" +
     64                         (typeof parents[2]) + "'.");
     65     }
     66
     67     attr = JXG.copyAttributes(attributes, board.options, 'sector');
     68
     69     el = board.create('curve', [[0], [0]], attr);
     70     el.type = JXG.OBJECT_TYPE_SECTOR;
     71
     72     el.elType = 'sector';
     73     el.parents = [parents[0].id, parents[1].id, parents[2].id];
     74
     75     /**
     76      * Midpoint of the sector.
     77      * @memberOf Sector.prototype
     78      * @name point1
     79      * @type JXG.Point
     80      */
     81     el.point1 = JXG.getReference(board, parents[0]);
     82
     83     /**
     84      * This point together with {@link Sector#point1} defines the radius..
     85      * @memberOf Sector.prototype
     86      * @name point2
     87      * @type JXG.Point
     88      */
     89     el.point2 = JXG.getReference(board, parents[1]);
     90
     91     /**
     92      * Defines the sector's angle.
     93      * @memberOf Sector.prototype
     94      * @name point3
     95      * @type JXG.Point
     96      */
     97     el.point3 = JXG.getReference(board, parents[2]);
     98
     99     /* Add arc as child to defining points */
    100     el.point1.addChild(el);
    101     el.point2.addChild(el);
    102     el.point3.addChild(el);
    103
    104     el.useDirection = attributes['usedirection'];      // useDirection is necessary for circumCircleSectors
    105
    106     /**
    107      * documented in JXG.Curve
    108      * @ignore
    109      */
    110     el.updateDataArray = function() {
    111         var A = this.point2,
    112             B = this.point1,
    113             C = this.point3,
    114             beta, co, si, matrix,
    115             phi = JXG.Math.Geometry.rad(A,B,C),
    116             i,
    117             n = Math.ceil(phi/Math.PI*90)+1,
    118             delta = phi/n,
    119             x = B.X(),
    120             y = B.Y(),
    121             v,
    122             det, p0c, p1c, p2c;
    123
    124         if (this.useDirection) {  // This is true for circumCircleArcs. In that case there is
    125                                   // a fourth parent element: [midpoint, point1, point3, point2]
    126             p0c = parents[1].coords.usrCoords,
    127             p1c = parents[3].coords.usrCoords,
    128             p2c = parents[2].coords.usrCoords;
    129             det = (p0c[1]-p2c[1])*(p0c[2]-p1c[2]) - (p0c[2]-p2c[2])*(p0c[1]-p1c[1]);
    130             if(det < 0) {
    131                 this.point2 = parents[1];
    132                 this.point3 = parents[2];
    133             }
    134             else {
    135                 this.point2 = parents[2];
    136                 this.point3 = parents[1];
    137             }
    138         }
    139         this.dataX = [B.X(),A.X()];
    140         this.dataY = [B.Y(),A.Y()];
    141         for (beta=delta,i=1; i<=n; i++, beta+=delta) {
    142             co = Math.cos(beta);
    143             si = Math.sin(beta);
    144             matrix = [[1,            0,   0],
    145                       [x*(1-co)+y*si,co,-si],
    146                       [y*(1-co)-x*si,si, co]];
    147             v = JXG.Math.matVecMult(matrix,A.coords.usrCoords);
    148             this.dataX.push(v[1]/v[0]);
    149             this.dataY.push(v[2]/v[0]);
    150         }
    151         this.dataX.push(B.X());
    152         this.dataY.push(B.Y());
    153     };
    154
    155     /**
    156      * Returns the radius of the sector.
    157      * @memberOf Sector.prototype
    158      * @name Radius
    159      * @function
    160      * @returns {Number} The distance between {@link Sector#point1} and {@link Sector#point2}.
    161      */
    162     el.Radius = function() {
    163         return this.point2.Dist(this.point1);
    164     };
    165
    166     /**
    167      * deprecated
    168      * @ignore
    169      */
    170     el.getRadius = function() {
    171         return this.Radius();
    172     };
    173
    174     /**
    175      * Checks whether (x,y) is within the area defined by the sector.
    176      * @memberOf Sector.prototype
    177      * @name hasPointSector
    178      * @function
    179      * @param {Number} x Coordinate in x direction, screen coordinates.
    180      * @param {Number} y Coordinate in y direction, screen coordinates.
    181      * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise.
    182      */
    183     el.hasPointSector = function (x, y) {
    184         var checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board),
    185             r = this.Radius(),
    186             dist = this.point1.coords.distance(JXG.COORDS_BY_USER,checkPoint),
    187             has = (dist<r),
    188             angle;
    189
    190         if(has) {
    191             angle = JXG.Math.Geometry.rad(this.point2,this.point1,checkPoint.usrCoords.slice(1));
    192             if (angle>JXG.Math.Geometry.rad(this.point2,this.point1,this.point3)) { has = false; }
    193         }
    194         return has;
    195     };
    196
    197     /**
    198      * documented in GeometryElement
    199      * @ignore
    200      */
    201     el.getTextAnchor = function() {
    202         return this.point1.coords;
    203     };
    204
    205     /**
    206      * documented in GeometryElement
    207      * @ignore
    208      */
    209     el.getLabelAnchor = function() {
    210         var angle = JXG.Math.Geometry.rad(this.point2, this.point1, this.point3),
    211             dx = 10/(this.board.unitX),
    212             dy = 10/(this.board.unitY),
    213             p2c = this.point2.coords.usrCoords,
    214             pmc = this.point1.coords.usrCoords,
    215             bxminusax = p2c[1] - pmc[1],
    216             byminusay = p2c[2] - pmc[2],
    217             coords, vecx, vecy, len;
    218
    219         if(this.label.content != null) {
    220             this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0],this.board);
    221         }
    222
    223         coords = new JXG.Coords(JXG.COORDS_BY_USER,
    224                         [pmc[1]+ Math.cos(angle*0.5)*bxminusax - Math.sin(angle*0.5)*byminusay,
    225                         pmc[2]+ Math.sin(angle*0.5)*bxminusax + Math.cos(angle*0.5)*byminusay],
    226                         this.board);
    227
    228         vecx = coords.usrCoords[1] - pmc[1];
    229         vecy = coords.usrCoords[2] - pmc[2];
    230
    231         len = Math.sqrt(vecx*vecx+vecy*vecy);
    232         vecx = vecx*(len+dx)/len;
    233         vecy = vecy*(len+dy)/len;
    234
    235         return new JXG.Coords(JXG.COORDS_BY_USER, [pmc[1]+vecx,pmc[2]+vecy],this.board);
    236     };
    237
    238     el.prepareUpdate().update();
    239
    240     return el;
    241 };
    242
    243 JXG.JSXGraph.registerElement('sector', JXG.createSector);
    244
    245
    246 /**
    247  * @class A circumcircle sector is different from a {@link Sector} mostly in the way the parent elements are interpreted.
    248  * At first, the circum centre is determined from the three given points. Then the sector is drawn from <tt>p1</tt> through
    249  * <tt>p2</tt> to <tt>p3</tt>.
    250  * @pseudo
    251  * @name Circumcirclesector
    252  * @augments Sector
    253  * @constructor
    254  * @type Sector
    255  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    256  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A circumcircle sector is defined by the circumcircle which is determined
    257  * by these three given points. The circumcircle sector is always drawn from <tt>p1</tt> through <tt>p2</tt> to <tt>p3</tt>.
    258  * @example
    259  * // Create an arc out of three free points
    260  * var p1 = board.create('point', [1.5, 5.0]),
    261  *     p2 = board.create('point', [1.0, 0.5]),
    262  *     p3 = board.create('point', [5.0, 3.0]),
    263  *
    264  *     a = board.create('circumcirclesector', [p1, p2, p3]);
    265  * </pre><div id="695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04" style="width: 300px; height: 300px;"></div>
    266  * <script type="text/javascript">
    267  * (function () {
    268  *   var board = JXG.JSXGraph.initBoard('695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
    269  *     p1 = board.create('point', [1.5, 5.0]),
    270  *     p2 = board.create('point', [1.0, 0.5]),
    271  *     p3 = board.create('point', [5.0, 3.0]),
    272  *
    273  *     a = board.create('circumcirclesector', [p1, p2, p3]);
    274  * })();
    275  * </script><pre>
    276  */
    277  JXG.createCircumcircleSector = function(board, parents, attributes) {
    278     var el, mp, attr;
    279
    280     if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) && (JXG.isPoint(parents[2]))) {
    281         attr = JXG.copyAttributes(attributes, board.options, 'circumcirclesector', 'center');
    282         mp = board.create('circumcenter',[parents[0], parents[1], parents[2]], attr);
    283
    284         mp.dump = false;
    285
    286         attr = JXG.copyAttributes(attributes, board.options, 'circumcirclesector');
    287         el = board.create('sector', [mp,parents[0],parents[2],parents[1]], attr);
    288
    289         el.elType = 'circumcirclesector';
    290         el.parents = [parents[0].id, parents[1].id, parents[2].id];
    291
    292         /**
    293          * Center of the circumcirclesector
    294          * @memberOf CircumcircleSector.prototype
    295          * @name center
    296          * @type Circumcenter
    297          */
    298         el.center = mp;
    299         el.subs = {
    300             center: mp
    301         }
    302     } else {
    303         throw new Error("JSXGraph: Can't create circumcircle sector with parent types '" +
    304                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'.");
    305     }
    306
    307     return el;
    308 };
    309
    310 JXG.JSXGraph.registerElement('circumcirclesector', JXG.createCircumcircleSector);
    311
    312
    313 /**
    314  * @class The angle element is used to denote an angle defined by three points. Visually it is just a {@link Sector}
    315  * element with a radius not defined by the parent elements but by an attribute <tt>radius</tt>. As opposed to the sector,
    316  * an angle has two angle points and no radius point.
    317  * Sector is displayed if type=="sector".
    318  * If type=="square", instead of a sector a parallelogram is displayed.
    319  * In case of type=="auto", a square is displayed if the angle is near orthogonal.
    320  * If no name is provided the angle label is automatically set to a lower greek letter.
    321  * @pseudo
    322  * @name Angle
    323  * @augments Sector
    324  * @constructor
    325  * @type Sector
    326  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    327  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 An angle is always drawn counterclockwise from <tt>p1</tt> to
    328  * <tt>p3</tt> around <tt>p2</tt>.
    329  * @example
    330  * // Create an arc out of three free points
    331  * var p1 = board.create('point', [5.0, 3.0]),
    332  *     p2 = board.create('point', [1.0, 0.5]),
    333  *     p3 = board.create('point', [1.5, 5.0]),
    334  *
    335  *     a = board.create('angle', [p1, p2, p3]);
    336  * </pre><div id="a34151f9-bb26-480a-8d6e-9b8cbf789ae5" style="width: 300px; height: 300px;"></div>
    337  * <script type="text/javascript">
    338  * (function () {
    339  *   var board = JXG.JSXGraph.initBoard('a34151f9-bb26-480a-8d6e-9b8cbf789ae5', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
    340  *     p1 = board.create('point', [5.0, 3.0]),
    341  *     p2 = board.create('point', [1.0, 0.5]),
    342  *     p3 = board.create('point', [1.5, 5.0]),
    343  *
    344  *     a = board.create('angle', [p1, p2, p3]);
    345  * })();
    346  * </script><pre>
    347  */
    348 JXG.createAngle = function(board, parents, attributes) {
    349     var el, p, q, text, attr, attrsub,
    350         possibleNames = ['α', 'β', 'γ', 'δ', 'ε', 'ζ', '&eta', 'θ',
    351                                 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ',
    352                                 'ς', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω'],
    353         i = 0, j, x, pre, post, found, dot;
    354
    355     // Test if three points are given
    356     if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) && (JXG.isPoint(parents[2]))) {
    357         attr = JXG.copyAttributes(attributes, board.options, 'angle');
    358         //  If empty, create a new name
    359         text = attr.name;
    360         if (typeof text =='undefined' || text == '') {
    361             while(i < possibleNames.length) {
    362                 j=i;
    363                 x = possibleNames[i];
    364                 for(el in board.objects) {
    365                     if(board.objects[el].type == JXG.OBJECT_TYPE_ANGLE) {
    366                         if(board.objects[el].name == x) {
    367                             i++;
    368                             break;
    369                         }
    370                     }
    371                 }
    372                 if(i==j) {
    373                     text = x;
    374                     i = possibleNames.length+1;
    375                 }
    376             }
    377             if (i == possibleNames.length) {
    378                 pre = 'α_{';
    379                 post = '}';
    380                 found = false;
    381                 j=0;
    382                 while(!found) {
    383                     for(el in board.objects) {
    384                         if(board.objects[el].type == JXG.OBJECT_TYPE_ANGLE) {
    385                             if(board.objects[el].name == (pre+j+post)) {
    386                                 found = true;
    387                                 break;
    388                             }
    389                         }
    390                     }
    391                     if(found) {
    392                         found= false;
    393                     }
    394                     else {
    395                         found = true;
    396                         text = (pre+j+post);
    397                     }
    398                 }
    399             }
    400             attr.name = text;
    401         }
    402
    403         attrsub = JXG.copyAttributes(attributes, board.options, 'angle', 'radiuspoint');
    404         p = board.create('point', [
    405             function(){
    406                 var A = parents[0], S = parents[1],
    407                     r = JXG.evaluate(attr.radius),
    408                     d = S.Dist(A);
    409                 return [S.X()+(A.X()-S.X())*r/d, S.Y()+(A.Y()-S.Y())*r/d];
    410             }], attrsub);
    411
    412         p.dump = false;
    413
    414         attrsub = JXG.copyAttributes(attributes, board.options, 'angle', 'pointsquare');
    415         // Second helper point for square
    416         q = board.create('point', [
    417             function(){
    418                 var A = parents[2], S = parents[1],
    419                     r = JXG.evaluate(attr.radius),
    420                     d = S.Dist(A);
    421                 return [S.X()+(A.X()-S.X())*r/d, S.Y()+(A.Y()-S.Y())*r/d];
    422             }], attrsub);
    423
    424         q.dump = false;
    425
    426         // Sector is just a curve with its own updateDataArray method
    427         el = board.create('sector', [parents[1], p, parents[2]], attr);
    428
    429         el.elType = 'angle';
    430         el.parents = [parents[0].id, parents[1].id, parents[2].id];
    431         el.subs = {
    432             point: p,
    433             pointsquare: q
    434         };
    435
    436         el.updateDataArraySquare = function() {
    437             var S = parents[1],
    438                 v, l1, l2, r;
    439
    440             v = JXG.Math.crossProduct(q.coords.usrCoords, S.coords.usrCoords);
    441             l1 = [-p.X()*v[1]-p.Y()*v[2], p.Z()*v[1], p.Z()*v[2]];
    442             v = JXG.Math.crossProduct(p.coords.usrCoords, S.coords.usrCoords);
    443             l2 = [-q.X()*v[1]-q.Y()*v[2], q.Z()*v[1], q.Z()*v[2]];
    444             r = JXG.Math.crossProduct(l1,l2);
    445             r[1] /= r[0];
    446             r[2] /= r[0];
    447
    448             this.dataX = [S.X(), p.X(), r[1], q.X(), S.X()];
    449             this.dataY = [S.Y(), p.Y(), r[2], q.Y(), S.Y()];
    450         };
    451
    452         el.updateDataArraySector = el.updateDataArray;
    453         el.updateDataArray = function() {
    454             var rad = JXG.Math.Geometry.rad(parents[0], parents[1], parents[2]);
    455
    456             if (this.visProp.type=='square') {
    457                 this.updateDataArraySquare();
    458             } else if (this.visProp.type=='sector') {
    459                 this.updateDataArraySector();
    460                 if (Math.abs(rad-Math.PI*0.5)<0.0025) {
    461                     if (this.dot.visProp.visible === false) {
    462                         this.dot.setProperty({visible: true});
    463                     }
    464                 } else {
    465                     if (this.dot.visProp.visible) {
    466                         this.dot.setProperty({visible: false});
    467                     }
    468                 }
    469             } else {
    470                 if (Math.abs(rad-Math.PI*0.5)<0.0025) {
    471                     this.updateDataArraySquare();
    472                 } else {
    473                     this.updateDataArraySector();
    474                 }
    475             }
    476         };
    477
    478         /**
    479          * The point defining the radius of the angle element.
    480          * @type JXG.Point
    481          * @name radiuspoint
    482          * @memberOf Angle.prototype
    483          */
    484         el.radiuspoint = p;
    485
    486         /**
    487          * The point defining the radius of the angle element. Alias for {@link Angle.prototype#radiuspoint}.
    488          * @type JXG.Point
    489          * @name point
    490          * @memberOf Angle.prototype
    491          */
    492         el.point = p;
    493
    494         /**
    495          * Helper point for angles of type 'square'.
    496          * @type JXG.Point
    497          * @name pointsquare
    498          * @memberOf Angle.prototype
    499          */
    500         el.pointsquare = q;
    501
    502         dot = JXG.copyAttributes(attributes, board.options, 'angle', 'dot');
    503         /**
    504          * Indicates a right angle. Invisible by default, use <tt>dot.visible: true</tt> to show.
    505          * Though this dot indicates a right angle, it can be visible even if the angle is not a right
    506          * one.
    507          * @type JXG.Point
    508          * @name dot
    509          * @memberOf Angle.prototype
    510          */
    511         el.dot = board.create('point', [function () {
    512             if (JXG.exists(el.dot) && el.dot.visProp.visible === false) {
    513                 return [0, 0];
    514             }
    515
    516             var c = p.coords.usrCoords,
    517                 transform = board.create('transform', [-parents[1].X(), -parents[1].Y()], {type: 'translate'});
    518
    519             transform.melt(board.create('transform', [0.5, 0.5], {type: 'scale'}));
    520             transform.melt(board.create('transform', [JXG.Math.Geometry.rad(parents[0], parents[1], parents[2])/2, 0, 0], {type:'rotate'}));
    521             transform.melt(board.create('transform', [parents[1].X(), parents[1].Y()], {type: 'translate'}));
    522             transform.update();
    523
    524             return JXG.Math.matVecMult(transform.matrix, c);
    525         }], dot);
    526
    527         el.dot.dump = false;
    528         el.subs.dot = el.dot;
    529
    530         for (i = 0; i < 3; i++) {
    531             JXG.getRef(board,parents[i]).addChild(p);
    532             JXG.getRef(board,parents[i]).addChild(el.dot);
    533         }
    534
    535         el.type = JXG.OBJECT_TYPE_ANGLE;
    536         JXG.getRef(board,parents[0]).addChild(el);
    537
    538         // documented in GeometryElement
    539         el.getLabelAnchor = function() {
    540             var angle = JXG.Math.Geometry.rad(this.point2, this.point1, this.point3),
    541                 dx = 13/(this.board.unitX),
    542                 dy = 13/(this.board.unitY),
    543                 p2c = this.point2.coords.usrCoords,
    544                 pmc = this.point1.coords.usrCoords,
    545                 bxminusax = p2c[1] - pmc[1],
    546                 byminusay = p2c[2] - pmc[2],
    547                 coords, vecx, vecy, len;
    548
    549             if(this.label.content != null) {
    550                 this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0],this.board);
    551             }
    552
    553             coords = new JXG.Coords(JXG.COORDS_BY_USER,
    554                             [pmc[1]+ Math.cos(angle*0.5*1.125)*bxminusax - Math.sin(angle*0.5*1.125)*byminusay,
    555                              pmc[2]+ Math.sin(angle*0.5*1.125)*bxminusax + Math.cos(angle*0.5*1.125)*byminusay],
    556                             this.board);
    557
    558             vecx = coords.usrCoords[1] - pmc[1];
    559             vecy = coords.usrCoords[2] - pmc[2];
    560
    561             len = Math.sqrt(vecx*vecx+vecy*vecy);
    562             vecx = vecx*(len+dx)/len;
    563             vecy = vecy*(len+dy)/len;
    564
    565             return new JXG.Coords(JXG.COORDS_BY_USER, [pmc[1]+vecx,pmc[2]+vecy],this.board);
    566         };
    567
    568         el.Value = function () {
    569             return JXG.Math.Geometry.rad(this.point2, this.point1, this.point3);
    570         };
    571
    572         el.methodMap = JXG.deepCopy(el.methodMap, {
    573             Value: 'Value'
    574         });
    575
    576     } else {
    577         throw new Error("JSXGraph: Can't create angle with parent types '" +
    578                          (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'.");
    579     }
    580
    581     return el;
    582 };
    583
    584 JXG.JSXGraph.registerElement('angle', JXG.createAngle);
    585
    586