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