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