1 /*
  2     Copyright 2008-2021
  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 <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  math/geometry
 39  math/math
 40  base/coords
 41  base/constants
 42  utils/type
 43   elements:
 44    point
 45    curve
 46    circumcentre
 47    transform
 48  */
 49 
 50 define([
 51     'jxg', 'math/geometry', 'math/math', 'math/statistics', 'base/coords', 'base/constants', 'utils/type', 'base/point', 'base/curve',
 52     'base/transformation', 'element/composition'
 53 ], function (JXG, Geometry, Mat, Statistics, Coords, Const, Type, Point, Curve, Transform, Compositions) {
 54 
 55     "use strict";
 56 
 57     /**
 58      * @class A circular sector is a subarea of the area enclosed by a circle. It is enclosed by two radii and an arc.
 59      * @pseudo
 60      * @name Sector
 61      * @augments JXG.Curve
 62      * @constructor
 63      * @type JXG.Curve
 64      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
 65      *
 66      * First possiblity of input parameters are:
 67      * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A sector is defined by three points: The sector's center <tt>p1</tt>,
 68      * a second point <tt>p2</tt> defining the radius and a third point <tt>p3</tt> defining the angle of the sector. The
 69      * Sector is always drawn counter clockwise from <tt>p2</tt> to <tt>p3</tt>
 70      * <p>
 71      * Second possibility of input parameters are:
 72      * @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.
 73      * The two legs which define the sector are given by two coordinates arrays which are project initially two the two lines or by two directions (+/- 1).
 74      * The last parameter is the radius of the sector.
 75      *
 76      *
 77      * @example
 78      * // Create a sector out of three free points
 79      * var p1 = board.create('point', [1.5, 5.0]),
 80      *     p2 = board.create('point', [1.0, 0.5]),
 81      *     p3 = board.create('point', [5.0, 3.0]),
 82      *
 83      *     a = board.create('sector', [p1, p2, p3]);
 84      * </pre><div class="jxgbox" id="JXG49f59123-f013-4681-bfd9-338b89893156" style="width: 300px; height: 300px;"></div>
 85      * <script type="text/javascript">
 86      * (function () {
 87      *   var board = JXG.JSXGraph.initBoard('JXG49f59123-f013-4681-bfd9-338b89893156', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
 88      *     p1 = board.create('point', [1.5, 5.0]),
 89      *     p2 = board.create('point', [1.0, 0.5]),
 90      *     p3 = board.create('point', [5.0, 3.0]),
 91      *
 92      *     a = board.create('sector', [p1, p2, p3]);
 93      * })();
 94      * </script><pre>
 95      *
 96      * @example
 97      * // Create a sector out of two lines, two directions and a radius
 98      * var p1 = board.create('point', [-1, 4]),
 99      *  p2 = board.create('point', [4, 1]),
100      *  q1 = board.create('point', [-2, -3]),
101      *  q2 = board.create('point', [4,3]),
102      *
103      *  li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
104      *  li2 = board.create('line', [q1,q2], {lastArrow:true}),
105      *
106      *  sec1 = board.create('sector', [li1, li2, [5.5, 0], [4, 3], 3]),
107      *  sec2 = board.create('sector', [li1, li2, 1, -1, 4]);
108      *
109      * </pre><div class="jxgbox" id="JXGbb9e2809-9895-4ff1-adfa-c9c71d50aa53" style="width: 300px; height: 300px;"></div>
110      * <script type="text/javascript">
111      * (function () {
112      *   var board = JXG.JSXGraph.initBoard('JXGbb9e2809-9895-4ff1-adfa-c9c71d50aa53', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
113      *     p1 = board.create('point', [-1, 4]),
114      *     p2 = board.create('point', [4, 1]),
115      *     q1 = board.create('point', [-2, -3]),
116      *     q2 = board.create('point', [4,3]),
117      *
118      *     li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
119      *     li2 = board.create('line', [q1,q2], {lastArrow:true}),
120      *
121      *     sec1 = board.create('sector', [li1, li2, [5.5, 0], [4, 3], 3]),
122      *     sec2 = board.create('sector', [li1, li2, 1, -1, 4]);
123      * })();
124      * </script><pre>
125      *
126      * @example
127      * var t = board.create('transform', [2, 1.5], {type: 'scale'});
128      * var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], {
129      *                 anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true},
130      *                 fillColor: 'yellow', strokeColor: 'black'});
131      * var s2 = board.create('curve', [s1, t], {fillColor: 'yellow', strokeColor: 'black'});
132      *
133      * </pre><div id="JXG2e70ee14-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
134      * <script type="text/javascript">
135      *     (function() {
136      *         var board = JXG.JSXGraph.initBoard('JXG2e70ee14-6339-11e8-9fb9-901b0e1b8723',
137      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
138      *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
139      *     var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], {
140      *                     anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true},
141      *                     fillColor: 'yellow', strokeColor: 'black'});
142      *     var s2 = board.create('curve', [s1, t], {fillColor: 'yellow', strokeColor: 'black'});
143      *
144      *     })();
145      *
146      * </script><pre>
147      *
148      */
149     JXG.createSector = function (board, parents, attributes) {
150         var el, attr,
151             type = 'invalid',
152             s, v,
153             attrPoints = ['center', 'radiusPoint', 'anglePoint'],
154             points;
155 
156         // Three points?
157         if (parents[0].elementClass === Const.OBJECT_CLASS_LINE &&
158                 parents[1].elementClass === Const.OBJECT_CLASS_LINE &&
159                 (Type.isArray(parents[2]) || Type.isNumber(parents[2])) &&
160                 (Type.isArray(parents[3]) || Type.isNumber(parents[3])) &&
161                 (Type.isNumber(parents[4]) || Type.isFunction(parents[4]))) {
162 
163             type = '2lines';
164         } else {
165             points = Type.providePoints(board, parents, attributes, 'sector', attrPoints);
166             if (points === false) {
167                 throw new Error("JSXGraph: Can't create Sector with parent types '" +
168                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" +
169                     (typeof parents[2]) + "'.");
170             }
171             type = '3points';
172         }
173 
174 
175         attr = Type.copyAttributes(attributes, board.options, 'sector');
176         el = board.create('curve', [[0], [0]], attr);
177         el.type = Const.OBJECT_TYPE_SECTOR;
178         el.elType = 'sector';
179 
180         if (type === '2lines') {
181             el.Radius = function () {
182                 return Type.evaluate(parents[4]);
183             };
184 
185             el.line1 = board.select(parents[0]);
186             el.line2 = board.select(parents[1]);
187 
188             el.line1.addChild(el);
189             el.line2.addChild(el);
190             el.setParents(parents);
191 
192             el.point1 = {visProp: {}};
193             el.point2 = {visProp: {}};
194             el.point3 = {visProp: {}};
195 
196             /* Intersection point */
197             s = Geometry.meetLineLine(el.line1.stdform, el.line2.stdform, 0, board);
198 
199             if (Type.isArray(parents[2])) {
200                 /* project p1 to l1 */
201                 if (parents[2].length === 2) {
202                     parents[2] = [1].concat(parents[2]);
203                 }
204                 /*
205                 v = [0, el.line1.stdform[1], el.line1.stdform[2]];
206                 v = Mat.crossProduct(v, parents[2]);
207                 v = Geometry.meetLineLine(v, el.line1.stdform, 0, board);
208                 */
209                 v = Geometry.projectPointToLine({coords: {usrCoords: parents[2]}}, el.line1, board);
210                 v = Statistics.subtract(v.usrCoords, s.usrCoords);
211                 el.direction1 = (Mat.innerProduct(v, [0, el.line1.stdform[2], -el.line1.stdform[1]], 3) >= 0) ? +1 : -1;
212             } else {
213                 el.direction1 = (parents[2] >= 0) ? 1 : -1;
214             }
215 
216             if (Type.isArray(parents[3])) {
217                 /* project p2 to l2 */
218                 if (parents[3].length === 2) {
219                     parents[3] = [1].concat(parents[3]);
220                 }
221                 /*
222                 v = [0, el.line2.stdform[1], el.line2.stdform[2]];
223                 v = Mat.crossProduct(v, parents[3]);
224                 v = Geometry.meetLineLine(v, el.line2.stdform, 0, board);
225                 */
226                 v = Geometry.projectPointToLine({coords: {usrCoords: parents[3]}}, el.line2, board);
227                 v = Statistics.subtract(v.usrCoords, s.usrCoords);
228                 el.direction2 = (Mat.innerProduct(v, [0, el.line2.stdform[2], -el.line2.stdform[1]], 3) >= 0) ? +1 : -1;
229             } else {
230                 el.direction2 = (parents[3] >= 0) ? 1 : -1;
231             }
232 
233             el.updateDataArray = function () {
234                 var r, l1, l2,
235                     A = [0, 0, 0],
236                     B = [0, 0, 0],
237                     C = [0, 0, 0],
238                     ar;
239 
240                 l1 = this.line1;
241                 l2 = this.line2;
242 
243                 // Intersection point of the lines
244                 B = Mat.crossProduct(l1.stdform, l2.stdform);
245 
246                 if (Math.abs(B[0]) > Mat.eps * Mat.eps) {
247                     B[1] /= B[0];
248                     B[2] /= B[0];
249                     B[0] /= B[0];
250                 }
251                 // First point
252                 r = this.direction1 * this.Radius();
253                 A = Statistics.add(B, [0, r * l1.stdform[2], -r * l1.stdform[1]]);
254 
255                 // Second point
256                 r = this.direction2 * this.Radius();
257                 C = Statistics.add(B, [0, r * l2.stdform[2], -r * l2.stdform[1]]);
258 
259                 this.point2.coords = new Coords(Const.COORDS_BY_USER, A, el.board);
260                 this.point1.coords = new Coords(Const.COORDS_BY_USER, B, el.board);
261                 this.point3.coords = new Coords(Const.COORDS_BY_USER, C, el.board);
262 
263                 if (Math.abs(A[0]) < Mat.eps || Math.abs(B[0]) < Mat.eps || Math.abs(C[0]) < Mat.eps) {
264                     this.dataX = [NaN];
265                     this.dataY = [NaN];
266                     return;
267                 }
268 
269                 ar = Geometry.bezierArc(A, B, C, true, 1);
270 
271                 this.dataX = ar[0];
272                 this.dataY = ar[1];
273 
274                 this.bezierDegree = 3;
275             };
276 
277             el.methodMap = JXG.deepCopy(el.methodMap, {
278                 radius: 'getRadius',
279                 getRadius: 'getRadius',
280                 setRadius: 'setRadius'
281             });
282 
283             el.prepareUpdate().update();
284 
285         // end '2lines'
286 
287         } else if (type === '3points') {
288 
289             /**
290             * Midpoint of the sector.
291             * @memberOf Sector.prototype
292             * @name point1
293             * @type JXG.Point
294             */
295             el.point1 = points[0];
296 
297             /**
298             * This point together with {@link Sector#point1} defines the radius..
299             * @memberOf Sector.prototype
300             * @name point2
301             * @type JXG.Point
302             */
303             el.point2 = points[1];
304 
305             /**
306             * Defines the sector's angle.
307             * @memberOf Sector.prototype
308             * @name point3
309             * @type JXG.Point
310             */
311             el.point3 = points[2];
312 
313             /* Add arc as child to defining points */
314             el.point1.addChild(el);
315             el.point2.addChild(el);
316             el.point3.addChild(el);
317 
318             // useDirection is necessary for circumCircleSectors
319             el.useDirection = attributes.usedirection;
320             el.setParents(points);
321 
322             /**
323             * Defines the sectors orientation in case of circumCircleSectors.
324             * @memberOf Sector.prototype
325             * @name point4
326             * @type JXG.Point
327             */
328             if (Type.exists(points[3])) {
329                 el.point4 = points[3];
330                 el.point4.addChild(el);
331             }
332 
333             el.methodMap = JXG.deepCopy(el.methodMap, {
334                 arc: 'arc',
335                 center: 'center',
336                 radiuspoint: 'radiuspoint',
337                 anglepoint: 'anglepoint',
338                 radius: 'getRadius',
339                 getRadius: 'getRadius',
340                 setRadius: 'setRadius'
341             });
342 
343             /**
344             * documented in JXG.Curve
345             * @ignore
346             */
347             el.updateDataArray = function () {
348                 var ar, det, p0c, p1c, p2c,
349                     A = this.point2,
350                     B = this.point1,
351                     C = this.point3,
352                     phi, sgn = 1,
353                     vp_s = Type.evaluate(this.visProp.selection);
354 
355                 if (!A.isReal || !B.isReal || !C.isReal) {
356                     this.dataX = [NaN];
357                     this.dataY = [NaN];
358                     return;
359                 }
360 
361                 phi = Geometry.rad(A, B, C);
362                 if ((vp_s === 'minor' && phi > Math.PI) ||
363                         (vp_s === 'major' && phi < Math.PI)) {
364                     sgn = -1;
365                 }
366 
367                 // This is true for circumCircleSectors. In that case there is
368                 // a fourth parent element: [midpoint, point1, point3, point2]
369                 if (this.useDirection && Type.exists(this.point4)) {
370                     p0c = this.point2.coords.usrCoords;
371                     p1c = this.point4.coords.usrCoords;
372                     p2c = this.point3.coords.usrCoords;
373                     det = (p0c[1] - p2c[1]) * (p0c[2] - p1c[2]) - (p0c[2] - p2c[2]) * (p0c[1] - p1c[1]);
374 
375                     if (det >= 0.0) {
376                         C = this.point2;
377                         A = this.point3;
378                     }
379                 }
380 
381                 A = A.coords.usrCoords;
382                 B = B.coords.usrCoords;
383                 C = C.coords.usrCoords;
384 
385                 ar = Geometry.bezierArc(A, B, C, true, sgn);
386 
387                 this.dataX = ar[0];
388                 this.dataY = ar[1];
389                 this.bezierDegree = 3;
390             };
391 
392             /**
393             * Returns the radius of the sector.
394             * @memberOf Sector.prototype
395             * @name Radius
396             * @function
397             * @returns {Number} The distance between {@link Sector#point1} and {@link Sector#point2}.
398             */
399             el.Radius = function () {
400                 return this.point2.Dist(this.point1);
401             };
402 
403             attr = Type.copyAttributes(attributes, board.options, 'sector', 'arc');
404             attr.withLabel = false;
405             attr.name += '_arc';
406             el.arc = board.create('arc', [el.point1, el.point2, el.point3], attr);
407             el.addChild(el.arc);
408         }   // end '3points'
409 
410         el.center = el.point1;
411         el.radiuspoint = el.point2;
412         el.anglepoint = el.point3;
413 
414         // Default hasPoint method. Documented in geometry element
415         el.hasPointCurve = function (x, y) {
416             var angle, alpha, beta,
417                 prec, type,
418                 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
419                 r = this.Radius(),
420                 dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint),
421                 has,
422                 vp_s = Type.evaluate(this.visProp.selection);
423 
424             if (Type.isObject(Type.evaluate(this.visProp.precision))) {
425                 type = this.board._inputDevice;
426                 prec = Type.evaluate(this.visProp.precision[type]);
427             } else {
428                 // 'inherit'
429                 prec = this.board.options.precision.hasPoint;
430             }
431             prec /= Math.min(this.board.unitX, this.board.unitY);
432             has = (Math.abs(dist - r) < prec);
433             if (has) {
434                 angle = Geometry.rad(this.point2, this.center, checkPoint.usrCoords.slice(1));
435                 alpha = 0;
436                 beta = Geometry.rad(this.point2, this.center, this.point3);
437 
438                 if ((vp_s === 'minor' && beta > Math.PI) ||
439                         (vp_s === 'major' && beta < Math.PI)) {
440                     alpha = beta;
441                     beta = 2 * Math.PI;
442                 }
443 
444                 if (angle < alpha || angle > beta) {
445                     has = false;
446                 }
447             }
448 
449             return has;
450         };
451 
452         /**
453         * Checks whether (x,y) is within the area defined by the sector.
454         * @memberOf Sector.prototype
455         * @name hasPointSector
456         * @function
457         * @param {Number} x Coordinate in x direction, screen coordinates.
458         * @param {Number} y Coordinate in y direction, screen coordinates.
459         * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise.
460         */
461         el.hasPointSector = function (x, y) {
462             var angle,
463                 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
464                 r = this.Radius(),
465                 dist = this.point1.coords.distance(Const.COORDS_BY_USER, checkPoint),
466                 alpha,
467                 beta,
468                 has = (dist < r),
469                 vp_s = Type.evaluate(this.visProp.selection);
470 
471             if (has) {
472                 angle = Geometry.rad(this.radiuspoint, this.center, checkPoint.usrCoords.slice(1));
473                 alpha = 0.0;
474                 beta = Geometry.rad(this.radiuspoint, this.center, this.anglepoint);
475 
476                 if ((vp_s === 'minor' && beta > Math.PI) ||
477                         (vp_s === 'major' && beta < Math.PI)) {
478                     alpha = beta;
479                     beta = 2 * Math.PI;
480                 }
481                 //if (angle > Geometry.rad(this.point2, this.point1, this.point3)) {
482                 if (angle < alpha || angle > beta) {
483                     has = false;
484                 }
485             }
486             return has;
487         };
488 
489         el.hasPoint = function (x, y) {
490             if (Type.evaluate(this.visProp.highlightonsector) ||
491                     Type.evaluate(this.visProp.hasinnerpoints)) {
492                 return this.hasPointSector(x, y);
493             }
494 
495             return this.hasPointCurve(x, y);
496         };
497 
498         // documented in GeometryElement
499         el.getTextAnchor = function () {
500             return this.point1.coords;
501         };
502 
503         // documented in GeometryElement
504         // this method is very similar to arc.getLabelAnchor()
505         // there are some additions in the arc version though, mainly concerning
506         // "major" and "minor" arcs. but maybe these methods can be merged.
507         el.getLabelAnchor = function () {
508             var coords, vec, vecx, vecy, len,
509                 angle = Geometry.rad(this.point2, this.point1, this.point3),
510                 dx = 13 / this.board.unitX,
511                 dy = 13 / this.board.unitY,
512                 p2c = this.point2.coords.usrCoords,
513                 pmc = this.point1.coords.usrCoords,
514                 bxminusax = p2c[1] - pmc[1],
515                 byminusay = p2c[2] - pmc[2],
516                 vp_s = Type.evaluate(this.visProp.selection),
517                 l_vp = this.label ? this.label.visProp : this.visProp.label;
518 
519             // If this is uncommented, the angle label can not be dragged
520             //if (Type.exists(this.label)) {
521             //    this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
522             //}
523 
524             if ((vp_s === 'minor' && angle > Math.PI) ||
525                     (vp_s === 'major' && angle < Math.PI)) {
526                 angle = -(2 * Math.PI - angle);
527             }
528 
529             coords = new Coords(Const.COORDS_BY_USER, [
530                 pmc[1] + Math.cos(angle * 0.5) * bxminusax - Math.sin(angle * 0.5) * byminusay,
531                 pmc[2] + Math.sin(angle * 0.5) * bxminusax + Math.cos(angle * 0.5) * byminusay
532             ], this.board);
533 
534             vecx = coords.usrCoords[1] - pmc[1];
535             vecy = coords.usrCoords[2] - pmc[2];
536 
537             len = Math.sqrt(vecx * vecx + vecy * vecy);
538             vecx = vecx * (len + dx) / len;
539             vecy = vecy * (len + dy) / len;
540             vec = [pmc[1] + vecx, pmc[2] + vecy];
541 
542             l_vp.position = Geometry.calcLabelQuadrant(Geometry.rad([1,0],[0,0],vec));
543 
544             return new Coords(Const.COORDS_BY_USER, vec, this.board);
545         };
546 
547         /**
548          * Overwrite the Radius method of the sector.
549          * Used in {@link GeometryElement#setAttribute}.
550          * @param {Number, Function} value New radius.
551          */
552         el.setRadius = function (value) {
553             el.Radius = function () {
554                 return Type.evaluate(value);
555             };
556         };
557 
558         /**
559          * @deprecated
560          * @ignore
561          */
562         el.getRadius = function () {
563             JXG.deprecated('Sector.getRadius()', 'Sector.Radius()');
564             return this.Radius();
565         };
566 
567         /**
568          * Moves the sector by the difference of two coordinates.
569          * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
570          * @param {Array} coords coordinates in screen/user units
571          * @param {Array} oldcoords previous coordinates in screen/user units
572          * @returns {JXG.Curve} this element
573          */
574         if (type === '3points') {
575             el.setPositionDirectly = function (method, coords, oldcoords) {
576                 var dc, t, i,
577                     c = new Coords(method, coords, this.board),
578                     oldc = new Coords(method, oldcoords, this.board);
579 
580                 if (!el.point1.draggable() || !el.point2.draggable() || !el.point3.draggable()) {
581                     return this;
582                 }
583 
584                 dc = Statistics.subtract(c.usrCoords, oldc.usrCoords);
585                 t = this.board.create('transform', dc.slice(1), {type: 'translate'});
586                 t.applyOnce([el.point1, el.point2, el.point3]);
587 
588                 return this;
589             };
590         }
591 
592         el.prepareUpdate().update();
593 
594         return el;
595     };
596 
597     JXG.registerElement('sector', JXG.createSector);
598 
599     /**
600      * @class A circumcircle sector is different from a {@link Sector} mostly in the way the parent elements are interpreted.
601      * At first, the circum centre is determined from the three given points. Then the sector is drawn from <tt>p1</tt> through
602      * <tt>p2</tt> to <tt>p3</tt>.
603      * @pseudo
604      * @name CircumcircleSector
605      * @augments Sector
606      * @constructor
607      * @type Sector
608      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
609      * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A circumcircle sector is defined by the circumcircle which is determined
610      * by these three given points. The circumcircle sector is always drawn from <tt>p1</tt> through <tt>p2</tt> to <tt>p3</tt>.
611      * @example
612      * // Create an arc out of three free points
613      * var p1 = board.create('point', [1.5, 5.0]),
614      *     p2 = board.create('point', [1.0, 0.5]),
615      *     p3 = board.create('point', [5.0, 3.0]),
616      *
617      *     a = board.create('circumcirclesector', [p1, p2, p3]);
618      * </pre><div class="jxgbox" id="JXG695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04" style="width: 300px; height: 300px;"></div>
619      * <script type="text/javascript">
620      * (function () {
621  *   var board = JXG.JSXGraph.initBoard('JXG695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
622  *     p1 = board.create('point', [1.5, 5.0]),
623  *     p2 = board.create('point', [1.0, 0.5]),
624  *     p3 = board.create('point', [5.0, 3.0]),
625  *
626  *     a = board.create('circumcirclesector', [p1, p2, p3]);
627  * })();
628      * </script><pre>
629      */
630     JXG.createCircumcircleSector = function (board, parents, attributes) {
631         var el, mp, attr, points, i;
632 
633         points = Type.providePoints(board, parents, attributes, 'point');
634         if (points === false) {
635             throw new Error("JSXGraph: Can't create circumcircle sector with parent types '" +
636                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'.");
637         }
638 
639         mp = board.create('circumcenter', points.slice(0, 3), attr);
640         mp.dump = false;
641 
642         attr = Type.copyAttributes(attributes, board.options, 'circumcirclesector');
643         el = board.create('sector', [mp, points[0], points[2], points[1]], attr);
644 
645         el.elType = 'circumcirclesector';
646         el.setParents(points);
647 
648         /**
649          * Center of the circumcirclesector
650          * @memberOf CircumcircleSector.prototype
651          * @name center
652          * @type Circumcenter
653          */
654         el.center = mp;
655         el.subs = {
656             center: mp
657         };
658 
659         return el;
660     };
661 
662     JXG.registerElement('circumcirclesector', JXG.createCircumcircleSector);
663 
664     /**
665      * @class A minor sector is a sector of a circle having measure less than or equal to
666      * 180 degrees (pi radians). It is defined by a center, one point that
667      * defines the radius, and a third point that defines the angle of the sector.
668      * @pseudo
669      * @name MinorSector
670      * @augments Curve
671      * @constructor
672      * @type JXG.Curve
673      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
674      * @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
675      * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
676      * @example
677      * // Create sector out of three free points
678      * var p1 = board.create('point', [2.0, 2.0]);
679      * var p2 = board.create('point', [1.0, 0.5]);
680      * var p3 = board.create('point', [3.5, 1.0]);
681      *
682      * var a = board.create('minorsector', [p1, p2, p3]);
683      * </pre><div class="jxgbox" id="JXGaf27ddcc-265f-428f-90dd-d31ace945800" style="width: 300px; height: 300px;"></div>
684      * <script type="text/javascript">
685      * (function () {
686      *   var board = JXG.JSXGraph.initBoard('JXGaf27ddcc-265f-428f-90dd-d31ace945800', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
687      *       p1 = board.create('point', [2.0, 2.0]),
688      *       p2 = board.create('point', [1.0, 0.5]),
689      *       p3 = board.create('point', [3.5, 1.0]),
690      *
691      *       a = board.create('minorsector', [p1, p2, p3]);
692      * })();
693      * </script><pre>
694      */
695     JXG.createMinorSector = function (board, parents, attributes) {
696         attributes.selection = 'minor';
697         return JXG.createSector(board, parents, attributes);
698     };
699 
700     JXG.registerElement('minorsector', JXG.createMinorSector);
701 
702     /**
703      * @class A major sector is a sector of a circle having measure greater than or equal to
704      * 180 degrees (pi radians). It is defined by a center, one point that
705      * defines the radius, and a third point that defines the angle of the sector.
706      * @pseudo
707      * @name MajorSector
708      * @augments Curve
709      * @constructor
710      * @type JXG.Curve
711      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
712      * @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
713      * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
714      * @example
715      * // Create an arc out of three free points
716      * var p1 = board.create('point', [2.0, 2.0]);
717      * var p2 = board.create('point', [1.0, 0.5]);
718      * var p3 = board.create('point', [3.5, 1.0]);
719      *
720      * var a = board.create('majorsector', [p1, p2, p3]);
721      * </pre><div class="jxgbox" id="JXG83c6561f-7561-4047-b98d-036248a00932" style="width: 300px; height: 300px;"></div>
722      * <script type="text/javascript">
723      * (function () {
724      *   var board = JXG.JSXGraph.initBoard('JXG83c6561f-7561-4047-b98d-036248a00932', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
725      *       p1 = board.create('point', [2.0, 2.0]),
726      *       p2 = board.create('point', [1.0, 0.5]),
727      *       p3 = board.create('point', [3.5, 1.0]),
728      *
729      *       a = board.create('majorsector', [p1, p2, p3]);
730      * })();
731      * </script><pre>
732      */
733     JXG.createMajorSector = function (board, parents, attributes) {
734         attributes.selection = 'major';
735         return JXG.createSector(board, parents, attributes);
736     };
737 
738     JXG.registerElement('majorsector', JXG.createMajorSector);
739 
740     /**
741      * @class The angle element is used to denote an angle defined by three points. Visually it is just a {@link Sector}
742      * element with a radius not defined by the parent elements but by an attribute <tt>radius</tt>. As opposed to the sector,
743      * an angle has two angle points and no radius point.
744      * Sector is displayed if type=="sector".
745      * If type=="square", instead of a sector a parallelogram is displayed.
746      * In case of type=="auto", a square is displayed if the angle is near orthogonal.
747      * If no name is provided the angle label is automatically set to a lower greek letter.
748      * @pseudo
749      * @name Angle
750      * @augments Sector
751      * @constructor
752      * @type Sector
753      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
754      * First possiblity of input parameters are:
755      * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 An angle is always drawn counterclockwise from <tt>p1</tt> to
756      * <tt>p3</tt> around <tt>p2</tt>.
757      *
758      * Second possibility of input parameters are:
759      * @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.
760      * The two legs which define the angle are given by two coordinate arrays.
761      * The points given by these coordinate arrays are projected initially (i.e. only once) onto the two lines.
762      * The other possibility is to supply directions (+/- 1).
763      *
764      * @example
765      * // Create an angle out of three free points
766      * var p1 = board.create('point', [5.0, 3.0]),
767      *     p2 = board.create('point', [1.0, 0.5]),
768      *     p3 = board.create('point', [1.5, 5.0]),
769      *
770      *     a = board.create('angle', [p1, p2, p3]),
771      *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
772      * </pre><div class="jxgbox" id="JXGa34151f9-bb26-480a-8d6e-9b8cbf789ae5" style="width: 300px; height: 300px;"></div>
773      * <script type="text/javascript">
774      * (function () {
775      *   var board = JXG.JSXGraph.initBoard('JXGa34151f9-bb26-480a-8d6e-9b8cbf789ae5', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
776      *     p1 = board.create('point', [5.0, 3.0]),
777      *     p2 = board.create('point', [1.0, 0.5]),
778      *     p3 = board.create('point', [1.5, 5.0]),
779      *
780      *     a = board.create('angle', [p1, p2, p3]),
781      *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
782      * })();
783      * </script><pre>
784      *
785      * @example
786      * // Create an angle out of two lines and two directions
787      * var p1 = board.create('point', [-1, 4]),
788      *  p2 = board.create('point', [4, 1]),
789      *  q1 = board.create('point', [-2, -3]),
790      *  q2 = board.create('point', [4,3]),
791      *
792      *  li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
793      *  li2 = board.create('line', [q1,q2], {lastArrow:true}),
794      *
795      *  a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }),
796      *  a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 });
797      *
798      *
799      * </pre><div class="jxgbox" id="JXG3a667ddd-63dc-4594-b5f1-afac969b371f" style="width: 300px; height: 300px;"></div>
800      * <script type="text/javascript">
801      * (function () {
802      *   var board = JXG.JSXGraph.initBoard('JXG3a667ddd-63dc-4594-b5f1-afac969b371f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
803      *     p1 = board.create('point', [-1, 4]),
804      *     p2 = board.create('point', [4, 1]),
805      *     q1 = board.create('point', [-2, -3]),
806      *     q2 = board.create('point', [4,3]),
807      *
808      *     li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
809      *     li2 = board.create('line', [q1,q2], {lastArrow:true}),
810      *
811      *     a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }),
812      *     a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 });
813      * })();
814      * </script><pre>
815      *
816      *
817      * @example
818      * // Display the angle value instead of the name
819      * var p1 = board.create('point', [0,2]);
820      * var p2 = board.create('point', [0,0]);
821      * var p3 = board.create('point', [-2,0.2]);
822      *
823      * var a = board.create('angle', [p1, p2, p3], {
824      * 	 radius: 1,
825      *   name: function() {
826      *   	return JXG.Math.Geometry.trueAngle(p1, p2, p3).toFixed(1) + '°';
827      *   }});
828      *
829      * </pre><div id="JXGc813f601-8dd3-4030-9892-25c6d8671512" class="jxgbox" style="width: 300px; height: 300px;"></div>
830      * <script type="text/javascript">
831      *     (function() {
832      *         var board = JXG.JSXGraph.initBoard('JXGc813f601-8dd3-4030-9892-25c6d8671512',
833      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
834      *
835      *     var p1 = board.create('point', [0,2]);
836      *     var p2 = board.create('point', [0,0]);
837      *     var p3 = board.create('point', [-2,0.2]);
838      *
839      *     var a = board.create('angle', [p1, p2, p3], {
840      *     	radius: 1,
841      *       name: function() {
842      *       	return JXG.Math.Geometry.trueAngle(p1, p2, p3).toFixed(1) + '°';
843      *       }});
844      *
845      *     })();
846      *
847      * </script><pre>
848      *
849      *
850      * @example
851      * // Apply a transformation to an angle.
852      * var t = board.create('transform', [2, 1.5], {type: 'scale'});
853      * var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]);
854      * var an2 = board.create('curve', [an1, t]);
855      *
856      * </pre><div id="JXG4c8d9ed8-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
857      * <script type="text/javascript">
858      *     (function() {
859      *         var board = JXG.JSXGraph.initBoard('JXG4c8d9ed8-6339-11e8-9fb9-901b0e1b8723',
860      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
861      *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
862      *     var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]);
863      *     var an2 = board.create('curve', [an1, t]);
864      *
865      *     })();
866      *
867      * </script><pre>
868      *
869      */
870     JXG.createAngle = function (board, parents, attributes) {
871         var el, radius, attr, attrsub,
872             i, points,
873             type = 'invalid';
874 
875         // Two lines or three points?
876         if (parents[0].elementClass === Const.OBJECT_CLASS_LINE &&
877                 parents[1].elementClass === Const.OBJECT_CLASS_LINE &&
878                 (Type.isArray(parents[2]) || Type.isNumber(parents[2])) &&
879                 (Type.isArray(parents[3]) || Type.isNumber(parents[3]))) {
880 
881             type = '2lines';
882         } else {
883             points = Type.providePoints(board, parents, attributes, 'point');
884             if (points === false) {
885                 throw new Error("JSXGraph: Can't create angle with parent types '" +
886                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'.");
887             }
888             type = '3points';
889         }
890 
891         attr = Type.copyAttributes(attributes, board.options, 'angle');
892 
893         //  If empty, create a new name
894         if (!Type.exists(attr.name) || attr.name === '') {
895             attr.name = board.generateName({type: Const.OBJECT_TYPE_ANGLE});
896         }
897 
898         if (Type.exists(attr.radius)) {
899             radius = attr.radius;
900         } else {
901             radius = 0;
902         }
903 
904         if (type === '2lines') {
905             parents.push(radius);
906             el = board.create('sector', parents, attr);
907             el.updateDataArraySector = el.updateDataArray;
908 
909             // TODO
910             el.setAngle = function (val) {};
911             el.free = function (val) {};
912 
913         } else {
914             el = board.create('sector', [points[1], points[0], points[2]], attr);
915             el.arc.visProp.priv = true;
916 
917             /**
918              * The point defining the radius of the angle element.
919              * Alias for {@link Sector#radiuspoint}.
920              * @type JXG.Point
921              * @name point
922              * @memberOf Angle.prototype
923              *
924              */
925             el.point = el.point2 = el.radiuspoint = points[0];
926 
927             /**
928              * Helper point for angles of type 'square'.
929              * @type JXG.Point
930              * @name pointsquare
931              * @memberOf Angle.prototype
932              */
933             el.pointsquare = el.point3 = el.anglepoint = points[2];
934 
935             el.Radius = function () {
936                 return Type.evaluate(radius);
937             };
938 
939             el.updateDataArraySector = function () {
940                 var A = this.point2,
941                     B = this.point1,
942                     C = this.point3,
943                     r = this.Radius(),
944                     d = B.Dist(A),
945                     ar,
946                     phi,
947                     sgn = 1,
948                     vp_s = Type.evaluate(this.visProp.selection);
949 
950                 phi = Geometry.rad(A, B, C);
951                 if ((vp_s === 'minor' && phi > Math.PI) ||
952                         (vp_s === 'major' && phi < Math.PI)) {
953                     sgn = -1;
954                 }
955 
956                 A = A.coords.usrCoords;
957                 B = B.coords.usrCoords;
958                 C = C.coords.usrCoords;
959 
960                 A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d];
961                 C = [1, B[1] + (C[1] - B[1]) * r / d, B[2] + (C[2] - B[2]) * r / d];
962 
963                 ar = Geometry.bezierArc(A, B, C, true, sgn);
964 
965                 this.dataX = ar[0];
966                 this.dataY = ar[1];
967                 this.bezierDegree = 3;
968             };
969 
970             /**
971             * Set an angle to a prescribed value given in radians. This is only possible if the third point of the angle, i.e.
972             * the anglepoint is a free point.
973             * @name setAngle
974             * @function
975             * @param {Number|Function} val Number or Function which returns the size of the angle in Radians
976             * @returns {Object} Pointer to the angle element..
977             * @memberOf Angle.prototype
978             *
979             * @example
980             * var p1, p2, p3, c, a, s;
981             *
982             * p1 = board.create('point',[0,0]);
983             * p2 = board.create('point',[5,0]);
984             * p3 = board.create('point',[0,5]);
985             *
986             * c1 = board.create('circle',[p1, p2]);
987             *
988             * a = board.create('angle',[p2, p1, p3], {radius:3});
989             *
990             * a.setAngle(function() {
991             *     return Math.PI / 3;
992             * });
993             * board.update();
994             *
995             * </pre><div id="JXG987c-394f-11e6-af4a-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
996             * <script type="text/javascript">
997             *     (function() {
998             *         var board = JXG.JSXGraph.initBoard('JXG987c-394f-11e6-af4a-901b0e1b8723',
999             *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1000             *     var p1, p2, p3, c, a, s;
1001             *
1002             *     p1 = board.create('point',[0,0]);
1003             *     p2 = board.create('point',[5,0]);
1004             *     p3 = board.create('point',[0,5]);
1005             *
1006             *     c1 = board.create('circle',[p1, p2]);
1007             *
1008             *     a = board.create('angle',[p2, p1, p3], {radius: 3});
1009             *
1010             *     a.setAngle(function() {
1011             *         return Math.PI / 3;
1012             *     });
1013             *     board.update();
1014             *
1015             *     })();
1016             *
1017             * </script><pre>
1018             *
1019             * @example
1020             * var p1, p2, p3, c, a, s;
1021             *
1022             * p1 = board.create('point',[0,0]);
1023             * p2 = board.create('point',[5,0]);
1024             * p3 = board.create('point',[0,5]);
1025             *
1026             * c1 = board.create('circle',[p1, p2]);
1027             *
1028             * a = board.create('angle',[p2, p1, p3], {radius:3});
1029             * s = board.create('slider',[[-2,1], [2,1], [0, Math.PI*0.5, 2*Math.PI]]);
1030             *
1031             * a.setAngle(function() {
1032             *     return s.Value();
1033             * });
1034             * board.update();
1035             *
1036             * </pre><div id="JXG99957b1c-394f-11e6-af4a-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
1037             * <script type="text/javascript">
1038             *     (function() {
1039             *         var board = JXG.JSXGraph.initBoard('JXG99957b1c-394f-11e6-af4a-901b0e1b8723',
1040             *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1041             *     var p1, p2, p3, c, a, s;
1042             *
1043             *     p1 = board.create('point',[0,0]);
1044             *     p2 = board.create('point',[5,0]);
1045             *     p3 = board.create('point',[0,5]);
1046             *
1047             *     c1 = board.create('circle',[p1, p2]);
1048             *
1049             *     a = board.create('angle',[p2, p1, p3], {radius: 3});
1050             *     s = board.create('slider',[[-2,1], [2,1], [0, Math.PI*0.5, 2*Math.PI]]);
1051             *
1052             *     a.setAngle(function() {
1053             *         return s.Value();
1054             *     });
1055             *     board.update();
1056             *
1057             *     })();
1058             *
1059             * </script><pre>
1060             *
1061             */
1062             el.setAngle = function (val) {
1063                 var t,
1064                     p = this.anglepoint,
1065                     q = this.radiuspoint;
1066 
1067                 if (p.draggable()) {
1068                     t = this.board.create('transform', [val, this.center], {type: 'rotate'});
1069                     p.addTransform(q, t);
1070                     p.isDraggable = false;
1071                     p.setParents(q);
1072                 }
1073                 return this;
1074             };
1075 
1076             /**
1077             * Frees an angle from a prescribed value. This is only relevant if the angle size has been set by
1078             * setAngle() previously. The anglepoint is set to a free point.
1079             * @name free
1080             * @function
1081             * @returns {Object} Pointer to the angle element..
1082             * @memberOf Angle.prototype
1083             */
1084             el.free = function () {
1085                 var p = this.anglepoint;
1086                 if (p.transformations.length > 0) {
1087                     p.transformations.pop();
1088                     p.isDraggable = true;
1089                     p.parents = [];
1090                 }
1091                 return this;
1092             };
1093 
1094             el.setParents(points); // Important: This overwrites the parents order in underlying sector
1095 
1096         } // end '3points'
1097 
1098         // GEONExT compatible labels.
1099         if (Type.exists(el.visProp.text)) {
1100             el.label.setText(Type.evaluate(el.visProp.text));
1101         }
1102 
1103         el.elType = 'angle';
1104         el.type = Const.OBJECT_TYPE_ANGLE;
1105         el.subs = {};
1106 
1107         el.updateDataArraySquare = function () {
1108             var A, B, C,
1109                 r = this.Radius(),
1110                 d1, d2,
1111                 v, l1, l2;
1112 
1113 
1114             if (type === '2lines') {
1115                 // This is necessary to update this.point1, this.point2, this.point3.
1116                 this.updateDataArraySector();
1117             }
1118 
1119             A = this.point2;
1120             B = this.point1;
1121             C = this.point3;
1122 
1123             A = A.coords.usrCoords;
1124             B = B.coords.usrCoords;
1125             C = C.coords.usrCoords;
1126 
1127             d1 = Geometry.distance(A, B, 3);
1128             d2 = Geometry.distance(C, B, 3);
1129 
1130             // In case of type=='2lines' this is redundant, because r == d1 == d2
1131             A = [1, B[1] + (A[1] - B[1]) * r / d1, B[2] + (A[2] - B[2]) * r / d1];
1132             C = [1, B[1] + (C[1] - B[1]) * r / d2, B[2] + (C[2] - B[2]) * r / d2];
1133 
1134             v = Mat.crossProduct(C, B);
1135             l1 = [-A[1] * v[1] - A[2] * v[2], A[0] * v[1], A[0] * v[2]];
1136             v = Mat.crossProduct(A, B);
1137             l2 = [-C[1] * v[1] - C[2] * v[2], C[0] * v[1], C[0] * v[2]];
1138 
1139             v = Mat.crossProduct(l1, l2);
1140             v[1] /= v[0];
1141             v[2] /= v[0];
1142 
1143             this.dataX = [B[1], A[1], v[1], C[1], B[1]];
1144             this.dataY = [B[2], A[2], v[2], C[2], B[2]];
1145 
1146             this.bezierDegree = 1;
1147         };
1148 
1149         el.updateDataArrayNone = function () {
1150             this.dataX = [NaN];
1151             this.dataY = [NaN];
1152             this.bezierDegree = 1;
1153         };
1154 
1155         el.updateDataArray = function () {
1156             var type = Type.evaluate(this.visProp.type),
1157                 deg = Geometry.trueAngle(this.point2, this.point1, this.point3),
1158                 vp_s = Type.evaluate(this.visProp.selection);
1159 
1160             if ((vp_s === 'minor' && deg > 180.0) ||
1161                     (vp_s === 'major' && deg < 180.0)) {
1162                 deg = 360.0 - deg;
1163             }
1164 
1165             if (Math.abs(deg - 90.0) < Type.evaluate(this.visProp.orthosensitivity) + Mat.eps) {
1166                 type = Type.evaluate(this.visProp.orthotype);
1167             }
1168 
1169             if (type === 'none') {
1170                 this.updateDataArrayNone();
1171             } else if (type === 'square') {
1172                 this.updateDataArraySquare();
1173             } else if (type === 'sector') {
1174                 this.updateDataArraySector();
1175             } else if (type === 'sectordot') {
1176                 this.updateDataArraySector();
1177                 if (!this.dot.visProp.visible) {
1178                     this.dot.setAttribute({visible: true});
1179                 }
1180             }
1181 
1182             if (!this.visProp.visible || (type !== 'sectordot' && this.dot.visProp.visible)) {
1183                 this.dot.setAttribute({visible: false});
1184             }
1185         };
1186 
1187         /**
1188          * Indicates a right angle. Invisible by default, use <tt>dot.visible: true</tt> to show.
1189          * Though this dot indicates a right angle, it can be visible even if the angle is not a right
1190          * one.
1191          * @type JXG.Point
1192          * @name dot
1193          * @memberOf Angle.prototype
1194          */
1195         attrsub = Type.copyAttributes(attributes, board.options, 'angle', 'dot');
1196         el.dot = board.create('point', [function () {
1197             var A, B, r, d, a2, co, si, mat,
1198                 vp_s;
1199 
1200             if (Type.exists(el.dot) && !el.dot.visProp.visible) {
1201                 return [0, 0];
1202             }
1203 
1204             A = el.point2.coords.usrCoords;
1205             B = el.point1.coords.usrCoords;
1206             r = el.Radius();
1207             d = Geometry.distance(A, B, 3);
1208             a2 = Geometry.rad(el.point2, el.point1, el.point3);
1209 
1210             vp_s = Type.evaluate(el.visProp.selection);
1211             if ((vp_s === 'minor' && a2 > Math.PI) ||
1212                     (vp_s === 'major' && a2 < Math.PI)) {
1213                 a2 = -(2 * Math.PI - a2);
1214             }
1215             a2 *= 0.5;
1216 
1217             co = Math.cos(a2);
1218             si = Math.sin(a2);
1219 
1220             A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d];
1221 
1222             mat = [
1223                 [1, 0, 0],
1224                 [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5],
1225                 [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5,  co * 0.5]
1226             ];
1227             return Mat.matVecMult(mat, A);
1228         }], attrsub);
1229 
1230         el.dot.dump = false;
1231         el.subs.dot = el.dot;
1232 
1233         if (type === '2lines') {
1234             for (i = 0; i < 2; i++) {
1235                 board.select(parents[i]).addChild(el.dot);
1236             }
1237         } else {
1238             for (i = 0; i < 3; i++) {
1239                 board.select(points[i]).addChild(el.dot);
1240             }
1241         }
1242 
1243         // documented in GeometryElement
1244         el.getLabelAnchor = function () {
1245             var vec, dx = 12,
1246                 A, B, r, d, a2, co, si, mat,
1247                 vp_s = Type.evaluate(el.visProp.selection),
1248                 l_vp = this.label ? this.label.visProp : this.visProp.label;
1249 
1250             // If this is uncommented, the angle label can not be dragged
1251             //if (Type.exists(this.label)) {
1252             //    this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
1253             //}
1254 
1255             if (Type.exists(this.label.visProp.fontSize)) {
1256                 dx = Type.evaluate(this.label.visProp.fontSize);
1257             }
1258             dx /= this.board.unitX;
1259 
1260             A = el.point2.coords.usrCoords;
1261             B = el.point1.coords.usrCoords;
1262             r = el.Radius();
1263             d = Geometry.distance(A, B, 3);
1264             a2 = Geometry.rad(el.point2, el.point1, el.point3);
1265             if ((vp_s === 'minor' && a2 > Math.PI) ||
1266                     (vp_s === 'major' && a2 < Math.PI)) {
1267                 a2 = -(2 * Math.PI - a2);
1268             }
1269             a2 *= 0.5;
1270             co = Math.cos(a2);
1271             si = Math.sin(a2);
1272 
1273             A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d];
1274 
1275             mat = [
1276                 [1, 0, 0],
1277                 [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5],
1278                 [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5,  co * 0.5]
1279             ];
1280             vec = Mat.matVecMult(mat, A);
1281             vec[1] /= vec[0];
1282             vec[2] /= vec[0];
1283             vec[0] /= vec[0];
1284 
1285             d = Geometry.distance(vec, B, 3);
1286             vec = [vec[0], B[1] + (vec[1] - B[1]) * (r + dx) / d,  B[2] + (vec[2] - B[2]) * (r + dx) / d];
1287 
1288             l_vp.position = Geometry.calcLabelQuadrant(Geometry.rad([1,0],[0,0],vec));
1289 
1290             return new Coords(Const.COORDS_BY_USER, vec, this.board);
1291         };
1292 
1293         /**
1294          * Returns the value of the angle in Radians.
1295          * @memberOf Angle.prototype
1296          * @name Value
1297          * @function
1298          * @returns {Number} The angle value in Radians
1299          */
1300         el.Value = function () {
1301             return Geometry.rad(this.point2, this.point1, this.point3);
1302         };
1303 
1304         el.methodMap = Type.deepCopy(el.methodMap, {
1305             Value: 'Value',
1306             setAngle: 'setAngle',
1307             free: 'free'
1308         });
1309 
1310         return el;
1311     };
1312 
1313     JXG.registerElement('angle', JXG.createAngle);
1314 
1315     /**
1316      * @class A non-reflex angle is the acute or obtuse instance of an angle.
1317      * It is defined by a center, one point that
1318      * defines the radius, and a third point that defines the angle of the sector.
1319      * @pseudo
1320      * @name NonReflexAngle
1321      * @augments Angle
1322      * @constructor
1323      * @type Sector
1324      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
1325      * @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
1326      * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
1327      * @example
1328      * // Create a non-reflex angle out of three free points
1329      * var p1 = board.create('point', [5.0, 3.0]),
1330      *     p2 = board.create('point', [1.0, 0.5]),
1331      *     p3 = board.create('point', [1.5, 5.0]),
1332      *
1333      *     a = board.create('nonreflexangle', [p1, p2, p3], {radius: 2}),
1334      *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
1335      * </pre><div class="jxgbox" id="JXGd0ab6d6b-63a7-48b2-8749-b02bb5e744f9" style="width: 300px; height: 300px;"></div>
1336      * <script type="text/javascript">
1337      * (function () {
1338      *   var board = JXG.JSXGraph.initBoard('JXGd0ab6d6b-63a7-48b2-8749-b02bb5e744f9', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
1339      *     p1 = board.create('point', [5.0, 3.0]),
1340      *     p2 = board.create('point', [1.0, 0.5]),
1341      *     p3 = board.create('point', [1.5, 5.0]),
1342      *
1343      *     a = board.create('nonreflexangle', [p1, p2, p3], {radius: 2}),
1344      *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
1345      * })();
1346      * </script><pre>
1347      */
1348     JXG.createNonreflexAngle = function (board, parents, attributes) {
1349         var el;
1350 
1351         attributes.selection = 'minor';
1352         el = JXG.createAngle(board, parents, attributes);
1353 
1354         // Documented in createAngle
1355         el.Value = function () {
1356             var v = Geometry.rad(this.point2, this.point1, this.point3);
1357             return (v < Math.PI) ? v : 2.0 * Math.PI - v;
1358         };
1359         return el;
1360     };
1361 
1362     JXG.registerElement('nonreflexangle', JXG.createNonreflexAngle);
1363 
1364     /**
1365      * @class A reflex angle is the neither acute nor obtuse instance of an angle.
1366      * It is defined by a center, one point that
1367      * defines the radius, and a third point that defines the angle of the sector.
1368      * @pseudo
1369      * @name ReflexAngle
1370      * @augments Angle
1371      * @constructor
1372      * @type Sector
1373      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
1374      * @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
1375      * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
1376      * @example
1377      * // Create a non-reflex angle out of three free points
1378      * var p1 = board.create('point', [5.0, 3.0]),
1379      *     p2 = board.create('point', [1.0, 0.5]),
1380      *     p3 = board.create('point', [1.5, 5.0]),
1381      *
1382      *     a = board.create('reflexangle', [p1, p2, p3], {radius: 2}),
1383      *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
1384      * </pre><div class="jxgbox" id="JXGf2a577f2-553d-4f9f-a895-2d6d4b8c60e8" style="width: 300px; height: 300px;"></div>
1385      * <script type="text/javascript">
1386      * (function () {
1387      * var board = JXG.JSXGraph.initBoard('JXGf2a577f2-553d-4f9f-a895-2d6d4b8c60e8', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
1388      *     p1 = board.create('point', [5.0, 3.0]),
1389      *     p2 = board.create('point', [1.0, 0.5]),
1390      *     p3 = board.create('point', [1.5, 5.0]),
1391      *
1392      *     a = board.create('reflexangle', [p1, p2, p3], {radius: 2}),
1393      *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
1394      * })();
1395      * </script><pre>
1396      */
1397     JXG.createReflexAngle = function (board, parents, attributes) {
1398         var el;
1399 
1400         attributes.selection = 'major';
1401         el = JXG.createAngle(board, parents, attributes);
1402 
1403         // Documented in createAngle
1404         el.Value = function () {
1405             var v = Geometry.rad(this.point2, this.point1, this.point3);
1406             return (v >= Math.PI) ? v : 2.0 * Math.PI - v;
1407         };
1408         return el;
1409     };
1410 
1411     JXG.registerElement('reflexangle', JXG.createReflexAngle);
1412 
1413     return {
1414         createSector: JXG.createSector,
1415         createCircumcircleSector: JXG.createCircumcircleSector,
1416         createMinorSector: JXG.createMinorSector,
1417         createMajorSector: JXG.createMajorSector,
1418         createAngle: JXG.createAngle,
1419         createReflexAngle: JXG.createReflexAngle,
1420         createNonreflexAngle: JXG.createNonreflexAngle
1421     };
1422 });
1423