1 /*
  2     Copyright 2008-2010
  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  * @fileoverview The JXG.Board class is defined in this file. JXG.Board controls all properties and methods
 28  * used to manage a geonext board like managing geometric elements, managing mouse and touch events, etc.
 29  * @author graphjs
 30  * @version 0.1
 31  */
 32 
 33 /**
 34  * Constructs a new Board object.
 35  * @class JXG.Board controls all properties and methods used to manage a geonext board like managing geometric
 36  * elements, managing mouse and touch events, etc. You probably don't want to use this constructor directly.
 37  * Please use {@link JXG.JSXGraph#initBoard} to initialize a board.
 38  * @constructor
 39  * @param {String} container The id or reference of the HTML DOM element the board is drawn in. This is usually a HTML div.
 40  * @param {JXG.AbstractRenderer} renderer The reference of a renderer.
 41  * @param {String} id Unique identifier for the board, may be an empty string or null or even undefined.
 42  * @param {JXG.Coords} origin The coordinates where the origin is placed, in user coordinates.
 43  * @param {Number} zoomX Zoom factor in x-axis direction
 44  * @param {Number} zoomY Zoom factor in y-axis direction
 45  * @param {Number} unitX Units in x-axis direction
 46  * @param {Number} unitY Units in y-axis direction
 47  * @param {Number} canvasWidth  The width of canvas
 48  * @param {Number} canvasHeight The height of canvas
 49  * @param {Boolean} showCopyright Display the copyright text
 50  */
 51 JXG.Board = function(container, renderer, id, origin, zoomX, zoomY, unitX, unitY, canvasWidth, canvasHeight, showCopyright) {
 52     /**
 53      * Board is in no special mode, objects are highlighted on mouse over and objects may be
 54      * clicked to start drag&drop.
 55      * @type Number
 56      * @constant
 57      */
 58     this.BOARD_MODE_NONE = 0x0000;
 59 
 60     /**
 61      * Board is in drag mode, objects aren't highlighted on mouse over and the object referenced in
 62      * drag_obj is updated on mouse movement.
 63      * @type Number
 64      * @constant
 65      * @see JXG.Board#drag_obj
 66      */
 67     this.BOARD_MODE_DRAG = 0x0001;
 68 
 69     /**
 70      * In this mode a mouse move changes the origin's screen coordinates.
 71      * @type Number
 72      * @constant
 73      */
 74     this.BOARD_MODE_MOVE_ORIGIN = 0x0002;
 75 
 76     /**
 77      /* Update is made with low quality, e.g. graphs are evaluated at a lesser amount of points.
 78      * @type Number
 79      * @constant
 80      * @see JXG.Board#updateQuality
 81      */
 82     this.BOARD_QUALITY_LOW = 0x1;
 83 
 84     /**
 85      * Update is made with high quality, e.g. graphs are evaluated at much more points.
 86      * @type Number
 87      * @constant
 88      * @see JXG.Board#updateQuality
 89      */
 90     this.BOARD_QUALITY_HIGH = 0x2;
 91 
 92     // TODO: Do we still need the CONSTRUCTIOIN_TYPE_* properties?!?
 93     // BEGIN CONSTRUCTION_TYPE_* stuff
 94 
 95     /**
 96      * Board is in construction mode, objects are highlighted on mouse over and the behaviour of the board
 97      * is determined by the construction type stored in the field constructionType.
 98      * @type Number
 99      * @constant
100      */
101     this.BOARD_MODE_CONSTRUCT = 0x0010;
102 
103     /**
104      * When the board is in construction mode this construction type says we want to construct a point.
105      * @type Number
106      * @constant
107      */
108     this.CONSTRUCTION_TYPE_POINT         = 0x43545054;       // CTPT
109     /**
110      * When the board is in construction mode this construction type says we want to construct a circle.
111      * @type Number
112      * @constant
113      */
114     this.CONSTRUCTION_TYPE_CIRCLE        = 0x4354434C;       // CTCL
115     /**
116      * When the board is in construction mode this construction type says we want to construct a line.
117      * @type int
118      * @private
119      * @final
120      */
121     this.CONSTRUCTION_TYPE_LINE          = 0x43544C4E;       // CTLN
122     /**
123      * When the board is in construction mode this construction type says we want to construct a glider.
124      * @type int
125      * @private
126      * @final
127      */
128     this.CONSTRUCTION_TYPE_GLIDER        = 0x43544744;       // CTSD
129     /**
130      * When the board is in construction mode this construction type says we want to construct a midpoint.
131      * @type int
132      * @private
133      * @final
134      */
135     this.CONSTRUCTION_TYPE_MIDPOINT      = 0x43544D50;       // CTMP
136     /**
137      * When the board is in construction mode this construction type says we want to construct a perpendicular.
138      * @type int
139      * @private
140      * @final
141      */
142     this.CONSTRUCTION_TYPE_PERPENDICULAR = 0x43545044;       // CTPD
143     /**
144      * When the board is in construction mode this construction type says we want to construct a parallel.
145      * @type int
146      * @private
147      * @final
148      */
149     this.CONSTRUCTION_TYPE_PARALLEL      = 0x4354504C;       // CTPL
150     /**
151      * When the board is in construction mode this construction type says we want to construct a intersection.
152      * @type int
153      * @private
154      * @final
155      */
156     this.CONSTRUCTION_TYPE_INTERSECTION  = 0x43544953;       // CTIS
157     // END CONSTRUCTION_TYPE_* stuff
158 
159     /**
160      * The html-id of the html element containing the board.
161      * @type String
162      */
163     this.container = container;
164 
165     /**
166      * Pointer to the html element containing the board.
167      * @type Object
168      */
169     this.containerObj = document.getElementById(this.container);
170     if (this.containerObj==null) {
171         throw new Error("\nJSXGraph: HTML container element '" + (container) + "' not found.");
172     }
173 
174     /**
175      * A reference to this boards renderer.
176      * @type JXG.AbstractRenderer
177      */
178     this.renderer = renderer;
179 
180     /**
181      * Some standard options
182      * @type JXG.Options
183      */
184     this.options = JXG.deepCopy(JXG.Options);
185 
186     /**
187      * Dimension of the board.
188      * @default 2
189      * @type Number
190      */
191     this.dimension = 2;
192 
193     /**
194      * Coordinates of the boards origin. This a object with the two properties
195      * usrCoords and scrCoords. usrCoords always equals [1, 0, 0] and scrCoords
196      * stores the boards origin in homogeneous screen coordinates.
197      * @type Object
198      */
199     this.origin = {};
200     this.origin.usrCoords = [1, 0, 0];
201     this.origin.scrCoords = [1, origin[0], origin[1]];
202 
203     /**
204      * Zoom factor in X direction
205      * @type Number
206      */
207     this.zoomX = zoomX;
208 
209     /**
210      * Zoom factor in Y direction.
211      * @type Number
212      */
213     this.zoomY = zoomY;
214 
215     /**
216      * The number of pixels which represent one unit in user-coordinates in x direction.
217      * @type Number
218      */
219     this.unitX = unitX;
220 
221     /**
222      * The number of pixels which represent one unit in user-coordinates in y direction.
223      * @type Number
224      */
225     this.unitY = unitY;
226 
227     /**
228      * stretchX is the product of zoomX and unitX. This is basically to save some multiplications.
229      * @type Number
230      */
231     this.stretchX = this.zoomX*this.unitX;
232 
233     /**
234      * stretchY is the product of zoomY and unitY. This is basically to save some multiplications.
235      * @type Number
236      */
237     this.stretchY = this.zoomY*this.unitY;
238 
239     /**
240      * Canvas width.
241      * @type Number
242      */
243     this.canvasWidth = canvasWidth;
244 
245     /**
246      * Canvas Height
247      * @type Number
248      */
249     this.canvasHeight = canvasHeight;
250 
251     // If the given id is not valid, generate an unique id
252     if(JXG.exists(id) && id !== '' && !JXG.exists(document.getElementById(id))) {
253         this.id = id;
254     } else {
255         this.id = this.generateId();
256     }
257 
258     /**
259      * An array containing all hook functions.
260      * @type Array
261      * @see JXG.Board#addHook
262      * @see JXG.Board#removeHook
263      * @see JXG.Board#updateHooks
264      */
265     this.hooks = [];
266 
267     /**
268      * An array containing all other boards that are updated after this board has been updated.
269      * @type Array
270      * @see JXG.Board#addChild
271      * @see JXG.Board#removeChild
272      */
273     this.dependentBoards = [];
274 
275     /**
276      * An associative array containing all geometric objects belonging to the board. Key is the id of the object and value is a reference to the object.
277      * @type Object
278      */
279     this.objects = {};
280 
281     /**
282      * Stores all the objects that are currently running an animation.
283      * @type Object
284      */
285     this.animationObjects = {};
286 
287     /**
288      * An associative array containing all highlighted elements belonging to the board.
289      * @type Object
290      */
291     this.highlightedObjects = {};
292 
293     /**
294      * Number of objects ever created on this board. This includes every object, even invisible and deleted ones.
295      * @type Number
296      */
297     this.numObjects = 0;
298 
299     /**
300      * An associative array to store the objects of the board by name. the name of the object is the key and value is a reference to the object.
301      * @type Object
302      */
303     this.elementsByName = {};
304 
305     /**
306      * The board mode the board is currently in. Possible values are
307      * <ul>
308      * <li>JXG.Board.BOARD_MODE_NONE</li>
309      * <li>JXG.Board.BOARD_MODE_DRAG</li>
310      * <li>JXG.Board.BOARD_MODE_CONSTRUCT</li>
311      * <li>JXG.Board.BOARD_MODE_MOVE_ORIGIN</li>
312      * </ul>
313      * @type Number
314      */
315     this.mode = this.BOARD_MODE_NONE;
316 
317     /**
318      * The update quality of the board. In most cases this is set to {@link JXG.Board#BOARD_QUALITY_HIGH}.
319      * If {@link JXG.Board#mode} equals {@link JXG.Board#BOARD_MODE_DRAG} this is set to
320      * {@link JXG.Board#BOARD_QUALITY_LOW} to speed up the update process by e.g. reducing the number of
321      * evaluation points when plotting functions. Possible values are
322      * <ul>
323      * <li>BOARD_QUALITY_LOW</li>
324      * <li>BOARD_QUALITY_HIGH</li>
325      * </ul>
326      * @type Number
327      * @see JXG.Board#mode
328      */
329     this.updateQuality = this.BOARD_QUALITY_HIGH;
330 
331     /**
332      * If true updates are skipped.
333      * @type Boolean
334      */
335     this.isSuspendedRedraw = false;
336 
337     this.calculateSnapSizes();
338 
339     /**
340      * The distance from the mouse to the dragged object in x direction when the user clicked the mouse button.
341      * @type Number
342      * @see JXG.Board#drag_dy
343      * @see JXG.Board#drag_obj
344      */
345     this.drag_dx = 0;
346 
347     /**
348      * The distance from the mouse to the dragged object in y direction when the user clicked the mouse button.
349      * @type Number
350      * @see JXG.Board#drag_dx
351      * @see JXG.Board#drag_obj
352      */
353     this.drag_dy = 0;
354 
355     /**
356      * Absolute position of the mouse pointer in screen pixel from the top left corner
357      * of the HTML window.
358      * @type Array
359      */
360     this.mousePosAbs = [0,0];
361 
362     /**
363      * Relative position of the mouse pointer in screen pixel from the top left corner
364      * of the JSXGraph canvas (the div element contining the board).
365      * @type Array
366      */
367     this.mousePosRel = [0,0];
368 
369     /**
370      * An array of references to the objects that are dragged on the board. Usually these are an object of
371      * type {@link JXG.Point}.
372      * @type Array
373      */
374     this.drag_obj = [];
375 
376     /**
377      * This property is used to store the last time the user clicked on the board and the position he clicked.
378      * @type Object
379      */
380     this.last_click = {
381         time: 0,
382         posX: 0,
383         posY: 0
384     };
385 
386     /**
387      * A string containing the XML text of the construction. This is set in {@link JXG.FileReader#parseString}.
388      * Only useful if a construction is read from a GEONExT-, Intergeo-, Geogebra-, or Cinderella-File.
389      * @type String
390      */
391     this.xmlString = '';
392 
393     /**
394      * Display the licence text.
395      * @see JXG.JSXGraph#licenseText
396      * @see JXG.JSXGraph#initBoard
397      */
398     this.showCopyright = false;
399     if((showCopyright!=null && showCopyright) || (showCopyright==null && this.options.showCopyright)) {
400         this.renderer.displayCopyright(JXG.JSXGraph.licenseText, this.options.text.fontSize);
401         this.showCopyright = true;
402     }
403 
404     /**
405      * Full updates are needed after zoom and axis translates. This saves some time during an update.
406      * @default false
407      * @type Boolean
408      */
409     this.needsFullUpdate = false;
410 
411     /**
412      * If reducedUpdate is set to true then only the dragged element and few (e.g. 2) following
413      * elements are updated during mouse move. On mouse up the whole construction is
414      * updated. This enables us to be fast even on very slow devices.
415      * @type Boolean
416      * @default false
417      */
418     this.reducedUpdate = false;
419 
420     /**
421      * The current color blindness deficiency is stored in this property. If color blindness is not emulated
422      * at the moment, it's value is 'none'.
423      */
424     this.currentCBDef = 'none';
425 
426     /**
427      * If GEONExT constructions are displayed, then this property should be set to true. Then no stdform updates
428      * and no dragging of lines, circles and curves is possible. This is set in {@link JXG.GeonextReader#readGeonext}.
429      * @type Boolean
430      * @default false
431      * @see JXG.GeonextReader#readGeonext
432      */
433     this.geonextCompatibilityMode = false;
434 
435     if (this.options.text.useASCIIMathML && translateASCIIMath) {
436         init();
437     } else {
438         this.options.text.useASCIIMathML = false;
439     }
440 
441     // Introduce our event handlers to the browser
442     JXG.addEvent(this.containerObj, 'mousedown', this.mouseDownListener, this);
443     JXG.addEvent(this.containerObj, 'mousemove', this.mouseMoveListener, this);
444     JXG.addEvent(document, 'mouseup', this.mouseUpListener,this);
445     
446     // To run JSXGraph on mobile touch devices we need these event listeners.
447     JXG.addEvent(this.containerObj, 'touchstart', this.touchStartListener, this);
448     JXG.addEvent(this.containerObj, 'touchmove', this.touchMoveListener, this);
449     JXG.addEvent(this.containerObj, 'touchend', this.touchEndListener, this);
450 
451     // This one produces errors on IE
452     //   JXG.addEvent(this.containerObj, 'contextmenu', function(e) { e.preventDefault(); return false;}, this);
453     // this one works on IE, Firefox and Chromium with default configurations
454     // It's possible this doesn't work on some Safari or Opera versions by default, the user then has to allow the deactivation of the context menu.
455     this.containerObj.oncontextmenu = function(e) {if(JXG.exists(e)) e.preventDefault(); return false; };
456 };
457 
458 /**
459  * Generates an unique name for the given object. The result depends on the objects type, if the
460  * object is a {@link JXG.Point}, capital characters are used, if it is of type {@link JXG.Line}
461  * only lower case characters are used. If object is of type {@link JXG.Polygon}, a bunch of lower
462  * case characters prefixed with P_ are used. If object is of type {@link JXG.Circle} the name is
463  * generated using lower case characters. prefixed with k_ is used. In any other case, lower case
464  * chars prefixed with s_ is used.
465  * @param {Object} object Reference of an JXG.GeometryElement that is to be named.
466  * @returns {String} Unique name for the object.
467  */
468 JXG.Board.prototype.generateName = function(object) {
469     if(object.type == JXG.OBJECT_TYPE_TICKS) {
470         return '';
471     }
472 
473     var possibleNames,
474         maxNameLength = 3,
475         pre = '',
476         post = '',
477         indices = [],
478         name = '',
479         i, j;
480     
481     if(object.elementClass == JXG.OBJECT_CLASS_POINT) {
482         // points have capital letters
483         possibleNames = ['', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
484                                   'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
485     } else {
486         // all other elements get lowercase labels
487         possibleNames = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
488                                   'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
489     }
490 
491     switch(object.type) {
492         case JXG.OBJECT_TYPE_POLYGON:
493             pre = 'P_{';
494             post = '}';
495             break;
496         case JXG.OBJECT_TYPE_CIRCLE:
497             pre = 'k_{';
498             post = '}';
499             break;
500         case JXG.OBJECT_TYPE_ANGLE:
501             pre = 'W_{';
502             post = '}';
503             break;
504         default:
505             if(object.elementClass != JXG.OBJECT_CLASS_POINT && object.elementClass != JXG.OBJECT_CLASS_LINE) {
506                 pre = 's_{';
507                 post = '}';
508             }
509     }
510 
511     for(i=0; i<maxNameLength; i++) {
512         indices[i] = 0;
513     }
514 
515     while (indices[maxNameLength-1] < possibleNames.length) {
516         for(indices[0]=1; indices[0]<possibleNames.length; indices[0]++) {
517             name = pre;
518 
519             for(i=maxNameLength; i>0; i--) {
520                 name += possibleNames[indices[i-1]];
521             }
522 
523             if (this.elementsByName[name+post] == null) {
524                 return name+post;
525             }
526 
527         }
528         indices[0] = possibleNames.length;
529         for(i=1; i<maxNameLength; i++) {
530             if(indices[i-1] == possibleNames.length) {
531                 indices[i-1] = 1;
532                 indices[i]++;
533             }
534         }
535     }
536 
537     return '';
538 };
539 
540 /**
541  * Generates unique id for a board. The result is randomly generated and prefixed with 'jxgBoard'.
542  * @returns {String} Unique id for a board.
543  */
544 JXG.Board.prototype.generateId = function () {
545     var r = 1;
546 
547     // as long as we don't have an unique id generate a new one
548     while(JXG.JSXGraph.boards['jxgBoard' + r] != null) {
549         r = Math.round(Math.random()*33);
550     }
551 
552     return ('jxgBoard' + r);
553 };
554 
555 /**
556  * Composes an id for an element. If the ID is empty ('' or null) a new ID is generated, depending on the
557  * object type. Additionally, the id of the label is set. As a side effect {@link JXG.Board#numObjects}
558  * is updated.
559  * @param {Object} obj Reference of an geometry object that needs an id.
560  * @param {Number} type Type of the object.
561  * @returns {String} Unique id for an element.
562  */
563 JXG.Board.prototype.setId = function (obj, type) {
564     var num = this.numObjects++,
565         elId = obj.id;
566 
567     // Falls Id nicht vorgegeben, eine Neue generieren:
568     if(elId == '' || !JXG.exists(elId)) {
569         elId = this.id + type + num;
570     }
571     // Objekt an den Renderer zum Zeichnen uebergeben
572     obj.id = elId;
573     // Objekt in das assoziative Array einfuegen
574     this.objects[elId] = obj;
575 
576     if(true && obj.hasLabel) {
577         obj.label.content.id = elId+"Label";
578 
579         if(!obj.label.content.isLabel) {
580             this.renderer.drawText(obj.label.content);
581             if(!obj.label.content.visProp['visible']) {
582                 this.renderer.hide(obj.label.content);
583             }
584         }
585     }
586     return elId;
587 };
588 
589 /**
590  * After construction of the object the visibility is set
591  * and the label is constructed if necessary.
592  * @param {Object} obj The object to add.
593  */
594 JXG.Board.prototype.finalizeAdding = function (obj) {
595     if (obj.hasLabel) {
596         if(false) {
597             obj.label.content.id = obj.id + "Label";
598 
599             if(!obj.label.content.isLabel) {
600                 this.renderer.drawText(obj.label.content);
601                 if(!obj.label.content.visProp['visible']) {
602                     this.renderer.hide(obj.label.content);
603                 }
604             }
605         }
606         this.renderer.drawText(obj.label.content);
607     }
608     if(!obj.visProp['visible']) {
609         this.renderer.hide(obj);
610     }
611 
612     if(obj.hasLabel && !obj.label.content.visProp['visible']) {
613         this.renderer.hide(obj.label.content);
614     }
615 };
616 
617 /**
618  * Calculates mouse coordinates relative to the boards container.
619  * @returns {Array} Array of coordinates relative the boards container top left corner.
620  */
621 JXG.Board.prototype.getRelativeMouseCoordinates = function () {
622     var pCont = this.containerObj,
623         cPos = JXG.getOffset(pCont),
624         n;
625 
626     // add border width
627     n = parseInt(JXG.getStyle(pCont,'borderLeftWidth'));
628     if (isNaN(n)) n = 0; // IE problem if border-width not set explicitly
629     cPos[0] += n;
630 
631     n = parseInt(JXG.getStyle(pCont,'borderTopWidth'));
632     if (isNaN(n)) n = 0;
633     cPos[1] += n;
634 
635     // add padding
636     n = parseInt(JXG.getStyle(pCont,'paddingLeft'));
637     if (isNaN(n)) n = 0;
638     cPos[0] += n;
639 
640     n = parseInt(JXG.getStyle(pCont,'paddingTop'));
641     if (isNaN(n)) n = 0;
642     cPos[1] += n;
643 
644     return cPos;
645 };
646 
647 /**********************************************************
648  *
649  * Event Handler
650  *
651  **********************************************************/
652 
653 /**
654  * Handler for click on left arrow in the navigation bar
655  * @private
656  */
657 JXG.Board.prototype.clickLeftArrow = function () {
658     this.origin.scrCoords[1] += this.canvasWidth*0.1;
659     this.moveOrigin();
660     return this;
661 };
662 
663 /**
664  * Handler for click on right arrow in the navigation bar
665  * @private
666  */
667 JXG.Board.prototype.clickRightArrow = function () {
668     this.origin.scrCoords[1] -= this.canvasWidth*0.1;
669     this.moveOrigin();
670     return this;
671 };
672 
673 /**
674  * Handler for click on up arrow in the navigation bar
675  * @private
676  */
677 JXG.Board.prototype.clickUpArrow = function () {
678     this.origin.scrCoords[2] += this.canvasHeight*0.1;
679     this.moveOrigin();
680     return this;
681 };
682 
683 /**
684  * Handler for click on down arrow in the navigation bar
685  * @private
686  */
687 JXG.Board.prototype.clickDownArrow = function () {
688     this.origin.scrCoords[2] -= this.canvasHeight*0.1;
689     this.moveOrigin();
690     return this;
691 };
692 
693 /**
694  * iPhone-Events
695  */
696 JXG.Board.prototype.touchStartListener = function (evt) {
697 	evt.preventDefault();
698 
699 	// variable initialization
700 	var e = document.createEvent("MouseEvents"), i, shift = false;
701 	this.drag_obj = [];
702 
703 	// special gestures
704 	if((evt.targetTouches.length==2) && (JXG.Math.Geometry.distance([evt.targetTouches[0].screenX, evt.targetTouches[0].screenY], [evt.targetTouches[1].screenX, evt.targetTouches[1].screenY])<80)) {
705 	    evt.targetTouches.length = 1;
706 	    shift = true;
707 	}
708 
709 	// multitouch
710 	this.options.precision.hasPoint = this.options.precision.touch;
711 	for(i=0; i<evt.targetTouches.length; i++) {
712 		e.initMouseEvent('mousedown', true, false, this.containerObj, 0, evt.targetTouches[i].screenX, evt.targetTouches[i].screenY, evt.targetTouches[i].clientX, evt.targetTouches[i].clientY, false, false, shift, false, 0, null);
713                 e.fromTouch = true;
714 		this.mouseDownListener(e);
715 	}
716 };
717 
718 JXG.Board.prototype.touchMoveListener = function (evt) {
719     evt.preventDefault();
720     var i, myEvent;
721 
722     for(i=0; i<evt.targetTouches.length; i++) {
723         myEvent = {pageX: evt.targetTouches[i].pageX, pageY: evt.targetTouches[i].pageY, clientX: evt.targetTouches[i].clientX, clientY: evt.targetTouches[i].clientY};
724         myEvent.fromTouch = true;
725         this.mouseMoveListener(myEvent, i);
726     }
727 };
728 
729 JXG.Board.prototype.touchEndListener = function (evt) {
730     var e = document.createEvent("MouseEvents"), i;
731 
732     e.initMouseEvent('mouseup', true, false, this.containerObj, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
733     e.fromTouch = true;
734     this.mouseUpListener(e);
735     
736     this.options.precision.hasPoint = this.options.precision.mouse;
737 };
738 
739 /**
740  * This method is called by the browser when the mouse is moved.
741  * @param {Event} Evt The browsers event object.
742  * @private
743  */
744 JXG.Board.prototype.mouseDownListener = function (Evt) {
745     var el, pEl, cPos, absPos, dx, dy, nr;
746 
747     this.updateHooks('mousedown', Evt);
748 
749     // prevent accidental selection of text
750     if (document.selection) {
751         document.selection.empty();
752     } else if (window.getSelection) {
753         window.getSelection().removeAllRanges();
754     }
755 
756     cPos = this.getRelativeMouseCoordinates(Evt);
757     // position of mouse cursor relative to containers position of container
758     absPos = JXG.getPosition(Evt);
759     dx = absPos[0]-cPos[0]; //Event.pointerX(Evt) - cPos[0];
760     dy = absPos[1]-cPos[1]; //Event.pointerY(Evt) - cPos[1];
761     this.mousePosAbs = absPos; // Save the mouse position
762     this.mousePosRel = [dx,dy];
763 
764     if(Evt.shiftKey) {
765         this.drag_dx = dx - this.origin.scrCoords[1];
766         this.drag_dy = dy - this.origin.scrCoords[2];
767         this.mode = this.BOARD_MODE_MOVE_ORIGIN;
768         //Event.observe(this.container, 'mouseup', this.mouseUpListener.bind(this));
769         JXG.addEvent(document, 'mouseup', this.mouseUpListener, this);
770         return;
771     }
772     if (this.mode==this.BOARD_MODE_CONSTRUCT) return;
773 
774     if(((new Date()).getTime() - this.last_click.time <500) && (JXG.Math.Geometry.distance(absPos, [this.last_click.posX, this.last_click.posY]) < 30)) {
775 		this.zoom100();
776 	}
777 
778 	this.last_click.time = (new Date()).getTime();
779 	this.last_click.posX = absPos[0];
780 	this.last_click.posY = absPos[1];
781 
782     this.mode = this.BOARD_MODE_DRAG;
783     if (this.mode==this.BOARD_MODE_DRAG) {
784         nr = 0;
785         for(el in this.objects) {
786             pEl = this.objects[el];
787             if( JXG.exists(pEl.hasPoint)
788                     && ((pEl.type == JXG.OBJECT_TYPE_POINT) || (pEl.type == JXG.OBJECT_TYPE_GLIDER)
789                         /*|| (!this.geonextCompatibilityMode && pEl.type == JXG.OBJECT_TYPE_LINE)  // not yet
790                         || (!this.geonextCompatibilityMode && pEl.type == JXG.OBJECT_TYPE_CIRCLE)
791                         || (!this.geonextCompatibilityMode && pEl.elementClass == JXG.OBJECT_CLASS_CURVE)*/ )
792                     && (pEl.visProp['visible'])
793                     && (!pEl.fixed) && (!pEl.frozen)
794                     && (pEl.hasPoint(dx, dy))
795                     ) {
796                 // Points are preferred:
797                 if ((pEl.type == JXG.OBJECT_TYPE_POINT) || (pEl.type == JXG.OBJECT_TYPE_GLIDER)) {
798                     this.drag_obj.push({obj:this.objects[el],pos:nr}); // add the element and its number in this.object
799                     if (this.options.takeFirst) break;
800                 }
801             }
802             nr++;
803         }
804     }
805 
806     // if no draggable object can be found, get out here immediately
807     if(this.drag_obj.length == 0) {
808         this.mode = this.BOARD_MODE_NONE;
809         return true;
810     } else {
811         // prevent accidental text selection
812         // this could get us new trouble: input fields, links and drop down boxes placed as text
813         // on the board doesn't work anymore.
814         if (Evt && Evt.preventDefault) {
815             Evt.preventDefault();
816         } else {
817             window.event.returnValue = false;
818         }
819     }
820 
821     // New mouse position in screen coordinates.
822     this.dragObjCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [dx,dy], this);
823     //JXG.addEvent(document, 'mouseup', this.mouseUpListener,this);
824 
825     return false;
826 };
827 
828 /**
829  * This method is called by the browser when the left mouse button is released.
830  * @private
831  */
832 JXG.Board.prototype.mouseUpListener = function (Evt) {
833     this.updateHooks('mouseup', Evt);
834 
835     // redraw with high precision
836     this.updateQuality = this.BOARD_QUALITY_HIGH;
837 
838     // release mouseup listener
839     //JXG.removeEvent(document, 'mouseup', this.mouseUpListener, this);
840 
841     this.mode = this.BOARD_MODE_NONE;
842 
843     // if origin was moved update everything
844     if(this.mode == this.BOARD_MODE_MOVE_ORIGIN) {
845         this.moveOrigin();
846     } else {
847         //this.fullUpdate(); // Full update only needed on moveOrigin? (AW)
848         this.update();
849     }
850 
851     // release dragged object
852     this.drag_obj = [];
853 };
854 
855 /**
856  * This method is called by the browser when the left mouse button is clicked.
857  * @param {Event} Event The browsers event object.
858  * @private
859  */
860 JXG.Board.prototype.mouseMoveListener = function (Event, i) {
861     var el, pEl, cPos, absPos, newPos, dx, dy, drag, oldCoords;
862 
863     this.updateHooks('mousemove', Event, this.mode);
864 
865     // if not called from touch events, i is undefined
866     i = i || 0;
867 
868     cPos = this.getRelativeMouseCoordinates(Event);
869     // position of mouse cursor relative to containers position of container
870     absPos = JXG.getPosition(Event);
871     dx = absPos[0]-cPos[0]; //Event.pointerX(Evt) - cPos[0];
872     dy = absPos[1]-cPos[1]; //Event.pointerY(Evt) - cPos[1];
873 
874     this.mousePosAbs = absPos; // Save the mouse position
875     this.mousePosRel = [dx,dy];
876 
877     this.updateQuality = this.BOARD_QUALITY_LOW;
878 
879     this.dehighlightAll();
880     if(this.mode != this.BOARD_MODE_DRAG) {
881         this.renderer.hide(this.infobox);
882     }
883 
884     // we have to check for three cases:
885     //   * user moves origin
886     //   * user drags an object
887     //   * user just moves the mouse, here highlight all elements at
888     //     the current mouse position
889     if(this.mode == this.BOARD_MODE_MOVE_ORIGIN) {
890         this.origin.scrCoords[1] = dx - this.drag_dx;
891         this.origin.scrCoords[2] = dy - this.drag_dy;
892         this.moveOrigin();
893     } else if(this.mode == this.BOARD_MODE_DRAG) {
894         newPos = new JXG.Coords(JXG.COORDS_BY_SCREEN, this.getScrCoordsOfMouse(dx,dy), this);
895         drag = this.drag_obj[i].obj;
896 
897         if (drag.type == JXG.OBJECT_TYPE_POINT || drag.type == JXG.OBJECT_TYPE_LINE
898          || drag.type == JXG.OBJECT_TYPE_CIRCLE || drag.elementClass == JXG.OBJECT_CLASS_CURVE) {
899 
900             drag.setPositionDirectly(JXG.COORDS_BY_USER, newPos.usrCoords[1], newPos.usrCoords[2]);
901             this.update(drag);
902         } else if(drag.type == JXG.OBJECT_TYPE_GLIDER) {
903             oldCoords = drag.coords;
904 
905             // First the new position of the glider is set to the new mouse position
906             drag.setPositionDirectly(JXG.COORDS_BY_USER,newPos.usrCoords[1],newPos.usrCoords[2]);
907             // Then, from this position we compute the projection to the object the glider on which the glider lives.
908             if(drag.slideObject.type == JXG.OBJECT_TYPE_CIRCLE) {
909                 drag.coords = JXG.Math.Geometry.projectPointToCircle(drag, drag.slideObject, this);
910             } else if (drag.slideObject.type == JXG.OBJECT_TYPE_LINE) {
911                 drag.coords = JXG.Math.Geometry.projectPointToLine(drag, drag.slideObject, this);
912             }
913             // Now, we have to adjust the other group elements again.
914             if(drag.group.length != 0) {
915                 drag.group[drag.group.length-1].dX = drag.coords.scrCoords[1] - oldCoords.scrCoords[1];
916                 drag.group[drag.group.length-1].dY = drag.coords.scrCoords[2] - oldCoords.scrCoords[2];
917                 drag.group[drag.group.length-1].update(this);
918             } else {
919                 this.update(drag);
920             }
921         }
922         this.updateInfobox(drag);
923     } else { // BOARD_MODE_NONE or BOARD_MODE_CONSTRUCT
924         // Elements  below the mouse pointer which are not highlighted yet will be highlighted.
925         for(el in this.objects) {
926             pEl = this.objects[el];
927             if(JXG.exists(pEl.hasPoint) && pEl.visProp['visible'] && pEl.hasPoint(dx, dy)) {
928                 // this is required in any case because otherwise the box won't be shown until the point is dragged
929                 this.updateInfobox(pEl);
930                 if(this.highlightedObjects[el] == null) { // highlight only if not highlighted
931                     this.highlightedObjects[el] = pEl;
932                     pEl.highlight();
933                 }
934             }
935         }
936     }
937     this.updateQuality = this.BOARD_QUALITY_HIGH;
938 };
939 
940 /**********************************************************
941  *
942  * End of Event Handlers
943  *
944  **********************************************************/
945 
946 /**
947  * Updates and displays a little info box to show coordinates of current selected points.
948  * @param {JXG.GeometryElement} el A GeometryElement
949  * @returns {JXG.Board} Reference to the board
950  */
951 JXG.Board.prototype.updateInfobox = function(el) {
952     var x, y, xc, yc;
953 
954     if (!el.showInfobox) {
955         return this;
956     }
957 	if (el.elementClass == JXG.OBJECT_CLASS_POINT) {
958 		xc = el.coords.usrCoords[1];
959 		yc = el.coords.usrCoords[2];
960 
961 		this.infobox.setCoords(xc+this.infobox.distanceX/(this.stretchX),
962 							   yc+this.infobox.distanceY/(this.stretchY));
963         if (typeof(el.infoboxText)!="string") {
964             x = Math.abs(xc);
965             if (x>0.1) {
966                 x = xc.toFixed(2);
967             } else if (x>=0.01) {
968                 x = xc.toFixed(4);
969             } else if (x>=0.0001) {
970                 x = xc.toFixed(6);
971             } else {
972                 x = xc;
973             }
974             y = Math.abs(yc);
975             if (y>0.1) {
976                 y = yc.toFixed(2);
977             } else if (y>=0.01) {
978                 y = yc.toFixed(4);
979             } else if (y>=0.0001) {
980                 y = yc.toFixed(6);
981             } else {
982                 y = yc;
983             }
984 
985             this.highlightInfobox(x,y,el);
986         } else
987             this.highlightCustomInfobox(el.infoboxText, el);
988 
989         this.renderer.show(this.infobox);
990         this.renderer.updateText(this.infobox);
991     }
992     return this;
993 };
994 
995 /**
996  * Changes the text of the info box to what is provided via text.
997  * @param {String} text
998  * @returns {JXG.Board} Reference to the board.
999  */
1000 JXG.Board.prototype.highlightCustomInfobox = function(text) {
1001     this.infobox.setText('<span style="color:#bbbbbb;">' + text + '<'+'/span>');
1002     return this;
1003 };
1004 
1005 /**
1006  * Changes the text of the info box to show the given coordinates.
1007  * @param {Number} x
1008  * @param {Number} y
1009  * @returns {JXG.Board} Reference to the board.
1010  */
1011 JXG.Board.prototype.highlightInfobox = function(x, y) {
1012     this.highlightCustomInfobox('(' + x + ', ' + y + ')');
1013     return this;
1014 };
1015 
1016 /**
1017  * Remove highlighting of all elements.
1018  * @returns {JXG.Board} Reference to the board.
1019  */
1020 JXG.Board.prototype.dehighlightAll = function() {
1021     var el, pEl, needsDehighlight = false;
1022 
1023     for(el in this.highlightedObjects) {
1024         pEl = this.highlightedObjects[el];
1025         pEl.noHighlight();
1026         delete(this.highlightedObjects[el]);
1027         needsDehighlight = true;
1028 
1029         // In highlightedObjects should only be objects which fulfill all these conditions
1030         // And in case of complex elements, like a turtle based fractal, it should be faster to
1031         // just de-highlight the element instead of checking hasPoint...
1032         // if((!JXG.exists(pEl.hasPoint)) || !pEl.hasPoint(x, y) || !pEl.visProp['visible'])
1033     }
1034 
1035     
1036     /*
1037     // We do not need to redraw during dehighlighting in CanvasRenderer
1038     // because we are redrawing anyhow
1039     if (this.options.renderer=='canvas' && needsDehighlight) {
1040         this.prepareUpdate();
1041         this.renderer.suspendRedraw();
1042         this.updateRenderer();
1043         this.renderer.unsuspendRedraw();
1044     }
1045     */
1046     return this;
1047 };
1048 
1049 /**
1050  * In case of snapToGrid activated this method caclulates the screen coords of mouse "snapped to grid".
1051  * @param {Number} x X coordinate in screen coordinates
1052  * @param {Number} y Y coordinate in screen coordinates
1053  * @returns {Array} Coordinates of the mouse in screen coordinates, snapped to grid.
1054  */
1055 JXG.Board.prototype.getScrCoordsOfMouse = function (x, y) {
1056     if(this.options.grid.snapToGrid) {
1057         var newCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this);
1058         newCoords.setCoordinates(JXG.COORDS_BY_USER,
1059             [Math.round((newCoords.usrCoords[1])*this.options.grid.snapSizeX)/this.options.grid.snapSizeX,
1060              Math.round((newCoords.usrCoords[2])*this.options.grid.snapSizeY)/this.options.grid.snapSizeY]);
1061         return [newCoords.scrCoords[1], newCoords.scrCoords[2]];
1062     } else {
1063         return [x,y];
1064     }
1065 };
1066 
1067 /**
1068  * In case of snapToGrid activated this method caclulates the user coords of mouse "snapped to grid".
1069  * @param {Event} Evt Event object containing the mouse coordinates.
1070  * @returns {Array} Coordinates of the mouse in screen coordinates, snapped to grid.
1071  */
1072 JXG.Board.prototype.getUsrCoordsOfMouse = function (Evt) {
1073     var cPos = this.getRelativeMouseCoordinates(Evt),
1074         absPos = JXG.getPosition(Evt),
1075         x = absPos[0]-cPos[0],
1076         y = absPos[1]-cPos[1],
1077         newCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this);
1078 
1079     if(this.options.grid.snapToGrid) {
1080         newCoords.setCoordinates(JXG.COORDS_BY_USER,
1081             [Math.round((newCoords.usrCoords[1])*this.options.grid.snapSizeX)/this.options.grid.snapSizeX,
1082              Math.round((newCoords.usrCoords[2])*this.options.grid.snapSizeY)/this.options.grid.snapSizeY]);
1083     }
1084 
1085     return [newCoords.usrCoords[1], newCoords.usrCoords[2]];
1086 };
1087 
1088 /**
1089  * Collects all elements under current mouse position plus current user coordinates of mouse cursor.
1090  * @param {Event} Evt Event object containing the mouse coordinates.
1091  * @returns {Array} Array of elements at the current mouse position plus current user coordinates of mouse.
1092  */
1093 JXG.Board.prototype.getAllUnderMouse = function (Evt) {
1094     var elList = this.getAllObjectsUnderMouse(Evt);
1095 
1096     elList.push(this.getUsrCoordsOfMouse(Evt));
1097 
1098     return elList;
1099 };
1100 /**
1101  * Collects all elements under current mouse position.
1102  * @param {Event} Evt Event object containing the mouse coordinates.
1103  * @returns {Array} Array of elements at the current mouse position.
1104  */
1105 JXG.Board.prototype.getAllObjectsUnderMouse = function (Evt) {
1106     var cPos = this.getRelativeMouseCoordinates(Evt),
1107         absPos = JXG.getPosition(Evt),
1108         dx = absPos[0]-cPos[0],
1109         dy = absPos[1]-cPos[1],
1110         elList = [];
1111 
1112     for (var el in this.objects) {
1113         if (this.objects[el].visProp['visible'] && this.objects[el].hasPoint && this.objects[el].hasPoint(dx, dy)) {
1114             elList.push(this.objects[el]);
1115         }
1116     }
1117 
1118     return elList;
1119 };
1120 
1121 
1122 /**
1123  * Moves the origin and initializes an update of all elements.
1124  * @returns {JXG.Board} Reference to this board.
1125  */
1126 JXG.Board.prototype.moveOrigin = function () {
1127     var el, ob;
1128 
1129     for (ob in this.objects) {
1130         el = this.objects[ob];
1131         if (!el.frozen && (el.elementClass==JXG.OBJECT_CLASS_POINT ||
1132             el.elementClass==JXG.OBJECT_CLASS_CURVE ||
1133             el.type==JXG.OBJECT_TYPE_AXIS ||
1134             el.type==JXG.OBJECT_TYPE_TEXT)) {
1135             if (el.elementClass!=JXG.OBJECT_CLASS_CURVE && el.type!=JXG.OBJECT_TYPE_AXIS)
1136                 el.coords.usr2screen();
1137         }
1138     }
1139 
1140     this.clearTraces();
1141 
1142     this.fullUpdate();
1143     if(this.options.grid.hasGrid) {
1144         this.renderer.removeGrid(this);
1145         this.renderer.drawGrid(this);
1146     }
1147     return this;
1148 };
1149 
1150 /**
1151   * Add conditional updates to the elements.
1152   * @param {String} str String containing coniditional update in geonext syntax
1153   */
1154 JXG.Board.prototype.addConditions = function (str) {
1155     var plaintext = 'var el,x,y,c;\n',
1156         i = str.indexOf('<data>'),
1157         j = str.indexOf('<'+'/data>'),
1158         term, m, left, right, name, el;
1159 
1160     if (i<0) {
1161         return;
1162     }
1163 
1164     while (i>=0) {
1165         term = str.slice(i+6,j); // throw away <data>
1166         m = term.indexOf('=');
1167         left = term.slice(0,m);
1168         right = term.slice(m+1);
1169         m = left.indexOf('.'); // Dies erzeugt Probleme bei Variablennamen der Form " Steuern akt."
1170         name = left.slice(0,m);    //.replace(/\s+$/,''); // do NOT cut out name (with whitespace)
1171         el = this.elementsByName[JXG.unescapeHTML(name)];
1172 
1173         var property = left.slice(m+1).replace(/\s+/g,'').toLowerCase(); // remove whitespace in property
1174         right = JXG.GeonextParser.geonext2JS(right, this);
1175         right = right.replace(/this\.board\./g,'this.');
1176 
1177         // Debug
1178         if(!JXG.exists(this.elementsByName[name])){
1179             JXG.debug("debug conditions: |"+name+"| undefined");
1180         }
1181         plaintext += "el = this.objects[\"" + el.id + "\"];\n";
1182 
1183         switch (property) {
1184             case 'x':
1185                 plaintext += 'var y=el.coords.usrCoords[2];\n';  // y stays
1186                 plaintext += 'el.setPositionDirectly(JXG.COORDS_BY_USER,'+(right) +',y);\n';
1187                 plaintext += 'el.update();\n';
1188                 break;
1189             case 'y':
1190                 plaintext += 'var x=el.coords.usrCoords[1];\n';  // x stays
1191                 plaintext += 'el.coords=new JXG.Coords(JXG.COORDS_BY_USER,[x,'+(right)+'],this);\n';
1192                 break;
1193             case 'visible':
1194                 plaintext += 'var c='+(right)+';\n';
1195                 plaintext += 'if (c) {el.showElement();} else {el.hideElement();}\n';
1196                 break;
1197             case 'position':
1198                 plaintext += 'el.position = ' + (right) +';\n';
1199                 plaintext += 'el.update();\n';
1200                 break;
1201             case 'stroke':
1202                 plaintext += 'el.strokeColor = ' + (right) +';\n';
1203                 break;
1204             case 'style':
1205                 plaintext += 'el.setStyle(' + (right) +');\n';
1206                 break;
1207             case 'strokewidth':
1208                 plaintext += 'el.strokeWidth = ' + (right) +';\n';   // wird auch bei Punkten verwendet, was nicht realisiert ist.
1209                 break;
1210             case 'fill':
1211                 plaintext += 'var f='+(right)+';\n';
1212                 plaintext += 'el.setProperty({fillColor:f})\n';
1213                 break;
1214             case 'label':
1215                 break;
1216             default:
1217                 JXG.debug("property '" + property + "' in conditions not yet implemented:" + right);
1218                 break;
1219         }
1220         //plaintext += "}\n";
1221         str = str.slice(j+7); // cut off "</data>"
1222         i = str.indexOf('<data>');
1223         j = str.indexOf('<'+'/data>');
1224     }
1225     plaintext += 'this.prepareUpdate();\n';
1226     plaintext += 'this.updateElements();\n';
1227     plaintext += 'return true;\n';
1228 
1229     this.updateConditions = new Function(plaintext);
1230     this.updateConditions();
1231 };
1232 
1233 /**
1234  * Computes the commands in the conditions-section of the gxt file.
1235  * It is evaluated after an update, before the unsuspendRedraw.
1236  * The function is generated in
1237  * @see JXG.Board#addConditions
1238  * @private
1239  */
1240 JXG.Board.prototype.updateConditions = function() { return false; };
1241 
1242 /**
1243  * Calculates adequate snap sizes.
1244  * @returns {JXG.Board} Reference to the board.
1245  */
1246 JXG.Board.prototype.calculateSnapSizes = function() {
1247     var p1 = new JXG.Coords(JXG.COORDS_BY_USER,[0,0],this),
1248         p2 = new JXG.Coords(JXG.COORDS_BY_USER,[1/this.options.grid.gridX,1/this.options.grid.gridY],this),
1249         x = p1.scrCoords[1]-p2.scrCoords[1],
1250         y = p1.scrCoords[2]-p2.scrCoords[2];
1251 
1252     this.options.grid.snapSizeX = this.options.grid.gridX;
1253     while(Math.abs(x) > 25) {
1254         this.options.grid.snapSizeX *= 2;
1255         x /= 2;
1256     }
1257 
1258     this.options.grid.snapSizeY = this.options.grid.gridY;
1259     while(Math.abs(y) > 25) {
1260         this.options.grid.snapSizeY *= 2;
1261         y /= 2;
1262     }
1263     return this;
1264 };
1265 
1266 /**
1267  * Apply update on all objects with the new zoom-factors. Clears all traces.
1268  * @returns {JXG.Board} Reference to the board.
1269  */
1270 JXG.Board.prototype.applyZoom = function() {
1271     var el, ob;
1272     for(ob in this.objects) {
1273         el = this.objects[ob];
1274         if(!el.frozen && (el.elementClass==JXG.OBJECT_CLASS_POINT ||
1275             el.elementClass==JXG.OBJECT_CLASS_CURVE ||
1276             el.type==JXG.OBJECT_TYPE_AXIS ||
1277             el.type==JXG.OBJECT_TYPE_TEXT)) {
1278             if(el.elementClass!=JXG.OBJECT_CLASS_CURVE && el.type!=JXG.OBJECT_TYPE_AXIS)
1279                 el.coords.usr2screen();
1280         }
1281     }
1282     this.calculateSnapSizes();
1283 
1284     this.clearTraces();
1285 
1286     this.fullUpdate();
1287     if(this.options.grid.hasGrid) {
1288         this.renderer.removeGrid(this);
1289         this.renderer.drawGrid(this);
1290     }
1291     return this;
1292 };
1293 
1294 /**
1295  * Recalculate stretch factors.
1296  * Required after zooming or setting the bounding box.
1297  * @returns {JXG.Board} Reference to the board
1298  */
1299 JXG.Board.prototype.updateStretch = function() {
1300     this.stretchX = this.zoomX*this.unitX;
1301     this.stretchY = this.zoomY*this.unitY;
1302     return this;
1303 };
1304 
1305 /**
1306  * Zooms into the board by the factor board.options.zoom.factor and applies the zoom.
1307  * @returns {JXG.Board} Reference to the board
1308  */
1309 JXG.Board.prototype.zoomIn = function() {
1310     var oX, oY;
1311     this.zoomX *= this.options.zoom.factor;
1312     this.zoomY *= this.options.zoom.factor;
1313     oX = this.origin.scrCoords[1]*this.options.zoom.factor;
1314     oY = this.origin.scrCoords[2]*this.options.zoom.factor;
1315     this.origin = new JXG.Coords(JXG.COORDS_BY_SCREEN, [oX, oY], this);
1316     this.updateStretch();
1317     this.applyZoom();
1318     return this;
1319 };
1320 
1321 /**
1322  * Zooms out of the board by the factor board.options.zoom.factor and applies the zoom.
1323  * @returns {JXG.Board} Reference to the board
1324  */
1325 JXG.Board.prototype.zoomOut = function() {
1326     var oX, oY;
1327     this.zoomX /= this.options.zoom.factor;
1328     this.zoomY /= this.options.zoom.factor;
1329     oX = this.origin.scrCoords[1]/this.options.zoom.factor;
1330     oY = this.origin.scrCoords[2]/this.options.zoom.factor;
1331     this.origin = new JXG.Coords(JXG.COORDS_BY_SCREEN, [oX, oY], this);
1332     this.updateStretch();
1333     this.applyZoom();
1334     return this;
1335 };
1336 
1337 /**
1338  * Resets zoom factor to 100%.
1339  * @returns {JXG.Board} Reference to the board
1340  */
1341 JXG.Board.prototype.zoom100 = function() {
1342     var oX, oY, zX, zY;
1343 
1344     zX = this.zoomX;
1345     zY = this.zoomY;
1346     this.zoomX = 1.0;
1347     this.zoomY = 1.0;
1348 
1349     oX = this.origin.scrCoords[1]/zX;
1350     oY = this.origin.scrCoords[2]/zY;
1351     this.origin = new JXG.Coords(JXG.COORDS_BY_SCREEN, [oX, oY], this);
1352     this.updateStretch();
1353     this.applyZoom();
1354     return this;
1355 };
1356 
1357 /**
1358  * Zooms the board so every visible point is shown. Keeps aspect ratio.
1359  * @returns {JXG.Board} Reference to the board
1360  */
1361 JXG.Board.prototype.zoomAllPoints = function() {
1362     var ratio, minX, maxX, minY, maxY, el,
1363         border, borderX, borderY, distX, distY, newZoom, newZoomX, newZoomY,
1364         newOriginX, newOriginY;
1365 
1366     ratio = this.zoomX / this.zoomY;
1367     minX = 0; // (0,0) soll auch sichtbar bleiben
1368     maxX = 0;
1369     minY = 0;
1370     maxY = 0;
1371     for(el in this.objects) {
1372         if( (this.objects[el].elementClass == JXG.OBJECT_CLASS_POINT) &&
1373             this.objects[el].visProp['visible']) {
1374             if(this.objects[el].coords.usrCoords[1] < minX) {
1375                 minX = this.objects[el].coords.usrCoords[1];
1376             } else if(this.objects[el].coords.usrCoords[1] > maxX) {
1377                 maxX = this.objects[el].coords.usrCoords[1];
1378             }
1379             if(this.objects[el].coords.usrCoords[2] > maxY) {
1380                 maxY = this.objects[el].coords.usrCoords[2];
1381             } else if(this.objects[el].coords.usrCoords[2] < minY) {
1382                 minY = this.objects[el].coords.usrCoords[2];
1383             }
1384         }
1385     }
1386     border = 50;
1387     borderX = border/(this.unitX*this.zoomX);
1388     borderY = border/(this.unitY*this.zoomY);
1389 
1390     distX = maxX - minX + 2*borderX;
1391     distY = maxY - minY + 2*borderY;
1392 
1393     newZoom = Math.min(this.canvasWidth/(this.unitX*distX), this.canvasHeight/(this.unitY*distY));
1394     newZoomY = newZoom;
1395     newZoomX = newZoom*ratio;
1396 
1397     newOriginX = -(minX-borderX)*this.unitX*newZoomX;
1398     newOriginY = (maxY+borderY)*this.unitY*newZoomY;
1399     this.origin = new JXG.Coords(JXG.COORDS_BY_SCREEN, [newOriginX, newOriginY], this);
1400     this.zoomX = newZoomX;
1401     this.zoomY = newZoomY;
1402     this.updateStretch();
1403     this.applyZoom();
1404     return this;
1405 };
1406 
1407 /**
1408  * Removes object from board and renderer.
1409  * @param {JXG.GeometryElement} object The object to remove.
1410  * @returns {JXG.Board} Reference to the board
1411  */
1412 JXG.Board.prototype.removeObject = function(object) {
1413     var el, i;
1414 
1415     if(JXG.isArray(object)) {
1416         for(i=0; i<object.length; i++)
1417             this.removeObject(object[i]);
1418     }
1419 
1420     object = JXG.getReference(this, object);
1421 
1422     // If the object which is about to be removed unknown, do nothing.
1423     if(!JXG.exists(object)) {
1424         return this;
1425     }
1426 
1427     try{
1428         // remove all children.
1429         for(el in object.childElements) {
1430             object.childElements[el].board.removeObject(object.childElements[el]);
1431         }
1432 
1433         for(el in this.objects) {
1434             if(JXG.exists(this.objects[el].childElements))
1435                 delete(this.objects[el].childElements[object.id]);
1436         }
1437 
1438         // remove the object itself from our control structures
1439         delete(this.objects[object.id]);
1440         delete(this.elementsByName[object.name]);
1441 
1442         // the object deletion itself is handled by the object.
1443         if(JXG.exists(object.remove)) object.remove();
1444     } catch(e) {
1445         JXG.debug(object.id + ': Could not be removed, JS says:\n\n' + e);
1446     }
1447     return this;
1448 };
1449 
1450 /**
1451  * Initialize some objects which are contained in every GEONExT construction by default,
1452  * but are not contained in the gxt files.
1453  * @returns {JXG.Board} Reference to the board
1454  */
1455 JXG.Board.prototype.initGeonextBoard = function() {
1456     var p1, p2, p3, l1, l2;
1457 
1458     p1 = new JXG.Point(this, [0,0],this.id + 'gOOe0','Ursprung',false);
1459     p1.fixed = true;
1460     p2 = new JXG.Point(this, [1,0],this.id + 'gXOe0','Punkt_1_0',false);
1461     p2.fixed = true;
1462     p3 = new JXG.Point(this, [0,1],this.id + 'gYOe0','Punkt_0_1',false);
1463     p3.fixed = true;
1464     l1 = new JXG.Line(this, this.id + 'gOOe0', this.id + 'gXOe0', this.id + 'gXLe0','X-Achse', false);
1465     l1.hideElement();
1466     l2 = new JXG.Line(this, this.id + 'gOOe0', this.id + 'gYOe0', this.id + 'gYLe0','Y-Achse', false);
1467     l2.hideElement();
1468     return this;
1469 };
1470 
1471 /**
1472  * Initialize the info box object which is used to display
1473  * the coordinates of points near the mouse pointer,
1474  * @returns {JXG.Board} Reference to the board
1475  */
1476 JXG.Board.prototype.initInfobox= function() {
1477     this.infobox = new JXG.Text(this, '0,0', '', [0,0], this.id + '__infobox',null, null, false, 'html');
1478     this.infobox.distanceX = -20;
1479     this.infobox.distanceY = 25;
1480 	this.renderer.hide(this.infobox);
1481     return this;
1482 };
1483 
1484 /**
1485  * Change the height and width of the board's container.
1486  * @param {Number} canvasWidth New width of the container.
1487  * @param {Number} canvasHeight New height of the container.
1488  * @returns {JXG.Board} Reference to the board
1489  */
1490 JXG.Board.prototype.resizeContainer = function(canvasWidth, canvasHeight) {
1491     this.canvasWidth = parseFloat(canvasWidth);
1492     this.canvasHeight = parseFloat(canvasHeight);
1493     this.containerObj.style.width = (this.canvasWidth) + 'px';
1494     this.containerObj.style.height = (this.canvasHeight) + 'px';
1495     return this;
1496 };
1497 
1498 /**
1499  * Lists the dependencies graph in a new HTML-window.
1500  * @returns {JXG.Board} Reference to the board
1501  */
1502 JXG.Board.prototype.showDependencies = function() {
1503     var el, t, c, f, i;
1504 
1505     t = '<p>\n';
1506     for (el in this.objects) {
1507         i = 0;
1508         for (c in this.objects[el].childElements) {
1509             i++;
1510         }
1511         if (i>=0) {
1512             t += '<b>' + this.objects[el].id + ':<'+'/b> ';
1513         }
1514         for (c in this.objects[el].childElements) {
1515             t += this.objects[el].childElements[c].id+'('+this.objects[el].childElements[c].name+')'+', ';
1516         }
1517         t += '<p>\n';
1518     }
1519     t += '<'+'/p>\n';
1520     f = window.open();
1521     f.document.open();
1522     f.document.write(t);
1523     f.document.close();
1524     return this;
1525 };
1526 
1527 /**
1528  * Lists the XML code of the construction in a new HTML-window.
1529  * @returns {JXG.Board} Reference to the board
1530  */
1531 JXG.Board.prototype.showXML = function() {
1532     var f = window.open('');
1533     f.document.open();
1534     f.document.write('<pre>'+JXG.escapeHTML(this.xmlString)+'<'+'/pre>');
1535     f.document.close();
1536     return this;
1537 };
1538 
1539 /**
1540  * Sets for all objects the needsUpdate flag to "true".
1541  * @returns {JXG.Board} Reference to the board
1542  */
1543 JXG.Board.prototype.prepareUpdate = function() {
1544     var el;
1545     for(el in this.objects) {
1546        this.objects[el].needsUpdate = true;
1547     }
1548     return this;
1549 };
1550 
1551 /**
1552  * Runs through all elements and calls their update() method.
1553  * @param {JXG.GeometryElement} drag Element that caused the update.
1554  * @returns {JXG.Board} Reference to the board
1555  */
1556 JXG.Board.prototype.updateElements = function(drag) {
1557     var el, pEl;
1558         // isBeforeDrag: see updateRenderer
1559         //isBeforeDrag = true; // If possible, we start the update at the dragged object.
1560         
1561     drag = JXG.getRef(this, drag);
1562     // if (drag==null) { isBeforeDrag = false; }
1563 
1564     for(el in this.objects) {
1565         pEl = this.objects[el];
1566         //if (isBeforeDrag && drag!=null && pEl.id == drag.id) {
1567         //    isBeforeDrag = false;
1568         //}
1569         if (!this.needsFullUpdate && (/*isBeforeDrag ||*/ !pEl.needsRegularUpdate)) { continue; }
1570 
1571         // For updates of an element we distinguish if the dragged element is updated or
1572         // other elements are updated.
1573         // The difference lies in the treatment of gliders.
1574         if (drag==null || pEl.id!=drag.id) {
1575             pEl.update(true);   // an element following the dragged element is updated
1576         } else {
1577             pEl.update(false);  // the dragged object itself is updated
1578         }
1579     }
1580     return this;
1581 };
1582 
1583 /**
1584  * Runs through all elements and calls their update() method.
1585  * @param {JXG.GeometryElement} drag Element that caused the update.
1586  * @returns {JXG.Board} Reference to the board
1587  */
1588 JXG.Board.prototype.updateRenderer = function(drag) {
1589     var el, pEl;
1590         // isBeforDrag does not work because transformations may depend 
1591         // on a dragged element and can be bound to elements before the 
1592         // dragged element.
1593         //isBeforeDrag = true; // If possible, we start the update at the dragged object.
1594 
1595     if (this.options.renderer=='canvas') {
1596         this.updateRendererCanvas(drag);
1597     } else {
1598         // drag = JXG.getReference(this, drag);
1599         //if (drag==null) { isBeforeDrag = false; }
1600 
1601         for(el in this.objects) {
1602             pEl = this.objects[el];
1603             // if (isBeforeDrag && drag!=null && pEl.id == drag.id) { isBeforeDrag = false; }
1604             if ( !this.needsFullUpdate && (/*isBeforeDrag ||*/ !pEl.needsRegularUpdate) ) { 
1605                 continue; 
1606             }
1607             pEl.updateRenderer();
1608         }
1609     }
1610     return this;
1611 };
1612 
1613 /**
1614  * Runs through all elements and calls their update() method.
1615  * This is a special version for the CanvasRenderer.
1616  * Here, we have to do our own layer handling.
1617  * @param {JXG.GeometryElement} drag Element that caused the update.
1618  * @returns {JXG.Board} Reference to the board
1619  */
1620 JXG.Board.prototype.updateRendererCanvas = function(drag) {
1621     var el, pEl, i, 
1622         layers = this.options.layer,
1623         len = this.options.layer.numlayers,
1624         last = Number.NEGATIVE_INFINITY, layer;
1625     
1626     for (i=0;i<len;i++) {
1627         mini = Number.POSITIVE_INFINITY;
1628         for (la in layers) {
1629             if (layers[la]>last && layers[la]<mini) {
1630                 mini = layers[la];
1631             }
1632         }
1633         last = mini;
1634         for (el in this.objects) {
1635             pEl = this.objects[el];
1636             if (pEl.layer==mini) {
1637                 pEl.updateRenderer();
1638             }
1639         }
1640     }
1641     return this;
1642 };
1643 
1644 /**
1645  * Adds a hook to this board. A hook is a function which will be called on every board update.
1646  * @param {Function} hook A function to be called by the board after an update occured.
1647  * @param {String} m When the hook is to be called. Possible values are <i>mouseup</i>, <i>mousedown</i> and <i>update</i>.
1648  * @returns {Number} Id of the hook, required to remove the hook from the board.
1649  */
1650 JXG.Board.prototype.addHook = function(hook, m) {
1651     if(!JXG.exists(m))
1652         m = 'update';
1653 
1654     this.hooks.push({fn: hook, mode: m});
1655 
1656     if(m=='update')
1657         hook(this);
1658 
1659     return (this.hooks.length-1);
1660 };
1661 
1662 /**
1663  * Removes a hook from the board.
1664  * @param {Number} id Id for the hook. The number you got when you added the hook.
1665  * @returns {JXG.Board} Reference to the board
1666  */
1667 JXG.Board.prototype.removeHook = function(id) {
1668     this.hooks[id] = null;
1669     return this;
1670 };
1671 
1672 /**
1673  * Runs through all hooked functions and calls them.
1674  * @returns {JXG.Board} Reference to the board
1675  */
1676 JXG.Board.prototype.updateHooks = function(m) {
1677     var i, args = arguments.length > 1 ? Array.prototype.slice.call(arguments, 1) : [];
1678 
1679     if(!JXG.exists(m))
1680         m = 'update';
1681 
1682     for(i=0; i<this.hooks.length; i++) {
1683         if((this.hooks[i] != null) && (this.hooks[i].mode == m)) {
1684             this.hooks[i].fn.apply(this, args);
1685         }
1686     }
1687     return this;
1688 };
1689 
1690 /**
1691  * Adds a dependent board to this board.
1692  * @param {JXG.Board} board A reference to board which will be updated after an update of this board occured.
1693  * @returns {JXG.Board} Reference to the board
1694  */
1695 JXG.Board.prototype.addChild = function(board) {
1696     this.dependentBoards.push(board);
1697     this.update();
1698     return this;
1699 };
1700 
1701 /**
1702  * Deletes a board from the list of dependent boards.
1703  * @param {JXG.Board} board Reference to the board which will be removed.
1704  * @returns {JXG.Board} Reference to the board
1705  */
1706 JXG.Board.prototype.removeChild = function(board) {
1707     var i;
1708     for (i=this.dependentBoards.length-1; i>=0; i--) {
1709         if (this.dependentBoards[i] == board) {
1710             this.dependentBoards.splice(i,1);
1711         }
1712     }
1713     return this;
1714 };
1715 
1716 /**
1717  * Runs through most elements and calls their update() method and update the conditions.
1718  * @param {Object} drag Element that caused the update.
1719  * @returns {JXG.Board} Reference to the board
1720  */
1721 JXG.Board.prototype.update = function(drag) {
1722     var i, len, boardId, b;
1723     if (this.isSuspendedUpdate) { return this; }
1724     this.prepareUpdate(drag).updateElements(drag).updateConditions();
1725     this.renderer.suspendRedraw();
1726     this.updateRenderer(drag);
1727     this.renderer.unsuspendRedraw();
1728     this.updateHooks();
1729 
1730     // To resolve dependencies between boards
1731     //for(var board in JXG.JSXGraph.boards) {
1732     len = this.dependentBoards.length;
1733     for (i=0; i<len; i++) {
1734         boardId = this.dependentBoards[i].id;
1735         b = JXG.JSXGraph.boards[boardId];
1736         if( b != this) {
1737             b.updateQuality = this.updateQuality;
1738             b.prepareUpdate().updateElements().updateConditions();
1739             b.renderer.suspendRedraw();
1740             b.updateRenderer();
1741             b.renderer.unsuspendRedraw();
1742             b.updateHooks();
1743         }
1744 
1745     }
1746     return this;
1747 };
1748 
1749 /**
1750  * Runs through all elements and calls their update() method and update the conditions.
1751  * This is necessary after zooming and changing the bounding box.
1752  * @returns {JXG.Board} Reference to the board
1753  */
1754 JXG.Board.prototype.fullUpdate = function() {
1755     this.needsFullUpdate = true;
1756     this.update();
1757     this.needsFullUpdate = false;
1758     return this;
1759 };
1760 
1761 /**
1762  * Creates a new geometric element of type elementType.
1763  * @param {String} elementType Type of the element to be constructed given as a string e.g. 'point' or 'circle'.
1764  * @param {Array} parents Array of parent elements needed to construct the element e.g. coordinates for a point or two
1765  * points to construct a line. This highly depends on the elementType that is constructed. See the corresponding JXG.create*
1766  * methods for a list of possible parameters.
1767  * @param {Object} attributes An object containing the attributes to be set. This also depends on the elementType.
1768  * Common attributes are name, visible, strokeColor. See {@link JXG.GeometryElement#setProperty}.
1769  * @returns {Object} Reference to the created element. This is usually a GeometryElement, but can be an array containing
1770  * two or more elements.
1771  */
1772 JXG.Board.prototype.createElement = function(elementType, parents, attributes) {
1773     var el, i, s;
1774 
1775     // Turtle may have no parent elements
1776     if (elementType!='turtle' && (!JXG.exists(parents) || (parents.length && parents.length == 0))) {
1777         return null;
1778     }
1779     
1780     if (!JXG.exists(parents)) {
1781         parents = [];
1782     }
1783 
1784     elementType = elementType.toLowerCase();
1785 
1786     if (attributes==null) {
1787         attributes = {};
1788     }
1789     for (i=0; i<parents.length; i++) {
1790         parents[i] = JXG.getReference(this, parents[i]); // TODO: should not be done for content-parameter of JXG.Text
1791     }
1792 
1793     if(JXG.JSXGraph.elements[elementType] != null) {
1794 	if(typeof JXG.JSXGraph.elements[elementType] == 'function') {
1795             el = JXG.JSXGraph.elements[elementType](this, parents, attributes);
1796         } else {
1797             el = JXG.JSXGraph.elements[elementType].creator(this, parents, attributes);
1798         }
1799     } else {
1800         throw new Error("JSXGraph: JXG.createElement: Unknown element type given: "+elementType);
1801     }
1802 
1803     if (!JXG.exists(el)) {
1804         JXG.debug("JSXGraph: JXG.createElement: failure creating "+elementType);
1805         return el;
1806     }
1807 
1808     if(JXG.isArray(attributes)) {
1809         attributes = attributes[0];
1810     }
1811 
1812     if(el.multipleElements) {
1813         for(s in el) {
1814             if(el[s].setProperty)
1815                 el[s].setProperty(attributes);
1816         }
1817     } else {
1818         if(el.setProperty)
1819             el.setProperty(attributes);
1820     }
1821 
1822     this.update(el); // We start updating at the newly created element. AW
1823     return el;
1824 };
1825 
1826 /**
1827  * This is just a shortcut for {@link JXG.Board#createElement}.
1828  */
1829 JXG.Board.prototype.create = JXG.Board.prototype.createElement;
1830 
1831 /**
1832  * Delete the elements drawn as part of a trace of an element.
1833  * @returns {JXG.Board} Reference to the board
1834  */
1835 JXG.Board.prototype.clearTraces = function() {
1836     var el;
1837 
1838     for(el in this.objects) {
1839         if (this.objects[el].traced)
1840             this.objects[el].clearTrace();
1841     }
1842     this.numTraces = 0;
1843     return this;
1844 };
1845 
1846 /**
1847  * Stop updates of the board.
1848  * @returns {JXG.Board} Reference to the board
1849  */
1850 JXG.Board.prototype.suspendUpdate = function() {
1851     this.isSuspendedUpdate = true;
1852     return this;
1853 };
1854 
1855 /**
1856  * Enable updates of the board.
1857  * @returns {JXG.Board} Reference to the board
1858  */
1859 JXG.Board.prototype.unsuspendUpdate = function() {
1860     this.isSuspendedUpdate = false;
1861     this.update();
1862     return this;
1863 };
1864 
1865 /**
1866  * Set the bounding box of the board.
1867  * @param {Array} bbox New bounding box [x1,y1,x2,y2]
1868  * @param {Boolean} [keepaspectratio=false] If set to true, the aspect ratio will be 1:1, but
1869  * the resulting viewport may be larger.
1870  * @returns {JXG.Board} Reference to the board
1871  */
1872 JXG.Board.prototype.setBoundingBox = function(bbox, keepaspectratio) {
1873     if (!JXG.isArray(bbox)) return this;
1874 
1875     var h, w, oX, oY,
1876         dim = JXG.getDimensions(this.container);
1877 
1878     this.canvasWidth = parseInt(dim.width);
1879     this.canvasHeight = parseInt(dim.height);
1880     w = this.canvasWidth;
1881     h = this.canvasHeight;
1882     if (keepaspectratio) {
1883         this.unitX = w/(bbox[2]-bbox[0]);
1884         this.unitY = h/(-bbox[3]+bbox[1]);
1885         if (this.unitX<this.unitY) {
1886             this.unitY = this.unitX;
1887         } else {
1888             this.unitX = this.unitY;
1889         }
1890     } else {
1891         this.unitX = w/(bbox[2]-bbox[0]);
1892         this.unitY = h/(-bbox[3]+bbox[1]);
1893     }
1894     oX = -this.unitX*bbox[0]*this.zoomX;
1895     oY = this.unitY*bbox[1]*this.zoomY;
1896     this.origin = new JXG.Coords(JXG.COORDS_BY_SCREEN, [oX, oY], this);
1897     this.updateStretch();
1898     this.moveOrigin();
1899     return this;
1900 };
1901 
1902 /**
1903  * Get the bounding box of the board.
1904  * @returns {Array} bounding box [x1,y1,x2,y2] upper left corner, lower right corner
1905  */
1906 JXG.Board.prototype.getBoundingBox = function() {
1907     var ul = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0], this),
1908         lr = new JXG.Coords(JXG.COORDS_BY_SCREEN, [this.canvasWidth, this.canvasHeight], this);
1909     return [ul.usrCoords[1],ul.usrCoords[2],lr.usrCoords[1],lr.usrCoords[2]];
1910 };
1911 
1912 /**
1913  * Adds an animation. Animations are controlled by the boards, so the boards need to be aware of the
1914  * animated elements. This function tells the board about new elements to animate.
1915  * @param {JXG.GeometryElement} element The element which is to be animated.
1916  * @returns {JXG.Board} Reference to the board
1917  */
1918 JXG.Board.prototype.addAnimation = function(element) {
1919     this.animationObjects[element.id] = element;
1920 
1921     if(!this.animationIntervalCode) {
1922         this.animationIntervalCode = window.setInterval('JXG.JSXGraph.boards[\'' + this.id + '\'].animate();', 35);
1923     }
1924 
1925     return this;
1926 };
1927 
1928 /**
1929  * Cancels all running animations.
1930  * @returns {JXG.Board} Reference to the board
1931  */
1932 JXG.Board.prototype.stopAllAnimation = function() {
1933     var el;
1934 
1935     for(el in this.animationObjects) {
1936         if(this.animationObjects[el] === null)
1937             continue;
1938 
1939         this.animationObjects[el] = null;
1940         delete(this.animationObjects[el]);
1941     }
1942 
1943     window.clearInterval(this.animationIntervalCode);
1944     delete(this.animationIntervalCode);
1945 
1946     return this;
1947 };
1948 
1949 /**
1950  * General purpose animation function. This currently only supports moving points from one place to another. This
1951  * is faster than managing the animation per point, especially if there is more than one animated point at the same time.
1952  * @returns {JXG.Board} Reference to the board
1953  */
1954 JXG.Board.prototype.animate = function() {
1955     var count = 0,
1956         el, o, newCoords, r, p, c,
1957         obj=null;
1958 
1959     for(el in this.animationObjects) {
1960         if(this.animationObjects[el] === null)
1961             continue;
1962 
1963         count++;
1964         o = this.animationObjects[el];
1965         if(o.animationPath) {
1966             if(JXG.isFunction(o.animationPath)) {
1967                 newCoords = o.animationPath(new Date().getTime() - o.animationStart);
1968             } else {
1969                 newCoords = o.animationPath.pop();
1970             }
1971 
1972             if((!JXG.exists(newCoords)) || (!JXG.isArray(newCoords) && isNaN(newCoords))) {
1973                 delete(o.animationPath);
1974             } else {
1975                 //o.setPositionByTransform(JXG.COORDS_BY_USER, newCoords[0] - o.coords.usrCoords[1], newCoords[1] - o.coords.usrCoords[2]);
1976                 o.setPositionDirectly(JXG.COORDS_BY_USER, newCoords[0], newCoords[1]);
1977                 //this.update(o);  // May slow down the animation, but is important
1978                                  // for dependent glider objects (see tangram.html).
1979                                  // Otherwise the intended projection may be incorrect.
1980                 o.prepareUpdate().update().updateRenderer();
1981                 obj = o;
1982             }
1983         }
1984         if(o.animationData) {
1985             c = 0;
1986             for(r in o.animationData) {
1987                 p = o.animationData[r].pop();
1988                 if(!JXG.exists(p)) {
1989                     delete(o.animationData[p]);
1990                 } else {
1991                     c++;
1992                     o.setProperty(r + ':' + p);
1993                 }
1994             }
1995             if(c==0)
1996                 delete(o.animationData);
1997         }
1998 
1999         if(!JXG.exists(o.animationData) && !JXG.exists(o.animationPath)) {
2000             this.animationObjects[el] = null;
2001             delete(this.animationObjects[el]);
2002         }
2003     }
2004 
2005     if(count == 0) {
2006         window.clearInterval(this.animationIntervalCode);
2007         delete(this.animationIntervalCode);
2008     } else {
2009         this.update(obj);
2010     }
2011 
2012     return this;
2013 };
2014 
2015 /**
2016  * Initializes color blindness simulation.
2017  * @param {String} deficiency Describes the color blindness deficiency which is simulated. Accepted values are 'protanopia', 'deuteranopia', and 'tritanopia'.
2018  * @returns {JXG.Board} Reference to the board
2019  */
2020 JXG.Board.prototype.emulateColorblindness = function(deficiency) {
2021     var e, o, brd=this;
2022 
2023     if(!JXG.exists(deficiency))
2024         deficiency = 'none';
2025 
2026     if(this.currentCBDef == deficiency)
2027         return this;
2028 
2029     for(e in brd.objects) {
2030         o = brd.objects[e];
2031         if(deficiency != 'none') {
2032             if(this.currentCBDef == 'none')
2033                 o.visPropOriginal = JXG.deepCopy(o.visProp);
2034             o.setProperty({strokeColor: JXG.rgb2cb(o.visPropOriginal.strokeColor, deficiency), fillColor: JXG.rgb2cb(o.visPropOriginal.fillColor, deficiency),
2035                            highlightStrokeColor: JXG.rgb2cb(o.visPropOriginal.highlightStrokeColor, deficiency), highlightFillColor: JXG.rgb2cb(o.visPropOriginal.highlightFillColor, deficiency)});
2036         } else if(JXG.exists(o.visPropOriginal)) {
2037             o.visProp = JXG.deepCopy(o.visPropOriginal);
2038         }
2039     }
2040     this.currentCBDef = deficiency;
2041     this.update();
2042 
2043     return this;
2044 };
2045 
2046         /**
2047          * Function to animate a curve rolling on another curve.
2048          * @param {Curve} c1 JSXGraph curve building the floor where c2 rolls 
2049          * @param {Curve} c2 JSXGraph curve which rolls on c1.
2050          * @param {number} start_c1 The parameter t such that c1(t) touches c2. This is the start position of the 
2051          *                          rolling process
2052          * @param {Number} stepsize Increase in t in each step for the curve c1
2053          * @param {Number} time Delay time for setInterval()
2054          * @returns {Array} pointlist Array of points which are rolled in each step. This list should contain
2055          *      all points which define c2 and gliders on c2.
2056          * 
2057          * @example
2058          *
2059          * // Line which will be the floor to roll upon.
2060          * var line = brd.create('curve', [function(t) { return t;}, function(t){ return 1;}], {strokeWidth:6});
2061          * // Center of the rolling circle
2062          * var C = brd.create('point',[0,2],{name:'C'});
2063          * // Starting point of the rolling circle
2064          * var P = brd.create('point',[0,1],{name:'P', trace:true});
2065          * // Circle defined as a curve. The circle "starts" at P, i.e. circle(0) = P
2066          * var circle = brd.create('curve',[
2067          *           function(t){var d = P.Dist(C),
2068          *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
2069          *                       t += beta;
2070          *                       return C.X()+d*Math.cos(t);
2071          *           },
2072          *           function(t){var d = P.Dist(C),
2073          *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
2074          *                       t += beta;
2075          *                       return C.Y()+d*Math.sin(t);
2076          *           },
2077          *           0,2*Math.PI],
2078          *           {strokeWidth:6, strokeColor:'green'});
2079          *
2080          * // Point on circle
2081          * var B = brd.create('glider',[0,2,circle],{name:'B', color:'blue',trace:false});
2082          * var roll = brd.createRoulette(line, circle, 0, Math.PI/20, 1, 100, [C,P,B]); 
2083          * roll.start() // Start the rolling, to be stopped by roll.stop()
2084          * 
2085          * </pre><div id="e5e1b53c-a036-4a46-9e35-190d196beca5" style="width: 300px; height: 300px;"></div>
2086          * <script type="text/javascript">
2087          * var brd = JXG.JSXGraph.initBoard('e5e1b53c-a036-4a46-9e35-190d196beca5', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright:false, shownavigation: false});
2088          * // Line which will be the floor to roll upon.
2089          * var line = brd.create('curve', [function(t) { return t;}, function(t){ return 1;}], {strokeWidth:6});
2090          * // Center of the rolling circle
2091          * var C = brd.create('point',[0,2],{name:'C'});
2092          * // Starting point of the rolling circle
2093          * var P = brd.create('point',[0,1],{name:'P', trace:true});
2094          * // Circle defined as a curve. The circle "starts" at P, i.e. circle(0) = P
2095          * var circle = brd.create('curve',[
2096          *           function(t){var d = P.Dist(C),
2097          *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
2098          *                       t += beta;
2099          *                       return C.X()+d*Math.cos(t);
2100          *           },
2101          *           function(t){var d = P.Dist(C),
2102          *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
2103          *                       t += beta;
2104          *                       return C.Y()+d*Math.sin(t);
2105          *           },
2106          *           0,2*Math.PI],
2107          *           {strokeWidth:6, strokeColor:'green'});
2108          *
2109          * // Point on circle
2110          * var B = brd.create('glider',[0,2,circle],{name:'B', color:'blue',trace:false});
2111          * var roll = brd.createRoulette(line, circle, 0, Math.PI/20, 1, 100, [C,P,B]); 
2112          * roll.start() // Start the rolling, to be stopped by roll.stop()
2113          * </script><pre>
2114          * 
2115          */
2116 JXG.Board.prototype.createRoulette = function(c1, c2, start_c1, stepsize, direction, time, pointlist) {
2117     var brd = this;
2118     var Roulette = function() {
2119         var alpha = 0, Tx = 0, Ty = 0,
2120             t1 = start_c1,
2121             t2 = JXG.Math.Numerics.root(
2122                 function(t) { 
2123                     var c1x = c1.X(t1),
2124                         c1y = c1.Y(t1),
2125                         c2x = c2.X(t),
2126                         c2y = c2.Y(t);
2127                     return (c1x-c2x)*(c1x-c2x) + (c1y-c2y)*(c1y-c2y);
2128                 },
2129                 [0,Math.PI*2]),
2130             t1_new = 0.0, t2_new = 0.0, 
2131             c1dist,
2132             rotation = brd.create('transform',[function(){ return alpha;}], {type:'rotate'}),
2133             rotationLocal = brd.create('transform',[function(){ return alpha;}, 
2134                                            function(){ return c1.X(t1);},
2135                                            function(){ return c1.Y(t1);}], 
2136                                           {type:'rotate'}),
2137             translate = brd.create('transform',[function(){ return Tx;}, function(){ return Ty;}], {type:'translate'}),
2138             
2139             //
2140             // arc length via Simpson's rule.
2141             arclen = function(c,a,b) {
2142                 var cpxa = JXG.Math.Numerics.D(c.X)(a), cpya = JXG.Math.Numerics.D(c.Y)(a),
2143                     cpxb = JXG.Math.Numerics.D(c.X)(b), cpyb = JXG.Math.Numerics.D(c.Y)(b),
2144                     cpxab = JXG.Math.Numerics.D(c.X)((a+b)*0.5), cpyab = JXG.Math.Numerics.D(c.Y)((a+b)*0.5),
2145                     fa = Math.sqrt(cpxa*cpxa+cpya*cpya),
2146                     fb = Math.sqrt(cpxb*cpxb+cpyb*cpyb),
2147                     fab = Math.sqrt(cpxab*cpxab+cpyab*cpyab);
2148                 return (fa+4*fab+fb)*(b-a)/6.0;
2149             },
2150             exactDist = function(t) {
2151                 return c1dist - arclen(c2,t2,t);
2152                 },   
2153             beta = Math.PI/18.0,
2154             beta9 = beta*9,
2155             interval = null; 
2156 
2157         this.rolling = function(){
2158             t1_new = t1+direction*stepsize;
2159             c1dist = arclen(c1,t1,t1_new);             // arc length between c1(t1) and c1(t1_new)
2160             t2_new = JXG.Math.Numerics.root(exactDist, t2);
2161                                                        // find t2_new such that arc length between c2(t2) and c1(t2_new)
2162                                                        // equals c1dist.
2163             
2164             var h = new JXG.Complex(c1.X(t1_new),c1.Y(t1_new));    // c1(t) as complex number
2165             var g = new JXG.Complex(c2.X(t2_new),c2.Y(t2_new));    // c2(t) as complex number
2166             var hp = new JXG.Complex(JXG.Math.Numerics.D(c1.X)(t1_new),JXG.Math.Numerics.D(c1.Y)(t1_new));
2167             var gp = new JXG.Complex(JXG.Math.Numerics.D(c2.X)(t2_new),JXG.Math.Numerics.D(c2.Y)(t2_new));
2168             var z = JXG.C.div(hp,gp);                  // z is angle between the tangents of  
2169                                                        // c1 at t1_new, and c2 at t2_new
2170             alpha = Math.atan2(z.imaginary, z.real);
2171             z.div(JXG.C.abs(z));                       // Normalizing the quotient
2172             z.mult(g);
2173             Tx = h.real-z.real;
2174             Ty = h.imaginary-z.imaginary;              // T = h(t1_new)-g(t2_new)*h'(t1_new)/g'(t2_new);
2175 
2176             if (alpha <-beta && alpha>-beta9) {        // -(10-90) degrees: make corners roll smoothly
2177                 alpha = -beta;
2178                 rotationLocal.applyOnce(pointlist);
2179             } else if (alpha>beta && alpha<beta9) {
2180                 alpha = beta;
2181                 rotationLocal.applyOnce(pointlist);
2182             } else {
2183                 rotation.applyOnce(pointlist);
2184                 translate.applyOnce(pointlist);
2185                 t1 = t1_new;
2186                 t2 = t2_new;
2187             }
2188             brd.update();
2189         };
2190     
2191         this.start = function() {
2192             if (time>0) {
2193                 interval = setInterval(this.rolling, time);
2194             }
2195             return this;
2196         };
2197     
2198         this.stop = function() {
2199             clearInterval(interval);
2200             return this;
2201         };
2202         return this;
2203     };
2204     return new Roulette();
2205 };
2206 
2207