1 /* 2 Copyright 2008-2011 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 JSXGraph object is defined in this file. JXG.JSXGraph controls all boards. 28 * It has methods to create, save, load and free boards. Additionally some helper functions are 29 * defined in this file directly in the JXG namespace. 30 * @version 0.83 31 */ 32 33 /** 34 * Detect browser support for VML. 35 * The code in comments is from google maps. 36 * But it does not work in JSXGraph because in the moment 37 * of calling supportsVML() document.body is still undefined. 38 * Therefore, the more vulnerable test of 39 * navigator.appVersion 40 * is used. 41 * Update: In stackoverflow the test 42 * !!document.namespaces 43 * has been suggested. 44 * @returns {Boolean} 45 */ 46 JXG.supportsVML = function() { 47 /* 48 var a, b, isSupported = false; 49 a = document.body.appendChild(document.createElement('div')); 50 a.innerHTML = '<v:shape id="vml_flag1" adj="1" />'; 51 b = a.firstChild; 52 b.style.behavior = "url(#default#VML)"; 53 isSupported = b ? typeof b.adj == "object": true; 54 a.parentNode.removeChild(a); 55 return isSupported; 56 */ 57 //var ie = navigator.appVersion.match(/MSIE (\d\.\d)/); 58 //if (ie && parseFloat(ie[1]) < 9.0 ) { 59 if (!!document.namespaces) { 60 return true; 61 } else { 62 return false; 63 } 64 }; 65 66 /** 67 * Detect browser support for SVG. 68 * @returns {Boolean} 69 */ 70 JXG.supportsSVG = function() { 71 return document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"); 72 }; 73 74 /** 75 * Constructs a new JSXGraph singleton object. 76 * @class The JXG.JSXGraph singleton stores all properties required 77 * to load, save, create and free a board. 78 */ 79 JXG.JSXGraph = { 80 81 /** 82 * The small gray version indicator in the top left corner of every JSXGraph board (if 83 * showCopyright is not set to false on board creation). 84 * @type String 85 */ 86 licenseText: 'JSXGraph v0.83rc4 Copyright (C) see http://jsxgraph.org', 87 88 /** 89 * Associative array that keeps references to all boards. 90 * @type Object 91 */ 92 boards: {}, 93 94 /** 95 * Associative array that keeps track of all constructable elements registered 96 * via {@link JXG.JSXGraph.registerElement}. 97 * @type Object 98 */ 99 elements: {}, 100 101 /** 102 * Stores the renderer that is used to draw the boards. 103 * @type String 104 */ 105 rendererType: (function() { 106 if (JXG.supportsSVG()) { 107 JXG.Options.renderer = 'svg'; 108 } else if (JXG.supportsVML()) { 109 JXG.Options.renderer = 'vml'; 110 // Ok, this is some real magic going on here. IE/VML always was so 111 // terribly slow, except in one place: Examples placed in a moodle course 112 // was almost as fast as in other browsers. So i grabbed all the css and 113 // js scripts from our moodle, added them to a jsxgraph example and it 114 // worked. next step was to strip all the css/js code which didn't affect 115 // the VML update speed. The following five lines are what was left after 116 // the last step and yes - it basically does nothing but reads two 117 // properties of document.body on every mouse move. why? we don't know. if 118 // you know, please let us know. 119 function MouseMove() { 120 document.body.scrollLeft; 121 document.body.scrollTop; 122 } 123 document.onmousemove = MouseMove; 124 } else { 125 JXG.Options.renderer = 'canvas'; 126 } 127 128 /* 129 var ie, opera, i, arr; 130 // Determine the users browser 131 ie = navigator.appVersion.match(/MSIE (\d\.\d)/); 132 opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1); 133 // set the rendererType according to the browser 134 if ((!ie) || (opera) || (ie && parseFloat(ie[1]) >= 9.0)) { 135 // we're NOT in IE 136 if (navigator.appVersion.match(/Android.*AppleWebKit/) 137 ||navigator.appVersion.match(/ElocityA7.*AppleWebKit/) ) { 138 // we're using canvas on android 139 JXG.Options.renderer = 'canvas'; 140 } else { 141 // let's hope the user's browser supports svg... 142 JXG.Options.renderer = 'svg'; 143 } 144 } else { 145 // IE 146 JXG.Options.renderer = 'vml'; 147 148 // Ok, this is some real magic going on here. IE/VML always was so 149 // terribly slow, except in one place: Examples placed in a moodle course 150 // was almost as fast as in other browsers. So i grabbed all the css and 151 // js scripts from our moodle, added them to a jsxgraph example and it 152 // worked. next step was to strip all the css/js code which didn't affect 153 // the VML update speed. The following five lines are what was left after 154 // the last step and yes - it basically does nothing but reads two 155 // properties of document.body on every mouse move. why? we don't know. if 156 // you know, please let us know. 157 function MouseMove() { 158 document.body.scrollLeft; 159 document.body.scrollTop; 160 } 161 162 document.onmousemove = MouseMove; 163 } 164 */ 165 166 // Load the source files for the renderer 167 arr = JXG.rendererFiles[JXG.Options.renderer].split(','); 168 for (i = 0; i < arr.length; i++) ( function(include) { 169 JXG.require(JXG.requirePath + include + '.js'); 170 } )(arr[i]); 171 172 return JXG.Options.renderer; 173 })(), 174 175 /** 176 * Initialise a new board. 177 * @param {String} box Html-ID to the Html-element in which the board is painted. 178 * @returns {JXG.Board} Reference to the created board. 179 */ 180 initBoard: function (box, attributes) { 181 var renderer, 182 originX, originY, unitX, unitY, 183 w, h, dimensions, 184 bbox, 185 zoomfactor, zoomX, zoomY, 186 showCopyright, showNavi, 187 board; 188 189 dimensions = JXG.getDimensions(box); 190 191 // parse attributes 192 if (typeof attributes == 'undefined') { 193 attributes = {}; 194 } 195 196 if (typeof attributes["boundingbox"] != 'undefined') { 197 bbox = attributes["boundingbox"]; 198 w = parseInt(dimensions.width); 199 h = parseInt(dimensions.height); 200 201 if (attributes["keepaspectratio"]) { 202 /* 203 * If the boundingbox attribute is given and the ratio of height and width of the 204 * sides defined by the bounding box and the ratio of the dimensions of the div tag 205 * which contains the board do not coincide, then the smaller side is chosen. 206 */ 207 unitX = w/(bbox[2]-bbox[0]); 208 unitY = h/(-bbox[3]+bbox[1]); 209 if (unitX<unitY) { 210 unitY = unitX; 211 } else { 212 unitX = unitY; 213 } 214 } else { 215 unitX = w/(bbox[2]-bbox[0]); 216 unitY = h/(-bbox[3]+bbox[1]); 217 } 218 originX = -unitX*bbox[0]; 219 originY = unitY*bbox[1]; 220 } else { 221 originX = ( (typeof attributes["originX"]) == 'undefined' ? 150 : attributes["originX"]); 222 originY = ( (typeof attributes["originY"]) == 'undefined' ? 150 : attributes["originY"]); 223 unitX = ( (typeof attributes["unitX"]) == 'undefined' ? 50 : attributes["unitX"]); 224 unitY = ( (typeof attributes["unitY"]) == 'undefined' ? 50 : attributes["unitY"]); 225 } 226 zoomfactor = ( (typeof attributes["zoom"]) == 'undefined' ? 1.0 : attributes["zoom"]); 227 zoomX = zoomfactor*( (typeof attributes["zoomX"]) == 'undefined' ? 1.0 : attributes["zoomX"]); 228 zoomY = zoomfactor*( (typeof attributes["zoomY"]) == 'undefined' ? 1.0 : attributes["zoomY"]); 229 230 showCopyright = ( (typeof attributes["showCopyright"]) == 'undefined' ? JXG.Options.showCopyright : attributes["showCopyright"]); 231 232 // create the renderer 233 if(JXG.Options.renderer == 'svg') { 234 renderer = new JXG.SVGRenderer(document.getElementById(box)); 235 } else if(JXG.Options.renderer == 'vml') { 236 renderer = new JXG.VMLRenderer(document.getElementById(box)); 237 } else if(JXG.Options.renderer == 'silverlight') { 238 renderer = new JXG.SilverlightRenderer(document.getElementById(box), dimensions.width, dimensions.height); 239 } else { 240 renderer = new JXG.CanvasRenderer(document.getElementById(box)); 241 } 242 243 // create the board 244 board = new JXG.Board(box, renderer, '', [originX, originY], 1.0, 1.0, unitX, unitY, dimensions.width, dimensions.height,showCopyright); 245 this.boards[board.id] = board; 246 247 // create elements like axes, grid, navigation, ... 248 board.suspendUpdate(); 249 board.initInfobox(); 250 251 if(attributes["axis"]) { 252 board.defaultAxes = {}; 253 board.defaultAxes.x = board.create('axis', [[0,0], [1,0]], {}); 254 board.defaultAxes.y = board.create('axis', [[0,0], [0,1]], {}); 255 } 256 257 if(attributes["grid"]) { 258 board.renderer.drawGrid(board); 259 } 260 261 if (typeof attributes["shownavigation"] != 'undefined') attributes["showNavigation"] = attributes["shownavigation"]; 262 showNavi = ( (typeof attributes["showNavigation"]) == 'undefined' ? board.options.showNavigation : attributes["showNavigation"]); 263 if (showNavi) { 264 board.renderer.drawZoomBar(board); 265 } 266 board.unsuspendUpdate(); 267 268 return board; 269 }, 270 271 /** 272 * Load a board from a file containing a construction made with either GEONExT, 273 * Intergeo, Geogebra, or Cinderella. 274 * @param {String} box HTML-ID to the HTML-element in which the board is painted. 275 * @param {String} file base64 encoded string. 276 * @param {String} format containing the file format: 'Geonext' or 'Intergeo'. 277 * @returns {JXG.Board} Reference to the created board. 278 * 279 * @see JXG.FileReader 280 * @see JXG.GeonextReader 281 * @see JXG.GeogebraReader 282 * @see JXG.IntergeoReader 283 * @see JXG.CinderellaReader 284 */ 285 loadBoardFromFile: function (box, file, format) { 286 var renderer, board, dimensions; 287 288 if(JXG.Options.renderer == 'svg') { 289 renderer = new JXG.SVGRenderer(document.getElementById(box)); 290 } else if(JXG.Options.renderer == 'vml') { 291 renderer = new JXG.VMLRenderer(document.getElementById(box)); 292 } else if(JXG.Options.renderer == 'silverlight') { 293 renderer = new JXG.SilverlightRenderer(document.getElementById(box), dimensions.width, dimensions.height); 294 } else { 295 renderer = new JXG.CanvasRenderer(document.getElementById(box)); 296 } 297 298 //var dimensions = document.getElementById(box).getDimensions(); 299 dimensions = JXG.getDimensions(box); 300 301 /* User default parameters, in parse* the values in the gxt files are submitted to board */ 302 board = new JXG.Board(box, renderer, '', [150, 150], 1.0, 1.0, 50, 50, dimensions.width, dimensions.height); 303 board.initInfobox(); 304 305 JXG.FileReader.parseFileContent(file, board, format); 306 if(board.options.showNavigation) { 307 board.renderer.drawZoomBar(board); 308 } 309 this.boards[board.id] = board; 310 return board; 311 }, 312 313 /** 314 * Load a board from a base64 encoded string containing a construction made with either GEONExT, 315 * Intergeo, Geogebra, or Cinderella. 316 * @param {String} box HTML-ID to the HTML-element in which the board is painted. 317 * @param {String} string base64 encoded string. 318 * @param {String} format containing the file format: 'Geonext' or 'Intergeo'. 319 * @returns {JXG.Board} Reference to the created board. 320 * 321 * @see JXG.FileReader 322 * @see JXG.GeonextReader 323 * @see JXG.GeogebraReader 324 * @see JXG.IntergeoReader 325 * @see JXG.CinderellaReader 326 */ 327 loadBoardFromString: function(box, string, format) { 328 var renderer, dimensions, board; 329 330 if(JXG.Options.renderer == 'svg') { 331 renderer = new JXG.SVGRenderer(document.getElementById(box)); 332 } else if(JXG.Options.renderer == 'vml') { 333 renderer = new JXG.VMLRenderer(document.getElementById(box)); 334 } else if(JXG.Options.renderer == 'silverlight') { 335 renderer = new JXG.SilverlightRenderer(document.getElementById(box), dimensions.width, dimensions.height); 336 } else { 337 renderer = new JXG.CanvasRenderer(document.getElementById(box)); 338 } 339 //var dimensions = document.getElementById(box).getDimensions(); 340 dimensions = JXG.getDimensions(box); 341 342 /* User default parameters, in parse* the values in the gxt files are submitted to board */ 343 board = new JXG.Board(box, renderer, '', [150, 150], 1.0, 1.0, 50, 50, dimensions.width, dimensions.height); 344 board.initInfobox(); 345 346 JXG.FileReader.parseString(string, board, format, true); 347 if (board.options.showNavigation) { 348 board.renderer.drawZoomBar(board); 349 } 350 351 this.boards[board.id] = board; 352 return board; 353 }, 354 355 /** 356 * Delete a board and all its contents. 357 * @param {String} board HTML-ID to the DOM-element in which the board is drawn. 358 */ 359 freeBoard: function (board) { 360 var el; 361 362 if(typeof(board) == 'string') { 363 board = this.boards[board]; 364 } 365 366 // Remove the event listeners 367 JXG.removeEvent(document, 'mousedown', board.mouseDownListener, board); 368 JXG.removeEvent(document, 'mouseup', board.mouseUpListener,board); 369 JXG.removeEvent(board.containerObj, 'mousemove', board.mouseMoveListener, board); 370 371 // Remove all objects from the board. 372 for(el in board.objects) { 373 board.removeObject(board.objects[el]); 374 } 375 376 // Remove all the other things, left on the board 377 board.containerObj.innerHTML = ''; 378 379 // Tell the browser the objects aren't needed anymore 380 for(el in board.objects) { 381 delete(board.objects[el]); 382 } 383 384 // Free the renderer and the algebra object 385 delete(board.renderer); 386 delete(board.algebra); 387 388 // Finally remove the board itself from the boards array 389 delete(this.boards[board.id]); 390 }, 391 392 /** 393 * This registers a new construction element to JSXGraph for the construction via the {@link JXG.Board.create} 394 * interface. 395 * @param {String} element The elements name. This is case-insensitive, existing elements with the same name 396 * will be overwritten. 397 * @param {Function} creator A reference to a function taking three parameters: First the board, the element is 398 * to be created on, a parent element array, and an attributes object. See {@link JXG.createPoint} or any other 399 * <tt>JXG.create...</tt> function for an example. 400 */ 401 registerElement: function (element, creator) { 402 element = element.toLowerCase(); 403 this.elements[element] = creator; 404 405 if(JXG.Board.prototype['_' + element]) 406 throw new Error("JSXGraph: Can't create wrapper method in JXG.Board because member '_" + element + "' already exists'"); 407 JXG.Board.prototype['_' + element] = function (parents, attributes) { 408 return this.create(element, parents, attributes); 409 }; 410 411 }, 412 413 /** 414 * The opposite of {@link JXG.JSXGraph.registerElement}, it removes a given element from 415 * the element list. You probably don't need this. 416 * @param {String} element The name of the element which is to be removed from the element list. 417 */ 418 unregisterElement: function (element) { 419 delete (this.elements[element.toLowerCase()]); 420 delete (JXG.Board.prototype['_' + element.toLowerCase()]); 421 } 422 }; 423 424 /** 425 * s may be a string containing the name or id of an element or even a reference 426 * to the element itself. This function returns a reference to the element. Search order: id, name. 427 * @param {JXG.Board} board Reference to the board the element belongs to. 428 * @param {String} s String or reference to a JSXGraph element. 429 * @returns {Object} Reference to the object given in parameter object 430 */ 431 JXG.getReference = function(board, s) { 432 if(typeof(s) == 'string') { 433 if(board.objects[s] != null) { // Search by ID 434 s = board.objects[s]; 435 } else if (board.elementsByName[s] != null) { // Search by name 436 s = board.elementsByName[s]; 437 } 438 } 439 440 return s; 441 }; 442 443 /** 444 * This is just a shortcut to {@link JXG.getReference}. 445 */ 446 JXG.getRef = JXG.getReference; 447 448 /** 449 * Checks if the value of a given variable is of type string. 450 * @param v A variable of any type. 451 * @returns {Boolean} True, if v is of type string. 452 */ 453 JXG.isString = function(v) { 454 return typeof v == "string"; 455 }; 456 457 /** 458 * Checks if the value of a given variable is of type number. 459 * @param v A variable of any type. 460 * @returns {Boolean} True, if v is of type number. 461 */ 462 JXG.isNumber = function(v) { 463 return typeof v == "number"; 464 }; 465 466 /** 467 * Checks if a given variable references a function. 468 * @param v A variable of any type. 469 * @returns {Boolean} True, if v is a function. 470 */ 471 JXG.isFunction = function(v) { 472 return typeof v == "function"; 473 }; 474 475 /** 476 * Checks if a given variable references an array. 477 * @param v A variable of any type. 478 * @returns {Boolean} True, if v is of type array. 479 */ 480 JXG.isArray = function(v) { 481 // Borrowed from prototype.js 482 return v != null && typeof v == "object" && 'splice' in v && 'join' in v; 483 }; 484 485 /** 486 * Checks if a given variable is a reference of a JSXGraph Point element. 487 * @param v A variable of any type. 488 * @returns {Boolean} True, if v is of type JXG.Point. 489 */ 490 JXG.isPoint = function(v) { 491 if(typeof v == 'object') { 492 return (v.elementClass == JXG.OBJECT_CLASS_POINT); 493 } 494 495 return false; 496 }; 497 498 /** 499 * Checks if a given variable is neither undefined nor null. You should not use this together with global 500 * variables! 501 * @param v A variable of any type. 502 * @returns {Boolean} True, if v is neither undefined nor null. 503 */ 504 JXG.exists = (function(undefined) { 505 return function(v) { 506 return !(v === undefined || v === null); 507 } 508 })(); 509 510 /** 511 * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value. 512 * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>. 513 * @returns {Boolean} String typed boolean value converted to boolean. 514 */ 515 JXG.str2Bool = function(s) { 516 if (!JXG.exists(s)) { 517 return true; 518 } 519 if (typeof s == 'boolean') { 520 return s; 521 } 522 return (s.toLowerCase()=='true'); 523 }; 524 525 /** 526 * Shortcut for {@link JXG.JSXGraph.initBoard}. 527 */ 528 JXG._board = function(box, attributes) { 529 return JXG.JSXGraph.initBoard(box, attributes); 530 }; 531 532 /** 533 * Convert a String, a number or a function into a function. This method is used in Transformation.js 534 * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given 535 * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param 536 * values is of type string. 537 * @param {Array} param An array containing strings, numbers, or functions. 538 * @returns {Function} A function taking one parameter k which specifies the index of the param element 539 * to evaluate. 540 */ 541 JXG.createEvalFunction = function(board, param, n) { 542 // convert GEONExT syntax into function 543 var f = [], i, str; 544 545 for (i=0;i<n;i++) { 546 if (typeof param[i] == 'string') { 547 str = JXG.GeonextParser.geonext2JS(param[i],board); 548 str = str.replace(/this\.board\./g,'board.'); 549 f[i] = new Function('','return ' + (str) + ';'); 550 } 551 } 552 553 return function(k) { 554 var a = param[k]; 555 if (typeof a == 'string') { 556 return f[k](); 557 } else if (typeof a=='function') { 558 return a(); 559 } else if (typeof a=='number') { 560 return a; 561 } 562 return 0; 563 }; 564 }; 565 566 /** 567 * Convert a String, number or function into a function. 568 * @param term A variable of type string, function or number. 569 * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given 570 * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param 571 * values is of type string. 572 * @param {String} variableName Only required if evalGeonext is set to true. Describes the variable name 573 * of the variable in a GEONE<sub>X</sub>T string given as term. 574 * @param {Boolean} evalGeonext Set this true, if term should be treated as a GEONE<sub>X</sub>T string. 575 * @returns {Function} A function evaluation the value given by term or null if term is not of type string, 576 * function or number. 577 */ 578 JXG.createFunction = function(term, board, variableName, evalGeonext) { 579 var newTerm; 580 581 if ((evalGeonext==null || evalGeonext) && JXG.isString(term)) { 582 // Convert GEONExT syntax into JavaScript syntax 583 newTerm = JXG.GeonextParser.geonext2JS(term, board); 584 return new Function(variableName,'return ' + newTerm + ';'); 585 } else if (JXG.isFunction(term)) { 586 return term; 587 } else if (JXG.isNumber(term)) { 588 return function() { return term; }; 589 } else if (JXG.isString(term)) { // In case of string function like fontsize 590 return function() { return term; }; 591 } 592 return null; 593 }; 594 595 /** 596 * Checks given parents array against expectations. To be implemented 597 * @param {Array} parents A parents array 598 * @param {Array} expects TODO: describe this 599 * @returns {Array} A new parents array prepared for the use within a create* method 600 */ 601 JXG.checkParents = function(parents, expects) { 602 /* 603 structure of expects, e.g. for midpoint: 604 605 idea 1: 606 [ 607 [ 608 JXG.OBJECT_CLASS_POINT, 609 JXG.OBJECT_CLASS_POINT 610 ], 611 [ 612 JXG.OBJECT_CLASS_LINE 613 ] 614 ] 615 616 this is good for describing what is expected, but this way the parent elements 617 can't be sorted. how is this method supposed to know, that in case of a line it 618 has to return the line's defining points? 619 620 621 see below the commented out functions checkParameter and readParameter for 622 another idea from alfred. 623 */ 624 }; 625 626 /* 627 JXG.checkParameter = function(board, parameter, input, output) { 628 var r; 629 if (input=='point') { 630 if (JXG.isPoint(input) && output=='point') { return parameter; } 631 if (JXG.isString(input) && output=='point') { 632 r = JXG.getReference(board,parameter); 633 if (JXG.isString(r)) { return false; } else { return r; } 634 } 635 } else if (input=='array') { 636 if (JXG.isArray(input) && output=='point') { 637 return = board.create('point', parameter, {visible:false,fixed:true}); 638 } 639 } else if (input=='line') { 640 ... 641 } 642 } 643 644 JXG.readParameter = function(board, parameter, input, output) { 645 var i, j, lenOut = output.length, 646 len, result; 647 648 if (lenOut==1) { 649 len = input.length; 650 for (j=0;j<len;j++) { 651 result = JXG.checkParameter(board, parameter, input[j], output[0]); 652 if (result!=false) return result; 653 } 654 } else { 655 for (i=0;i<lenOut;i++) { 656 len = input[i].length; 657 for (j=0;j<len;j++) { 658 result = JXG.checkParameter(board, parameter, input[i][j], output[i]); 659 if (result!=false) return result; 660 } 661 } 662 } 663 return false; 664 }; 665 */ 666 667 /** 668 * Reads the configuration parameter of an attribute of an element from a {@link JXG.Options} object 669 * @param {JXG.Options} options Reference to an instance of JXG.Options. You usually want to use the 670 * options property of your board. 671 * @param {String} element The name of the element which options you wish to read, e.g. 'point' or 672 * 'elements' for general attributes. 673 * @param {String} key The name of the attribute to read, e.g. 'strokeColor' or 'withLabel' 674 * @returns The value of the selected configuration parameter. 675 * @see JXG.Options 676 */ 677 JXG.readOption = function(options, element, key) { 678 var val = options.elements[key]; 679 680 if (JXG.exists(options[element][key])) 681 val = options[element][key]; 682 683 return val; 684 }; 685 686 /** 687 * Checks an attributes object and fills it with default values if there are properties missing. 688 * @param {Object} attributes 689 * @param {Object} defaults 690 * @returns {Object} The given attributes object with missing properties added via the defaults object. 691 */ 692 JXG.checkAttributes = function(attributes, defaults) { 693 var key; 694 695 // Make sure attributes is an object. 696 if (!JXG.exists(attributes)) { 697 attributes = {}; 698 } 699 700 // Go through all keys of defaults and check for their existence 701 // in attributes. If one doesn't exist, it is created with the 702 // same value as in defaults. 703 for (key in defaults) { 704 if(!JXG.exists(attributes[key])) { 705 attributes[key] = defaults[key]; 706 } 707 } 708 709 return attributes; 710 }; 711 712 /** 713 * Reads the width and height of an HTML element. 714 * @param {String} elementId The HTML id of an HTML DOM node. 715 * @returns {Object} An object with the two properties width and height. 716 */ 717 JXG.getDimensions = function(elementId) { 718 var element, display, els, originalVisibility, originalPosition, 719 originalDisplay, originalWidth, originalHeight; 720 721 // Borrowed from prototype.js 722 element = document.getElementById(elementId); 723 if (!JXG.exists(element)) { 724 throw new Error("\nJSXGraph: HTML container element '" + (elementId) + "' not found."); 725 } 726 727 display = element.style['display']; 728 if (display != 'none' && display != null) {// Safari bug 729 return {width: element.offsetWidth, height: element.offsetHeight}; 730 } 731 732 // All *Width and *Height properties give 0 on elements with display set to none, 733 // hence we show the element temporarily 734 els = element.style; 735 736 // save style 737 originalVisibility = els.visibility; 738 originalPosition = els.position; 739 originalDisplay = els.display; 740 741 // show element 742 els.visibility = 'hidden'; 743 els.position = 'absolute'; 744 els.display = 'block'; 745 746 // read the dimension 747 originalWidth = element.clientWidth; 748 originalHeight = element.clientHeight; 749 750 // restore original css values 751 els.display = originalDisplay; 752 els.position = originalPosition; 753 els.visibility = originalVisibility; 754 755 return { 756 width: originalWidth, 757 height: originalHeight 758 }; 759 }; 760 761 /** 762 * Adds an event listener to a DOM element. 763 * @param {Object} obj Reference to a DOM node. 764 * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'. 765 * @param {Function} fn The function to call when the event is triggered. 766 * @param {Object} owner The scope in which the event trigger is called. 767 */ 768 JXG.addEvent = function( obj, type, fn, owner ) { 769 owner['x_internal'+type] = function() {return fn.apply(owner,arguments);}; 770 771 if (JXG.exists(obj.addEventListener)) { // Non-IE browser 772 obj.addEventListener(type, owner['x_internal'+type], false); 773 } else { // IE 774 obj.attachEvent('on'+type, owner['x_internal'+type]); 775 } 776 }; 777 778 /** 779 * Removes an event listener from a DOM element. 780 * @param {Object} obj Reference to a DOM node. 781 * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'. 782 * @param {Function} fn The function to call when the event is triggered. 783 * @param {Object} owner The scope in which the event trigger is called. 784 */ 785 JXG.removeEvent = function( obj, type, fn, owner ) { 786 try { 787 if (JXG.exists(obj.addEventListener)) { // Non-IE browser 788 obj.removeEventListener(type, owner['x_internal'+type], false); 789 } else { // IE 790 obj.detachEvent('on'+type, owner['x_internal'+type]); 791 } 792 } catch(e) { 793 JXG.debug('JSXGraph: Can\'t remove event listener on' + type + ': ' + owner['x_internal' + type]); 794 } 795 }; 796 797 /** 798 * Generates a function which calls the function fn in the scope of owner. 799 * @param {Function} fn Function to call. 800 * @param {Object} owner Scope in which fn is executed. 801 * @returns {Function} A function with the same signature as fn. 802 */ 803 JXG.bind = function(fn, owner) { 804 return function() { 805 return fn.apply(owner,arguments); 806 }; 807 }; 808 809 /** 810 * Cross browser mouse coordinates retrieval relative to the board's top left corner. 811 * @param {Object} [e] The browsers event object. If omitted, <tt>window.event</tt> will be used. 812 * @returns {Array} Contains the position as x,y-coordinates in the first resp. second component. 813 */ 814 JXG.getPosition = function (e) { 815 var posx = 0, 816 posy = 0; 817 818 if (!e) { 819 e = window.event; 820 } 821 822 if (e.pageX || e.pageY) { 823 posx = e.pageX; 824 posy = e.pageY; 825 } else if (e.clientX || e.clientY) { 826 posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 827 posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 828 } 829 830 return [posx,posy]; 831 }; 832 833 /** 834 * Calculates recursively the offset of the DOM element in which the board is stored. 835 * @param {Object} obj A DOM element 836 * @returns {Array} An array with the elements left and top offset. 837 */ 838 JXG.getOffset = function (obj) { 839 var o=obj, 840 l=o.offsetLeft, 841 t=o.offsetTop; 842 843 while(o=o.offsetParent) { 844 l+=o.offsetLeft; 845 t+=o.offsetTop; 846 if(o.offsetParent) { 847 l+=o.clientLeft; 848 t+=o.clientTop; 849 } 850 } 851 return [l,t]; 852 }; 853 854 /** 855 * Access CSS style sheets. 856 * @param {Object} obj A DOM element 857 * @param {String} stylename The CSS property to read. 858 * @returns The value of the CSS property and <tt>undefined</tt> if it is not set. 859 */ 860 JXG.getStyle = function (obj, stylename) { 861 return obj.style[stylename]; 862 }; 863 864 /** 865 * Extracts the keys of a given object. 866 * @param object The object the keys are to be extracted 867 * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected 868 * the object owns itself and not some other object in the prototype chain. 869 * @returns {Array} All keys of the given object. 870 */ 871 JXG.keys = function(object, onlyOwn) { 872 var keys = [], property; 873 874 for (property in object) { 875 if(onlyOwn) { 876 if(object.hasOwnProperty(property)) { 877 keys.push(property); 878 } 879 } else { 880 keys.push(property); 881 } 882 } 883 return keys; 884 }; 885 886 /** 887 * Replaces all occurences of & by &, > by >, and < by <. 888 * @param str 889 */ 890 JXG.escapeHTML = function(str) { 891 return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); 892 }; 893 894 /** 895 * Eliminates all substrings enclosed by < and > and replaces all occurences of 896 * & by &, > by >, and < by <. 897 * @param str 898 */ 899 JXG.unescapeHTML = function(str) { 900 return str.replace(/<\/?[^>]+>/gi, '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); 901 }; 902 903 /** 904 * This outputs an object with a base class reference to the given object. This is useful if 905 * you need a copy of an e.g. attributes object and want to overwrite some of the attributes 906 * without changing the original object. 907 * @param {Object} obj Object to be embedded. 908 * @returns {Object} An object with a base class reference to <tt>obj</tt>. 909 */ 910 JXG.clone = function(obj) { 911 var cObj = {}; 912 cObj.prototype = obj; 913 return cObj; 914 }; 915 916 /** 917 * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object 918 * to the new one. Warning: The copied properties of obj2 are just flat copies. 919 * @param {Object} obj Object to be copied. 920 * @param {Object} obj2 Object with data that is to be copied to the new one as well. 921 * @returns {Object} Copy of given object including some new/overwritten data from obj2. 922 */ 923 JXG.cloneAndCopy = function(obj, obj2) { 924 var cObj = {}, r; 925 cObj.prototype = obj; 926 for(r in obj2) 927 cObj[r] = obj2[r]; 928 929 return cObj; 930 }; 931 932 /** 933 * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp. 934 * element-wise instead of just copying the reference. 935 * @param {Object} obj This object will be copied. 936 * @returns {Object} Ccopy of obj. 937 */ 938 JXG.deepCopy = function(obj) { 939 var c, i, prop, j; 940 941 if (typeof obj !== 'object' || obj == null) { 942 return obj; 943 } 944 if (this.isArray(obj)) { 945 c = []; 946 for (i=0; i<obj.length; i++) { 947 prop = obj[i]; 948 if (typeof prop == 'object') { 949 if (this.isArray(prop)) { 950 c[i] = []; 951 for (j = 0; j < prop.length; j++) { 952 if (typeof prop[j] != 'object') { 953 c[i].push(prop[j]); 954 } else { 955 c[i].push(this.deepCopy(prop[j])); 956 } 957 } 958 } else { 959 c[i] = this.deepCopy(prop); 960 } 961 } else { 962 c[i] = prop; 963 } 964 } 965 } else { 966 c = {}; 967 for (i in obj) { 968 prop = obj[i]; 969 if (typeof prop == 'object') { 970 if (this.isArray(prop)) { 971 c[i] = []; 972 for (j = 0; j < prop.length; j++) { 973 if (typeof prop[j] != 'object') { 974 c[i].push(prop[j]); 975 } else { 976 c[i].push(this.deepCopy(prop[j])); 977 } 978 } 979 } else { 980 c[i] = this.deepCopy(prop); 981 } 982 } else { 983 c[i] = prop; 984 } 985 } 986 } 987 return c; 988 }; 989 990 /** 991 * Converts a JavaScript object into a JSON string. 992 * @param {Object} obj A JavaScript object, functions will be ignored. 993 * @returns {String} The given object stored in a JSON string. 994 */ 995 JXG.toJSON = function(obj) { 996 var s; 997 998 // check for native JSON support: 999 if(window.JSON && window.JSON.stringify) { 1000 try { 1001 s = JSON.stringify(obj); 1002 return s; 1003 } catch(e) { 1004 // if something goes wrong, e.g. if obj contains functions we won't return 1005 // and use our own implementation as a fallback 1006 } 1007 } 1008 1009 switch (typeof obj) { 1010 case 'object': 1011 if (obj) { 1012 var list = []; 1013 if (obj instanceof Array) { 1014 for (var i=0;i < obj.length;i++) { 1015 list.push(JXG.toJSON(obj[i])); 1016 } 1017 return '[' + list.join(',') + ']'; 1018 } else { 1019 for (var prop in obj) { 1020 list.push('"' + prop + '":' + JXG.toJSON(obj[prop])); 1021 } 1022 return '{' + list.join(',') + '}'; 1023 } 1024 } else { 1025 return 'null'; 1026 } 1027 case 'string': 1028 return '"' + obj.replace(/(["'])/g, '\\$1') + '"'; 1029 case 'number': 1030 case 'boolean': 1031 return new String(obj); 1032 } 1033 }; 1034 1035 /** 1036 * Makes a string lower case except for the first character which will be upper case. 1037 * @param {String} str Arbitrary string 1038 * @returns {String} The capitalized string. 1039 */ 1040 JXG.capitalize = function(str) { 1041 return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); 1042 }; 1043 1044 /** 1045 * Process data in timed chunks. Data which takes long to process, either because it is such 1046 * a huge amount of data or the processing takes some time, causes warnings in browsers about 1047 * irresponsive scripts. To prevent these warnings, the processing is split into smaller pieces 1048 * called chunks which will be processed in serial order. 1049 * Copyright 2009 Nicholas C. Zakas. All rights reserved. MIT Licensed 1050 * @param {Array} items to do 1051 * @param {Function} process Function that is applied for every array item 1052 * @param {Object} context The scope of function process 1053 * @param {Function} callback This function is called after the last array element has been processed. 1054 */ 1055 JXG.timedChunk = function(items, process, context, callback) { 1056 var todo = items.concat(); //create a clone of the original 1057 setTimeout(function(){ 1058 var start = +new Date(); 1059 do { 1060 process.call(context, todo.shift()); 1061 } while (todo.length > 0 && (+new Date() - start < 300)); 1062 if (todo.length > 0){ 1063 setTimeout(arguments.callee, 1); 1064 } else { 1065 callback(items); 1066 } 1067 }, 1); 1068 }; 1069 1070 /** 1071 * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes. 1072 * @param {String} str 1073 * @returns {String} 1074 */ 1075 JXG.trimNumber = function(str) { 1076 str = str.replace(/^0+/, ""); 1077 str = str.replace(/0+$/, ""); 1078 if(str[str.length-1] == '.' || str[str.length-1] == ',') { 1079 str = str.slice(0, -1); 1080 } 1081 if(str[0] == '.' || str[0] == ',') { 1082 str = "0" + str; 1083 } 1084 1085 return str; 1086 }; 1087 1088 /** 1089 * Remove all leading and trailing whitespaces from a given string. 1090 * @param {String} str 1091 * @returns {String} 1092 */ 1093 JXG.trim = function(str) { 1094 str = str.replace(/^w+/, ""); 1095 str = str.replace(/w+$/, ""); 1096 1097 return str; 1098 }; 1099 1100 /** 1101 * Add something to the debug log. If available a JavaScript debug console is used. Otherwise 1102 * we're looking for a HTML div with id "debug". If this doesn't exist, too, the output is omitted. 1103 * @param {String} s A debug message. 1104 */ 1105 JXG.debug = function(s) { 1106 if (console && console.log) { 1107 if (typeof s === 'string') s = s.replace(/<\S[^><]*>/g, "") 1108 console.log(s); 1109 } else if (document.getElementById('debug')) { 1110 document.getElementById('debug').innerHTML += s + "<br/>"; 1111 } 1112 // else: do nothing 1113 }; 1114 1115 1116 // JessieScript startup: Search for script tags of type text/jessiescript and interpret them. 1117 JXG.addEvent(window, 'load', function () { 1118 var scripts = document.getElementsByTagName('script'), type, 1119 i, j, div, board, width, height, bbox, axis, grid; 1120 1121 for(i=0;i<scripts.length;i++) { 1122 type = scripts[i].getAttribute('type', false); 1123 if (!JXG.exists(type)) continue; 1124 if(type.toLowerCase() === 'text/jessiescript' || type.toLowerCase === 'jessiescript') { 1125 width = scripts[i].getAttribute('width', false) || '500px'; 1126 height = scripts[i].getAttribute('height', false) || '500px'; 1127 bbox = scripts[i].getAttribute('boundingbox', false) || '-5, 5, 5, -5'; 1128 bbox = bbox.split(','); 1129 if(bbox.length!==4) { 1130 bbox = [-5, 5, 5, -5]; 1131 } else { 1132 for(j=0;j<bbox.length;j++) { 1133 bbox[j] = parseFloat(bbox[j]); 1134 } 1135 } 1136 axis = JXG.str2Bool(scripts[i].getAttribute('axis', false) || 'false'); 1137 grid = JXG.str2Bool(scripts[i].getAttribute('grid', false) || 'false'); 1138 1139 div = document.createElement('div'); 1140 div.setAttribute('id', 'jessiescript_autgen_jxg_'+i); 1141 div.setAttribute('style', 'width:'+width+'; height:'+height+'; float:left'); 1142 div.setAttribute('class', 'jxgbox'); 1143 document.body.insertBefore(div, scripts[i]); 1144 1145 board = JXG.JSXGraph.initBoard('jessiescript_autgen_jxg_'+i, {boundingbox: bbox, keepaspectratio:true, grid: grid, axis: axis}); 1146 board.construct(scripts[i].innerHTML); 1147 } 1148 } 1149 }, window); 1150