1 /*
  2     Copyright 2008-2015
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, window: true, document: true, init: true, translateASCIIMath: true, google: true*/
 34 
 35 /*jslint nomen: true, plusplus: true*/
 36 
 37 /* depends:
 38  jxg
 39  base/constants
 40  base/coords
 41  options
 42  math/numerics
 43  math/math
 44  math/geometry
 45  math/complex
 46  parser/jessiecode
 47  parser/geonext
 48  utils/color
 49  utils/type
 50  utils/event
 51  utils/env
 52   elements:
 53    transform
 54    point
 55    line
 56    text
 57    grid
 58  */
 59 
 60 /**
 61  * @fileoverview The JXG.Board class is defined in this file. JXG.Board controls all properties and methods
 62  * used to manage a geonext board like managing geometric elements, managing mouse and touch events, etc.
 63  */
 64 
 65 define([
 66     'jxg', 'base/constants', 'base/coords', 'options', 'math/numerics', 'math/math', 'math/geometry', 'math/complex',
 67     'parser/jessiecode', 'parser/geonext', 'utils/color', 'utils/type', 'utils/event', 'utils/env', 'base/transformation',
 68     'base/point', 'base/line', 'base/text', 'element/composition', 'base/composition'
 69 ], function (JXG, Const, Coords, Options, Numerics, Mat, Geometry, Complex, JessieCode, GeonextParser, Color, Type,
 70                 EventEmitter, Env, Transform, Point, Line, Text, Composition, EComposition) {
 71 
 72     'use strict';
 73 
 74     /**
 75      * Constructs a new Board object.
 76      * @class JXG.Board controls all properties and methods used to manage a geonext board like managing geometric
 77      * elements, managing mouse and touch events, etc. You probably don't want to use this constructor directly.
 78      * Please use {@link JXG.JSXGraph#initBoard} to initialize a board.
 79      * @constructor
 80      * @param {String} container The id or reference of the HTML DOM element the board is drawn in. This is usually a HTML div.
 81      * @param {JXG.AbstractRenderer} renderer The reference of a renderer.
 82      * @param {String} id Unique identifier for the board, may be an empty string or null or even undefined.
 83      * @param {JXG.Coords} origin The coordinates where the origin is placed, in user coordinates.
 84      * @param {Number} zoomX Zoom factor in x-axis direction
 85      * @param {Number} zoomY Zoom factor in y-axis direction
 86      * @param {Number} unitX Units in x-axis direction
 87      * @param {Number} unitY Units in y-axis direction
 88      * @param {Number} canvasWidth  The width of canvas
 89      * @param {Number} canvasHeight The height of canvas
 90      * @param {Object} attributes The attributes object given to {@link JXG.JSXGraph#initBoard}
 91      * @borrows JXG.EventEmitter#on as this.on
 92      * @borrows JXG.EventEmitter#off as this.off
 93      * @borrows JXG.EventEmitter#triggerEventHandlers as this.triggerEventHandlers
 94      * @borrows JXG.EventEmitter#eventHandlers as this.eventHandlers
 95      */
 96     JXG.Board = function (container, renderer, id, origin, zoomX, zoomY, unitX, unitY, canvasWidth, canvasHeight, attributes) {
 97         /**
 98          * Board is in no special mode, objects are highlighted on mouse over and objects may be
 99          * clicked to start drag&drop.
100          * @type Number
101          * @constant
102          */
103         this.BOARD_MODE_NONE = 0x0000;
104 
105         /**
106          * Board is in drag mode, objects aren't highlighted on mouse over and the object referenced in
107          * {JXG.Board#mouse} is updated on mouse movement.
108          * @type Number
109          * @constant
110          * @see JXG.Board#drag_obj
111          */
112         this.BOARD_MODE_DRAG = 0x0001;
113 
114         /**
115          * In this mode a mouse move changes the origin's screen coordinates.
116          * @type Number
117          * @constant
118          */
119         this.BOARD_MODE_MOVE_ORIGIN = 0x0002;
120 
121         /**
122          * Update is made with low quality, e.g. graphs are evaluated at a lesser amount of points.
123          * @type Number
124          * @constant
125          * @see JXG.Board#updateQuality
126          */
127         this.BOARD_QUALITY_LOW = 0x1;
128 
129         /**
130          * Update is made with high quality, e.g. graphs are evaluated at much more points.
131          * @type Number
132          * @constant
133          * @see JXG.Board#updateQuality
134          */
135         this.BOARD_QUALITY_HIGH = 0x2;
136 
137         /**
138          * Update is made with high quality, e.g. graphs are evaluated at much more points.
139          * @type Number
140          * @constant
141          * @see JXG.Board#updateQuality
142          */
143         this.BOARD_MODE_ZOOM = 0x0011;
144 
145         /**
146          * Pointer to the document element containing the board.
147          * @type Object
148          */
149         // Former version:
150         // this.document = attributes.document || document;
151         if (Type.exists(attributes.document) && attributes.document !== false) {
152             this.document = attributes.document;
153         } else if (typeof document === 'object') {
154             this.document = document;
155         }
156 
157         /**
158          * The html-id of the html element containing the board.
159          * @type String
160          */
161         this.container = container;
162 
163         /**
164          * Pointer to the html element containing the board.
165          * @type Object
166          */
167         this.containerObj = (Env.isBrowser ? this.document.getElementById(this.container) : null);
168 
169         if (Env.isBrowser && this.containerObj === null) {
170             throw new Error("\nJSXGraph: HTML container element '" + container + "' not found.");
171         }
172 
173         /**
174          * A reference to this boards renderer.
175          * @type JXG.AbstractRenderer
176          */
177         this.renderer = renderer;
178 
179         /**
180          * Grids keeps track of all grids attached to this board.
181          */
182         this.grids = [];
183 
184         /**
185          * Some standard options
186          * @type JXG.Options
187          */
188         this.options = Type.deepCopy(Options);
189         this.attr = attributes;
190 
191         /**
192          * Dimension of the board.
193          * @default 2
194          * @type Number
195          */
196         this.dimension = 2;
197 
198         this.jc = new JessieCode();
199         this.jc.use(this);
200 
201         /**
202          * Coordinates of the boards origin. This a object with the two properties
203          * usrCoords and scrCoords. usrCoords always equals [1, 0, 0] and scrCoords
204          * stores the boards origin in homogeneous screen coordinates.
205          * @type Object
206          */
207         this.origin = {};
208         this.origin.usrCoords = [1, 0, 0];
209         this.origin.scrCoords = [1, origin[0], origin[1]];
210 
211         /**
212          * Zoom factor in X direction. It only stores the zoom factor to be able
213          * to get back to 100% in zoom100().
214          * @type Number
215          */
216         this.zoomX = zoomX;
217 
218         /**
219          * Zoom factor in Y direction. It only stores the zoom factor to be able
220          * to get back to 100% in zoom100().
221          * @type Number
222          */
223         this.zoomY = zoomY;
224 
225         /**
226          * The number of pixels which represent one unit in user-coordinates in x direction.
227          * @type Number
228          */
229         this.unitX = unitX * this.zoomX;
230 
231         /**
232          * The number of pixels which represent one unit in user-coordinates in y direction.
233          * @type Number
234          */
235         this.unitY = unitY * this.zoomY;
236 
237         /**
238          * Keep aspect ratio if bounding box is set and the width/height ratio differs from the 
239          * width/height ratio of the canvas.
240          */
241         this.keepaspectratio = false;
242 
243         /**
244          * Canvas width.
245          * @type Number
246          */
247         this.canvasWidth = canvasWidth;
248 
249         /**
250          * Canvas Height
251          * @type Number
252          */
253         this.canvasHeight = canvasHeight;
254 
255         // If the given id is not valid, generate an unique id
256         if (Type.exists(id) && id !== '' && Env.isBrowser && !Type.exists(this.document.getElementById(id))) {
257             this.id = id;
258         } else {
259             this.id = this.generateId();
260         }
261 
262         EventEmitter.eventify(this);
263 
264         this.hooks = [];
265 
266         /**
267          * An array containing all other boards that are updated after this board has been updated.
268          * @type Array
269          * @see JXG.Board#addChild
270          * @see JXG.Board#removeChild
271          */
272         this.dependentBoards = [];
273 
274         /**
275          * During the update process this is set to false to prevent an endless loop.
276          * @default false
277          * @type Boolean
278          */
279         this.inUpdate = false;
280 
281         /**
282          * 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.
283          * @type Object
284          */
285         this.objects = {};
286 
287         /**
288          * An array containing all geometric objects on the board in the order of construction.
289          * @type {Array}
290          */
291         this.objectsList = [];
292 
293         /**
294          * An associative array containing all groups belonging to the board. Key is the id of the group and value is a reference to the object.
295          * @type Object
296          */
297         this.groups = {};
298 
299         /**
300          * Stores all the objects that are currently running an animation.
301          * @type Object
302          */
303         this.animationObjects = {};
304 
305         /**
306          * An associative array containing all highlighted elements belonging to the board.
307          * @type Object
308          */
309         this.highlightedObjects = {};
310 
311         /**
312          * Number of objects ever created on this board. This includes every object, even invisible and deleted ones.
313          * @type Number
314          */
315         this.numObjects = 0;
316 
317         /**
318          * 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.
319          * @type Object
320          */
321         this.elementsByName = {};
322 
323         /**
324          * The board mode the board is currently in. Possible values are
325          * <ul>
326          * <li>JXG.Board.BOARD_MODE_NONE</li>
327          * <li>JXG.Board.BOARD_MODE_DRAG</li>
328          * <li>JXG.Board.BOARD_MODE_MOVE_ORIGIN</li>
329          * </ul>
330          * @type Number
331          */
332         this.mode = this.BOARD_MODE_NONE;
333 
334         /**
335          * The update quality of the board. In most cases this is set to {@link JXG.Board#BOARD_QUALITY_HIGH}.
336          * If {@link JXG.Board#mode} equals {@link JXG.Board#BOARD_MODE_DRAG} this is set to
337          * {@link JXG.Board#BOARD_QUALITY_LOW} to speed up the update process by e.g. reducing the number of
338          * evaluation points when plotting functions. Possible values are
339          * <ul>
340          * <li>BOARD_QUALITY_LOW</li>
341          * <li>BOARD_QUALITY_HIGH</li>
342          * </ul>
343          * @type Number
344          * @see JXG.Board#mode
345          */
346         this.updateQuality = this.BOARD_QUALITY_HIGH;
347 
348         /**
349          * If true updates are skipped.
350          * @type Boolean
351          */
352         this.isSuspendedRedraw = false;
353 
354         this.calculateSnapSizes();
355 
356         /**
357          * The distance from the mouse to the dragged object in x direction when the user clicked the mouse button.
358          * @type Number
359          * @see JXG.Board#drag_dy
360          * @see JXG.Board#drag_obj
361          */
362         this.drag_dx = 0;
363 
364         /**
365          * The distance from the mouse to the dragged object in y direction when the user clicked the mouse button.
366          * @type Number
367          * @see JXG.Board#drag_dx
368          * @see JXG.Board#drag_obj
369          */
370         this.drag_dy = 0;
371 
372         /**
373          * The last position where a drag event has been fired.
374          * @type Array
375          * @see JXG.Board#moveObject
376          */
377         this.drag_position = [0, 0];
378 
379         /**
380          * References to the object that is dragged with the mouse on the board.
381          * @type {@link JXG.GeometryElement}.
382          * @see {JXG.Board#touches}
383          */
384         this.mouse = {};
385 
386         /**
387          * Keeps track on touched elements, like {@link JXG.Board#mouse} does for mouse events.
388          * @type Array
389          * @see {JXG.Board#mouse}
390          */
391         this.touches = [];
392 
393         /**
394          * A string containing the XML text of the construction. This is set in {@link JXG.FileReader#parseString}.
395          * Only useful if a construction is read from a GEONExT-, Intergeo-, Geogebra-, or Cinderella-File.
396          * @type String
397          */
398         this.xmlString = '';
399 
400         /**
401          * Cached result of getCoordsTopLeftCorner for touch/mouseMove-Events to save some DOM operations.
402          * @type Array
403          */
404         this.cPos = [];
405 
406         /**
407          * Contains the last time (epoch, msec) since the last touchMove event which was not thrown away or since
408          * touchStart because Android's Webkit browser fires too much of them.
409          * @type Number
410          */
411         this.touchMoveLast = 0;
412 
413         /**
414          * Contains the last time (epoch, msec) since the last getCoordsTopLeftCorner call which was not thrown away.
415          * @type Number
416          */
417         this.positionAccessLast = 0;
418 
419         /**
420          * Collects all elements that triggered a mouse down event.
421          * @type Array
422          */
423         this.downObjects = [];
424 
425         if (this.attr.showcopyright) {
426             this.renderer.displayCopyright(Const.licenseText, parseInt(this.options.text.fontSize, 10));
427         }
428 
429         /**
430          * Full updates are needed after zoom and axis translates. This saves some time during an update.
431          * @default false
432          * @type Boolean
433          */
434         this.needsFullUpdate = false;
435 
436         /**
437          * If reducedUpdate is set to true then only the dragged element and few (e.g. 2) following
438          * elements are updated during mouse move. On mouse up the whole construction is
439          * updated. This enables us to be fast even on very slow devices.
440          * @type Boolean
441          * @default false
442          */
443         this.reducedUpdate = false;
444 
445         /**
446          * The current color blindness deficiency is stored in this property. If color blindness is not emulated
447          * at the moment, it's value is 'none'.
448          */
449         this.currentCBDef = 'none';
450 
451         /**
452          * If GEONExT constructions are displayed, then this property should be set to true.
453          * At the moment there should be no difference. But this may change.
454          * This is set in {@link JXG.GeonextReader#readGeonext}.
455          * @type Boolean
456          * @default false
457          * @see JXG.GeonextReader#readGeonext
458          */
459         this.geonextCompatibilityMode = false;
460 
461         if (this.options.text.useASCIIMathML && translateASCIIMath) {
462             init();
463         } else {
464             this.options.text.useASCIIMathML = false;
465         }
466 
467         /**
468          * A flag which tells if the board registers mouse events.
469          * @type Boolean
470          * @default false
471          */
472         this.hasMouseHandlers = false;
473 
474         /**
475          * A flag which tells if the board registers touch events.
476          * @type Boolean
477          * @default false
478          */
479         this.hasTouchHandlers = false;
480 
481         /**
482          * A flag which stores if the board registered pointer events.
483          * @type {Boolean}
484          * @default false
485          */
486         this.hasPointerHandlers = false;
487 
488         /**
489          * This bool flag stores the current state of the mobile Safari specific gesture event handlers.
490          * @type {boolean}
491          * @default false
492          */
493         this.hasGestureHandlers = false;
494 
495         /**
496          * A flag which tells if the board the JXG.Board#mouseUpListener is currently registered.
497          * @type Boolean
498          * @default false
499          */
500         this.hasMouseUp = false;
501 
502         /**
503          * A flag which tells if the board the JXG.Board#touchEndListener is currently registered.
504          * @type Boolean
505          * @default false
506          */
507         this.hasTouchEnd = false;
508 
509         /**
510          * A flag which tells us if the board has a pointerUp event registered at the moment.
511          * @type {Boolean}
512          * @default false
513          */
514         this.hasPointerUp = false;
515 
516         if (this.attr.registerevents) {
517             this.addEventHandlers();
518         }
519 
520         this.methodMap = {
521             update: 'update',
522             fullUpdate: 'fullUpdate',
523             on: 'on',
524             off: 'off',
525             trigger: 'trigger',
526             setView: 'setBoundingBox',
527             setBoundingBox: 'setBoundingBox',
528             migratePoint: 'migratePoint',
529             colorblind: 'emulateColorblindness',
530             suspendUpdate: 'suspendUpdate',
531             unsuspendUpdate: 'unsuspendUpdate',
532             clearTraces: 'clearTraces',
533             left: 'clickLeftArrow',
534             right: 'clickRightArrow',
535             up: 'clickUpArrow',
536             down: 'clickDownArrow',
537             zoomIn: 'zoomIn',
538             zoomOut: 'zoomOut',
539             zoom100: 'zoom100',
540             zoomElements: 'zoomElements',
541             remove: 'removeObject',
542             removeObject: 'removeObject'
543         };
544     };
545 
546     JXG.extend(JXG.Board.prototype, /** @lends JXG.Board.prototype */ {
547 
548         /**
549          * Generates an unique name for the given object. The result depends on the objects type, if the
550          * object is a {@link JXG.Point}, capital characters are used, if it is of type {@link JXG.Line}
551          * only lower case characters are used. If object is of type {@link JXG.Polygon}, a bunch of lower
552          * case characters prefixed with P_ are used. If object is of type {@link JXG.Circle} the name is
553          * generated using lower case characters. prefixed with k_ is used. In any other case, lower case
554          * chars prefixed with s_ is used.
555          * @param {Object} object Reference of an JXG.GeometryElement that is to be named.
556          * @returns {String} Unique name for the object.
557          */
558         generateName: function (object) {
559             var possibleNames, i, j,
560                 maxNameLength = 2,
561                 pre = '',
562                 post = '',
563                 indices = [],
564                 name = '';
565 
566             if (object.type === Const.OBJECT_TYPE_TICKS) {
567                 return '';
568             }
569 
570             if (Type.isPoint(object)) {
571                 // points have capital letters
572                 possibleNames = ['', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
573                     'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
574             } else if (object.type === Const.OBJECT_TYPE_ANGLE) {
575                 possibleNames = ['', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ',
576                     'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ',
577                     'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω'];
578             } else {
579                 // all other elements get lowercase labels
580                 possibleNames = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
581                     'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
582             }
583 
584             if (!Type.isPoint(object) &&
585                     object.elementClass !== Const.OBJECT_CLASS_LINE &&
586                     object.type !== Const.OBJECT_TYPE_ANGLE) {
587                 if (object.type === Const.OBJECT_TYPE_POLYGON) {
588                     pre = 'P_{';
589                 } else if (object.elementClass === Const.OBJECT_CLASS_CIRCLE) {
590                     pre = 'k_{';
591                 } else if (object.elementClass === Const.OBJECT_CLASS_TEXT) {
592                     pre = 't_{';
593                 } else {
594                     pre = 's_{';
595                 }
596                 post = '}';
597             }
598 
599             for (i = 0; i < maxNameLength; i++) {
600                 indices[i] = 0;
601             }
602 
603             while (indices[maxNameLength - 1] < possibleNames.length) {
604                 for (indices[0] = 1; indices[0] < possibleNames.length; indices[0]++) {
605                     name = pre;
606 
607                     for (i = maxNameLength; i > 0; i--) {
608                         name += possibleNames[indices[i - 1]];
609                     }
610 
611                     if (!Type.exists(this.elementsByName[name + post])) {
612                         return name + post;
613                     }
614 
615                 }
616                 indices[0] = possibleNames.length;
617 
618                 for (i = 1; i < maxNameLength; i++) {
619                     if (indices[i - 1] === possibleNames.length) {
620                         indices[i - 1] = 1;
621                         indices[i] += 1;
622                     }
623                 }
624             }
625 
626             return '';
627         },
628 
629         /**
630          * Generates unique id for a board. The result is randomly generated and prefixed with 'jxgBoard'.
631          * @returns {String} Unique id for a board.
632          */
633         generateId: function () {
634             var r = 1;
635 
636             // as long as we don't have a unique id generate a new one
637             while (Type.exists(JXG.boards['jxgBoard' + r])) {
638                 r = Math.round(Math.random() * 65535);
639             }
640 
641             return ('jxgBoard' + r);
642         },
643 
644         /**
645          * Composes an id for an element. If the ID is empty ('' or null) a new ID is generated, depending on the
646          * object type. Additionally, the id of the label is set. As a side effect {@link JXG.Board#numObjects}
647          * is updated.
648          * @param {Object} obj Reference of an geometry object that needs an id.
649          * @param {Number} type Type of the object.
650          * @returns {String} Unique id for an element.
651          */
652         setId: function (obj, type) {
653             var num = this.numObjects,
654                 elId = obj.id;
655 
656             this.numObjects += 1;
657 
658             // Falls Id nicht vorgegeben, eine Neue generieren:
659             if (elId === '' || !Type.exists(elId)) {
660                 elId = this.id + type + num;
661             }
662 
663             obj.id = elId;
664             this.objects[elId] = obj;
665             obj._pos = this.objectsList.length;
666             this.objectsList[this.objectsList.length] = obj;
667 
668             return elId;
669         },
670 
671         /**
672          * After construction of the object the visibility is set
673          * and the label is constructed if necessary.
674          * @param {Object} obj The object to add.
675          */
676         finalizeAdding: function (obj) {
677             if (!obj.visProp.visible) {
678                 this.renderer.hide(obj);
679             }
680         },
681 
682         finalizeLabel: function (obj) {
683             if (obj.hasLabel && !obj.label.visProp.islabel && !obj.label.visProp.visible) {
684                 this.renderer.hide(obj.label);
685             }
686         },
687 
688         /**********************************************************
689          *
690          * Event Handler helpers
691          *
692          **********************************************************/
693 
694         /**
695          * Calculates mouse coordinates relative to the boards container.
696          * @returns {Array} Array of coordinates relative the boards container top left corner.
697          */
698         getCoordsTopLeftCorner: function () {
699             var cPos, doc, crect, scrollLeft, scrollTop,
700                 docElement = this.document.documentElement || this.document.body.parentNode,
701                 docBody = this.document.body,
702                 container = this.containerObj;
703 
704             /**
705              * During drags and origin moves the container element is usually not changed.
706              * Check the position of the upper left corner at most every 500 msecs
707              */
708             if (this.cPos.length > 0 &&
709                     (this.mode === this.BOARD_MODE_DRAG || this.mode === this.BOARD_MODE_MOVE_ORIGIN ||
710                     (new Date()).getTime() - this.positionAccessLast < 1000)) {
711                 return this.cPos;
712             }
713 
714             this.positionAccessLast = (new Date()).getTime();
715 
716             // Check if getBoundingClientRect exists. If so, use this as this covers *everything*
717             // even CSS3D transformations etc.
718             if (container.getBoundingClientRect) {
719                 if (typeof window.pageXOffset === 'number') {
720                     scrollLeft = window.pageXOffset;
721                 } else {
722                     if (docElement.ScrollLeft === 'number') {
723                         scrollLeft = docElement.ScrollLeft;
724                     } else {
725                         scrollLeft = this.document.body.scrollLeft;
726                     }
727                 }
728 
729                 if (typeof window.pageYOffset === 'number') {
730                     scrollTop = window.pageYOffset;
731                 } else {
732                     if (docElement.ScrollTop === 'number') {
733                         scrollTop = docElement.ScrollTop;
734                     } else {
735                         scrollTop = this.document.body.scrollTop;
736                     }
737                 }
738 
739                 crect = container.getBoundingClientRect();
740                 cPos = [crect.left + scrollLeft, crect.top + scrollTop];
741 
742                 // add border width
743                 cPos[0] += Env.getProp(container, 'border-left-width');
744                 cPos[1] += Env.getProp(container, 'border-top-width');
745 
746                 // vml seems to ignore paddings
747                 if (this.renderer.type !== 'vml') {
748                     // add padding
749                     cPos[0] += Env.getProp(container, 'padding-left');
750                     cPos[1] += Env.getProp(container, 'padding-top');
751                 }
752 
753                 this.cPos = cPos.slice();
754                 return this.cPos;
755             }
756 
757             cPos = Env.getOffset(container);
758             doc = this.document.documentElement.ownerDocument;
759 
760             if (!this.containerObj.currentStyle && doc.defaultView) {     // Non IE
761                 // this is for hacks like this one used in wordpress for the admin bar:
762                 // html { margin-top: 28px }
763                 // seems like it doesn't work in IE
764 
765                 cPos[0] += Env.getProp(docElement, 'margin-left');
766                 cPos[1] += Env.getProp(docElement, 'margin-top');
767 
768                 cPos[0] += Env.getProp(docElement, 'border-left-width');
769                 cPos[1] += Env.getProp(docElement, 'border-top-width');
770 
771                 cPos[0] += Env.getProp(docElement, 'padding-left');
772                 cPos[1] += Env.getProp(docElement, 'padding-top');
773             }
774 
775             if (docBody) {
776                 cPos[0] += Env.getProp(docBody, 'left');
777                 cPos[1] += Env.getProp(docBody, 'top');
778             }
779 
780             // Google Translate offers widgets for web authors. These widgets apparently tamper with the clientX
781             // and clientY coordinates of the mouse events. The minified sources seem to be the only publicly
782             // available version so we're doing it the hacky way: Add a fixed offset.
783             // see https://groups.google.com/d/msg/google-translate-general/H2zj0TNjjpY/jw6irtPlCw8J
784             if (typeof google === 'object' && google.translate) {
785                 cPos[0] += 10;
786                 cPos[1] += 25;
787             }
788 
789             // add border width
790             cPos[0] += Env.getProp(container, 'border-left-width');
791             cPos[1] += Env.getProp(container, 'border-top-width');
792 
793             // vml seems to ignore paddings
794             if (this.renderer.type !== 'vml') {
795                 // add padding
796                 cPos[0] += Env.getProp(container, 'padding-left');
797                 cPos[1] += Env.getProp(container, 'padding-top');
798             }
799 
800             cPos[0] += this.attr.offsetx;
801             cPos[1] += this.attr.offsety;
802 
803             this.cPos = cPos.slice();
804             return this.Pos;
805         },
806 
807         /**
808          * Get the position of the mouse in screen coordinates, relative to the upper left corner
809          * of the host tag.
810          * @param {Event} e Event object given by the browser.
811          * @param {Number} [i] Only use in case of touch events. This determines which finger to use and should not be set
812          * for mouseevents.
813          * @returns {Array} Contains the mouse coordinates in user coordinates, ready  for {@link JXG.Coords}
814          */
815         getMousePosition: function (e, i) {
816             var cPos = this.getCoordsTopLeftCorner(),
817                 absPos,
818                 v;
819 
820             // This fixes the object-drag bug on zoomed webpages on Android powered devices with the default WebKit browser
821             // Seems to be obsolete now
822             //if (Env.isWebkitAndroid()) {
823             //    cPos[0] -= document.body.scrollLeft;
824             //    cPos[1] -= document.body.scrollTop;
825             //}
826 
827             // position of mouse cursor relative to containers position of container
828             absPos = Env.getPosition(e, i, this.document);
829 
830             /**
831              * In case there has been no down event before.
832              */
833             if (!Type.exists(this.cssTransMat)) {
834                 this.updateCSSTransforms();
835             }
836             v = [1, absPos[0] - cPos[0], absPos[1] - cPos[1]];
837             v = Mat.matVecMult(this.cssTransMat, v);
838             v[1] /= v[0];
839             v[2] /= v[0];
840             return [v[1], v[2]];
841 
842             // Method without CSS transformation
843             /*
844              return [absPos[0] - cPos[0], absPos[1] - cPos[1]];
845              */
846         },
847 
848         /**
849          * Initiate moving the origin. This is used in mouseDown and touchStart listeners.
850          * @param {Number} x Current mouse/touch coordinates
851          * @param {Number} y Current mouse/touch coordinates
852          */
853         initMoveOrigin: function (x, y) {
854             this.drag_dx = x - this.origin.scrCoords[1];
855             this.drag_dy = y - this.origin.scrCoords[2];
856 
857             this.mode = this.BOARD_MODE_MOVE_ORIGIN;
858             this.updateQuality = this.BOARD_QUALITY_LOW;
859         },
860 
861         /**
862          * Collects all elements below the current mouse pointer and fulfilling the following constraints:
863          * <ul><li>isDraggable</li><li>visible</li><li>not fixed</li><li>not frozen</li></ul>
864          * @param {Number} x Current mouse/touch coordinates
865          * @param {Number} y current mouse/touch coordinates
866          * @param {Object} evt An event object
867          * @param {String} type What type of event? 'touch' or 'mouse'.
868          * @returns {Array} A list of geometric elements.
869          */
870         initMoveObject: function (x, y, evt, type) {
871             var pEl, el, collect = [], haspoint, len = this.objectsList.length,
872                 dragEl = {visProp: {layer: -10000}};
873 
874             //for (el in this.objects) {
875             for (el = 0; el < len; el++) {
876                 pEl = this.objectsList[el];
877                 haspoint = pEl.hasPoint && pEl.hasPoint(x, y);
878 
879                 if (pEl.visProp.visible && haspoint) {
880                     pEl.triggerEventHandlers([type + 'down', 'down'], [evt]);
881                     this.downObjects.push(pEl);
882                 }
883 
884                 if (((this.geonextCompatibilityMode &&
885                         (Type.isPoint(pEl) ||
886                           pEl.elementClass === Const.OBJECT_CLASS_TEXT)) ||
887                         !this.geonextCompatibilityMode) &&
888                         pEl.isDraggable &&
889                         pEl.visProp.visible &&
890                         (!pEl.visProp.fixed) && (!pEl.visProp.frozen) &&
891                         haspoint) {
892                     // Elements in the highest layer get priority.
893                     if (pEl.visProp.layer > dragEl.visProp.layer ||
894                             (pEl.visProp.layer === dragEl.visProp.layer && pEl.lastDragTime.getTime() >= dragEl.lastDragTime.getTime())) {
895                         // If an element and its label have the focus
896                         // simultaneously, the element is taken.
897                         // This only works if we assume that every browser runs
898                         // through this.objects in the right order, i.e. an element A
899                         // added before element B turns up here before B does.
900                         if (!this.attr.ignorelabels || (!Type.exists(dragEl.label) || pEl !== dragEl.label)) {
901                             dragEl = pEl;
902                             collect[0] = dragEl;
903 
904                             // we can't drop out of this loop because of the event handling system
905                             //if (this.attr.takefirst) {
906                             //    return collect;
907                             //}
908                         }
909                     }
910                 }
911             }
912 
913             if (collect.length > 0) {
914                 this.mode = this.BOARD_MODE_DRAG;
915             }
916 
917             if (this.attr.takefirst) {
918                 collect.length = 1;
919             }
920 
921             return collect;
922         },
923 
924         /**
925          * Moves an object.
926          * @param {Number} x Coordinate
927          * @param {Number} y Coordinate
928          * @param {Object} o The touch object that is dragged: {JXG.Board#mouse} or {JXG.Board#touches}.
929          * @param {Object} evt The event object.
930          * @param {String} type Mouse or touch event?
931          */
932         moveObject: function (x, y, o, evt, type) {
933             var newPos = new Coords(Const.COORDS_BY_SCREEN, this.getScrCoordsOfMouse(x, y), this),
934                 drag = o.obj,
935                 oldCoords;
936 
937             if (!drag) {
938                 return;
939             }
940 
941             /*
942              * Save the position.
943              */
944             //this.drag_position = newPos.scrCoords.slice(1);
945             this.drag_position = [newPos.scrCoords[1], newPos.scrCoords[2]];
946 
947             if (drag.type !== Const.OBJECT_TYPE_GLIDER) {
948                 if (!isNaN(o.targets[0].Xprev + o.targets[0].Yprev)) {
949                     drag.setPositionDirectly(Const.COORDS_BY_SCREEN,
950                         [newPos.scrCoords[1], newPos.scrCoords[2]],
951                         [o.targets[0].Xprev, o.targets[0].Yprev]
952                         );
953                 }
954                 // Remember the actual position for the next move event. Then we are able to
955                 // compute the difference vector.
956                 o.targets[0].Xprev = newPos.scrCoords[1];
957                 o.targets[0].Yprev = newPos.scrCoords[2];
958                 //this.update(drag);
959                 drag.prepareUpdate().update(false).updateRenderer();
960             } else if (drag.type === Const.OBJECT_TYPE_GLIDER) {
961                 oldCoords = drag.coords;  // Used in group mode
962 
963                 // First the new position of the glider is set to the new mouse position
964                 drag.setPositionDirectly(Const.COORDS_BY_USER, newPos.usrCoords.slice(1));
965 
966                 // Now, we have to adjust the other group elements again.
967                 if (drag.group.length !== 0) {
968                     // Then, from this position we compute the projection to the object the glider on which the glider lives.
969                     // Do we really need this?
970                     if (drag.slideObject.elementClass === Const.OBJECT_CLASS_CIRCLE) {
971                         drag.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToCircle(drag, drag.slideObject, this).usrCoords, false);
972                     } else if (drag.slideObject.elementClass === Const.OBJECT_CLASS_LINE) {
973                         drag.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToLine(drag, drag.slideObject, this).usrCoords, false);
974                     }
975 
976                     drag.group[drag.group.length - 1].dX = drag.coords.scrCoords[1] - oldCoords.scrCoords[1];
977                     drag.group[drag.group.length - 1].dY = drag.coords.scrCoords[2] - oldCoords.scrCoords[2];
978                     drag.group[drag.group.length - 1].update(this);
979                 } else {
980                     // This update triggers Point.updateGlider() instead of Point.updateGliderFromParent():
981                     //
982                     //this.update(drag);
983                     drag.prepareUpdate().update(false).updateRenderer();
984                 }
985             }
986 
987             drag.triggerEventHandlers([type + 'drag', 'drag'], [evt]);
988 
989             this.updateInfobox(drag);
990             this.update();
991             drag.highlight(true);
992 
993             drag.lastDragTime = new Date();
994         },
995 
996         /**
997          * Moves elements in multitouch mode.
998          * @param {Array} p1 x,y coordinates of first touch
999          * @param {Array} p2 x,y coordinates of second touch
1000          * @param {Object} o The touch object that is dragged: {JXG.Board#touches}.
1001          * @param {Object} evt The event object that lead to this movement.
1002          */
1003         twoFingerMove: function (p1, p2, o, evt) {
1004             var np1c, np2c, drag;
1005 
1006             if (Type.exists(o) && Type.exists(o.obj)) {
1007                 drag = o.obj;
1008             } else {
1009                 return;
1010             }
1011 
1012             // New finger position
1013             np1c = new Coords(Const.COORDS_BY_SCREEN, this.getScrCoordsOfMouse(p1[0], p1[1]), this);
1014             np2c = new Coords(Const.COORDS_BY_SCREEN, this.getScrCoordsOfMouse(p2[0], p2[1]), this);
1015 
1016             if (drag.elementClass === Const.OBJECT_CLASS_LINE ||
1017                     drag.type === Const.OBJECT_TYPE_POLYGON) {
1018                 this.twoFingerTouchObject(np1c, np2c, o, drag);
1019             } else if (drag.elementClass === Const.OBJECT_CLASS_CIRCLE) {
1020                 this.twoFingerTouchCircle(np1c, np2c, o, drag);
1021             }
1022             drag.triggerEventHandlers(['touchdrag', 'drag'], [evt]);
1023 
1024             o.targets[0].Xprev = np1c.scrCoords[1];
1025             o.targets[0].Yprev = np1c.scrCoords[2];
1026             o.targets[1].Xprev = np2c.scrCoords[1];
1027             o.targets[1].Yprev = np2c.scrCoords[2];
1028         },
1029 
1030         /**
1031          * Moves a line or polygon with two fingers
1032          * @param {JXG.Coords} np1c x,y coordinates of first touch
1033          * @param {JXG.Coords} np2c x,y coordinates of second touch
1034          * @param {object} o The touch object that is dragged: {JXG.Board#touches}.
1035          * @param {object} drag The object that is dragged:
1036          */
1037         twoFingerTouchObject: function (np1c, np2c, o, drag) {
1038             var np1, np2, op1, op2,
1039                 nmid, omid, nd, od,
1040                 d,
1041                 S, alpha, t1, t2, t3, t4, t5;
1042 
1043             if (Type.exists(o.targets[0]) &&
1044                     Type.exists(o.targets[1]) &&
1045                     !isNaN(o.targets[0].Xprev + o.targets[0].Yprev + o.targets[1].Xprev + o.targets[1].Yprev)) {
1046                 np1 = np1c.usrCoords;
1047                 np2 = np2c.usrCoords;
1048                 // Previous finger position
1049                 op1 = (new Coords(Const.COORDS_BY_SCREEN, [o.targets[0].Xprev, o.targets[0].Yprev], this)).usrCoords;
1050                 op2 = (new Coords(Const.COORDS_BY_SCREEN, [o.targets[1].Xprev, o.targets[1].Yprev], this)).usrCoords;
1051 
1052                 // Affine mid points of the old and new positions
1053                 omid = [1, (op1[1] + op2[1]) * 0.5, (op1[2] + op2[2]) * 0.5];
1054                 nmid = [1, (np1[1] + np2[1]) * 0.5, (np1[2] + np2[2]) * 0.5];
1055 
1056                 // Old and new directions
1057                 od = Mat.crossProduct(op1, op2);
1058                 nd = Mat.crossProduct(np1, np2);
1059                 S = Mat.crossProduct(od, nd);
1060 
1061                 // If parallel, translate otherwise rotate
1062                 if (Math.abs(S[0]) < Mat.eps) {
1063                     return;
1064                 }
1065 
1066                 S[1] /= S[0];
1067                 S[2] /= S[0];
1068                 alpha = Geometry.rad(omid.slice(1), S.slice(1), nmid.slice(1));
1069                 t1 = this.create('transform', [alpha, S[1], S[2]], {type: 'rotate'});
1070 
1071                 // Old midpoint of fingers after first transformation:
1072                 t1.update();
1073                 omid = Mat.matVecMult(t1.matrix, omid);
1074                 omid[1] /= omid[0];
1075                 omid[2] /= omid[0];
1076 
1077                 // Shift to the new mid point
1078                 t2 = this.create('transform', [nmid[1] - omid[1], nmid[2] - omid[2]], {type: 'translate'});
1079                 t2.update();
1080                 //omid = Mat.matVecMult(t2.matrix, omid);
1081 
1082                 t1.melt(t2);
1083                 if (drag.visProp.scalable) {
1084                     // Scale
1085                     d = Geometry.distance(np1, np2) / Geometry.distance(op1, op2);
1086                     t3 = this.create('transform', [-nmid[1], -nmid[2]], {type: 'translate'});
1087                     t4 = this.create('transform', [d, d], {type: 'scale'});
1088                     t5 = this.create('transform', [nmid[1], nmid[2]], {type: 'translate'});
1089                     t1.melt(t3).melt(t4).melt(t5);
1090                 }
1091 
1092                 if (drag.elementClass === Const.OBJECT_CLASS_LINE) {
1093                     t1.applyOnce([drag.point1, drag.point2]);
1094                 } else if (drag.type === Const.OBJECT_TYPE_POLYGON) {
1095                     t1.applyOnce(drag.vertices.slice(0, -1));
1096                 }
1097 
1098                 this.update();
1099                 drag.highlight(true);
1100             }
1101         },
1102 
1103         /*
1104          * Moves a circle with two fingers
1105          * @param {JXG.Coords} np1c x,y coordinates of first touch
1106          * @param {JXG.Coords} np2c x,y coordinates of second touch
1107          * @param {object} o The touch object that is dragged: {JXG.Board#touches}.
1108          * @param {object} drag The object that is dragged:
1109          */
1110         twoFingerTouchCircle: function (np1c, np2c, o, drag) {
1111             var np1, np2, op1, op2,
1112                 d, alpha, t1, t2, t3, t4, t5;
1113 
1114             if (drag.method === 'pointCircle' ||
1115                     drag.method === 'pointLine') {
1116                 return;
1117             }
1118 
1119             if (Type.exists(o.targets[0]) &&
1120                     Type.exists(o.targets[1]) &&
1121                     !isNaN(o.targets[0].Xprev + o.targets[0].Yprev + o.targets[1].Xprev + o.targets[1].Yprev)) {
1122 
1123                 np1 = np1c.usrCoords;
1124                 np2 = np2c.usrCoords;
1125                 // Previous finger position
1126                 op1 = (new Coords(Const.COORDS_BY_SCREEN, [o.targets[0].Xprev, o.targets[0].Yprev], this)).usrCoords;
1127                 op2 = (new Coords(Const.COORDS_BY_SCREEN, [o.targets[1].Xprev, o.targets[1].Yprev], this)).usrCoords;
1128 
1129                 // Shift by the movement of the first finger
1130                 t1 = this.create('transform', [np1[1] - op1[1], np1[2] - op1[2]], {type: 'translate'});
1131                 alpha = Geometry.rad(op2.slice(1), np1.slice(1), np2.slice(1));
1132 
1133                 // Rotate and scale by the movement of the second finger
1134                 t2 = this.create('transform', [-np1[1], -np1[2]], {type: 'translate'});
1135                 t3 = this.create('transform', [alpha], {type: 'rotate'});
1136                 t1.melt(t2).melt(t3);
1137 
1138                 if (drag.visProp.scalable) {
1139                     d = Geometry.distance(np1, np2) / Geometry.distance(op1, op2);
1140                     t4 = this.create('transform', [d, d], {type: 'scale'});
1141                     t1.melt(t4);
1142                 }
1143                 t5 = this.create('transform', [ np1[1], np1[2]], {type: 'translate'});
1144                 t1.melt(t5);
1145 
1146                 t1.applyOnce([drag.center]);
1147 
1148                 if (drag.method === 'twoPoints') {
1149                     t1.applyOnce([drag.point2]);
1150                 } else if (drag.method === 'pointRadius') {
1151                     if (Type.isNumber(drag.updateRadius.origin)) {
1152                         drag.setRadius(drag.radius * d);
1153                     }
1154                 }
1155                 this.update(drag.center);
1156                 drag.highlight(true);
1157             }
1158         },
1159 
1160         highlightElements: function (x, y, evt, target) {
1161             var el, pEl, pId,
1162                 overObjects = {},
1163                 len = this.objectsList.length;
1164 
1165             // Elements  below the mouse pointer which are not highlighted yet will be highlighted.
1166             for (el = 0; el < len; el++) {
1167                 pEl = this.objectsList[el];
1168                 pId = pEl.id;
1169                 if (Type.exists(pEl.hasPoint) && pEl.visProp.visible && pEl.hasPoint(x, y)) {
1170                     // this is required in any case because otherwise the box won't be shown until the point is dragged
1171                     this.updateInfobox(pEl);
1172 
1173                     if (!Type.exists(this.highlightedObjects[pId])) { // highlight only if not highlighted
1174                         overObjects[pId] = pEl;
1175                         pEl.highlight();
1176                         this.triggerEventHandlers(['mousehit', 'hit'], [evt, pEl, target]);
1177                     }
1178 
1179                     if (pEl.mouseover) {
1180                         pEl.triggerEventHandlers(['mousemove', 'move'], [evt]);
1181                     } else {
1182                         pEl.triggerEventHandlers(['mouseover', 'over'], [evt]);
1183                         pEl.mouseover = true;
1184                     }
1185                 }
1186             }
1187 
1188             for (el = 0; el < len; el++) {
1189                 pEl = this.objectsList[el];
1190                 pId = pEl.id;
1191                 if (pEl.mouseover) {
1192                     if (!overObjects[pId]) {
1193                         pEl.triggerEventHandlers(['mouseout', 'out'], [evt]);
1194                         pEl.mouseover = false;
1195                     }
1196                 }
1197             }
1198         },
1199 
1200         /**
1201          * Helper function which returns a reasonable starting point for the object being dragged.
1202          * Formerly known as initXYstart().
1203          * @private
1204          * @param {JXG.GeometryElement} obj The object to be dragged
1205          * @param {Array} targets Array of targets. It is changed by this function.
1206          */
1207         saveStartPos: function (obj, targets) {
1208             var xy = [], i, len;
1209 
1210             if (obj.type === Const.OBJECT_TYPE_TICKS) {
1211                 xy.push([1, NaN, NaN]);
1212             } else if (obj.elementClass === Const.OBJECT_CLASS_LINE) {
1213                 xy.push(obj.point1.coords.usrCoords);
1214                 xy.push(obj.point2.coords.usrCoords);
1215             } else if (obj.elementClass === Const.OBJECT_CLASS_CIRCLE) {
1216                 xy.push(obj.center.coords.usrCoords);
1217                 if (obj.method === "twoPoints") {
1218                     xy.push(obj.point2.coords.usrCoords);
1219                 }
1220             } else if (obj.type === Const.OBJECT_TYPE_POLYGON) {
1221                 len = obj.vertices.length - 1;
1222                 for (i = 0; i < len; i++) {
1223                     xy.push(obj.vertices[i].coords.usrCoords);
1224                 }
1225             } else if (obj.type === Const.OBJECT_TYPE_SECTOR) {
1226                 xy.push(obj.point1.coords.usrCoords);
1227                 xy.push(obj.point2.coords.usrCoords);
1228                 xy.push(obj.point3.coords.usrCoords);
1229             } else if (Type.isPoint(obj) || obj.type === Const.OBJECT_TYPE_GLIDER) {
1230                 xy.push(obj.coords.usrCoords);
1231             } else if (obj.elementClass === Const.OBJECT_CLASS_CURVE) {
1232                 if (JXG.exists(obj.parents)) {
1233                     len = obj.parents.length;
1234                     for (i = 0; i < len; i++) {
1235                         xy.push(this.select(obj.parents[i]).coords.usrCoords);
1236                     }
1237                 }
1238             } else {
1239                 try {
1240                     xy.push(obj.coords.usrCoords);
1241                 } catch (e) {
1242                     JXG.debug('JSXGraph+ saveStartPos: obj.coords.usrCoords not available: ' + e);
1243                 }
1244             }
1245 
1246             len = xy.length;
1247             for (i = 0; i < len; i++) {
1248                 targets.Zstart.push(xy[i][0]);
1249                 targets.Xstart.push(xy[i][1]);
1250                 targets.Ystart.push(xy[i][2]);
1251             }
1252         },
1253 
1254         mouseOriginMoveStart: function (evt) {
1255             var r = this.attr.pan.enabled && (!this.attr.pan.needshift || evt.shiftKey),
1256                 pos;
1257 
1258             if (r) {
1259                 pos = this.getMousePosition(evt);
1260                 this.initMoveOrigin(pos[0], pos[1]);
1261             }
1262 
1263             return r;
1264         },
1265 
1266         mouseOriginMove: function (evt) {
1267             var r = (this.mode === this.BOARD_MODE_MOVE_ORIGIN),
1268                 pos;
1269 
1270             if (r) {
1271                 pos = this.getMousePosition(evt);
1272                 this.moveOrigin(pos[0], pos[1], true);
1273             }
1274 
1275             return r;
1276         },
1277 
1278         touchOriginMoveStart: function (evt) {
1279             var touches = evt[JXG.touchProperty],
1280                 twoFingersCondition = (touches.length === 2 && Geometry.distance([touches[0].screenX, touches[0].screenY], [touches[1].screenX, touches[1].screenY]) < 80),
1281                 r = this.attr.pan.enabled && (!this.attr.pan.needtwofingers || twoFingersCondition),
1282                 pos;
1283 
1284             if (r) {
1285                 pos = this.getMousePosition(evt, 0);
1286                 this.initMoveOrigin(pos[0], pos[1]);
1287             }
1288 
1289             return r;
1290         },
1291 
1292         touchOriginMove: function (evt) {
1293             var r = (this.mode === this.BOARD_MODE_MOVE_ORIGIN),
1294                 pos;
1295 
1296             if (r) {
1297                 pos = this.getMousePosition(evt, 0);
1298                 this.moveOrigin(pos[0], pos[1], true);
1299             }
1300 
1301             return r;
1302         },
1303 
1304         originMoveEnd: function () {
1305             this.updateQuality = this.BOARD_QUALITY_HIGH;
1306             this.mode = this.BOARD_MODE_NONE;
1307         },
1308 
1309         /**********************************************************
1310          *
1311          * Event Handler
1312          *
1313          **********************************************************/
1314 
1315         /**
1316          *  Add all possible event handlers to the board object
1317          */
1318         addEventHandlers: function () {
1319             if (Env.supportsPointerEvents()) {
1320                 this.addPointerEventHandlers();
1321             } else {
1322                 this.addMouseEventHandlers();
1323                 this.addTouchEventHandlers();
1324             }
1325         },
1326 
1327         /**
1328          * Registers the MSPointer* event handlers.
1329          */
1330         addPointerEventHandlers: function () {
1331             if (!this.hasPointerHandlers && Env.isBrowser) {
1332                 if (window.navigator.pointerEnabled) {  // IE11+
1333                     Env.addEvent(this.containerObj, 'pointerdown', this.pointerDownListener, this);
1334                     Env.addEvent(this.containerObj, 'pointermove', this.pointerMoveListener, this);
1335                 } else {
1336                     Env.addEvent(this.containerObj, 'MSPointerDown', this.pointerDownListener, this);
1337                     Env.addEvent(this.containerObj, 'MSPointerMove', this.pointerMoveListener, this);
1338                 }
1339                 this.hasPointerHandlers = true;
1340             }
1341         },
1342 
1343         /**
1344          * Registers mouse move, down and wheel event handlers.
1345          */
1346         addMouseEventHandlers: function () {
1347             if (!this.hasMouseHandlers && Env.isBrowser) {
1348                 Env.addEvent(this.containerObj, 'mousedown', this.mouseDownListener, this);
1349                 Env.addEvent(this.containerObj, 'mousemove', this.mouseMoveListener, this);
1350 
1351                 Env.addEvent(this.containerObj, 'mousewheel', this.mouseWheelListener, this);
1352                 Env.addEvent(this.containerObj, 'DOMMouseScroll', this.mouseWheelListener, this);
1353 
1354                 this.hasMouseHandlers = true;
1355 
1356                 // This one produces errors on IE
1357                 //   Env.addEvent(this.containerObj, 'contextmenu', function (e) { e.preventDefault(); return false;}, this);
1358 
1359                 // This one works on IE, Firefox and Chromium with default configurations. On some Safari
1360                 // or Opera versions the user must explicitly allow the deactivation of the context menu.
1361                 this.containerObj.oncontextmenu = function (e) {
1362                     if (Type.exists(e)) {
1363                         e.preventDefault();
1364                     }
1365 
1366                     return false;
1367                 };
1368             }
1369         },
1370 
1371         /**
1372          * Register touch start and move and gesture start and change event handlers.
1373          * @param {Boolean} appleGestures If set to false the gesturestart and gesturechange event handlers
1374          * will not be registered.
1375          */
1376         addTouchEventHandlers: function (appleGestures) {
1377             if (!this.hasTouchHandlers && Env.isBrowser) {
1378                 Env.addEvent(this.containerObj, 'touchstart', this.touchStartListener, this);
1379                 Env.addEvent(this.containerObj, 'touchmove', this.touchMoveListener, this);
1380 
1381                 if (!Type.exists(appleGestures) || appleGestures) {
1382                     Env.addEvent(this.containerObj, 'gesturestart', this.gestureStartListener, this);
1383                     Env.addEvent(this.containerObj, 'gesturechange', this.gestureChangeListener, this);
1384                     this.hasGestureHandlers = true;
1385                 }
1386 
1387                 this.hasTouchHandlers = true;
1388             }
1389         },
1390 
1391         /**
1392          * Remove MSPointer* Event handlers.
1393          */
1394         removePointerEventHandlers: function () {
1395             if (this.hasPointerHandlers && Env.isBrowser) {
1396                 if (window.navigator.pointerEnabled) {  // IE11+
1397                     Env.removeEvent(this.containerObj, 'pointerdown', this.pointerDownListener, this);
1398                     Env.removeEvent(this.containerObj, 'pointermove', this.pointerMoveListener, this);
1399                 } else {
1400                     Env.removeEvent(this.containerObj, 'MSPointerDown', this.pointerDownListener, this);
1401                     Env.removeEvent(this.containerObj, 'MSPointerMove', this.pointerMoveListener, this);
1402                 }
1403 
1404                 if (this.hasPointerUp) {
1405                     if (window.navigator.pointerEnabled) {  // IE11+
1406                         Env.removeEvent(this.document, 'pointerup', this.pointerUpListener, this);
1407                     } else {
1408                         Env.removeEvent(this.document, 'MSPointerUp', this.pointerUpListener, this);
1409                     }
1410                     this.hasPointerUp = false;
1411                 }
1412 
1413                 this.hasPointerHandlers = false;
1414             }
1415         },
1416 
1417         /**
1418          * De-register mouse event handlers.
1419          */
1420         removeMouseEventHandlers: function () {
1421             if (this.hasMouseHandlers && Env.isBrowser) {
1422                 Env.removeEvent(this.containerObj, 'mousedown', this.mouseDownListener, this);
1423                 Env.removeEvent(this.containerObj, 'mousemove', this.mouseMoveListener, this);
1424 
1425                 if (this.hasMouseUp) {
1426                     Env.removeEvent(this.document, 'mouseup', this.mouseUpListener, this);
1427                     this.hasMouseUp = false;
1428                 }
1429 
1430                 Env.removeEvent(this.containerObj, 'mousewheel', this.mouseWheelListener, this);
1431                 Env.removeEvent(this.containerObj, 'DOMMouseScroll', this.mouseWheelListener, this);
1432 
1433                 this.hasMouseHandlers = false;
1434             }
1435         },
1436 
1437         /**
1438          * Remove all registered touch event handlers.
1439          */
1440         removeTouchEventHandlers: function () {
1441             if (this.hasTouchHandlers && Env.isBrowser) {
1442                 Env.removeEvent(this.containerObj, 'touchstart', this.touchStartListener, this);
1443                 Env.removeEvent(this.containerObj, 'touchmove', this.touchMoveListener, this);
1444 
1445                 if (this.hasTouchEnd) {
1446                     Env.removeEvent(this.document, 'touchend', this.touchEndListener, this);
1447                     this.hasTouchEnd = false;
1448                 }
1449 
1450                 if (this.hasGestureHandlers) {
1451                     Env.removeEvent(this.containerObj, 'gesturestart', this.gestureStartListener, this);
1452                     Env.removeEvent(this.containerObj, 'gesturechange', this.gestureChangeListener, this);
1453                     this.hasGestureHandlers = false;
1454                 }
1455 
1456                 this.hasTouchHandlers = false;
1457             }
1458         },
1459 
1460         /**
1461          * Remove all event handlers from the board object
1462          */
1463         removeEventHandlers: function () {
1464             this.removeMouseEventHandlers();
1465             this.removeTouchEventHandlers();
1466             this.removePointerEventHandlers();
1467         },
1468 
1469         /**
1470          * Handler for click on left arrow in the navigation bar
1471          */
1472         clickLeftArrow: function () {
1473             this.moveOrigin(this.origin.scrCoords[1] + this.canvasWidth * 0.1, this.origin.scrCoords[2]);
1474             return false;
1475         },
1476 
1477         /**
1478          * Handler for click on right arrow in the navigation bar
1479          */
1480         clickRightArrow: function () {
1481             this.moveOrigin(this.origin.scrCoords[1] - this.canvasWidth * 0.1, this.origin.scrCoords[2]);
1482             return false;
1483         },
1484 
1485         /**
1486          * Handler for click on up arrow in the navigation bar
1487          */
1488         clickUpArrow: function () {
1489             this.moveOrigin(this.origin.scrCoords[1], this.origin.scrCoords[2] - this.canvasHeight * 0.1);
1490             return false;
1491         },
1492 
1493         /**
1494          * Handler for click on down arrow in the navigation bar
1495          */
1496         clickDownArrow: function () {
1497             this.moveOrigin(this.origin.scrCoords[1], this.origin.scrCoords[2] + this.canvasHeight * 0.1);
1498             return false;
1499         },
1500 
1501         /**
1502          * Triggered on iOS/Safari while the user inputs a gesture (e.g. pinch) and is used to zoom into the board. Only works on iOS/Safari.
1503          * @param {Event} evt Browser event object
1504          * @return {Boolean}
1505          */
1506         gestureChangeListener: function (evt) {
1507             var c,
1508                 zx = this.attr.zoom.factorx,
1509                 zy = this.attr.zoom.factory;
1510 
1511             if (!this.attr.zoom.wheel) {
1512                 return true;
1513             }
1514 
1515             evt.preventDefault();
1516 
1517             if (this.mode === this.BOARD_MODE_ZOOM) {
1518                 c = new Coords(Const.COORDS_BY_SCREEN, this.getMousePosition(evt), this);
1519 
1520                 this.attr.zoom.factorx = evt.scale / this.prevScale;
1521                 this.attr.zoom.factory = evt.scale / this.prevScale;
1522 
1523                 this.zoomIn(c.usrCoords[1], c.usrCoords[2]);
1524                 this.prevScale = evt.scale;
1525 
1526                 this.attr.zoom.factorx = zx;
1527                 this.attr.zoom.factory = zy;
1528             }
1529 
1530             return false;
1531         },
1532 
1533         /**
1534          * Called by iOS/Safari as soon as the user starts a gesture (only works on iOS/Safari).
1535          * @param {Event} evt
1536          * @return {Boolean}
1537          */
1538         gestureStartListener: function (evt) {
1539 
1540             if (!this.attr.zoom.wheel) {
1541                 return true;
1542             }
1543 
1544             evt.preventDefault();
1545             this.prevScale = 1;
1546 
1547             if (this.mode === this.BOARD_MODE_NONE) {
1548                 this.mode = this.BOARD_MODE_ZOOM;
1549             }
1550 
1551             return false;
1552         },
1553 
1554         /**
1555          * pointer-Events
1556          */
1557 
1558         /**
1559          * This method is called by the browser when a pointing device is pressed on the screen.
1560          * @param {Event} evt The browsers event object.
1561          * @param {Object} object If the object to be dragged is already known, it can be submitted via this parameter
1562          * @returns {Boolean} ...
1563          */
1564         pointerDownListener: function (evt, object) {
1565             var i, j, k, pos, elements,
1566                 eps = this.options.precision.touch,
1567                 found, target, result;
1568 
1569             if (!this.hasPointerUp) {
1570                 if (window.navigator.pointerEnabled) {  // IE11+
1571                     Env.addEvent(this.document, 'pointerup', this.pointerUpListener, this);
1572                 } else {
1573                     Env.addEvent(this.document, 'MSPointerUp', this.pointerUpListener, this);
1574                 }
1575                 this.hasPointerUp = true;
1576             }
1577 
1578             if (this.hasMouseHandlers) {
1579                 this.removeMouseEventHandlers();
1580             }
1581 
1582             if (this.hasTouchHandlers) {
1583                 this.removeTouchEventHandlers();
1584             }
1585 
1586             // prevent accidental selection of text
1587             if (this.document.selection && typeof this.document.selection.empty === 'function') {
1588                 this.document.selection.empty();
1589             } else if (window.getSelection) {
1590                 window.getSelection().removeAllRanges();
1591             }
1592 
1593             // Touch or pen device
1594             if (JXG.isBrowser && (window.navigator.msMaxTouchPoints && window.navigator.msMaxTouchPoints > 1)) {
1595                 this.options.precision.hasPoint = eps;
1596             }
1597 
1598             // This should be easier than the touch events. Every pointer device gets its own pointerId, e.g. the mouse
1599             // always has id 1, fingers and pens get unique ids every time a pointerDown event is fired and they will
1600             // keep this id until a pointerUp event is fired. What we have to do here is:
1601             //  1. collect all elements under the current pointer
1602             //  2. run through the touches control structure
1603             //    a. look for the object collected in step 1.
1604             //    b. if an object is found, check the number of pointers. if appropriate, add the pointer.
1605 
1606             pos = this.getMousePosition(evt);
1607 
1608             if (object) {
1609                 elements = [ object ];
1610                 this.mode = this.BOARD_MODE_DRAG;
1611             } else {
1612                 elements = this.initMoveObject(pos[0], pos[1], evt, 'mouse');
1613             }
1614 
1615             // if no draggable object can be found, get out here immediately
1616             if (elements.length > 0) {
1617                 // check touches structure
1618                 target = elements[elements.length - 1];
1619                 found = false;
1620 
1621                 for (i = 0; i < this.touches.length; i++) {
1622                     // the target is already in our touches array, try to add the pointer to the existing touch
1623                     if (this.touches[i].obj === target) {
1624                         j = i;
1625                         k = this.touches[i].targets.push({
1626                             num: evt.pointerId,
1627                             X: pos[0],
1628                             Y: pos[1],
1629                             Xprev: NaN,
1630                             Yprev: NaN,
1631                             Xstart: [],
1632                             Ystart: [],
1633                             Zstart: []
1634                         }) - 1;
1635 
1636                         found = true;
1637                         break;
1638                     }
1639                 }
1640 
1641                 if (!found) {
1642                     k = 0;
1643                     j = this.touches.push({
1644                         obj: target,
1645                         targets: [{
1646                             num: evt.pointerId,
1647                             X: pos[0],
1648                             Y: pos[1],
1649                             Xprev: NaN,
1650                             Yprev: NaN,
1651                             Xstart: [],
1652                             Ystart: [],
1653                             Zstart: []
1654                         }]
1655                     }) - 1;
1656                 }
1657 
1658                 this.dehighlightAll();
1659                 target.highlight(true);
1660 
1661                 this.saveStartPos(target, this.touches[j].targets[k]);
1662 
1663                 // prevent accidental text selection
1664                 // this could get us new trouble: input fields, links and drop down boxes placed as text
1665                 // on the board don't work anymore.
1666                 if (evt && evt.preventDefault) {
1667                     evt.preventDefault();
1668                 } else if (window.event) {
1669                     window.event.returnValue = false;
1670                 }
1671             }
1672 
1673             if (this.touches.length > 0) {
1674                 evt.preventDefault();
1675                 evt.stopPropagation();
1676             }
1677 
1678             // move origin - but only if we're not in drag mode
1679             if (this.mode === this.BOARD_MODE_NONE && this.mouseOriginMoveStart(evt)) {
1680                 this.triggerEventHandlers(['touchstart', 'down', 'pointerdown', 'MSPointerDown'], [evt]);
1681                 return false;
1682             }
1683 
1684             this.options.precision.hasPoint = this.options.precision.mouse;
1685             this.triggerEventHandlers(['touchstart', 'down', 'pointerdown', 'MSPointerDown'], [evt]);
1686 
1687             return result;
1688         },
1689 
1690         /**
1691          * Called periodically by the browser while the user moves a pointing device across the screen.
1692          * @param {Event} evt
1693          * @return {Boolean}
1694          */
1695         pointerMoveListener: function (evt) {
1696             var i, j, pos, time,
1697                 evtTouches = evt[JXG.touchProperty];
1698 
1699             if (this.mode !== this.BOARD_MODE_DRAG) {
1700                 this.dehighlightAll();
1701                 this.renderer.hide(this.infobox);
1702             }
1703 
1704             if (this.mode !== this.BOARD_MODE_NONE) {
1705                 evt.preventDefault();
1706                 evt.stopPropagation();
1707             }
1708 
1709             // Touch or pen device
1710             if (JXG.isBrowser && (window.navigator.msMaxTouchPoints && window.navigator.msMaxTouchPoints > 1)) {
1711                 this.options.precision.hasPoint = this.options.precision.touch;
1712             }
1713             this.updateQuality = this.BOARD_QUALITY_LOW;
1714 
1715             // try with mouseOriginMove because the evt objects are quite similar
1716             if (!this.mouseOriginMove(evt)) {
1717                 if (this.mode === this.BOARD_MODE_DRAG) {
1718                     // Runs through all elements which are touched by at least one finger.
1719                     for (i = 0; i < this.touches.length; i++) {
1720                         for (j = 0; j < this.touches[i].targets.length; j++) {
1721                             if (this.touches[i].targets[j].num === evt.pointerId) {
1722                                 // Touch by one finger:  this is possible for all elements that can be dragged
1723                                 if (this.touches[i].targets.length === 1) {
1724                                     this.touches[i].targets[j].X = evt.pageX;
1725                                     this.touches[i].targets[j].Y = evt.pageY;
1726                                     pos = this.getMousePosition(evt);
1727                                     this.moveObject(pos[0], pos[1], this.touches[i], evt, 'touch');
1728                                 // Touch by two fingers: moving lines
1729                                 } else if (this.touches[i].targets.length === 2 &&
1730                                         this.touches[i].targets[0].num > -1 && this.touches[i].targets[1].num > -1) {
1731 
1732                                     this.touches[i].targets[j].X = evt.pageX;
1733                                     this.touches[i].targets[j].Y = evt.pageY;
1734 
1735                                     this.twoFingerMove(
1736                                         this.getMousePosition({
1737                                             pageX: this.touches[i].targets[0].X,
1738                                             pageY: this.touches[i].targets[0].Y
1739                                         }),
1740                                         this.getMousePosition({
1741                                             pageX: this.touches[i].targets[1].X,
1742                                             pageY: this.touches[i].targets[1].Y
1743                                         }),
1744                                         this.touches[i],
1745                                         evt
1746                                     );
1747                                 }
1748 
1749                                 // there is only one pointer in the evt object, there's no point in looking further
1750                                 break;
1751                             }
1752                         }
1753 
1754                     }
1755                 } else {
1756                     pos = this.getMousePosition(evt);
1757                     this.highlightElements(pos[0], pos[1], evt, -1);
1758                 }
1759             }
1760 
1761             // Hiding the infobox is commentet out, since it prevents showing the infobox
1762             // on IE 11+ on 'over'
1763             //if (this.mode !== this.BOARD_MODE_DRAG) {
1764                 //this.renderer.hide(this.infobox);
1765             //}
1766 
1767             this.options.precision.hasPoint = this.options.precision.mouse;
1768             this.triggerEventHandlers(['touchmove', 'move', 'pointermove', 'MSPointerMove'], [evt, this.mode]);
1769 
1770             return this.mode === this.BOARD_MODE_NONE;
1771         },
1772 
1773         /**
1774          * Triggered as soon as the user stops touching the device with at least one finger.
1775          * @param {Event} evt
1776          * @return {Boolean}
1777          */
1778         pointerUpListener: function (evt) {
1779             var i, j, k, found, foundNumber,
1780                 tmpTouches = [],
1781                 eps = this.options.precision.touch;
1782 
1783             this.triggerEventHandlers(['touchend', 'up', 'pointerup', 'MSPointerUp'], [evt]);
1784             this.renderer.hide(this.infobox);
1785 
1786             if (evt) {
1787                 for (i = 0; i < this.touches.length; i++) {
1788                     for (j = 0; j < this.touches[i].targets.length; j++) {
1789                         if (this.touches[i].targets[j].num === evt.pointerId) {
1790                             this.touches[i].targets.splice(j, 1);
1791 
1792                             if (this.touches[i].targets.length === 0) {
1793                                 this.touches.splice(i, 1);
1794                             }
1795 
1796                             break;
1797                         }
1798                     }
1799                 }
1800             }
1801 
1802             for (i = this.downObjects.length - 1; i > -1; i--) {
1803                 found = false;
1804                 for (j = 0; j < this.touches.length; j++) {
1805                     if (this.touches[j].obj.id === this.downObjects[i].id) {
1806                         found = true;
1807                     }
1808                 }
1809                 if (!found) {
1810                     this.downObjects[i].triggerEventHandlers(['touchend', 'up', 'pointerup', 'MSPointerUp'], [evt]);
1811                     this.downObjects[i].snapToGrid();
1812                     this.downObjects[i].snapToPoints();
1813                     this.downObjects.splice(i, 1);
1814                 }
1815             }
1816 
1817             if (this.touches.length === 0) {
1818                 if (this.hasPointerUp) {
1819                     if (window.navigator.pointerEnabled) {  // IE11+
1820                         Env.removeEvent(this.document, 'pointerup', this.pointerUpListener, this);
1821                     } else {
1822                         Env.removeEvent(this.document, 'MSPointerUp', this.pointerUpListener, this);
1823                     }
1824                     this.hasPointerUp = false;
1825                 }
1826 
1827                 this.dehighlightAll();
1828                 this.updateQuality = this.BOARD_QUALITY_HIGH;
1829 
1830                 this.originMoveEnd();
1831                 this.update();
1832             }
1833 
1834             return true;
1835         },
1836 
1837         /**
1838          * Touch-Events
1839          */
1840 
1841         /**
1842          * This method is called by the browser when a finger touches the surface of the touch-device.
1843          * @param {Event} evt The browsers event object.
1844          * @returns {Boolean} ...
1845          */
1846         touchStartListener: function (evt) {
1847             var i, pos, elements, j, k, time,
1848                 eps = this.options.precision.touch,
1849                 obj, found, targets,
1850                 evtTouches = evt[JXG.touchProperty],
1851                 target;
1852 
1853             if (!this.hasTouchEnd) {
1854                 Env.addEvent(this.document, 'touchend', this.touchEndListener, this);
1855                 this.hasTouchEnd = true;
1856             }
1857 
1858             if (this.hasMouseHandlers) {
1859                 this.removeMouseEventHandlers();
1860             }
1861 
1862             // prevent accidental selection of text
1863             if (this.document.selection && typeof this.document.selection.empty === 'function') {
1864                 this.document.selection.empty();
1865             } else if (window.getSelection) {
1866                 window.getSelection().removeAllRanges();
1867             }
1868 
1869             // multitouch
1870             this.options.precision.hasPoint = this.options.precision.touch;
1871 
1872             // this is the most critical part. first we should run through the existing touches and collect all targettouches that don't belong to our
1873             // previous touches. once this is done we run through the existing touches again and watch out for free touches that can be attached to our existing
1874             // touches, e.g. we translate (parallel translation) a line with one finger, now a second finger is over this line. this should change the operation to
1875             // a rotational translation. or one finger moves a circle, a second finger can be attached to the circle: this now changes the operation from translation to
1876             // stretching. as a last step we're going through the rest of the targettouches and initiate new move operations:
1877             //  * points have higher priority over other elements.
1878             //  * if we find a targettouch over an element that could be transformed with more than one finger, we search the rest of the targettouches, if they are over
1879             //    this element and add them.
1880             // ADDENDUM 11/10/11:
1881             //  (1) run through the touches control object,
1882             //  (2) try to find the targetTouches for every touch. on touchstart only new touches are added, hence we can find a targettouch
1883             //      for every target in our touches objects
1884             //  (3) if one of the targettouches was bound to a touches targets array, mark it
1885             //  (4) run through the targettouches. if the targettouch is marked, continue. otherwise check for elements below the targettouch:
1886             //      (a) if no element could be found: mark the target touches and continue
1887             //      --- in the following cases, "init" means:
1888             //           (i) check if the element is already used in another touches element, if so, mark the targettouch and continue
1889             //          (ii) if not, init a new touches element, add the targettouch to the touches property and mark it
1890             //      (b) if the element is a point, init
1891             //      (c) if the element is a line, init and try to find a second targettouch on that line. if a second one is found, add and mark it
1892             //      (d) if the element is a circle, init and try to find TWO other targettouches on that circle. if only one is found, mark it and continue. otherwise
1893             //          add both to the touches array and mark them.
1894             for (i = 0; i < evtTouches.length; i++) {
1895                 evtTouches[i].jxg_isused = false;
1896             }
1897 
1898             for (i = 0; i < this.touches.length; i++) {
1899                 for (j = 0; j < this.touches[i].targets.length; j++) {
1900                     this.touches[i].targets[j].num = -1;
1901                     eps = this.options.precision.touch;
1902 
1903                     do {
1904                         for (k = 0; k < evtTouches.length; k++) {
1905                             // find the new targettouches
1906                             if (Math.abs(Math.pow(evtTouches[k].screenX - this.touches[i].targets[j].X, 2) +
1907                                     Math.pow(evtTouches[k].screenY - this.touches[i].targets[j].Y, 2)) < eps * eps) {
1908                                 this.touches[i].targets[j].num = k;
1909 
1910                                 this.touches[i].targets[j].X = evtTouches[k].screenX;
1911                                 this.touches[i].targets[j].Y = evtTouches[k].screenY;
1912                                 evtTouches[k].jxg_isused = true;
1913                                 break;
1914                             }
1915                         }
1916 
1917                         eps *= 2;
1918 
1919                     } while (this.touches[i].targets[j].num === -1 && eps < this.options.precision.touchMax);
1920 
1921                     if (this.touches[i].targets[j].num === -1) {
1922                         JXG.debug('i couldn\'t find a targettouches for target no ' + j + ' on ' + this.touches[i].obj.name + ' (' + this.touches[i].obj.id + '). Removed the target.');
1923                         JXG.debug('eps = ' + eps + ', touchMax = ' + Options.precision.touchMax);
1924                         this.touches[i].targets.splice(i, 1);
1925                     }
1926 
1927                 }
1928             }
1929 
1930             // we just re-mapped the targettouches to our existing touches list. now we have to initialize some touches from additional targettouches
1931             for (i = 0; i < evtTouches.length; i++) {
1932                 if (!evtTouches[i].jxg_isused) {
1933                     pos = this.getMousePosition(evt, i);
1934                     elements = this.initMoveObject(pos[0], pos[1], evt, 'touch');
1935 
1936                     if (elements.length !== 0) {
1937                         obj = elements[elements.length - 1];
1938 
1939                         if (Type.isPoint(obj) ||
1940                                 obj.elementClass === Const.OBJECT_CLASS_TEXT ||
1941                                 obj.type === Const.OBJECT_TYPE_TICKS ||
1942                                 obj.type === Const.OBJECT_TYPE_IMAGE) {
1943                             // it's a point, so it's single touch, so we just push it to our touches
1944                             targets = [{ num: i, X: evtTouches[i].screenX, Y: evtTouches[i].screenY, Xprev: NaN, Yprev: NaN, Xstart: [], Ystart: [], Zstart: [] }];
1945 
1946                             // For the UNDO/REDO of object moves
1947                             this.saveStartPos(obj, targets[0]);
1948 
1949                             this.touches.push({ obj: obj, targets: targets });
1950                             obj.highlight(true);
1951 
1952                         } else if (obj.elementClass === Const.OBJECT_CLASS_LINE ||
1953                                 obj.elementClass === Const.OBJECT_CLASS_CIRCLE ||
1954                                 obj.type === Const.OBJECT_TYPE_POLYGON) {
1955                             found = false;
1956 
1957                             // first check if this geometric object is already capture in this.touches
1958                             for (j = 0; j < this.touches.length; j++) {
1959                                 if (obj.id === this.touches[j].obj.id) {
1960                                     found = true;
1961                                     // only add it, if we don't have two targets in there already
1962                                     if (this.touches[j].targets.length === 1) {
1963                                         target = { num: i, X: evtTouches[i].screenX, Y: evtTouches[i].screenY, Xprev: NaN, Yprev: NaN, Xstart: [], Ystart: [], Zstart: [] };
1964 
1965                                         // For the UNDO/REDO of object moves
1966                                         this.saveStartPos(obj, target);
1967                                         this.touches[j].targets.push(target);
1968                                     }
1969 
1970                                     evtTouches[i].jxg_isused = true;
1971                                 }
1972                             }
1973 
1974                             // we couldn't find it in touches, so we just init a new touches
1975                             // IF there is a second touch targetting this line, we will find it later on, and then add it to
1976                             // the touches control object.
1977                             if (!found) {
1978                                 targets = [{ num: i, X: evtTouches[i].screenX, Y: evtTouches[i].screenY, Xprev: NaN, Yprev: NaN, Xstart: [], Ystart: [], Zstart: [] }];
1979 
1980                                 // For the UNDO/REDO of object moves
1981                                 this.saveStartPos(obj, targets[0]);
1982                                 this.touches.push({ obj: obj, targets: targets });
1983                                 obj.highlight(true);
1984                             }
1985                         }
1986                     }
1987 
1988                     evtTouches[i].jxg_isused = true;
1989                 }
1990             }
1991 
1992             if (this.touches.length > 0) {
1993                 evt.preventDefault();
1994                 evt.stopPropagation();
1995             }
1996 
1997             // move origin - but only if we're not in drag mode
1998             if (this.mode === this.BOARD_MODE_NONE && this.touchOriginMoveStart(evt)) {
1999                 this.triggerEventHandlers(['touchstart', 'down'], [evt]);
2000                 return false;
2001             }
2002 
2003             if (Env.isWebkitAndroid()) {
2004                 time = new Date();
2005                 this.touchMoveLast = time.getTime() - 200;
2006             }
2007 
2008             this.options.precision.hasPoint = this.options.precision.mouse;
2009 
2010             this.triggerEventHandlers(['touchstart', 'down'], [evt]);
2011 
2012             return this.touches.length > 0;
2013         },
2014 
2015         /**
2016          * Called periodically by the browser while the user moves his fingers across the device.
2017          * @param {Event} evt
2018          * @return {Boolean}
2019          */
2020         touchMoveListener: function (evt) {
2021             var i, pos1, pos2, time,
2022                 evtTouches = evt[JXG.touchProperty];
2023 
2024             if (this.mode !== this.BOARD_MODE_NONE) {
2025                 evt.preventDefault();
2026                 evt.stopPropagation();
2027             }
2028 
2029             // Reduce update frequency for Android devices
2030             if (Env.isWebkitAndroid()) {
2031                 time = new Date();
2032                 time = time.getTime();
2033 
2034                 if (time - this.touchMoveLast < 80) {
2035                     this.updateQuality = this.BOARD_QUALITY_HIGH;
2036                     this.triggerEventHandlers(['touchmove', 'move'], [evt, this.mode]);
2037 
2038                     return false;
2039                 }
2040 
2041                 this.touchMoveLast = time;
2042             }
2043 
2044             if (this.mode !== this.BOARD_MODE_DRAG) {
2045                 this.renderer.hide(this.infobox);
2046             }
2047 
2048             this.options.precision.hasPoint = this.options.precision.touch;
2049             this.updateQuality = this.BOARD_QUALITY_LOW;
2050 
2051             if (!this.touchOriginMove(evt)) {
2052                 if (this.mode === this.BOARD_MODE_DRAG) {
2053                     // Runs over through all elements which are touched
2054                     // by at least one finger.
2055                     for (i = 0; i < this.touches.length; i++) {
2056                         // Touch by one finger:  this is possible for all elements that can be dragged
2057                         if (this.touches[i].targets.length === 1) {
2058                             if (evtTouches[this.touches[i].targets[0].num]) {
2059                                 pos1 = this.getMousePosition(evt, this.touches[i].targets[0].num);
2060                                 if (pos1[0] < 0 || pos1[0] > this.canvasWidth ||  pos1[1] < 0 || pos1[1] > this.canvasHeight) {
2061                                     return;
2062                                 }
2063                                 this.touches[i].targets[0].X = evtTouches[this.touches[i].targets[0].num].screenX;
2064                                 this.touches[i].targets[0].Y = evtTouches[this.touches[i].targets[0].num].screenY;
2065                                 this.moveObject(pos1[0], pos1[1], this.touches[i], evt, 'touch');
2066                             }
2067                             // Touch by two fingers: moving lines
2068                         } else if (this.touches[i].targets.length === 2 && this.touches[i].targets[0].num > -1 && this.touches[i].targets[1].num > -1) {
2069                             if (evtTouches[this.touches[i].targets[0].num] && evtTouches[this.touches[i].targets[1].num]) {
2070                                 pos1 = this.getMousePosition(evt, this.touches[i].targets[0].num);
2071                                 pos2 = this.getMousePosition(evt, this.touches[i].targets[1].num);
2072                                 if (pos1[0] < 0 || pos1[0] > this.canvasWidth ||  pos1[1] < 0 || pos1[1] > this.canvasHeight ||
2073                                         pos2[0] < 0 || pos2[0] > this.canvasWidth ||  pos2[1] < 0 || pos2[1] > this.canvasHeight) {
2074                                     return;
2075                                 }
2076                                 this.touches[i].targets[0].X = evtTouches[this.touches[i].targets[0].num].screenX;
2077                                 this.touches[i].targets[0].Y = evtTouches[this.touches[i].targets[0].num].screenY;
2078                                 this.touches[i].targets[1].X = evtTouches[this.touches[i].targets[1].num].screenX;
2079                                 this.touches[i].targets[1].Y = evtTouches[this.touches[i].targets[1].num].screenY;
2080                                 this.twoFingerMove(pos1, pos2, this.touches[i], evt);
2081                             }
2082                         }
2083                     }
2084                 }
2085             }
2086 
2087             if (this.mode !== this.BOARD_MODE_DRAG) {
2088                 this.renderer.hide(this.infobox);
2089             }
2090 
2091             /*
2092               this.updateQuality = this.BOARD_QUALITY_HIGH; is set in touchEnd
2093             */
2094             this.options.precision.hasPoint = this.options.precision.mouse;
2095             this.triggerEventHandlers(['touchmove', 'move'], [evt, this.mode]);
2096 
2097             return this.mode === this.BOARD_MODE_NONE;
2098         },
2099 
2100         /**
2101          * Triggered as soon as the user stops touching the device with at least one finger.
2102          * @param {Event} evt
2103          * @return {Boolean}
2104          */
2105         touchEndListener: function (evt) {
2106             var i, j, k,
2107                 eps = this.options.precision.touch,
2108                 tmpTouches = [], found, foundNumber,
2109                 evtTouches = evt && evt[JXG.touchProperty];
2110 
2111             this.triggerEventHandlers(['touchend', 'up'], [evt]);
2112             this.renderer.hide(this.infobox);
2113 
2114             if (evtTouches && evtTouches.length > 0) {
2115                 for (i = 0; i < this.touches.length; i++) {
2116                     tmpTouches[i] = this.touches[i];
2117                 }
2118                 this.touches.length = 0;
2119 
2120                 // try to convert the operation, e.g. if a lines is rotated and translated with two fingers and one finger is lifted,
2121                 // convert the operation to a simple one-finger-translation.
2122                 // ADDENDUM 11/10/11:
2123                 // see addendum to touchStartListener from 11/10/11
2124                 // (1) run through the tmptouches
2125                 // (2) check the touches.obj, if it is a
2126                 //     (a) point, try to find the targettouch, if found keep it and mark the targettouch, else drop the touch.
2127                 //     (b) line with
2128                 //          (i) one target: try to find it, if found keep it mark the targettouch, else drop the touch.
2129                 //         (ii) two targets: if none can be found, drop the touch. if one can be found, remove the other target. mark all found targettouches
2130                 //     (c) circle with [proceed like in line]
2131 
2132                 // init the targettouches marker
2133                 for (i = 0; i < evtTouches.length; i++) {
2134                     evtTouches[i].jxg_isused = false;
2135                 }
2136 
2137                 for (i = 0; i < tmpTouches.length; i++) {
2138                     // could all targets of the current this.touches.obj be assigned to targettouches?
2139                     found = false;
2140                     foundNumber = 0;
2141 
2142                     for (j = 0; j < tmpTouches[i].targets.length; j++) {
2143                         tmpTouches[i].targets[j].found = false;
2144                         for (k = 0; k < evtTouches.length; k++) {
2145                             if (Math.abs(Math.pow(evtTouches[k].screenX - tmpTouches[i].targets[j].X, 2) + Math.pow(evtTouches[k].screenY - tmpTouches[i].targets[j].Y, 2)) < eps * eps) {
2146                                 tmpTouches[i].targets[j].found = true;
2147                                 tmpTouches[i].targets[j].num = k;
2148                                 tmpTouches[i].targets[j].X = evtTouches[k].screenX;
2149                                 tmpTouches[i].targets[j].Y = evtTouches[k].screenY;
2150                                 foundNumber += 1;
2151                                 break;
2152                             }
2153                         }
2154                     }
2155 
2156                     if (Type.isPoint(tmpTouches[i].obj)) {
2157                         found = (tmpTouches[i].targets[0] && tmpTouches[i].targets[0].found);
2158                     } else if (tmpTouches[i].obj.elementClass === Const.OBJECT_CLASS_LINE) {
2159                         found = (tmpTouches[i].targets[0] && tmpTouches[i].targets[0].found) || (tmpTouches[i].targets[1] && tmpTouches[i].targets[1].found);
2160                     } else if (tmpTouches[i].obj.elementClass === Const.OBJECT_CLASS_CIRCLE) {
2161                         found = foundNumber === 1 || foundNumber === 3;
2162                     }
2163 
2164                     // if we found this object to be still dragged by the user, add it back to this.touches
2165                     if (found) {
2166                         this.touches.push({
2167                             obj: tmpTouches[i].obj,
2168                             targets: []
2169                         });
2170 
2171                         for (j = 0; j < tmpTouches[i].targets.length; j++) {
2172                             if (tmpTouches[i].targets[j].found) {
2173                                 this.touches[this.touches.length - 1].targets.push({
2174                                     num: tmpTouches[i].targets[j].num,
2175                                     X: tmpTouches[i].targets[j].screenX,
2176                                     Y: tmpTouches[i].targets[j].screenY,
2177                                     Xprev: NaN,
2178                                     Yprev: NaN,
2179                                     Xstart: tmpTouches[i].targets[j].Xstart,
2180                                     Ystart: tmpTouches[i].targets[j].Ystart,
2181                                     Zstart: tmpTouches[i].targets[j].Zstart
2182                                 });
2183                             }
2184                         }
2185 
2186                     } else {
2187                         tmpTouches[i].obj.noHighlight();
2188                     }
2189                 }
2190 
2191             } else {
2192                 this.touches.length = 0;
2193             }
2194 
2195             for (i = this.downObjects.length - 1; i > -1; i--) {
2196                 found = false;
2197                 for (j = 0; j < this.touches.length; j++) {
2198                     if (this.touches[j].obj.id === this.downObjects[i].id) {
2199                         found = true;
2200                     }
2201                 }
2202                 if (!found) {
2203                     this.downObjects[i].triggerEventHandlers(['touchup', 'up'], [evt]);
2204                     this.downObjects[i].snapToGrid();
2205                     this.downObjects[i].snapToPoints();
2206                     this.downObjects.splice(i, 1);
2207                 }
2208             }
2209 
2210             if (!evtTouches || evtTouches.length === 0) {
2211 
2212                 if (this.hasTouchEnd) {
2213                     Env.removeEvent(this.document, 'touchend', this.touchEndListener, this);
2214                     this.hasTouchEnd = false;
2215                 }
2216 
2217                 this.dehighlightAll();
2218                 this.updateQuality = this.BOARD_QUALITY_HIGH;
2219 
2220                 this.originMoveEnd();
2221                 this.update();
2222             }
2223 
2224             return true;
2225         },
2226 
2227         /**
2228          * This method is called by the browser when the mouse button is clicked.
2229          * @param {Event} evt The browsers event object.
2230          * @returns {Boolean} True if no element is found under the current mouse pointer, false otherwise.
2231          */
2232         mouseDownListener: function (evt) {
2233             var pos, elements, result;
2234 
2235             // prevent accidental selection of text
2236             if (this.document.selection && typeof this.document.selection.empty === 'function') {
2237                 this.document.selection.empty();
2238             } else if (window.getSelection) {
2239                 window.getSelection().removeAllRanges();
2240             }
2241 
2242             if (!this.hasMouseUp) {
2243                 Env.addEvent(this.document, 'mouseup', this.mouseUpListener, this);
2244                 this.hasMouseUp = true;
2245             } else {
2246                 // In case this.hasMouseUp==true, it may be that there was a
2247                 // mousedown event before which was not followed by an mouseup event.
2248                 // This seems to happen with interactive whiteboard pens sometimes.
2249                 return;
2250             }
2251 
2252             pos = this.getMousePosition(evt);
2253             elements = this.initMoveObject(pos[0], pos[1], evt, 'mouse');
2254 
2255             // if no draggable object can be found, get out here immediately
2256             if (elements.length === 0) {
2257                 this.mode = this.BOARD_MODE_NONE;
2258                 result = true;
2259             } else {
2260                 this.mouse = {
2261                     obj: null,
2262                     targets: [{
2263                         X: pos[0],
2264                         Y: pos[1],
2265                         Xprev: NaN,
2266                         Yprev: NaN
2267                     }]
2268                 };
2269                 this.mouse.obj = elements[elements.length - 1];
2270 
2271                 this.dehighlightAll();
2272                 this.mouse.obj.highlight(true);
2273 
2274                 this.mouse.targets[0].Xstart = [];
2275                 this.mouse.targets[0].Ystart = [];
2276                 this.mouse.targets[0].Zstart = [];
2277 
2278                 this.saveStartPos(this.mouse.obj, this.mouse.targets[0]);
2279 
2280                 // prevent accidental text selection
2281                 // this could get us new trouble: input fields, links and drop down boxes placed as text
2282                 // on the board don't work anymore.
2283                 if (evt && evt.preventDefault) {
2284                     evt.preventDefault();
2285                 } else if (window.event) {
2286                     window.event.returnValue = false;
2287                 }
2288             }
2289 
2290             if (this.mode === this.BOARD_MODE_NONE) {
2291                 result = this.mouseOriginMoveStart(evt);
2292             }
2293 
2294             this.triggerEventHandlers(['mousedown', 'down'], [evt]);
2295 
2296             return result;
2297         },
2298 
2299         /**
2300          * This method is called by the browser when the mouse button is released.
2301          * @param {Event} evt
2302          */
2303         mouseUpListener: function (evt) {
2304             var i;
2305 
2306             this.triggerEventHandlers(['mouseup', 'up'], [evt]);
2307 
2308             // redraw with high precision
2309             this.updateQuality = this.BOARD_QUALITY_HIGH;
2310 
2311             if (this.mouse && this.mouse.obj) {
2312                 // The parameter is needed for lines with snapToGrid enabled
2313                 this.mouse.obj.snapToGrid(this.mouse.targets[0]);
2314                 this.mouse.obj.snapToPoints();
2315             }
2316 
2317             this.originMoveEnd();
2318             this.dehighlightAll();
2319             this.update();
2320 
2321             for (i = 0; i < this.downObjects.length; i++) {
2322                 this.downObjects[i].triggerEventHandlers(['mouseup', 'up'], [evt]);
2323             }
2324 
2325             this.downObjects.length = 0;
2326 
2327             if (this.hasMouseUp) {
2328                 Env.removeEvent(this.document, 'mouseup', this.mouseUpListener, this);
2329                 this.hasMouseUp = false;
2330             }
2331 
2332             // release dragged mouse object
2333             this.mouse = null;
2334         },
2335 
2336         /**
2337          * This method is called by the browser when the mouse is moved.
2338          * @param {Event} evt The browsers event object.
2339          */
2340         mouseMoveListener: function (evt) {
2341             var pos;
2342 
2343             pos = this.getMousePosition(evt);
2344 
2345             this.updateQuality = this.BOARD_QUALITY_LOW;
2346 
2347             if (this.mode !== this.BOARD_MODE_DRAG) {
2348                 this.dehighlightAll();
2349                 this.renderer.hide(this.infobox);
2350             }
2351 
2352             // we have to check for three cases:
2353             //   * user moves origin
2354             //   * user drags an object
2355             //   * user just moves the mouse, here highlight all elements at
2356             //     the current mouse position
2357 
2358             if (!this.mouseOriginMove(evt)) {
2359                 if (this.mode === this.BOARD_MODE_DRAG) {
2360                     this.moveObject(pos[0], pos[1], this.mouse, evt, 'mouse');
2361                 } else { // BOARD_MODE_NONE
2362                     this.highlightElements(pos[0], pos[1], evt, -1);
2363                 }
2364             }
2365 
2366             this.updateQuality = this.BOARD_QUALITY_HIGH;
2367 
2368             this.triggerEventHandlers(['mousemove', 'move'], [evt, this.mode]);
2369         },
2370 
2371         /**
2372          * Handler for mouse wheel events. Used to zoom in and out of the board.
2373          * @param {Event} evt
2374          * @returns {Boolean}
2375          */
2376         mouseWheelListener: function (evt) {
2377             if (!this.attr.zoom.wheel || (this.attr.zoom.needshift && !evt.shiftKey)) {
2378                 return true;
2379             }
2380 
2381             evt = evt || window.event;
2382             var wd = evt.detail ? -evt.detail : evt.wheelDelta / 40,
2383                 pos = new Coords(Const.COORDS_BY_SCREEN, this.getMousePosition(evt), this);
2384 
2385             if (wd > 0) {
2386                 this.zoomIn(pos.usrCoords[1], pos.usrCoords[2]);
2387             } else {
2388                 this.zoomOut(pos.usrCoords[1], pos.usrCoords[2]);
2389             }
2390 
2391             evt.preventDefault();
2392             return false;
2393         },
2394 
2395         /**********************************************************
2396          *
2397          * End of Event Handlers
2398          *
2399          **********************************************************/
2400 
2401         /**
2402          * Updates and displays a little info box to show coordinates of current selected points.
2403          * @param {JXG.GeometryElement} el A GeometryElement
2404          * @returns {JXG.Board} Reference to the board
2405          */
2406         updateInfobox: function (el) {
2407             var x, y, xc, yc;
2408 
2409             if (!el.visProp.showinfobox) {
2410                 return this;
2411             }
2412             if (Type.isPoint(el)) {
2413                 xc = el.coords.usrCoords[1];
2414                 yc = el.coords.usrCoords[2];
2415 
2416                 this.infobox.setCoords(xc + this.infobox.distanceX / this.unitX, yc + this.infobox.distanceY / this.unitY);
2417 
2418                 if (typeof el.infoboxText !== 'string') {
2419                     if (el.visProp.infoboxdigits === 'auto') {
2420                         x = Type.autoDigits(xc);
2421                         y = Type.autoDigits(yc);
2422                     } else if (Type.isNumber(el.visProp.infoboxdigits)) {
2423                         x = xc.toFixed(el.visProp.infoboxdigits);
2424                         y = yc.toFixed(el.visProp.infoboxdigits);
2425                     } else {
2426                         x = xc;
2427                         y = yc;
2428                     }
2429 
2430                     this.highlightInfobox(x, y, el);
2431                 } else {
2432                     this.highlightCustomInfobox(el.infoboxText, el);
2433                 }
2434 
2435                 this.renderer.show(this.infobox);
2436             }
2437             return this;
2438         },
2439 
2440         /**
2441          * Changes the text of the info box to what is provided via text.
2442          * @param {String} text
2443          * @param {JXG.GeometryElement} [el]
2444          * @returns {JXG.Board} Reference to the board.
2445          */
2446         highlightCustomInfobox: function (text, el) {
2447             this.infobox.setText(text);
2448             return this;
2449         },
2450 
2451         /**
2452          * Changes the text of the info box to show the given coordinates.
2453          * @param {Number} x
2454          * @param {Number} y
2455          * @param {JXG.GeometryElement} [el] The element the mouse is pointing at
2456          * @returns {JXG.Board} Reference to the board.
2457          */
2458         highlightInfobox: function (x, y, el) {
2459             this.highlightCustomInfobox('(' + x + ', ' + y + ')', el);
2460             return this;
2461         },
2462 
2463         /**
2464          * Remove highlighting of all elements.
2465          * @returns {JXG.Board} Reference to the board.
2466          */
2467         dehighlightAll: function () {
2468             var el, pEl, needsDehighlight = false;
2469 
2470             for (el in this.highlightedObjects) {
2471                 if (this.highlightedObjects.hasOwnProperty(el)) {
2472                     pEl = this.highlightedObjects[el];
2473 
2474                     if (this.hasMouseHandlers || this.hasPointerHandlers) {
2475                         pEl.noHighlight();
2476                     }
2477 
2478                     needsDehighlight = true;
2479 
2480                     // In highlightedObjects should only be objects which fulfill all these conditions
2481                     // And in case of complex elements, like a turtle based fractal, it should be faster to
2482                     // just de-highlight the element instead of checking hasPoint...
2483                     // if ((!Type.exists(pEl.hasPoint)) || !pEl.hasPoint(x, y) || !pEl.visProp.visible)
2484                 }
2485             }
2486 
2487             this.highlightedObjects = {};
2488 
2489             // We do not need to redraw during dehighlighting in CanvasRenderer
2490             // because we are redrawing anyhow
2491             //  -- We do need to redraw during dehighlighting. Otherwise objects won't be dehighlighted until
2492             // another object is highlighted.
2493             if (this.renderer.type === 'canvas' && needsDehighlight) {
2494                 this.prepareUpdate();
2495                 this.renderer.suspendRedraw(this);
2496                 this.updateRenderer();
2497                 this.renderer.unsuspendRedraw();
2498             }
2499 
2500             return this;
2501         },
2502 
2503         /**
2504          * Returns the input parameters in an array. This method looks pointless and it really is, but it had a purpose
2505          * once.
2506          * @param {Number} x X coordinate in screen coordinates
2507          * @param {Number} y Y coordinate in screen coordinates
2508          * @returns {Array} Coordinates of the mouse in screen coordinates.
2509          */
2510         getScrCoordsOfMouse: function (x, y) {
2511             return [x, y];
2512         },
2513 
2514         /**
2515          * This method calculates the user coords of the current mouse coordinates.
2516          * @param {Event} evt Event object containing the mouse coordinates.
2517          * @returns {Array} Coordinates of the mouse in screen coordinates.
2518          */
2519         getUsrCoordsOfMouse: function (evt) {
2520             var cPos = this.getCoordsTopLeftCorner(),
2521                 absPos = Env.getPosition(evt, null, this.document),
2522                 x = absPos[0] - cPos[0],
2523                 y = absPos[1] - cPos[1],
2524                 newCoords = new Coords(Const.COORDS_BY_SCREEN, [x, y], this);
2525 
2526             return newCoords.usrCoords.slice(1);
2527         },
2528 
2529         /**
2530          * Collects all elements under current mouse position plus current user coordinates of mouse cursor.
2531          * @param {Event} evt Event object containing the mouse coordinates.
2532          * @returns {Array} Array of elements at the current mouse position plus current user coordinates of mouse.
2533          */
2534         getAllUnderMouse: function (evt) {
2535             var elList = this.getAllObjectsUnderMouse(evt);
2536             elList.push(this.getUsrCoordsOfMouse(evt));
2537 
2538             return elList;
2539         },
2540 
2541         /**
2542          * Collects all elements under current mouse position.
2543          * @param {Event} evt Event object containing the mouse coordinates.
2544          * @returns {Array} Array of elements at the current mouse position.
2545          */
2546         getAllObjectsUnderMouse: function (evt) {
2547             var cPos = this.getCoordsTopLeftCorner(),
2548                 absPos = Env.getPosition(evt, null, this.document),
2549                 dx = absPos[0] - cPos[0],
2550                 dy = absPos[1] - cPos[1],
2551                 elList = [],
2552                 el,
2553                 pEl,
2554                 len = this.objectsList.length;
2555 
2556             for (el = 0; el < len; el++) {
2557                 pEl = this.objectsList[el];
2558                 if (pEl.visProp.visible && pEl.hasPoint && pEl.hasPoint(dx, dy)) {
2559                     elList[elList.length] = pEl;
2560                 }
2561             }
2562 
2563             return elList;
2564         },
2565 
2566         /**
2567          * Update the coords object of all elements which possess this
2568          * property. This is necessary after changing the viewport.
2569          * @returns {JXG.Board} Reference to this board.
2570          **/
2571         updateCoords: function () {
2572             var el, ob, len = this.objectsList.length;
2573 
2574             for (ob = 0; ob < len; ob++) {
2575                 el = this.objectsList[ob];
2576 
2577                 if (Type.exists(el.coords)) {
2578                     if (el.visProp.frozen) {
2579                         el.coords.screen2usr();
2580                     } else {
2581                         el.coords.usr2screen();
2582                     }
2583                 }
2584             }
2585             return this;
2586         },
2587 
2588         /**
2589          * Moves the origin and initializes an update of all elements.
2590          * @param {Number} x
2591          * @param {Number} y
2592          * @param {Boolean} [diff=false]
2593          * @returns {JXG.Board} Reference to this board.
2594          */
2595         moveOrigin: function (x, y, diff) {
2596             if (Type.exists(x) && Type.exists(y)) {
2597                 this.origin.scrCoords[1] = x;
2598                 this.origin.scrCoords[2] = y;
2599 
2600                 if (diff) {
2601                     this.origin.scrCoords[1] -= this.drag_dx;
2602                     this.origin.scrCoords[2] -= this.drag_dy;
2603                 }
2604             }
2605 
2606             this.updateCoords().clearTraces().fullUpdate();
2607 
2608             this.triggerEventHandlers(['boundingbox']);
2609 
2610             return this;
2611         },
2612 
2613         /**
2614          * Add conditional updates to the elements.
2615          * @param {String} str String containing coniditional update in geonext syntax
2616          */
2617         addConditions: function (str) {
2618             var term, m, left, right, name, el, property,
2619                 functions = [],
2620                 plaintext = 'var el, x, y, c, rgbo;\n',
2621                 i = str.indexOf('<data>'),
2622                 j = str.indexOf('<' + '/data>'),
2623 
2624                 xyFun = function (board, el, f, what) {
2625                     return function () {
2626                         var e, t;
2627 
2628                         e = board.select(el.id);
2629                         t = e.coords.usrCoords[what];
2630 
2631                         if (what === 2) {
2632                             e.setPositionDirectly(Const.COORDS_BY_USER, [f(), t]);
2633                         } else {
2634                             e.setPositionDirectly(Const.COORDS_BY_USER, [t, f()]);
2635                         }
2636                         e.prepareUpdate().update();
2637                     };
2638                 },
2639 
2640                 visFun = function (board, el, f) {
2641                     return function () {
2642                         var e, v;
2643 
2644                         e = board.select(el.id);
2645                         v = f();
2646 
2647                         e.setAttribute({visible: v});
2648                     };
2649                 },
2650 
2651                 colFun = function (board, el, f, what) {
2652                     return function () {
2653                         var e, v;
2654 
2655                         e = board.select(el.id);
2656                         v = f();
2657 
2658                         if (what === 'strokewidth') {
2659                             e.visProp.strokewidth = v;
2660                         } else {
2661                             v = Color.rgba2rgbo(v);
2662                             e.visProp[what + 'color'] = v[0];
2663                             e.visProp[what + 'opacity'] = v[1];
2664                         }
2665                     };
2666                 },
2667 
2668                 posFun = function (board, el, f) {
2669                     return function () {
2670                         var e = board.select(el.id);
2671 
2672                         e.position = f();
2673                     };
2674                 },
2675 
2676                 styleFun = function (board, el, f) {
2677                     return function () {
2678                         var e = board.select(el.id);
2679 
2680                         e.setStyle(f());
2681                     };
2682                 };
2683 
2684             if (i < 0) {
2685                 return;
2686             }
2687 
2688             while (i >= 0) {
2689                 term = str.slice(i + 6, j);   // throw away <data>
2690                 m = term.indexOf('=');
2691                 left = term.slice(0, m);
2692                 right = term.slice(m + 1);
2693                 m = left.indexOf('.');     // Dies erzeugt Probleme bei Variablennamen der Form " Steuern akt."
2694                 name = left.slice(0, m);    //.replace(/\s+$/,''); // do NOT cut out name (with whitespace)
2695                 el = this.elementsByName[Type.unescapeHTML(name)];
2696 
2697                 property = left.slice(m + 1).replace(/\s+/g, '').toLowerCase(); // remove whitespace in property
2698                 right = Type.createFunction(right, this, '', true);
2699 
2700                 // Debug
2701                 if (!Type.exists(this.elementsByName[name])) {
2702                     JXG.debug("debug conditions: |" + name + "| undefined");
2703                 } else {
2704                     plaintext += "el = this.objects[\"" + el.id + "\"];\n";
2705 
2706                     switch (property) {
2707                     case 'x':
2708                         functions.push(xyFun(this, el, right, 2));
2709                         break;
2710                     case 'y':
2711                         functions.push(xyFun(this, el, right, 1));
2712                         break;
2713                     case 'visible':
2714                         functions.push(visFun(this, el, right));
2715                         break;
2716                     case 'position':
2717                         functions.push(posFun(this, el, right));
2718                         break;
2719                     case 'stroke':
2720                         functions.push(colFun(this, el, right, 'stroke'));
2721                         break;
2722                     case 'style':
2723                         functions.push(styleFun(this, el, right));
2724                         break;
2725                     case 'strokewidth':
2726                         functions.push(colFun(this, el, right, 'strokewidth'));
2727                         break;
2728                     case 'fill':
2729                         functions.push(colFun(this, el, right, 'fill'));
2730                         break;
2731                     case 'label':
2732                         break;
2733                     default:
2734                         JXG.debug("property '" + property + "' in conditions not yet implemented:" + right);
2735                         break;
2736                     }
2737                 }
2738                 str = str.slice(j + 7); // cut off "</data>"
2739                 i = str.indexOf('<data>');
2740                 j = str.indexOf('<' + '/data>');
2741             }
2742 
2743             this.updateConditions = function () {
2744                 var i;
2745 
2746                 for (i = 0; i < functions.length; i++) {
2747                     functions[i]();
2748                 }
2749 
2750                 this.prepareUpdate().updateElements();
2751                 return true;
2752             };
2753             this.updateConditions();
2754         },
2755 
2756         /**
2757          * Computes the commands in the conditions-section of the gxt file.
2758          * It is evaluated after an update, before the unsuspendRedraw.
2759          * The function is generated in
2760          * @see JXG.Board#addConditions
2761          * @private
2762          */
2763         updateConditions: function () {
2764             return false;
2765         },
2766 
2767         /**
2768          * Calculates adequate snap sizes.
2769          * @returns {JXG.Board} Reference to the board.
2770          */
2771         calculateSnapSizes: function () {
2772             var p1 = new Coords(Const.COORDS_BY_USER, [0, 0], this),
2773                 p2 = new Coords(Const.COORDS_BY_USER, [this.options.grid.gridX, this.options.grid.gridY], this),
2774                 x = p1.scrCoords[1] - p2.scrCoords[1],
2775                 y = p1.scrCoords[2] - p2.scrCoords[2];
2776 
2777             this.options.grid.snapSizeX = this.options.grid.gridX;
2778             while (Math.abs(x) > 25) {
2779                 this.options.grid.snapSizeX *= 2;
2780                 x /= 2;
2781             }
2782 
2783             this.options.grid.snapSizeY = this.options.grid.gridY;
2784             while (Math.abs(y) > 25) {
2785                 this.options.grid.snapSizeY *= 2;
2786                 y /= 2;
2787             }
2788 
2789             return this;
2790         },
2791 
2792         /**
2793          * Apply update on all objects with the new zoom-factors. Clears all traces.
2794          * @returns {JXG.Board} Reference to the board.
2795          */
2796         applyZoom: function () {
2797             this.updateCoords().calculateSnapSizes().clearTraces().fullUpdate();
2798 
2799             return this;
2800         },
2801 
2802         /**
2803          * Zooms into the board by the factors board.attr.zoom.factorX and board.attr.zoom.factorY and applies the zoom.
2804          * @param {Number} [x]
2805          * @param {Number} [y]
2806          * @returns {JXG.Board} Reference to the board
2807          */
2808         zoomIn: function (x, y) {
2809             var bb = this.getBoundingBox(),
2810                 zX = this.attr.zoom.factorx,
2811                 zY = this.attr.zoom.factory,
2812                 dX = (bb[2] - bb[0]) * (1.0 - 1.0 / zX),
2813                 dY = (bb[1] - bb[3]) * (1.0 - 1.0 / zY),
2814                 lr = 0.5,
2815                 tr = 0.5;
2816 
2817             if (typeof x === 'number' && typeof y === 'number') {
2818                 lr = (x - bb[0]) / (bb[2] - bb[0]);
2819                 tr = (bb[1] - y) / (bb[1] - bb[3]);
2820             }
2821 
2822             this.setBoundingBox([bb[0] + dX * lr, bb[1] - dY * tr, bb[2] - dX * (1 - lr), bb[3] + dY * (1 - tr)], false);
2823             this.zoomX *= zX;
2824             this.zoomY *= zY;
2825             this.applyZoom();
2826 
2827             return false;
2828         },
2829 
2830         /**
2831          * Zooms out of the board by the factors board.attr.zoom.factorX and board.attr.zoom.factorY and applies the zoom.
2832          * @param {Number} [x]
2833          * @param {Number} [y]
2834          * @returns {JXG.Board} Reference to the board
2835          */
2836         zoomOut: function (x, y) {
2837             var bb = this.getBoundingBox(),
2838                 zX = this.attr.zoom.factorx,
2839                 zY = this.attr.zoom.factory,
2840                 dX = (bb[2] - bb[0]) * (1.0 - zX),
2841                 dY = (bb[1] - bb[3]) * (1.0 - zY),
2842                 lr = 0.5,
2843                 tr = 0.5;
2844 
2845             if (this.zoomX < this.attr.zoom.eps || this.zoomY < this.attr.zoom.eps) {
2846                 return false;
2847             }
2848 
2849             if (typeof x === 'number' && typeof y === 'number') {
2850                 lr = (x - bb[0]) / (bb[2] - bb[0]);
2851                 tr = (bb[1] - y) / (bb[1] - bb[3]);
2852             }
2853 
2854             this.setBoundingBox([bb[0] + dX * lr, bb[1] - dY * tr, bb[2] - dX * (1 - lr), bb[3] + dY * (1 - tr)], false);
2855             this.zoomX /= zX;
2856             this.zoomY /= zY;
2857 
2858             this.applyZoom();
2859             return false;
2860         },
2861 
2862         /**
2863          * Resets zoom factor to 100%.
2864          * @returns {JXG.Board} Reference to the board
2865          */
2866         zoom100: function () {
2867             var bb = this.getBoundingBox(),
2868                 dX = (bb[2] - bb[0]) * (1.0 - this.zoomX) * 0.5,
2869                 dY = (bb[1] - bb[3]) * (1.0 - this.zoomY) * 0.5;
2870 
2871             this.setBoundingBox([bb[0] + dX, bb[1] - dY, bb[2] - dX, bb[3] + dY], false);
2872             this.zoomX = 1.0;
2873             this.zoomY = 1.0;
2874             this.applyZoom();
2875             return false;
2876         },
2877 
2878         /**
2879          * Zooms the board so every visible point is shown. Keeps aspect ratio.
2880          * @returns {JXG.Board} Reference to the board
2881          */
2882         zoomAllPoints: function () {
2883             var el, border, borderX, borderY, pEl,
2884                 minX = 0,
2885                 maxX = 0,
2886                 minY = 0,
2887                 maxY = 0,
2888                 len = this.objectsList.length;
2889 
2890             for (el = 0; el < len; el++) {
2891                 pEl = this.objectsList[el];
2892 
2893                 if (Type.isPoint(pEl) && pEl.visProp.visible) {
2894                     if (pEl.coords.usrCoords[1] < minX) {
2895                         minX = pEl.coords.usrCoords[1];
2896                     } else if (pEl.coords.usrCoords[1] > maxX) {
2897                         maxX = pEl.coords.usrCoords[1];
2898                     }
2899                     if (pEl.coords.usrCoords[2] > maxY) {
2900                         maxY = pEl.coords.usrCoords[2];
2901                     } else if (pEl.coords.usrCoords[2] < minY) {
2902                         minY = pEl.coords.usrCoords[2];
2903                     }
2904                 }
2905             }
2906 
2907             border = 50;
2908             borderX = border / this.unitX;
2909             borderY = border / this.unitY;
2910 
2911             this.zoomX = 1.0;
2912             this.zoomY = 1.0;
2913 
2914             this.setBoundingBox([minX - borderX, maxY + borderY, maxX + borderX, minY - borderY], true);
2915 
2916             this.applyZoom();
2917 
2918             return this;
2919         },
2920 
2921         /**
2922          * Reset the bounding box and the zoom level to 100% such that a given set of elements is within the board's viewport.
2923          * @param {Array} elements A set of elements given by id, reference, or name.
2924          * @returns {JXG.Board} Reference to the board.
2925          */
2926         zoomElements: function (elements) {
2927             var i, j, e, box,
2928                 newBBox = [0, 0, 0, 0],
2929                 dir = [1, -1, -1, 1];
2930 
2931             if (!Type.isArray(elements) || elements.length === 0) {
2932                 return this;
2933             }
2934 
2935             for (i = 0; i < elements.length; i++) {
2936                 e = this.select(elements[i]);
2937 
2938                 box = e.bounds();
2939                 if (Type.isArray(box)) {
2940                     if (Type.isArray(newBBox)) {
2941                         for (j = 0; j < 4; j++) {
2942                             if (dir[j] * box[j] < dir[j] * newBBox[j]) {
2943                                 newBBox[j] = box[j];
2944                             }
2945                         }
2946                     } else {
2947                         newBBox = box;
2948                     }
2949                 }
2950             }
2951 
2952             if (Type.isArray(newBBox)) {
2953                 for (j = 0; j < 4; j++) {
2954                     newBBox[j] -= dir[j];
2955                 }
2956 
2957                 this.zoomX = 1.0;
2958                 this.zoomY = 1.0;
2959                 this.setBoundingBox(newBBox, true);
2960             }
2961 
2962             return this;
2963         },
2964 
2965         /**
2966          * Sets the zoom level to <tt>fX</tt> resp <tt>fY</tt>.
2967          * @param {Number} fX
2968          * @param {Number} fY
2969          * @returns {JXG.Board}
2970          */
2971         setZoom: function (fX, fY) {
2972             var oX = this.attr.zoom.factorx,
2973                 oY = this.attr.zoom.factory;
2974 
2975             this.attr.zoom.factorx = fX / this.zoomX;
2976             this.attr.zoom.factory = fY / this.zoomY;
2977 
2978             this.zoomIn();
2979 
2980             this.attr.zoom.factorx = oX;
2981             this.attr.zoom.factory = oY;
2982 
2983             return this;
2984         },
2985 
2986         /**
2987          * Removes object from board and renderer.
2988          * @param {JXG.GeometryElement} object The object to remove.
2989          * @returns {JXG.Board} Reference to the board
2990          */
2991         removeObject: function (object) {
2992             var el, i;
2993 
2994             if (Type.isArray(object)) {
2995                 for (i = 0; i < object.length; i++) {
2996                     this.removeObject(object[i]);
2997                 }
2998 
2999                 return this;
3000             }
3001 
3002             object = this.select(object);
3003 
3004             // If the object which is about to be removed unknown or a string, do nothing.
3005             // it is a string if a string was given and could not be resolved to an element.
3006             if (!Type.exists(object) || Type.isString(object)) {
3007                 return this;
3008             }
3009 
3010             try {
3011                 // remove all children.
3012                 for (el in object.childElements) {
3013                     if (object.childElements.hasOwnProperty(el)) {
3014                         object.childElements[el].board.removeObject(object.childElements[el]);
3015                     }
3016                 }
3017 
3018                 for (el in this.objects) {
3019                     if (this.objects.hasOwnProperty(el) && Type.exists(this.objects[el].childElements)) {
3020                         delete this.objects[el].childElements[object.id];
3021                         delete this.objects[el].descendants[object.id];
3022                     }
3023                 }
3024 
3025                 // remove the object itself from our control structures
3026                 if (object._pos > -1) {
3027                     this.objectsList.splice(object._pos, 1);
3028                     for (el = object._pos; el < this.objectsList.length; el++) {
3029                         this.objectsList[el]._pos--;
3030                     }
3031                 } else {
3032                     JXG.debug('Board.removeObject: object ' + object.id + ' not found in list.');
3033                 }
3034                 delete this.objects[object.id];
3035                 delete this.elementsByName[object.name];
3036 
3037                 if (object.visProp && object.visProp.trace) {
3038                     object.clearTrace();
3039                 }
3040 
3041                 // the object deletion itself is handled by the object.
3042                 if (Type.exists(object.remove)) {
3043                     object.remove();
3044                 }
3045             } catch (e) {
3046                 JXG.debug(object.id + ': Could not be removed: ' + e);
3047             }
3048 
3049             this.update();
3050 
3051             return this;
3052         },
3053 
3054 
3055         /**
3056          * Removes the ancestors of an object an the object itself from board and renderer.
3057          * @param {JXG.GeometryElement} object The object to remove.
3058          * @returns {JXG.Board} Reference to the board
3059          */
3060         removeAncestors: function (object) {
3061             var anc;
3062 
3063             for (anc in object.ancestors) {
3064                 if (object.ancestors.hasOwnProperty(anc)) {
3065                     this.removeAncestors(object.ancestors[anc]);
3066                 }
3067             }
3068 
3069             this.removeObject(object);
3070 
3071             return this;
3072         },
3073 
3074         /**
3075          * Initialize some objects which are contained in every GEONExT construction by default,
3076          * but are not contained in the gxt files.
3077          * @returns {JXG.Board} Reference to the board
3078          */
3079         initGeonextBoard: function () {
3080             var p1, p2, p3;
3081 
3082             p1 = this.create('point', [0, 0], {
3083                 id: this.id + 'g00e0',
3084                 name: 'Ursprung',
3085                 withLabel: false,
3086                 visible: false,
3087                 fixed: true
3088             });
3089 
3090             p2 = this.create('point', [1, 0], {
3091                 id: this.id + 'gX0e0',
3092                 name: 'Punkt_1_0',
3093                 withLabel: false,
3094                 visible: false,
3095                 fixed: true
3096             });
3097 
3098             p3 = this.create('point', [0, 1], {
3099                 id: this.id + 'gY0e0',
3100                 name: 'Punkt_0_1',
3101                 withLabel: false,
3102                 visible: false,
3103                 fixed: true
3104             });
3105 
3106             this.create('line', [p1, p2], {
3107                 id: this.id + 'gXLe0',
3108                 name: 'X-Achse',
3109                 withLabel: false,
3110                 visible: false
3111             });
3112 
3113             this.create('line', [p1, p3], {
3114                 id: this.id + 'gYLe0',
3115                 name: 'Y-Achse',
3116                 withLabel: false,
3117                 visible: false
3118             });
3119 
3120             return this;
3121         },
3122 
3123         /**
3124          * Initialize the info box object which is used to display
3125          * the coordinates of points near the mouse pointer,
3126          * @returns {JXG.Board} Reference to the board
3127          */
3128         initInfobox: function () {
3129             var  attr = Type.copyAttributes({}, this.options, 'infobox');
3130 
3131             attr.id = this.id + '_infobox';
3132 
3133             this.infobox = this.create('text', [0, 0, '0,0'], attr);
3134 
3135             this.infobox.distanceX = -20;
3136             this.infobox.distanceY = 25;
3137             // this.infobox.needsUpdateSize = false;  // That is not true, but it speeds drawing up.
3138 
3139             this.infobox.dump = false;
3140 
3141             this.renderer.hide(this.infobox);
3142             return this;
3143         },
3144 
3145         /**
3146          * Change the height and width of the board's container.
3147          * After doing so, {@link JXG.JSXGraph#setBoundingBox} is called using
3148          * the actual size of the bounding box and the actual value of keepaspectratio.
3149          * If setBoundingbox() should not be called automatically, 
3150          * call resizeContainer with dontSetBoundingBox == true.
3151          * @param {Number} canvasWidth New width of the container.
3152          * @param {Number} canvasHeight New height of the container.
3153          * @param {Boolean} [dontset=false] If true do not set the height of the DOM element.
3154          * @param {Boolean} [dontSetBoundingBox=false] If true do not call setBoundingBox().
3155          * @returns {JXG.Board} Reference to the board
3156          */
3157         resizeContainer: function (canvasWidth, canvasHeight, dontset, dontSetBoundingBox) {
3158             var box;
3159 
3160             if (!dontSetBoundingBox) {
3161                 box = this.getBoundingBox();
3162             }
3163             this.canvasWidth = parseInt(canvasWidth, 10);
3164             this.canvasHeight = parseInt(canvasHeight, 10);
3165 
3166             if (!dontset) {
3167                 this.containerObj.style.width = (this.canvasWidth) + 'px';
3168                 this.containerObj.style.height = (this.canvasHeight) + 'px';
3169             }
3170 
3171             this.renderer.resize(this.canvasWidth, this.canvasHeight);
3172 
3173             if (!dontSetBoundingBox) {
3174                 this.setBoundingBox(box, this.keepaspectratio);
3175             }
3176 
3177             return this;
3178         },
3179 
3180         /**
3181          * Lists the dependencies graph in a new HTML-window.
3182          * @returns {JXG.Board} Reference to the board
3183          */
3184         showDependencies: function () {
3185             var el, t, c, f, i;
3186 
3187             t = '<p>\n';
3188             for (el in this.objects) {
3189                 if (this.objects.hasOwnProperty(el)) {
3190                     i = 0;
3191                     for (c in this.objects[el].childElements) {
3192                         if (this.objects[el].childElements.hasOwnProperty(c)) {
3193                             i += 1;
3194                         }
3195                     }
3196                     if (i >= 0) {
3197                         t += '<strong>' + this.objects[el].id + ':<' + '/strong> ';
3198                     }
3199 
3200                     for (c in this.objects[el].childElements) {
3201                         if (this.objects[el].childElements.hasOwnProperty(c)) {
3202                             t += this.objects[el].childElements[c].id + '(' + this.objects[el].childElements[c].name + ')' + ', ';
3203                         }
3204                     }
3205                     t += '<p>\n';
3206                 }
3207             }
3208             t += '<' + '/p>\n';
3209             f = window.open();
3210             f.document.open();
3211             f.document.write(t);
3212             f.document.close();
3213             return this;
3214         },
3215 
3216         /**
3217          * Lists the XML code of the construction in a new HTML-window.
3218          * @returns {JXG.Board} Reference to the board
3219          */
3220         showXML: function () {
3221             var f = window.open('');
3222             f.document.open();
3223             f.document.write('<pre>' + Type.escapeHTML(this.xmlString) + '<' + '/pre>');
3224             f.document.close();
3225             return this;
3226         },
3227 
3228         /**
3229          * Sets for all objects the needsUpdate flag to "true".
3230          * @returns {JXG.Board} Reference to the board
3231          */
3232         prepareUpdate: function () {
3233             var el, pEl, len = this.objectsList.length;
3234 
3235             /*
3236             if (this.attr.updatetype === 'hierarchical') {
3237                 return this;
3238             }
3239             */
3240 
3241             for (el = 0; el < len; el++) {
3242                 pEl = this.objectsList[el];
3243                 pEl.needsUpdate = pEl.needsRegularUpdate || this.needsFullUpdate;
3244             }
3245 
3246             for (el in this.groups) {
3247                 if (this.groups.hasOwnProperty(el)) {
3248                     pEl = this.groups[el];
3249                     pEl.needsUpdate = pEl.needsRegularUpdate || this.needsFullUpdate;
3250                 }
3251             }
3252 
3253             return this;
3254         },
3255 
3256         /**
3257          * Runs through all elements and calls their update() method.
3258          * @param {JXG.GeometryElement} drag Element that caused the update.
3259          * @returns {JXG.Board} Reference to the board
3260          */
3261         updateElements: function (drag) {
3262             var el, pEl;
3263             //var childId, i = 0;
3264 
3265             drag = this.select(drag);
3266 
3267             /*
3268             if (Type.exists(drag)) {
3269                 for (el = 0; el < this.objectsList.length; el++) {
3270                     pEl = this.objectsList[el];
3271                     if (pEl.id === drag.id) {
3272                         i = el;
3273                         break;
3274                     }
3275                 }
3276             }
3277             */
3278 
3279             for (el = 0; el < this.objectsList.length; el++) {
3280                 pEl = this.objectsList[el];
3281                 // For updates of an element we distinguish if the dragged element is updated or
3282                 // other elements are updated.
3283                 // The difference lies in the treatment of gliders.
3284                 pEl.update(!Type.exists(drag) || pEl.id !== drag.id);
3285 
3286                 /*
3287                 if (this.attr.updatetype === 'hierarchical') {
3288                     for (childId in pEl.childElements) {
3289                         pEl.childElements[childId].needsUpdate = pEl.childElements[childId].needsRegularUpdate;
3290                     }
3291                 }
3292                 */
3293             }
3294 
3295             // update groups last
3296             for (el in this.groups) {
3297                 if (this.groups.hasOwnProperty(el)) {
3298                     this.groups[el].update(drag);
3299                 }
3300             }
3301 
3302             return this;
3303         },
3304 
3305         /**
3306          * Runs through all elements and calls their update() method.
3307          * @returns {JXG.Board} Reference to the board
3308          */
3309         updateRenderer: function () {
3310             var el, pEl,
3311                 len = this.objectsList.length;
3312 
3313             /*
3314             objs = this.objectsList.slice(0);
3315             objs.sort(function(a, b) {
3316                 if (a.visProp.layer < b.visProp.layer) {
3317                     return -1;
3318                 } else if (a.visProp.layer === b.visProp.layer) {
3319                     return b.lastDragTime.getTime() - a.lastDragTime.getTime();
3320                 } else {
3321                     return 1;
3322                 }
3323             });
3324             */
3325 
3326             if (this.renderer.type === 'canvas') {
3327                 this.updateRendererCanvas();
3328             } else {
3329                 for (el = 0; el < len; el++) {
3330                     pEl = this.objectsList[el];
3331                     pEl.updateRenderer();
3332                 }
3333             }
3334             return this;
3335         },
3336 
3337         /**
3338          * Runs through all elements and calls their update() method.
3339          * This is a special version for the CanvasRenderer.
3340          * Here, we have to do our own layer handling.
3341          * @returns {JXG.Board} Reference to the board
3342          */
3343         updateRendererCanvas: function () {
3344             var el, pEl, i, mini, la,
3345                 olen = this.objectsList.length,
3346                 layers = this.options.layer,
3347                 len = this.options.layer.numlayers,
3348                 last = Number.NEGATIVE_INFINITY;
3349 
3350             for (i = 0; i < len; i++) {
3351                 mini = Number.POSITIVE_INFINITY;
3352 
3353                 for (la in layers) {
3354                     if (layers.hasOwnProperty(la)) {
3355                         if (layers[la] > last && layers[la] < mini) {
3356                             mini = layers[la];
3357                         }
3358                     }
3359                 }
3360 
3361                 last = mini;
3362 
3363                 for (el = 0; el < olen; el++) {
3364                     pEl = this.objectsList[el];
3365 
3366                     if (pEl.visProp.layer === mini) {
3367                         pEl.prepareUpdate().updateRenderer();
3368                     }
3369                 }
3370             }
3371             return this;
3372         },
3373 
3374         /**
3375          * Please use {@link JXG.Board#on} instead.
3376          * @param {Function} hook A function to be called by the board after an update occured.
3377          * @param {String} [m='update'] When the hook is to be called. Possible values are <i>mouseup</i>, <i>mousedown</i> and <i>update</i>.
3378          * @param {Object} [context=board] Determines the execution context the hook is called. This parameter is optional, default is the
3379          * board object the hook is attached to.
3380          * @returns {Number} Id of the hook, required to remove the hook from the board.
3381          * @deprecated
3382          */
3383         addHook: function (hook, m, context) {
3384             m = Type.def(m, 'update');
3385 
3386             context = Type.def(context, this);
3387 
3388             this.hooks.push([m, hook]);
3389             this.on(m, hook, context);
3390 
3391             return this.hooks.length - 1;
3392         },
3393 
3394         /**
3395          * Alias of {@link JXG.Board#on}.
3396          */
3397         addEvent: JXG.shortcut(JXG.Board.prototype, 'on'),
3398 
3399         /**
3400          * Please use {@link JXG.Board#off} instead.
3401          * @param {Number|function} id The number you got when you added the hook or a reference to the event handler.
3402          * @returns {JXG.Board} Reference to the board
3403          * @deprecated
3404          */
3405         removeHook: function (id) {
3406             if (this.hooks[id]) {
3407                 this.off(this.hooks[id][0], this.hooks[id][1]);
3408                 this.hooks[id] = null;
3409             }
3410 
3411             return this;
3412         },
3413 
3414         /**
3415          * Alias of {@link JXG.Board#off}.
3416          */
3417         removeEvent: JXG.shortcut(JXG.Board.prototype, 'off'),
3418 
3419         /**
3420          * Runs through all hooked functions and calls them.
3421          * @returns {JXG.Board} Reference to the board
3422          * @deprecated
3423          */
3424         updateHooks: function (m) {
3425             var arg = Array.prototype.slice.call(arguments, 0);
3426 
3427             arg[0] = Type.def(arg[0], 'update');
3428             this.triggerEventHandlers([arg[0]], arguments);
3429 
3430             return this;
3431         },
3432 
3433         /**
3434          * Adds a dependent board to this board.
3435          * @param {JXG.Board} board A reference to board which will be updated after an update of this board occured.
3436          * @returns {JXG.Board} Reference to the board
3437          */
3438         addChild: function (board) {
3439             if (Type.exists(board) && Type.exists(board.containerObj)) {
3440                 this.dependentBoards.push(board);
3441                 this.update();
3442             }
3443             return this;
3444         },
3445 
3446         /**
3447          * Deletes a board from the list of dependent boards.
3448          * @param {JXG.Board} board Reference to the board which will be removed.
3449          * @returns {JXG.Board} Reference to the board
3450          */
3451         removeChild: function (board) {
3452             var i;
3453 
3454             for (i = this.dependentBoards.length - 1; i >= 0; i--) {
3455                 if (this.dependentBoards[i] === board) {
3456                     this.dependentBoards.splice(i, 1);
3457                 }
3458             }
3459             return this;
3460         },
3461 
3462         /**
3463          * Runs through most elements and calls their update() method and update the conditions.
3464          * @param {JXG.GeometryElement} [drag] Element that caused the update.
3465          * @returns {JXG.Board} Reference to the board
3466          */
3467         update: function (drag) {
3468             var i, len, b, insert;
3469 
3470             if (this.inUpdate || this.isSuspendedUpdate) {
3471                 return this;
3472             }
3473             this.inUpdate = true;
3474 
3475             if (this.attr.minimizereflow === 'all' && this.containerObj && this.renderer.type !== 'vml') {
3476                 insert = this.renderer.removeToInsertLater(this.containerObj);
3477             }
3478 
3479             if (this.attr.minimizereflow === 'svg' && this.renderer.type === 'svg') {
3480                 insert = this.renderer.removeToInsertLater(this.renderer.svgRoot);
3481             }
3482 
3483             this.prepareUpdate().updateElements(drag).updateConditions();
3484 
3485             this.renderer.suspendRedraw(this);
3486             this.updateRenderer();
3487             this.renderer.unsuspendRedraw();
3488             this.triggerEventHandlers(['update'], []);
3489 
3490             if (insert) {
3491                 insert();
3492             }
3493 
3494             // To resolve dependencies between boards
3495             // for (var board in JXG.boards) {
3496             len = this.dependentBoards.length;
3497             for (i = 0; i < len; i++) {
3498                 b = this.dependentBoards[i];
3499                 if (Type.exists(b) && b !== this) {
3500                     b.updateQuality = this.updateQuality;
3501                     b.prepareUpdate().updateElements().updateConditions();
3502                     b.renderer.suspendRedraw();
3503                     b.updateRenderer();
3504                     b.renderer.unsuspendRedraw();
3505                     b.triggerEventHandlers(['update'], []);
3506                 }
3507 
3508             }
3509 
3510             this.inUpdate = false;
3511             return this;
3512         },
3513 
3514         /**
3515          * Runs through all elements and calls their update() method and update the conditions.
3516          * This is necessary after zooming and changing the bounding box.
3517          * @returns {JXG.Board} Reference to the board
3518          */
3519         fullUpdate: function () {
3520             this.needsFullUpdate = true;
3521             this.update();
3522             this.needsFullUpdate = false;
3523             return this;
3524         },
3525 
3526         /**
3527          * Adds a grid to the board according to the settings given in board.options.
3528          * @returns {JXG.Board} Reference to the board.
3529          */
3530         addGrid: function () {
3531             this.create('grid', []);
3532 
3533             return this;
3534         },
3535 
3536         /**
3537          * Removes all grids assigned to this board. Warning: This method also removes all objects depending on one or
3538          * more of the grids.
3539          * @returns {JXG.Board} Reference to the board object.
3540          */
3541         removeGrids: function () {
3542             var i;
3543 
3544             for (i = 0; i < this.grids.length; i++) {
3545                 this.removeObject(this.grids[i]);
3546             }
3547 
3548             this.grids.length = 0;
3549             this.update(); // required for canvas renderer
3550 
3551             return this;
3552         },
3553 
3554         /**
3555          * Creates a new geometric element of type elementType.
3556          * @param {String} elementType Type of the element to be constructed given as a string e.g. 'point' or 'circle'.
3557          * @param {Array} parents Array of parent elements needed to construct the element e.g. coordinates for a point or two
3558          * points to construct a line. This highly depends on the elementType that is constructed. See the corresponding JXG.create*
3559          * methods for a list of possible parameters.
3560          * @param {Object} [attributes] An object containing the attributes to be set. This also depends on the elementType.
3561          * Common attributes are name, visible, strokeColor.
3562          * @returns {Object} Reference to the created element. This is usually a GeometryElement, but can be an array containing
3563          * two or more elements.
3564          */
3565         create: function (elementType, parents, attributes) {
3566             var el, i;
3567 
3568             elementType = elementType.toLowerCase();
3569 
3570             if (!Type.exists(parents)) {
3571                 parents = [];
3572             }
3573 
3574             if (!Type.exists(attributes)) {
3575                 attributes = {};
3576             }
3577 
3578             for (i = 0; i < parents.length; i++) {
3579                 if (typeof parents[i] === 'string' && (elementType !== 'text' || i !== 2)) {
3580                     parents[i] = this.select(parents[i]);
3581                 }
3582             }
3583 
3584             if (typeof JXG.elements[elementType] === 'function') {
3585                 el = JXG.elements[elementType](this, parents, attributes);
3586             } else {
3587                 throw new Error("JSXGraph: create: Unknown element type given: " + elementType);
3588             }
3589 
3590             if (!Type.exists(el)) {
3591                 JXG.debug("JSXGraph: create: failure creating " + elementType);
3592                 return el;
3593             }
3594 
3595             if (el.prepareUpdate && el.update && el.updateRenderer) {
3596                 el.prepareUpdate().update().updateRenderer();
3597             }
3598             return el;
3599         },
3600 
3601         /**
3602          * Deprecated name for {@link JXG.Board#create}.
3603          * @deprecated
3604          */
3605         createElement: JXG.shortcut(JXG.Board.prototype, 'create'),
3606 
3607 
3608         /**
3609          * Delete the elements drawn as part of a trace of an element.
3610          * @returns {JXG.Board} Reference to the board
3611          */
3612         clearTraces: function () {
3613             var el;
3614 
3615             for (el = 0; el < this.objectsList.length; el++) {
3616                 this.objectsList[el].clearTrace();
3617             }
3618 
3619             this.numTraces = 0;
3620             return this;
3621         },
3622 
3623         /**
3624          * Stop updates of the board.
3625          * @returns {JXG.Board} Reference to the board
3626          */
3627         suspendUpdate: function () {
3628             if (!this.inUpdate) {
3629                 this.isSuspendedUpdate = true;
3630             }
3631             return this;
3632         },
3633 
3634         /**
3635          * Enable updates of the board.
3636          * @returns {JXG.Board} Reference to the board
3637          */
3638         unsuspendUpdate: function () {
3639             if (this.isSuspendedUpdate) {
3640                 this.isSuspendedUpdate = false;
3641                 this.update();
3642             }
3643             return this;
3644         },
3645 
3646         /**
3647          * Set the bounding box of the board.
3648          * @param {Array} bbox New bounding box [x1,y1,x2,y2]
3649          * @param {Boolean} [keepaspectratio=false] If set to true, the aspect ratio will be 1:1, but
3650          * the resulting viewport may be larger.
3651          * @returns {JXG.Board} Reference to the board
3652          */
3653         setBoundingBox: function (bbox, keepaspectratio) {
3654             var h, w,
3655                 dim = Env.getDimensions(this.container, this.document);
3656 
3657             if (!Type.isArray(bbox)) {
3658                 return this;
3659             }
3660 
3661             this.plainBB = bbox;
3662 
3663             this.canvasWidth = parseInt(dim.width, 10);
3664             this.canvasHeight = parseInt(dim.height, 10);
3665             w = this.canvasWidth;
3666             h = this.canvasHeight;
3667 
3668             if (keepaspectratio) {
3669                 this.unitX = w / (bbox[2] - bbox[0]);
3670                 this.unitY = h / (bbox[1] - bbox[3]);
3671                 if (Math.abs(this.unitX) < Math.abs(this.unitY)) {
3672                     this.unitY = Math.abs(this.unitX) * this.unitY / Math.abs(this.unitY);
3673                 } else {
3674                     this.unitX = Math.abs(this.unitY) * this.unitX / Math.abs(this.unitX);
3675                 }
3676                 this.keepaspectratio = true;
3677             } else {
3678                 this.unitX = w / (bbox[2] - bbox[0]);
3679                 this.unitY = h / (bbox[1] - bbox[3]);
3680                 this.keepaspectratio = false;
3681             }
3682 
3683             this.moveOrigin(-this.unitX * bbox[0], this.unitY * bbox[1]);
3684 
3685             return this;
3686         },
3687 
3688         /**
3689          * Get the bounding box of the board.
3690          * @returns {Array} bounding box [x1,y1,x2,y2] upper left corner, lower right corner
3691          */
3692         getBoundingBox: function () {
3693             var ul = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this),
3694                 lr = new Coords(Const.COORDS_BY_SCREEN, [this.canvasWidth, this.canvasHeight], this);
3695 
3696             return [ul.usrCoords[1], ul.usrCoords[2], lr.usrCoords[1], lr.usrCoords[2]];
3697         },
3698 
3699         /**
3700          * Adds an animation. Animations are controlled by the boards, so the boards need to be aware of the
3701          * animated elements. This function tells the board about new elements to animate.
3702          * @param {JXG.GeometryElement} element The element which is to be animated.
3703          * @returns {JXG.Board} Reference to the board
3704          */
3705         addAnimation: function (element) {
3706             var that = this;
3707 
3708             this.animationObjects[element.id] = element;
3709 
3710             if (!this.animationIntervalCode) {
3711                 this.animationIntervalCode = window.setInterval(function () {
3712                     that.animate();
3713                 }, element.board.attr.animationdelay);
3714             }
3715 
3716             return this;
3717         },
3718 
3719         /**
3720          * Cancels all running animations.
3721          * @returns {JXG.Board} Reference to the board
3722          */
3723         stopAllAnimation: function () {
3724             var el;
3725 
3726             for (el in this.animationObjects) {
3727                 if (this.animationObjects.hasOwnProperty(el) && Type.exists(this.animationObjects[el])) {
3728                     this.animationObjects[el] = null;
3729                     delete this.animationObjects[el];
3730                 }
3731             }
3732 
3733             window.clearInterval(this.animationIntervalCode);
3734             delete this.animationIntervalCode;
3735 
3736             return this;
3737         },
3738 
3739         /**
3740          * General purpose animation function. This currently only supports moving points from one place to another. This
3741          * is faster than managing the animation per point, especially if there is more than one animated point at the same time.
3742          * @returns {JXG.Board} Reference to the board
3743          */
3744         animate: function () {
3745             var props, el, o, newCoords, r, p, c, cbtmp,
3746                 count = 0,
3747                 obj = null;
3748 
3749             for (el in this.animationObjects) {
3750                 if (this.animationObjects.hasOwnProperty(el) && Type.exists(this.animationObjects[el])) {
3751                     count += 1;
3752                     o = this.animationObjects[el];
3753 
3754                     if (o.animationPath) {
3755                         if (Type.isFunction(o.animationPath)) {
3756                             newCoords = o.animationPath(new Date().getTime() - o.animationStart);
3757                         } else {
3758                             newCoords = o.animationPath.pop();
3759                         }
3760 
3761                         if ((!Type.exists(newCoords)) || (!Type.isArray(newCoords) && isNaN(newCoords))) {
3762                             delete o.animationPath;
3763                         } else {
3764                             o.setPositionDirectly(Const.COORDS_BY_USER, newCoords);
3765                             o.prepareUpdate().update().updateRenderer();
3766                             obj = o;
3767                         }
3768                     }
3769                     if (o.animationData) {
3770                         c = 0;
3771 
3772                         for (r in o.animationData) {
3773                             if (o.animationData.hasOwnProperty(r)) {
3774                                 p = o.animationData[r].pop();
3775 
3776                                 if (!Type.exists(p)) {
3777                                     delete o.animationData[p];
3778                                 } else {
3779                                     c += 1;
3780                                     props = {};
3781                                     props[r] = p;
3782                                     o.setAttribute(props);
3783                                 }
3784                             }
3785                         }
3786 
3787                         if (c === 0) {
3788                             delete o.animationData;
3789                         }
3790                     }
3791 
3792                     if (!Type.exists(o.animationData) && !Type.exists(o.animationPath)) {
3793                         this.animationObjects[el] = null;
3794                         delete this.animationObjects[el];
3795 
3796                         if (Type.exists(o.animationCallback)) {
3797                             cbtmp = o.animationCallback;
3798                             o.animationCallback = null;
3799                             cbtmp();
3800                         }
3801                     }
3802                 }
3803             }
3804 
3805             if (count === 0) {
3806                 window.clearInterval(this.animationIntervalCode);
3807                 delete this.animationIntervalCode;
3808             } else {
3809                 this.update(obj);
3810             }
3811 
3812             return this;
3813         },
3814 
3815         /**
3816          * Migrate the dependency properties of the point src
3817          * to the point dest and  delete the point src.
3818          * For example, a circle around the point src
3819          * receives the new center dest. The old center src
3820          * will be deleted.
3821          * @param {JXG.Point} src Original point which will be deleted
3822          * @param {JXG.Point} dest New point with the dependencies of src.
3823          * @param {Boolean} copyName Flag which decides if the name of the src element is copied to the
3824          *  dest element.
3825          * @returns {JXG.Board} Reference to the board
3826          */
3827         migratePoint: function (src, dest, copyName) {
3828             var child, childId, prop, found, i, srcLabelId, srcHasLabel = false;
3829 
3830             src = this.select(src);
3831             dest = this.select(dest);
3832 
3833             if (JXG.exists(src.label)) {
3834                 srcLabelId = src.label.id;
3835                 srcHasLabel = true;
3836                 this.removeObject(src.label);
3837             }
3838 
3839             for (childId in src.childElements) {
3840                 if (src.childElements.hasOwnProperty(childId)) {
3841                     child = src.childElements[childId];
3842                     found = false;
3843 
3844                     for (prop in child) {
3845                         if (child.hasOwnProperty(prop)) {
3846                             if (child[prop] ===  src) {
3847                                 child[prop] = dest;
3848                                 found = true;
3849                             }
3850                         }
3851                     }
3852 
3853                     if (found) {
3854                         delete src.childElements[childId];
3855                     }
3856 
3857                     for (i = 0; i < child.parents.length; i++) {
3858                         if (child.parents[i] === src.id) {
3859                             child.parents[i] = dest.id;
3860                         }
3861                     }
3862 
3863                     dest.addChild(child);
3864                 }
3865             }
3866 
3867             // The destination object should receive the name
3868             // and the label of the originating (src) object
3869             if (copyName) {
3870                 if (srcHasLabel) {
3871                     delete dest.childElements[srcLabelId];
3872                     delete dest.descendants[srcLabelId];
3873                 }
3874 
3875                 if (dest.label) {
3876                     this.removeObject(dest.label);
3877                 }
3878 
3879                 delete this.elementsByName[dest.name];
3880                 dest.name = src.name;
3881                 if (srcHasLabel) {
3882                     dest.createLabel();
3883                 }
3884             }
3885 
3886             this.removeObject(src);
3887 
3888             if (Type.exists(dest.name) && dest.name !== '') {
3889                 this.elementsByName[dest.name] = dest;
3890             }
3891 
3892             this.update();
3893 
3894             return this;
3895         },
3896 
3897         /**
3898          * Initializes color blindness simulation.
3899          * @param {String} deficiency Describes the color blindness deficiency which is simulated. Accepted values are 'protanopia', 'deuteranopia', and 'tritanopia'.
3900          * @returns {JXG.Board} Reference to the board
3901          */
3902         emulateColorblindness: function (deficiency) {
3903             var e, o;
3904 
3905             if (!Type.exists(deficiency)) {
3906                 deficiency = 'none';
3907             }
3908 
3909             if (this.currentCBDef === deficiency) {
3910                 return this;
3911             }
3912 
3913             for (e in this.objects) {
3914                 if (this.objects.hasOwnProperty(e)) {
3915                     o = this.objects[e];
3916 
3917                     if (deficiency !== 'none') {
3918                         if (this.currentCBDef === 'none') {
3919                             // this could be accomplished by JXG.extend, too. But do not use
3920                             // JXG.deepCopy as this could result in an infinite loop because in
3921                             // visProp there could be geometry elements which contain the board which
3922                             // contains all objects which contain board etc.
3923                             o.visPropOriginal = {
3924                                 strokecolor: o.visProp.strokecolor,
3925                                 fillcolor: o.visProp.fillcolor,
3926                                 highlightstrokecolor: o.visProp.highlightstrokecolor,
3927                                 highlightfillcolor: o.visProp.highlightfillcolor
3928                             };
3929                         }
3930                         o.setAttribute({
3931                             strokecolor: Color.rgb2cb(o.visPropOriginal.strokecolor, deficiency),
3932                             fillcolor: Color.rgb2cb(o.visPropOriginal.fillcolor, deficiency),
3933                             highlightstrokecolor: Color.rgb2cb(o.visPropOriginal.highlightstrokecolor, deficiency),
3934                             highlightfillcolor: Color.rgb2cb(o.visPropOriginal.highlightfillcolor, deficiency)
3935                         });
3936                     } else if (Type.exists(o.visPropOriginal)) {
3937                         JXG.extend(o.visProp, o.visPropOriginal);
3938                     }
3939                 }
3940             }
3941             this.currentCBDef = deficiency;
3942             this.update();
3943 
3944             return this;
3945         },
3946 
3947         /**
3948          * Select a single or multiple elements at once.
3949          * @param {String|Object|function} str The name, id or a reference to a JSXGraph element on this board. An object will
3950          * be used as a filter to return multiple elements at once filtered by the properties of the object.
3951          * @returns {JXG.GeometryElement|JXG.Composition}
3952          * @example
3953          * // select the element with name A
3954          * board.select('A');
3955          *
3956          * // select all elements with strokecolor set to 'red' (but not '#ff0000')
3957          * board.select({
3958          *   strokeColor: 'red'
3959          * });
3960          *
3961          * // select all points on or below the x axis and make them black.
3962          * board.select({
3963          *   elementClass: JXG.OBJECT_CLASS_POINT,
3964          *   Y: function (v) {
3965          *     return v <= 0;
3966          *   }
3967          * }).setAttribute({color: 'black'});
3968          *
3969          * // select all elements
3970          * board.select(function (el) {
3971          *   return true;
3972          * });
3973          */
3974         select: function (str) {
3975             var flist, olist, i, l,
3976                 s = str;
3977 
3978             if (s === null) {
3979                 return s;
3980             }
3981 
3982             // it's a string, most likely an id or a name.
3983             if (typeof s === 'string' && s !== '') {
3984                 // Search by ID
3985                 if (Type.exists(this.objects[s])) {
3986                     s = this.objects[s];
3987                 // Search by name
3988                 } else if (Type.exists(this.elementsByName[s])) {
3989                     s = this.elementsByName[s];
3990                 // Search by group ID
3991                 } else if (Type.exists(this.groups[s])) {
3992                     s = this.groups[s];
3993                 }
3994             // it's a function or an object, but not an element
3995             } else if (typeof s === 'function' || (typeof s === 'object' && !JXG.isArray(s) && typeof s.setAttribute !== 'function')) {
3996 
3997                 flist = Type.filterElements(this.objectsList, s);
3998 
3999                 olist = {};
4000                 l = flist.length;
4001                 for (i = 0; i < l; i++) {
4002                     olist[flist[i].id] = flist[i];
4003                 }
4004                 s = new EComposition(olist);
4005             // it's an element which has been deleted (and still hangs around, e.g. in an attractor list
4006             } else if (typeof s === 'object' && JXG.exists(s.id) && !JXG.exists(this.objects[s.id])) {
4007                 s = null;
4008             }
4009 
4010             return s;
4011         },
4012 
4013         /**
4014          * Checks if the given point is inside the boundingbox.
4015          * @param {Number|JXG.Coords} x User coordinate or {@link JXG.Coords} object.
4016          * @param {Number} [y] User coordinate. May be omitted in case <tt>x</tt> is a {@link JXG.Coords} object.
4017          * @returns {Boolean}
4018          */
4019         hasPoint: function (x, y) {
4020             var px = x,
4021                 py = y,
4022                 bbox = this.getBoundingBox();
4023 
4024             if (JXG.exists(x) && JXG.isArray(x.usrCoords)) {
4025                 px = x.usrCoords[1];
4026                 py = x.usrCoords[2];
4027             }
4028 
4029             if (typeof px === 'number' && typeof py === 'number' &&
4030                     bbox[0] < px && px < bbox[2] && bbox[1] > py && py > bbox[3]) {
4031                 return true;
4032             }
4033 
4034             return false;
4035         },
4036 
4037         /**
4038          * Update CSS transformations of sclaing type. It is used to correct the mouse position
4039          * in {@link JXG.Board#getMousePosition}.
4040          * The inverse transformation matrix is updated on each mouseDown and touchStart event.
4041          *
4042          * It is up to the user to call this method after an update of the CSS transformation
4043          * in the DOM.
4044          */
4045         updateCSSTransforms: function () {
4046             var obj = this.containerObj,
4047                 o = obj,
4048                 o2 = obj;
4049 
4050             this.cssTransMat = Env.getCSSTransformMatrix(o);
4051 
4052             /*
4053              * In Mozilla and Webkit: offsetParent seems to jump at least to the next iframe,
4054              * if not to the body. In IE and if we are in an position:absolute environment
4055              * offsetParent walks up the DOM hierarchy.
4056              * In order to walk up the DOM hierarchy also in Mozilla and Webkit
4057              * we need the parentNode steps.
4058              */
4059             o = o.offsetParent;
4060             while (o) {
4061                 this.cssTransMat = Mat.matMatMult(Env.getCSSTransformMatrix(o), this.cssTransMat);
4062 
4063                 o2 = o2.parentNode;
4064                 while (o2 !== o) {
4065                     this.cssTransMat = Mat.matMatMult(Env.getCSSTransformMatrix(o), this.cssTransMat);
4066                     o2 = o2.parentNode;
4067                 }
4068 
4069                 o = o.offsetParent;
4070             }
4071             this.cssTransMat = Mat.inverse(this.cssTransMat);
4072 
4073             return this;
4074         },
4075 
4076 
4077         /* **************************
4078          *     EVENT DEFINITION
4079          * for documentation purposes
4080          * ************************** */
4081 
4082         //region Event handler documentation
4083 
4084         /**
4085          * @event
4086          * @description Whenever the user starts to touch or click the board.
4087          * @name JXG.Board#down
4088          * @param {Event} e The browser's event object.
4089          */
4090         __evt__down: function (e) { },
4091 
4092         /**
4093          * @event
4094          * @description Whenever the user starts to click on the board.
4095          * @name JXG.Board#mousedown
4096          * @param {Event} e The browser's event object.
4097          */
4098         __evt__mousedown: function (e) { },
4099 
4100         /**
4101          * @event
4102          * @description Whenever the user starts to touch the board.
4103          * @name JXG.Board#touchstart
4104          * @param {Event} e The browser's event object.
4105          */
4106         __evt__touchstart: function (e) { },
4107 
4108         /**
4109          * @event
4110          * @description Whenever the user stops to touch or click the board.
4111          * @name JXG.Board#up
4112          * @param {Event} e The browser's event object.
4113          */
4114         __evt__up: function (e) { },
4115 
4116         /**
4117          * @event
4118          * @description Whenever the user releases the mousebutton over the board.
4119          * @name JXG.Board#mouseup
4120          * @param {Event} e The browser's event object.
4121          */
4122         __evt__mouseup: function (e) { },
4123 
4124         /**
4125          * @event
4126          * @description Whenever the user stops touching the board.
4127          * @name JXG.Board#touchend
4128          * @param {Event} e The browser's event object.
4129          */
4130         __evt__touchend: function (e) { },
4131 
4132         /**
4133          * @event
4134          * @description This event is fired whenever the user is moving the finger or mouse pointer over the board.
4135          * @name JXG.Board#move
4136          * @param {Event} e The browser's event object.
4137          * @param {Number} mode The mode the board currently is in
4138          * @see {JXG.Board#mode}
4139          */
4140         __evt__move: function (e, mode) { },
4141 
4142         /**
4143          * @event
4144          * @description This event is fired whenever the user is moving the mouse over the board.
4145          * @name JXG.Board#mousemove
4146          * @param {Event} e The browser's event object.
4147          * @param {Number} mode The mode the board currently is in
4148          * @see {JXG.Board#mode}
4149          */
4150         __evt__mousemove: function (e, mode) { },
4151 
4152         /**
4153          * @event
4154          * @description This event is fired whenever the user is moving the finger over the board.
4155          * @name JXG.Board#touchmove
4156          * @param {Event} e The browser's event object.
4157          * @param {Number} mode The mode the board currently is in
4158          * @see {JXG.Board#mode}
4159          */
4160         __evt__touchmove: function (e, mode) { },
4161 
4162         /**
4163          * @event
4164          * @description Whenever an element is highlighted this event is fired.
4165          * @name JXG.Board#hit
4166          * @param {Event} e The browser's event object.
4167          * @param {JXG.GeometryElement} el The hit element.
4168          * @param target
4169          */
4170         __evt__hit: function (e, el, target) { },
4171 
4172         /**
4173          * @event
4174          * @description Whenever an element is highlighted this event is fired.
4175          * @name JXG.Board#mousehit
4176          * @param {Event} e The browser's event object.
4177          * @param {JXG.GeometryElement} el The hit element.
4178          * @param target
4179          */
4180         __evt__mousehit: function (e, el, target) { },
4181 
4182         /**
4183          * @event
4184          * @description This board is updated.
4185          * @name JXG.Board#update
4186          */
4187         __evt__update: function () { },
4188 
4189         /**
4190          * @event
4191          * @description The bounding box of the board has changed.
4192          * @name JXG.Board#boundingbox
4193          */
4194         __evt__boundingbox: function () { },
4195 
4196         /**
4197          * @ignore
4198          */
4199         __evt: function () {},
4200 
4201         //endregion
4202 
4203         /**
4204          * Function to animate a curve rolling on another curve.
4205          * @param {Curve} c1 JSXGraph curve building the floor where c2 rolls
4206          * @param {Curve} c2 JSXGraph curve which rolls on c1.
4207          * @param {number} start_c1 The parameter t such that c1(t) touches c2. This is the start position of the
4208          *                          rolling process
4209          * @param {Number} stepsize Increase in t in each step for the curve c1
4210          * @param {Number} direction
4211          * @param {Number} time Delay time for setInterval()
4212          * @param {Array} pointlist Array of points which are rolled in each step. This list should contain
4213          *      all points which define c2 and gliders on c2.
4214          *
4215          * @example
4216          *
4217          * // Line which will be the floor to roll upon.
4218          * var line = brd.create('curve', [function (t) { return t;}, function (t){ return 1;}], {strokeWidth:6});
4219          * // Center of the rolling circle
4220          * var C = brd.create('point',[0,2],{name:'C'});
4221          * // Starting point of the rolling circle
4222          * var P = brd.create('point',[0,1],{name:'P', trace:true});
4223          * // Circle defined as a curve. The circle "starts" at P, i.e. circle(0) = P
4224          * var circle = brd.create('curve',[
4225          *           function (t){var d = P.Dist(C),
4226          *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
4227          *                       t += beta;
4228          *                       return C.X()+d*Math.cos(t);
4229          *           },
4230          *           function (t){var d = P.Dist(C),
4231          *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
4232          *                       t += beta;
4233          *                       return C.Y()+d*Math.sin(t);
4234          *           },
4235          *           0,2*Math.PI],
4236          *           {strokeWidth:6, strokeColor:'green'});
4237          *
4238          * // Point on circle
4239          * var B = brd.create('glider',[0,2,circle],{name:'B', color:'blue',trace:false});
4240          * var roll = brd.createRoulette(line, circle, 0, Math.PI/20, 1, 100, [C,P,B]);
4241          * roll.start() // Start the rolling, to be stopped by roll.stop()
4242          *
4243          * </pre><div id="e5e1b53c-a036-4a46-9e35-190d196beca5" style="width: 300px; height: 300px;"></div>
4244          * <script type="text/javascript">
4245          * var brd = JXG.JSXGraph.initBoard('e5e1b53c-a036-4a46-9e35-190d196beca5', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright:false, shownavigation: false});
4246          * // Line which will be the floor to roll upon.
4247          * var line = brd.create('curve', [function (t) { return t;}, function (t){ return 1;}], {strokeWidth:6});
4248          * // Center of the rolling circle
4249          * var C = brd.create('point',[0,2],{name:'C'});
4250          * // Starting point of the rolling circle
4251          * var P = brd.create('point',[0,1],{name:'P', trace:true});
4252          * // Circle defined as a curve. The circle "starts" at P, i.e. circle(0) = P
4253          * var circle = brd.create('curve',[
4254          *           function (t){var d = P.Dist(C),
4255          *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
4256          *                       t += beta;
4257          *                       return C.X()+d*Math.cos(t);
4258          *           },
4259          *           function (t){var d = P.Dist(C),
4260          *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
4261          *                       t += beta;
4262          *                       return C.Y()+d*Math.sin(t);
4263          *           },
4264          *           0,2*Math.PI],
4265          *           {strokeWidth:6, strokeColor:'green'});
4266          *
4267          * // Point on circle
4268          * var B = brd.create('glider',[0,2,circle],{name:'B', color:'blue',trace:false});
4269          * var roll = brd.createRoulette(line, circle, 0, Math.PI/20, 1, 100, [C,P,B]);
4270          * roll.start() // Start the rolling, to be stopped by roll.stop()
4271          * </script><pre>
4272          */
4273         createRoulette: function (c1, c2, start_c1, stepsize, direction, time, pointlist) {
4274             var brd = this,
4275                 Roulette = function () {
4276                     var alpha = 0, Tx = 0, Ty = 0,
4277                         t1 = start_c1,
4278                         t2 = Numerics.root(
4279                             function (t) {
4280                                 var c1x = c1.X(t1),
4281                                     c1y = c1.Y(t1),
4282                                     c2x = c2.X(t),
4283                                     c2y = c2.Y(t);
4284 
4285                                 return (c1x - c2x) * (c1x - c2x) + (c1y - c2y) * (c1y - c2y);
4286                             },
4287                             [0, Math.PI * 2]
4288                         ),
4289                         t1_new = 0.0, t2_new = 0.0,
4290                         c1dist,
4291 
4292                         rotation = brd.create('transform', [
4293                             function () {
4294                                 return alpha;
4295                             }
4296                         ], {type: 'rotate'}),
4297 
4298                         rotationLocal = brd.create('transform', [
4299                             function () {
4300                                 return alpha;
4301                             },
4302                             function () {
4303                                 return c1.X(t1);
4304                             },
4305                             function () {
4306                                 return c1.Y(t1);
4307                             }
4308                         ], {type: 'rotate'}),
4309 
4310                         translate = brd.create('transform', [
4311                             function () {
4312                                 return Tx;
4313                             },
4314                             function () {
4315                                 return Ty;
4316                             }
4317                         ], {type: 'translate'}),
4318 
4319                         // arc length via Simpson's rule.
4320                         arclen = function (c, a, b) {
4321                             var cpxa = Numerics.D(c.X)(a),
4322                                 cpya = Numerics.D(c.Y)(a),
4323                                 cpxb = Numerics.D(c.X)(b),
4324                                 cpyb = Numerics.D(c.Y)(b),
4325                                 cpxab = Numerics.D(c.X)((a + b) * 0.5),
4326                                 cpyab = Numerics.D(c.Y)((a + b) * 0.5),
4327 
4328                                 fa = Math.sqrt(cpxa * cpxa + cpya * cpya),
4329                                 fb = Math.sqrt(cpxb * cpxb + cpyb * cpyb),
4330                                 fab = Math.sqrt(cpxab * cpxab + cpyab * cpyab);
4331 
4332                             return (fa + 4 * fab + fb) * (b - a) / 6;
4333                         },
4334 
4335                         exactDist = function (t) {
4336                             return c1dist - arclen(c2, t2, t);
4337                         },
4338 
4339                         beta = Math.PI / 18,
4340                         beta9 = beta * 9,
4341                         interval = null;
4342 
4343                     this.rolling = function () {
4344                         var h, g, hp, gp, z;
4345 
4346                         t1_new = t1 + direction * stepsize;
4347 
4348                         // arc length between c1(t1) and c1(t1_new)
4349                         c1dist = arclen(c1, t1, t1_new);
4350 
4351                         // find t2_new such that arc length between c2(t2) and c1(t2_new) equals c1dist.
4352                         t2_new = Numerics.root(exactDist, t2);
4353 
4354                         // c1(t) as complex number
4355                         h = new Complex(c1.X(t1_new), c1.Y(t1_new));
4356 
4357                         // c2(t) as complex number
4358                         g = new Complex(c2.X(t2_new), c2.Y(t2_new));
4359 
4360                         hp = new Complex(Numerics.D(c1.X)(t1_new), Numerics.D(c1.Y)(t1_new));
4361                         gp = new Complex(Numerics.D(c2.X)(t2_new), Numerics.D(c2.Y)(t2_new));
4362 
4363                         // z is angle between the tangents of c1 at t1_new, and c2 at t2_new
4364                         z = Complex.C.div(hp, gp);
4365 
4366                         alpha = Math.atan2(z.imaginary, z.real);
4367                         // Normalizing the quotient
4368                         z.div(Complex.C.abs(z));
4369                         z.mult(g);
4370                         Tx = h.real - z.real;
4371 
4372                         // T = h(t1_new)-g(t2_new)*h'(t1_new)/g'(t2_new);
4373                         Ty = h.imaginary - z.imaginary;
4374 
4375                         // -(10-90) degrees: make corners roll smoothly
4376                         if (alpha < -beta && alpha > -beta9) {
4377                             alpha = -beta;
4378                             rotationLocal.applyOnce(pointlist);
4379                         } else if (alpha > beta && alpha < beta9) {
4380                             alpha = beta;
4381                             rotationLocal.applyOnce(pointlist);
4382                         } else {
4383                             rotation.applyOnce(pointlist);
4384                             translate.applyOnce(pointlist);
4385                             t1 = t1_new;
4386                             t2 = t2_new;
4387                         }
4388                         brd.update();
4389                     };
4390 
4391                     this.start = function () {
4392                         if (time > 0) {
4393                             interval = window.setInterval(this.rolling, time);
4394                         }
4395                         return this;
4396                     };
4397 
4398                     this.stop = function () {
4399                         window.clearInterval(interval);
4400                         return this;
4401                     };
4402                     return this;
4403                 };
4404             return new Roulette();
4405         }
4406     });
4407 
4408     return JXG.Board;
4409 });
4410