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