1 /*
  2     Copyright 2008-2013
  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      *
 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 id="49f59123-f013-4681-bfd9-338b89893156" style="width: 300px; height: 300px;"></div>
 85      * <script type="text/javascript">
 86      * (function () {
 87      *   var board = JXG.JSXGraph.initBoard('49f59123-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 id="bb9e2809-9895-4ff1-adfa-c9c71d50aa53" style="width: 300px; height: 300px;"></div>
110      * <script type="text/javascript">
111      * (function () {
112      *   var board = JXG.JSXGraph.initBoard('bb9e2809-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     JXG.createSector = function (board, parents, attributes) {
127         var el, i, attr,
128             type = 'invalid',
129             s, v,
130             points = ['center', 'radiuspoint', 'anglepoint'];
131 
132         // Three points?
133         if (Type.isPoint(parents[0]) && Type.isPoint(parents[1]) && Type.isPoint(parents[2])) {
134             type = '3points';
135         } else if (parents[0].elementClass === Const.OBJECT_CLASS_LINE &&
136                     parents[1].elementClass === Const.OBJECT_CLASS_LINE &&
137                     (Type.isArray(parents[2]) || Type.isNumber(parents[2])) &&
138                     (Type.isArray(parents[3]) || Type.isNumber(parents[3])) &&
139                     (Type.isNumber(parents[4]) || Type.isFunction(parents[4]))) {
140             type = '2lines';
141         }
142 
143         if (type === 'invalid') {
144              // Second try for 3 point sector
145             try {
146 
147                 for (i = 0; i < parents.length; i++) {
148                     if (!Type.isPoint(parents[i])) {
149                         attr = Type.copyAttributes(attributes, board.options, 'sector', points[i]);
150                         parents[i] = board.create('point', parents[i], attr);
151                     }
152                 }
153 
154                 type = '3points';
155 
156             } catch (e) {
157                 throw new Error("JSXGraph: Can't create Sector with parent types '" +
158                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" +
159                     (typeof parents[2]) + "'.");
160             }
161         }
162 
163         attr = Type.copyAttributes(attributes, board.options, 'sector');
164         el = board.create('curve', [[0], [0]], attr);
165         el.type = Const.OBJECT_TYPE_SECTOR;
166         el.elType = 'sector';
167 
168         if (type === '2lines') {
169             el.Radius = function () {
170                 return Type.evaluate(parents[4]);
171             };
172 
173             el.line1 = board.select(parents[0]);
174             el.line2 = board.select(parents[1]);
175 
176             el.line1.addChild(el);
177             el.line2.addChild(el);
178             el.parents = [parents[0].id, parents[1].id];
179 
180             el.point1 = {visProp: {}};
181             el.point2 = {visProp: {}};
182             el.point3 = {visProp: {}};
183 
184             /* Intersection point */
185             s = Geometry.meetLineLine(el.line1.stdform, el.line2.stdform, 0, board);
186 
187             if (Type.isArray(parents[2])) {
188                 /* project p1 to l1 */
189                 if (parents[2].length === 2) {
190                     parents[2] = [1].concat(parents[2]);
191                 }
192                 v = [0, el.line1.stdform[1], el.line1.stdform[2]];
193                 v = Mat.crossProduct(v, parents[2]);
194                 v = Geometry.meetLineLine(v, el.line1.stdform, 0, board);
195                 v = Statistics.subtract(v.usrCoords, s.usrCoords);
196                 el.direction1 = (Mat.innerProduct(v, [0, el.line1.stdform[2], -el.line1.stdform[1]], 3) >= 0) ? +1 : -1;
197             } else {
198                 el.direction1 = (parents[2] >= 0) ? 1 : -1;
199             }
200 
201             if (Type.isArray(parents[3])) {
202                 /* project p2 to l2 */
203                 if (parents[3].length === 2) {
204                     parents[3] = [1].concat(parents[3]);
205                 }
206                 v = [0, el.line2.stdform[1], el.line2.stdform[2]];
207                 v = Mat.crossProduct(v, parents[3]);
208                 v = Geometry.meetLineLine(v, el.line2.stdform, 0, board);
209                 v = Statistics.subtract(v.usrCoords, s.usrCoords);
210                 el.direction2 = (Mat.innerProduct(v, [0, el.line2.stdform[2], -el.line2.stdform[1]], 3) >= 0) ? +1 : -1;
211             } else {
212                 el.direction2 = (parents[3] >= 0) ? 1 : -1;
213             }
214 
215             el.updateDataArray = function () {
216                 var r, l1, l2, A, B, C, ar;
217 
218                 l1 = this.line1;
219                 l2 = this.line2;
220 
221                 // Intersection point of the lines
222                 B = Mat.crossProduct(l1.stdform, l2.stdform);
223                 B[1] /= B[0];
224                 B[2] /= B[0];
225                 B[0] /= B[0];
226 
227                 // First point
228                 r = this.direction1 * this.Radius();
229                 A = Statistics.add(B, [0, r * l1.stdform[2], -r * l1.stdform[1]]);
230 
231                 // Second point
232                 r = this.direction2 * this.Radius();
233                 C = Statistics.add(B, [0, r * l2.stdform[2], -r * l2.stdform[1]]);
234 
235                 this.point2.coords = new Coords(Const.COORDS_BY_USER, A, el.board);
236                 this.point1.coords = new Coords(Const.COORDS_BY_USER, B, el.board);
237                 this.point3.coords = new Coords(Const.COORDS_BY_USER, C, el.board);
238 
239                 if (Math.abs(A[0]) < Mat.eps || Math.abs(B[0]) < Mat.eps || Math.abs(C[0]) < Mat.eps) {
240                     this.dataX = [NaN];
241                     this.dataY = [NaN];
242                     return;
243                 }
244 
245                 ar = Geometry.bezierArc(A, B, C, true, 1);
246 
247                 this.dataX = ar[0];
248                 this.dataY = ar[1];
249 
250                 this.bezierDegree = 3;
251             };
252 
253             el.methodMap = JXG.deepCopy(el.methodMap, {
254                 radius: 'getRadius',
255                 getRadius: 'getRadius',
256                 setRadius: 'setRadius'
257             });
258 
259             el.prepareUpdate().update();
260 
261         // end '2lines'
262 
263         } else if (type === '3points') {
264 
265             /**
266             * Midpoint of the sector.
267             * @memberOf Sector.prototype
268             * @name point1
269             * @type JXG.Point
270             */
271             el.point1 = board.select(parents[0]);
272 
273             /**
274             * This point together with {@link Sector#point1} defines the radius..
275             * @memberOf Sector.prototype
276             * @name point2
277             * @type JXG.Point
278             */
279             el.point2 = board.select(parents[1]);
280 
281             /**
282             * Defines the sector's angle.
283             * @memberOf Sector.prototype
284             * @name point3
285             * @type JXG.Point
286             */
287             el.point3 = board.select(parents[2]);
288 
289             /* Add arc as child to defining points */
290             el.point1.addChild(el);
291             el.point2.addChild(el);
292             el.point3.addChild(el);
293 
294             // useDirection is necessary for circumCircleSectors
295             el.useDirection = attributes.usedirection;
296             el.parents = [parents[0].id, parents[1].id, parents[2].id];
297 
298             /**
299             * Defines the sectors orientation in case of circumCircleSectors.
300             * @memberOf Sector.prototype
301             * @name point4
302             * @type JXG.Point
303             */
304             if (Type.exists(parents[3])) {
305                 el.point4 = board.select(parents[3]);
306                 el.point4.addChild(el);
307                 // el.parents.push(parents[3].id);
308             }
309 
310             el.methodMap = JXG.deepCopy(el.methodMap, {
311                 center: 'center',
312                 radiuspoint: 'radiuspoint',
313                 anglepoint: 'anglepoint',
314                 radius: 'getRadius',
315                 getRadius: 'getRadius',
316                 setRadius: 'setRadius'
317             });
318 
319             /**
320             * documented in JXG.Curve
321             * @ignore
322             */
323             el.updateDataArray = function () {
324                 var ar, det, p0c, p1c, p2c,
325                     A = this.point2,
326                     B = this.point1,
327                     C = this.point3;
328 
329                 if (!A.isReal || !B.isReal || !C.isReal) {
330                     this.dataX = [NaN];
331                     this.dataY = [NaN];
332                     return;
333                 }
334 
335                 // This is true for circumCircleSectors. In that case there is
336                 // a fourth parent element: [midpoint, point1, point3, point2]
337                 if (this.useDirection && Type.exists(this.point4)) {
338                     p0c = this.point2.coords.usrCoords;
339                     p1c = this.point4.coords.usrCoords;
340                     p2c = this.point3.coords.usrCoords;
341                     det = (p0c[1] - p2c[1]) * (p0c[2] - p1c[2]) - (p0c[2] - p2c[2]) * (p0c[1] - p1c[1]);
342 
343                     if (det >= 0.0) {
344                         C = this.point2;
345                         A = this.point3;
346                     }
347                 }
348 
349                 A = A.coords.usrCoords;
350                 B = B.coords.usrCoords;
351                 C = C.coords.usrCoords;
352 
353                 ar = Geometry.bezierArc(A, B, C, true, 1);
354 
355                 this.dataX = ar[0];
356                 this.dataY = ar[1];
357                 this.bezierDegree = 3;
358             };
359 
360             /**
361             * Returns the radius of the sector.
362             * @memberOf Sector.prototype
363             * @name Radius
364             * @function
365             * @returns {Number} The distance between {@link Sector#point1} and {@link Sector#point2}.
366             */
367             el.Radius = function () {
368                 return this.point2.Dist(this.point1);
369             };
370 
371         }   // end '3points'
372 
373         el.center = el.point1;
374         el.radiuspoint = el.point2;
375         el.anglepoint = el.point3;
376 
377         // Default hasPoint method. Documented in geometry element
378         el.hasPointCurve = function (x, y) {
379             var angle, alpha, beta,
380                 prec = this.board.options.precision.hasPoint / (this.board.unitX),
381                 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
382                 r = this.Radius(),
383                 dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint),
384                 has = (Math.abs(dist - r) < prec);
385 
386             if (has) {
387                 angle = Geometry.rad(this.point2, this.center, checkPoint.usrCoords.slice(1));
388                 alpha = 0;
389                 beta = Geometry.rad(this.point2, this.center, this.point3);
390 
391                 if (angle < alpha || angle > beta) {
392                     has = false;
393                 }
394             }
395 
396             return has;
397         };
398 
399         /**
400         * Checks whether (x,y) is within the area defined by the sector.
401         * @memberOf Sector.prototype
402         * @name hasPointSector
403         * @function
404         * @param {Number} x Coordinate in x direction, screen coordinates.
405         * @param {Number} y Coordinate in y direction, screen coordinates.
406         * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise.
407         */
408         el.hasPointSector = function (x, y) {
409             var angle,
410                 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
411                 r = this.Radius(),
412                 dist = this.point1.coords.distance(Const.COORDS_BY_USER, checkPoint),
413                 has = (dist < r);
414 
415             if (has) {
416                 angle = Geometry.rad(this.point2, this.point1, checkPoint.usrCoords.slice(1));
417 
418                 if (angle > Geometry.rad(this.point2, this.point1, this.point3)) {
419                     has = false;
420                 }
421             }
422             return has;
423         };
424 
425         el.hasPoint = function (x, y) {
426             if (this.visProp.highlightonsector) {
427                 return this.hasPointSector(x, y);
428             }
429 
430             return this.hasPointCurve(x, y);
431         };
432 
433         // documented in GeometryElement
434         el.getTextAnchor = function () {
435             return this.point1.coords;
436         };
437 
438         // documented in GeometryElement
439         // this method is very similar to arc.getLabelAnchor()
440         // there are some additions in the arc version though, mainly concerning
441         // "major" and "minor" arcs. but maybe these methods can be merged.
442         el.getLabelAnchor = function () {
443             var coords, vecx, vecy, len,
444                 angle = Geometry.rad(this.point2, this.point1, this.point3),
445                 dx = 13 / this.board.unitX,
446                 dy = 13 / this.board.unitY,
447                 p2c = this.point2.coords.usrCoords,
448                 pmc = this.point1.coords.usrCoords,
449                 bxminusax = p2c[1] - pmc[1],
450                 byminusay = p2c[2] - pmc[2];
451 
452             if (Type.exists(this.label)) {
453                 this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
454             }
455 
456             coords = new Coords(Const.COORDS_BY_USER, [
457                 pmc[1] + Math.cos(angle * 0.5) * bxminusax - Math.sin(angle * 0.5) * byminusay,
458                 pmc[2] + Math.sin(angle * 0.5) * bxminusax + Math.cos(angle * 0.5) * byminusay
459             ], this.board);
460 
461             vecx = coords.usrCoords[1] - pmc[1];
462             vecy = coords.usrCoords[2] - pmc[2];
463 
464             len = Math.sqrt(vecx * vecx + vecy * vecy);
465             vecx = vecx * (len + dx) / len;
466             vecy = vecy * (len + dy) / len;
467 
468             return new Coords(Const.COORDS_BY_USER, [pmc[1] + vecx, pmc[2] + vecy], this.board);
469         };
470 
471         /**
472          * Overwrite the Radius method of the sector.
473          * Used in {@link GeometryElement#setAttribute}.
474          * @param {Number, Function} value New radius.
475          */
476         el.setRadius = function (value) {
477             el.Radius = function () {
478                 return Type.evaluate(value);
479             };
480         };
481 
482         /**
483          * deprecated
484          * @ignore
485          */
486         el.getRadius = function () {
487             return this.Radius();
488         };
489 
490         el.prepareUpdate().update();
491 
492         return el;
493     };
494 
495     JXG.registerElement('sector', JXG.createSector);
496 
497 
498     /**
499      * @class A circumcircle sector is different from a {@link Sector} mostly in the way the parent elements are interpreted.
500      * At first, the circum centre is determined from the three given points. Then the sector is drawn from <tt>p1</tt> through
501      * <tt>p2</tt> to <tt>p3</tt>.
502      * @pseudo
503      * @name CircumcircleSector
504      * @augments Sector
505      * @constructor
506      * @type Sector
507      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
508      * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A circumcircle sector is defined by the circumcircle which is determined
509      * by these three given points. The circumcircle sector is always drawn from <tt>p1</tt> through <tt>p2</tt> to <tt>p3</tt>.
510      * @example
511      * // Create an arc out of three free points
512      * var p1 = board.create('point', [1.5, 5.0]),
513      *     p2 = board.create('point', [1.0, 0.5]),
514      *     p3 = board.create('point', [5.0, 3.0]),
515      *
516      *     a = board.create('circumcirclesector', [p1, p2, p3]);
517      * </pre><div id="695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04" style="width: 300px; height: 300px;"></div>
518      * <script type="text/javascript">
519      * (function () {
520  *   var board = JXG.JSXGraph.initBoard('695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
521  *     p1 = board.create('point', [1.5, 5.0]),
522  *     p2 = board.create('point', [1.0, 0.5]),
523  *     p3 = board.create('point', [5.0, 3.0]),
524  *
525  *     a = board.create('circumcirclesector', [p1, p2, p3]);
526  * })();
527      * </script><pre>
528      */
529     JXG.createCircumcircleSector = function (board, parents, attributes) {
530         var el, mp, attr;
531 
532         if ((Type.isPoint(parents[0])) && (Type.isPoint(parents[1])) && (Type.isPoint(parents[2]))) {
533             attr = Type.copyAttributes(attributes, board.options, 'circumcirclesector', 'center');
534             mp = board.create('circumcenter', [parents[0], parents[1], parents[2]], attr);
535 
536             mp.dump = false;
537 
538             attr = Type.copyAttributes(attributes, board.options, 'circumcirclesector');
539             el = board.create('sector', [mp, parents[0], parents[2], parents[1]], attr);
540 
541             el.elType = 'circumcirclesector';
542             el.parents = [parents[0].id, parents[1].id, parents[2].id];
543 
544             /**
545              * Center of the circumcirclesector
546              * @memberOf CircumcircleSector.prototype
547              * @name center
548              * @type Circumcenter
549              */
550             el.center = mp;
551             el.subs = {
552                 center: mp
553             };
554         } else {
555             throw new Error("JSXGraph: Can't create circumcircle sector with parent types '" +
556                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'.");
557         }
558 
559         return el;
560     };
561 
562     JXG.registerElement('circumcirclesector', JXG.createCircumcircleSector);
563 
564 
565     /**
566      * @class The angle element is used to denote an angle defined by three points. Visually it is just a {@link Sector}
567      * element with a radius not defined by the parent elements but by an attribute <tt>radius</tt>. As opposed to the sector,
568      * an angle has two angle points and no radius point.
569      * Sector is displayed if type=="sector".
570      * If type=="square", instead of a sector a parallelogram is displayed.
571      * In case of type=="auto", a square is displayed if the angle is near orthogonal.
572      * If no name is provided the angle label is automatically set to a lower greek letter.
573      * @pseudo
574      * @name Angle
575      * @augments Sector
576      * @constructor
577      * @type Sector
578      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
579      * First possiblity of input parameters are:
580      * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 An angle is always drawn counterclockwise from <tt>p1</tt> to
581      * <tt>p3</tt> around <tt>p2</tt>.
582      *
583      * Second possibility of input parameters are:
584      * @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.
585      * The two legs which define the angle are given by two coordinate arrays.
586      * The points given by these coordinate arrays are projected initially (i.e. only once) onto the two lines.
587      * The other possibility is to supply directions (+/- 1).
588      *
589      * @example
590      * // Create an angle out of three free points
591      * var p1 = board.create('point', [5.0, 3.0]),
592      *     p2 = board.create('point', [1.0, 0.5]),
593      *     p3 = board.create('point', [1.5, 5.0]),
594      *
595      *     a = board.create('angle', [p1, p2, p3]);
596      * </pre><div id="a34151f9-bb26-480a-8d6e-9b8cbf789ae5" style="width: 300px; height: 300px;"></div>
597      * <script type="text/javascript">
598      * (function () {
599      *   var board = JXG.JSXGraph.initBoard('a34151f9-bb26-480a-8d6e-9b8cbf789ae5', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
600      *     p1 = board.create('point', [5.0, 3.0]),
601      *     p2 = board.create('point', [1.0, 0.5]),
602      *     p3 = board.create('point', [1.5, 5.0]),
603      *
604      *     a = board.create('angle', [p1, p2, p3]);
605      * })();
606      * </script><pre>
607      *
608      * @example
609      * // Create an angle out of two lines and two directions
610      * var p1 = board.create('point', [-1, 4]),
611      *  p2 = board.create('point', [4, 1]),
612      *  q1 = board.create('point', [-2, -3]),
613      *  q2 = board.create('point', [4,3]),
614      *
615      *  li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
616      *  li2 = board.create('line', [q1,q2], {lastArrow:true}),
617      *
618      *  a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }),
619      *  a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 });
620      *
621      * </pre><div id="3a667ddd-63dc-4594-b5f1-afac969b371f" style="width: 300px; height: 300px;"></div>
622      * <script type="text/javascript">
623      * (function () {
624      *   var board = JXG.JSXGraph.initBoard('3a667ddd-63dc-4594-b5f1-afac969b371f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
625      *     p1 = board.create('point', [-1, 4]),
626      *     p2 = board.create('point', [4, 1]),
627      *     q1 = board.create('point', [-2, -3]),
628      *     q2 = board.create('point', [4,3]),
629      *
630      *     li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
631      *     li2 = board.create('line', [q1,q2], {lastArrow:true}),
632      *
633      *     a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }),
634      *     a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 });
635      * })();
636      * </script><pre>
637      */
638     JXG.createAngle = function (board, parents, attributes) {
639         var el, radius, text, attr, attrsub,
640             i, dot,
641             type = 'invalid';
642 
643         // Three points?
644         if (Type.isPoint(parents[0]) && Type.isPoint(parents[1]) && Type.isPoint(parents[2])) {
645             type = '3points';
646         } else if (parents[0].elementClass === Const.OBJECT_CLASS_LINE &&
647                     parents[1].elementClass === Const.OBJECT_CLASS_LINE &&
648                     (Type.isArray(parents[2]) || Type.isNumber(parents[2])) &&
649                     (Type.isArray(parents[3]) || Type.isNumber(parents[3]))) {
650             type = '2lines';
651         }
652 
653         if (type === 'invalid') {
654             throw new Error("JSXGraph: Can't create angle with parent types '" +
655                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'.");
656 
657         }
658 
659         attr = Type.copyAttributes(attributes, board.options, 'angle');
660 
661         //  If empty, create a new name
662         text = attr.name;
663         if (!Type.exists(text) || text === '') {
664             text = board.generateName({type: Const.OBJECT_TYPE_ANGLE});
665             attr.name = text;
666         }
667 
668         if (Type.exists(attr.radius)) {
669             radius = attr.radius;
670         } else {
671             radius = 0;
672         }
673 
674         if (type === '2lines') {
675             el = board.create('sector', [parents[0], parents[1], parents[2], parents[3], radius], attr);
676 
677             el.updateDataArraySector = el.updateDataArray;
678 
679             // Todo
680             el.setAngle = function (val) {};
681             el.free = function (val) {};
682 
683         } else {
684             el = board.create('sector', [parents[1], parents[0], parents[2]], attr);
685 
686             /**
687              * The point defining the radius of the angle element. Alias for {@link Angle.prototype#radiuspoint}.
688              * @type JXG.Point
689              * @name point
690              * @memberOf Angle.prototype
691              */
692             el.point = el.point2 = el.radiuspoint = parents[0];
693 
694             /**
695              * Helper point for angles of type 'square'.
696              * @type JXG.Point
697              * @name pointsquare
698              * @memberOf Angle.prototype
699              */
700             el.pointsquare = el.point3 = el.anglepoint = parents[2];
701 
702             el.Radius = function () {
703                 return Type.evaluate(radius);
704             };
705 
706             el.updateDataArraySector = function () {
707                 var A = this.point2,
708                     B = this.point1,
709                     C = this.point3,
710                     r = this.Radius(),
711                     d = B.Dist(A),
712                     ar;
713 
714                 A = A.coords.usrCoords;
715                 B = B.coords.usrCoords;
716                 C = C.coords.usrCoords;
717 
718                 A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d];
719                 C = [1, B[1] + (C[1] - B[1]) * r / d, B[2] + (C[2] - B[2]) * r / d];
720 
721                 ar = Geometry.bezierArc(A, B, C, true, 1);
722 
723                 this.dataX = ar[0];
724                 this.dataY = ar[1];
725                 this.bezierDegree = 3;
726             };
727 
728             /**
729             * Set an angle to a prescribed value given in radians. This is only possible if the third point of the angle, i.e.
730             * the anglepoint is a free point.
731             * @name setAngle
732             * @function
733             * @param {Number|Function} val Number or Function which returns the size of the angle in Radians
734             * @returns {Object} Pointer to the angle element..
735             * @memberOf Angle.prototype
736             */
737             el.setAngle = function (val) {
738                 var t,
739                     p = this.anglepoint,
740                     q = this.radiuspoint;
741 
742                 if (p.draggable()) {
743                     t = this.board.create('transform', [val, this.center], {type: 'rotate'});
744                     p.addTransform(q, t);
745                     p.isDraggable = false;
746                     p.parents = [q];
747                 }
748                 return this;
749             };
750 
751             /**
752             * Frees an angle from a prescribed value. This is only relevant if the angle size has been set by
753             * setAngle() previously. The anglepoint is set to a free point.
754             * @name free
755             * @function
756             * @returns {Object} Pointer to the angle element..
757             * @memberOf Angle.prototype
758             */
759             el.free = function () {
760                 var p = this.anglepoint;
761                 if (p.transformations.length > 0) {
762                     p.transformations.pop();
763                     p.isDraggable = true;
764                     p.parents = [];
765                 }
766                 return this;
767             };
768 
769         } // end '3points'
770 
771         el.elType = 'angle';
772         el.type = Const.OBJECT_TYPE_ANGLE;
773         el.parents = [parents[0].id, parents[1].id, parents[2].id];
774         el.subs = {};
775 
776         el.updateDataArraySquare = function () {
777             var A, B, C,
778                 r = this.Radius(),
779                 d1, d2,
780                 v, l1, l2;
781 
782 
783             if (type === '2lines') {
784                 // This is necessary to update this.point1, this.point2, this.point3.
785                 this.updateDataArraySector();
786             }
787 
788             A = this.point2;
789             B = this.point1;
790             C = this.point3;
791 
792             A = A.coords.usrCoords;
793             B = B.coords.usrCoords;
794             C = C.coords.usrCoords;
795 
796             d1 = Geometry.distance(A, B, 3);
797             d2 = Geometry.distance(C, B, 3);
798 
799             // In case of type=='2lines' this is redundant, because r == d1 == d2
800             A = [1, B[1] + (A[1] - B[1]) * r / d1, B[2] + (A[2] - B[2]) * r / d1];
801             C = [1, B[1] + (C[1] - B[1]) * r / d2, B[2] + (C[2] - B[2]) * r / d2];
802 
803             v = Mat.crossProduct(C, B);
804             l1 = [-A[1] * v[1] - A[2] * v[2], A[0] * v[1], A[0] * v[2]];
805             v = Mat.crossProduct(A, B);
806             l2 = [-C[1] * v[1] - C[2] * v[2], C[0] * v[1], C[0] * v[2]];
807 
808             v = Mat.crossProduct(l1, l2);
809             v[1] /= v[0];
810             v[2] /= v[0];
811 
812             this.dataX = [B[1], A[1], v[1], C[1], B[1]];
813             this.dataY = [B[2], A[2], v[2], C[2], B[2]];
814 
815             this.bezierDegree = 1;
816         };
817 
818         el.updateDataArrayNone = function () {
819             this.dataX = [NaN];
820             this.dataY = [NaN];
821             this.bezierDegree = 1;
822         };
823 
824         el.updateDataArray = function () {
825             var type = this.visProp.type,
826                 deg = Geometry.trueAngle(this.point2, this.point1, this.point3);
827 
828             if (Math.abs(deg - 90) < this.visProp.orthosensitivity) {
829                 type = this.visProp.orthotype;
830             }
831 
832             if (type === 'none') {
833                 this.updateDataArrayNone();
834             } else if (type === 'square') {
835                 this.updateDataArraySquare();
836             } else if (type === 'sector') {
837                 this.updateDataArraySector();
838             } else if (type === 'sectordot') {
839                 this.updateDataArraySector();
840                 if (!this.dot.visProp.visible) {
841                     this.dot.setAttribute({visible: true});
842                 }
843             }
844 
845             if (!this.visProp.visible || (type !== 'sectordot' && this.dot.visProp.visible)) {
846                 this.dot.setAttribute({visible: false});
847             }
848         };
849 
850         /**
851          * Indicates a right angle. Invisible by default, use <tt>dot.visible: true</tt> to show.
852          * Though this dot indicates a right angle, it can be visible even if the angle is not a right
853          * one.
854          * @type JXG.Point
855          * @name dot
856          * @memberOf Angle.prototype
857          */
858         attrsub = Type.copyAttributes(attributes, board.options, 'angle', 'dot');
859         el.dot = board.create('point', [function () {
860             var A, B, r, d, a2, co, si, mat;
861 
862             if (Type.exists(el.dot) && !el.dot.visProp.visible) {
863                 return [0, 0];
864             }
865 
866             A = el.point2.coords.usrCoords;
867             B = el.point1.coords.usrCoords;
868             r = el.Radius();
869             d = Geometry.distance(A, B, 3);
870             a2 = Geometry.rad(el.point2, el.point1, el.point3) * 0.5;
871             co = Math.cos(a2);
872             si = Math.sin(a2);
873 
874             A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d];
875 
876             mat = [
877                 [1, 0, 0],
878                 [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5],
879                 [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5,  co * 0.5]
880             ];
881             return Mat.matVecMult(mat, A);
882         }], attrsub);
883 
884         el.dot.dump = false;
885         el.subs.dot = el.dot;
886 
887         if (type === '2lines') {
888             for (i = 0; i < 2; i++) {
889                 board.select(parents[i]).addChild(el.dot);
890             }
891         } else {
892             for (i = 0; i < 3; i++) {
893                 board.select(parents[i]).addChild(el.dot);
894             }
895         }
896 
897         // documented in GeometryElement
898         el.getLabelAnchor = function () {
899             var vec, dx = 12, dy = 12,
900                 A, B, r, d, a2, co, si, mat;
901 
902             if (Type.exists(this.label)) {
903                 this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
904             }
905 
906             if (Type.exists(this.label.visProp.fontSize)) {
907                 dx = this.label.visProp.fontSize;
908                 dy = this.label.visProp.fontSize;
909             }
910             dx /= this.board.unitX;
911             dy /= this.board.unitY;
912 
913             A = el.point2.coords.usrCoords;
914             B = el.point1.coords.usrCoords;
915             r = el.Radius();
916             d = Geometry.distance(A, B, 3);
917             a2 = Geometry.rad(el.point2, el.point1, el.point3) * 0.5;
918             co = Math.cos(a2);
919             si = Math.sin(a2);
920 
921             A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d];
922 
923             mat = [
924                 [1, 0, 0],
925                 [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5],
926                 [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5,  co * 0.5]
927             ];
928             vec = Mat.matVecMult(mat, A);
929             vec[1] /= vec[0];
930             vec[2] /= vec[0];
931             vec[0] /= vec[0];
932 
933             d = Geometry.distance(vec, B, 3);
934             vec = [vec[0], B[1] + (vec[1] - B[1]) * (r + dx) / d,  B[2] + (vec[2] - B[2]) * (r + dx) / d];
935 
936             return new Coords(Const.COORDS_BY_USER, vec, this.board);
937         };
938 
939         el.Value = function () {
940             return Geometry.rad(this.point2, this.point1, this.point3);
941         };
942 
943         el.methodMap = Type.deepCopy(el.methodMap, {
944             Value: 'Value',
945             setAngle: 'setAngle',
946             free: 'free'
947         });
948 
949         return el;
950     };
951 
952     JXG.registerElement('angle', JXG.createAngle);
953 
954     return {
955         createSector: JXG.createSector,
956         createCircumcircleSector: JXG.createCircumcircleSector,
957         createAngle: JXG.createAngle
958     };
959 });
960