1 /*
  2     Copyright 2008,2009
  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  * Creates a new instance of Sector.
 28  * @class Sector stores all style and functional properties that are required
 29  * to draw a sector on a board.
 30  * @param {JXG.Board} board Reference to the board the sector is drawn on.
 31  * @param {JXG.Point} p1 Midpoint of the sector.
 32  * @param {JXG.Point} p2 Point defining the sectors radius
 33  * @param {JXG.Point} p3 This point defines the angle of the sectors section.
 34  * @param {Array} ids Unique identifiers for the derived objects (arc, endpoint of the arc, line from p1 to p2, line from p1 to the endpoint of the arc) . 
 35  * Must be Strings. If null or an empty string is given to any of these, an unique id will be generated.
 36  * @param {Array} names Names for the derived objects (arc, endpoint of the arc, line from p1 to p2, line from p1 to the endpoint of the arc) . 
 37  * Must be Strings. If null or an empty string is given to any of these, an unique id will be generated.
 38  * @param {String} id Unique identifier for this object.  If null or an empty string is given,
 39  * an unique id will be generated by Board
 40  * @param {String} name Not necessarily unique name, displayed on the board.  If null or an
 41  * empty string is given, an unique name will be generated.
 42  * @see JXG.Board#addSector
 43  * @constructor
 44  * @extends JXG.GeometryElement
 45  */
 46 
 47 JXG.createSector = function(board, parents, attributes) {
 48     var el, defaults, key, options;
 49         
 50     // Alles 3 Punkte?
 51     if ( !(JXG.isPoint(parents[0]) && JXG.isPoint(parents[1]) && JXG.isPoint(parents[2]))) {
 52         throw new Error("JSXGraph: Can't create Sector with parent types '" + 
 53                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + 
 54                         (typeof parents[2]) + "'.");
 55     }
 56 
 57     // Read the default values from Options and use them in case they are not set by the user
 58     // in attributes
 59     defaults = {withLabel:JXG.readOption(board.options,'elements','withLabel'), 
 60                 layer:JXG.readOption(board.options,'layer','sector'),
 61                 useDirection:false}; // useDirection is necessary for circumCircleSectors
 62     defaults['strokeWidth'] =  board.options.elements['strokeWidth'];
 63     options = board.options.sector;
 64     for (key in options) {
 65         defaults[key] = options[key];
 66     }
 67     attributes = JXG.checkAttributes(attributes, defaults);
 68         
 69     el = board.create('curve',[[0],[0]],attributes);
 70     el.type = JXG.OBJECT_TYPE_SECTOR;
 71     /**
 72      * Midpoint of the sector.
 73      * @type JXG.Point
 74      */
 75     el.point1 = JXG.getReference(board, parents[0]);
 76     el.midpoint = el.point1;
 77     /**
 78      * Point defining the arcs circle.
 79      * @type JXG.Point
 80      */
 81     el.point2 = JXG.getReference(board, parents[1]);
 82     /**
 83      * The point defining the angle of the arc.
 84      * @type JXG.Point
 85      */
 86     el.point3 = JXG.getReference(board, parents[2]);
 87     /* Add arc as child to defining points */
 88     el.point1.addChild(el);
 89     el.point2.addChild(el);
 90     el.point3.addChild(el);
 91     
 92     el.useDirection = attributes['useDirection'];      // useDirection is necessary for circumCircleSectors
 93 
 94     el.updateDataArray = function() {
 95         var A = this.point2,
 96             B = this.point1,
 97             C = this.point3,
 98             beta, co, si, matrix,
 99             phi = JXG.Math.Geometry.rad(A,B,C),
100             i,
101             //n = 100, 
102             n = Math.ceil(phi/Math.PI*90)+1,
103             delta = phi/n, //Math.PI/90.0, 
104             x = B.X(),
105             y = B.Y(),
106             v, 
107             det, p0c, p1c, p2c;
108 
109         if (this.useDirection) {  // This is true for circumCircleArcs. In that case there is
110                                   // a fourth parent element: [midpoint, point1, point3, point2]
111             var det, 
112                 p0c = parents[1].coords.usrCoords,
113                 p1c = parents[3].coords.usrCoords,
114                 p2c = parents[2].coords.usrCoords;
115             det = (p0c[1]-p2c[1])*(p0c[2]-p1c[2]) - (p0c[2]-p2c[2])*(p0c[1]-p1c[1]);
116             if(det < 0) {
117                 this.point2 = parents[1];
118                 this.point3 = parents[2];
119             }
120             else {
121                 this.point2 = parents[2];
122                 this.point3 = parents[1];
123             }
124         }
125         this.dataX = [B.X(),A.X()];
126         this.dataY = [B.Y(),A.Y()];
127         for (beta=delta,i=1; i<=n; i++, beta+=delta) {
128             co = Math.cos(beta); 
129             si = Math.sin(beta); 
130             matrix = [[1,            0,   0],
131                       [x*(1-co)+y*si,co,-si],
132                       [y*(1-co)-x*si,si, co]];    
133             v = JXG.Math.matVecMult(matrix,A.coords.usrCoords);
134             this.dataX.push(v[1]/v[0]);
135             this.dataY.push(v[2]/v[0]);
136         }
137         this.dataX.push(B.X());
138         this.dataY.push(B.Y());
139     };
140 
141     /**
142     * Calculates the arcs radius.
143     * @type float
144     * @return The arcs radius
145     */
146     el.Radius = function() {
147         return this.point2.Dist(this.point1);
148     };
149 
150     /**
151     * @deprecated
152     */
153     el.getRadius = function() {
154         return this.Radius();
155     };
156 
157     /**
158     * Checks whether (x,y) is within the sector defined by the arc.
159     * @param {int} x Coordinate in x direction, screen coordinates.
160     * @param {int} y Coordinate in y direction, screen coordinates.
161     * @return {bool} True if (x,y) is within the sector defined by the arc, False otherwise.
162     */
163     el.hasPointSector = function (x, y) { 
164         var checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board),
165             r = this.Radius(),
166             dist = this.point1.coords.distance(JXG.COORDS_BY_USER,checkPoint),
167             has = (dist<r),
168             angle;
169         
170         if(has) {
171             angle = JXG.Math.Geometry.rad(this.point2,this.point1,checkPoint.usrCoords.slice(1));
172             if (angle>JXG.Math.Geometry.rad(this.point2,this.point1,this.point3)) { has = false; }
173         }
174         return has;    
175     };
176 
177     /**
178     * return TextAnchor
179     */
180     el.getTextAnchor = function() {
181         return this.point1.coords;
182     };
183 
184     /**
185     * return LabelAnchor
186     */
187     el.getLabelAnchor = function() {
188         var angle = JXG.Math.Geometry.rad(this.point2, this.point1, this.point3),
189             dx = 10/(this.board.stretchX),
190             dy = 10/(this.board.stretchY),
191             p2c = this.point2.coords.usrCoords,
192             pmc = this.point1.coords.usrCoords,
193             bxminusax = p2c[1] - pmc[1],
194             byminusay = p2c[2] - pmc[2],
195             coords, vecx, vecy, len;
196 
197         if(this.label.content != null) {                          
198             this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0],this.board);                      
199         }  
200 
201         coords = new JXG.Coords(JXG.COORDS_BY_USER, 
202                         [pmc[1]+ Math.cos(angle*0.5)*bxminusax - Math.sin(angle*0.5)*byminusay, 
203                         pmc[2]+ Math.sin(angle*0.5)*bxminusax + Math.cos(angle*0.5)*byminusay], 
204                         this.board);
205 
206         vecx = coords.usrCoords[1] - pmc[1];
207         vecy = coords.usrCoords[2] - pmc[2];
208     
209         len = Math.sqrt(vecx*vecx+vecy*vecy);
210         vecx = vecx*(len+dx)/len;
211         vecy = vecy*(len+dy)/len;
212 
213         return new JXG.Coords(JXG.COORDS_BY_USER, [pmc[1]+vecx,pmc[2]+vecy],this.board);
214     };
215 
216     el.prepareUpdate().update();
217     
218     return el;
219 };
220 
221 JXG.JSXGraph.registerElement('sector', JXG.createSector);
222 
223 
224 /**
225  * Creates a new circumcircle sector through three defining points.
226  * @param {JXG.Board} board The board the sector is put on.
227  * @param {Array} parents Array of three points defining the circumcircle sector.
228  * @param {Object} attributs Object containing properties for the element such as stroke-color and visibility. See @see JXG.GeometryElement#setProperty
229  * @type JXG.Sector
230  * @return Reference to the created arc object.
231  */
232  JXG.createCircumcircleSector = function(board, parents, attributes) {
233     var el, mp, idmp='', det;
234     
235     attributes = JXG.checkAttributes(attributes,{withLabel:JXG.readOption(board.options,'sector','withLabel'), layer:null});
236     if(attributes['id'] != null) {
237         idmp = attributes['id']+'_mp';
238     }
239     
240     // Alles 3 Punkte?
241     if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) && (JXG.isPoint(parents[2]))) {
242         mp = board.create('circumcirclemidpoint',[parents[0], parents[1], parents[2]], {id:idmp, withLabel:false, visible:false});
243         attributes.useDirection = true;
244         el = board.create('sector', [mp,parents[0],parents[2],parents[1]], attributes);
245     } // Ansonsten eine fette Exception um die Ohren hauen
246     else {
247         throw new Error("JSXGraph: Can't create circumcircle sector with parent types '" + 
248                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'.");
249     }
250 
251     return el;
252 };
253 
254 JXG.JSXGraph.registerElement('circumcirclesector', JXG.createCircumcircleSector);
255 
256 
257 /**
258  * Creates a new angle.
259  * @param {JXG.Board} board The board the angle is put on.
260  * @param {Array} parents Array of three points defining the angle.
261  * @param {Object} attributes Object containing properties for the element such as stroke-color and visibility. @see JXG.GeometryElement#setProperty
262  * @type JXG.Angle
263  * @return Reference to the created angle object.
264  */
265 JXG.createAngle = function(board, parents, attributes) {
266     var el, p, defaults, options, key, text,
267         possibleNames = ['α', 'β', 'γ', 'δ', 'ε', 'ζ', '&eta', 'θ',
268                                 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 
269                                 'ς', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω'],
270         i = 0, tmp,
271         j, x, pre, post, found;
272 
273 
274     defaults = {withLabel:JXG.readOption(board.options,'elements','withLabel'), 
275                 layer:JXG.readOption(board.options,'layer','angle'),
276                 radius:JXG.readOption(board.options,'angle','radius'),
277                 text:''};
278     options = board.options.angle;
279     for (key in options) {
280         defaults[key] = options[key];
281     }
282     attributes = JXG.checkAttributes(attributes, defaults);
283     
284     // Alles 3 Punkte?
285     if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) && (JXG.isPoint(parents[2]))) {
286         //  If empty, create a new name
287         text = attributes.text;
288         if(text == '') {
289             while(i < possibleNames.length) {
290                 j=i;
291                 x = possibleNames[i];
292                 for(el in board.objects) {
293                     if(board.objects[el].type == JXG.OBJECT_TYPE_ANGLE) {
294                         if(board.objects[el].text == x) {
295                             i++;
296                             break;
297                         }
298                     }
299                 }
300                 if(i==j) {
301                     text = x;
302                     i = possibleNames.length+1;
303                 }
304             }
305             if(i == possibleNames.length) {
306                 pre = 'α_{';
307                 post = '}';
308                 found = false;
309                 j=0;
310                 while(!found) {
311                     for(el in board.objects) {
312                         if(board.objects[el].type == JXG.OBJECT_TYPE_ANGLE) {
313                             if(board.objects[el].text == (pre+j+post)) {
314                                 found = true;
315                                 break;
316                             }
317                         }
318                     }
319                     if(found) {
320                         found= false;
321                     }
322                     else {
323                         found = true;
324                         text = (pre+j+post);
325                     }
326                 }
327             }
328         }
329         p = board.create('point', [
330             function(){
331                 var A = parents[0], B = parents[1],
332                     r = attributes.radius,
333                     d = B.Dist(A);
334                     return [B.X()+(A.X()-B.X())*r/d,B.Y()+(A.Y()-B.Y())*r/d];
335             }], {withLabel:false, visible:false});
336         for (i=0;i<3;i++) {
337             JXG.getReference(board,parents[i]).addChild(p);
338         }
339         el = board.create('sector', [parents[1],p,parents[2]],attributes);
340         el.type = JXG.OBJECT_TYPE_ANGLE;
341         if (el.withLabel) {
342             el.label.content.setText(text);
343         }
344         JXG.getReference(board,parents[0]).addChild(el);
345         
346         /**
347         * return LabelAnchor
348         */
349         el.getLabelAnchor = function() {
350             var angle = JXG.Math.Geometry.rad(this.point2, this.point1, this.point3),
351                 dx = 10/(this.board.stretchX),
352                 dy = 10/(this.board.stretchY),
353                 p2c = this.point2.coords.usrCoords,
354                 pmc = this.point1.coords.usrCoords,
355                 bxminusax = p2c[1] - pmc[1],
356                 byminusay = p2c[2] - pmc[2],
357                 coords, vecx, vecy, len;
358 
359             if(this.label.content != null) {                          
360                 this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0],this.board);                      
361             }  
362 
363             coords = new JXG.Coords(JXG.COORDS_BY_USER, 
364                             [pmc[1]+ Math.cos(angle*0.5*1.125)*bxminusax - Math.sin(angle*0.5*1.125)*byminusay, 
365                             pmc[2]+ Math.sin(angle*0.5*1.125)*bxminusax + Math.cos(angle*0.5*1.125)*byminusay], 
366                             this.board);
367     
368             vecx = coords.usrCoords[1] - pmc[1];
369             vecy = coords.usrCoords[2] - pmc[2];
370         
371             len = Math.sqrt(vecx*vecx+vecy*vecy);
372             vecx = vecx*(len+dx)/len;
373             vecy = vecy*(len+dy)/len;
374 
375             return new JXG.Coords(JXG.COORDS_BY_USER, [pmc[1]+vecx,pmc[2]+vecy],this.board);
376         };
377 
378     } // Ansonsten eine fette Exception um die Ohren hauen
379     else {
380         throw new Error("JSXGraph: Can't create angle with parent types '" + 
381                          (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'.");
382     }
383 
384     return el;
385 };
386 
387 JXG.JSXGraph.registerElement('angle', JXG.createAngle);
388 
389