1 /* 2 Copyright 2008-2026 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, document:true, jQuery:true, define: true, window: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview The JSXGraph object is defined in this file. JXG.JSXGraph controls all boards. 37 * It has methods to create, save, load and free boards. Additionally some helper functions are 38 * defined in this file directly in the JXG namespace. 39 * 40 */ 41 42 import JXG from "./jxg.js"; 43 import Env from "./utils/env.js"; 44 import Type from "./utils/type.js"; 45 // import Mat from "./math/math.js"; 46 import Board from "./base/board.js"; 47 import FileReader from "./reader/file.js"; 48 import Options from "./options.js"; 49 import SVGRenderer from "./renderer/svg.js"; 50 import VMLRenderer from "./renderer/vml.js"; 51 import CanvasRenderer from "./renderer/canvas.js"; 52 import NoRenderer from "./renderer/no.js"; 53 54 /** 55 * Constructs a new JSXGraph singleton object. 56 * @class The JXG.JSXGraph singleton stores all properties required 57 * to load, store, create and free a board. 58 */ 59 JXG.JSXGraph = { 60 /** 61 * Stores the renderer that is used to draw the boards. 62 * @type String 63 */ 64 rendererType: (function () { 65 Options.board.renderer = 'no'; 66 67 if (Env.supportsVML()) { 68 Options.board.renderer = 'vml'; 69 // Ok, this is some real magic going on here. IE/VML always was so 70 // terribly slow, except in one place: Examples placed in a moodle course 71 // was almost as fast as in other browsers. So i grabbed all the css and 72 // lib scripts from our moodle, added them to a jsxgraph example and it 73 // worked. next step was to strip all the css/lib code which didn't affect 74 // the VML update speed. The following five lines are what was left after 75 // the last step and yes - it basically does nothing but reads two 76 // properties of document.body on every mouse move. why? we don't know. if 77 // you know, please let us know. 78 // 79 // If we want to use the strict mode we have to refactor this a little bit. Let's 80 // hope the magic isn't gone now. Anywho... it's only useful in old versions of IE 81 // which should not be used anymore. 82 document.onmousemove = function () { 83 var t; 84 85 if (document.body) { 86 t = document.body.scrollLeft; 87 t += document.body.scrollTop; 88 } 89 90 return t; 91 }; 92 } 93 94 if (Env.supportsCanvas()) { 95 Options.board.renderer = 'canvas'; 96 } 97 98 if (Env.supportsSVG()) { 99 Options.board.renderer = 'svg'; 100 } 101 102 // we are inside node 103 if (Env.isNode() && Env.supportsCanvas()) { 104 Options.board.renderer = 'canvas'; 105 } 106 107 if (Env.isNode() || Options.renderer === 'no') { 108 Options.text.display = 'internal'; 109 Options.infobox.display = 'internal'; 110 } 111 112 return Options.board.renderer; 113 })(), 114 115 /** 116 * Initialize the rendering engine 117 * 118 * @param {String} box id of or reference to the div element which hosts the JSXGraph construction 119 * @param {Object} dim The dimensions of the board 120 * @param {Object} doc Usually, this is document object of the browser window. If false or null, this defaults 121 * to the document object of the browser. 122 * @param {Object} attrRenderer Attribute 'renderer', specifies the rendering engine. Possible values are 'auto', 'svg', 123 * 'canvas', 'no', and 'vml'. 124 * @returns {Object} Reference to the rendering engine object. 125 * @private 126 */ 127 initRenderer: function (box, dim, doc, attrRenderer) { 128 var boxid, renderer, containerId; 129 130 // Former version: 131 // doc = doc || document 132 if ((!Type.exists(doc) || doc === false) && typeof document === 'object') { 133 doc = document; 134 } 135 136 if (typeof doc === "object" && box !== null) { 137 boxid = (Type.isString(box)) ? doc.getElementById(box) : box; 138 139 // SVG renderer resource names use the container ID as their prefix. Generate one 140 // before constructing the renderer so anonymous element references cannot create 141 // duplicate clip paths, filters, or navigation IDs. 142 if (boxid && !boxid.id) { 143 containerId = 1; 144 while (doc.getElementById("jxgbox" + containerId)) { 145 containerId += 1; 146 } 147 boxid.id = "jxgbox" + containerId; 148 } 149 150 // Remove everything from the container before initializing the renderer and the board 151 while (boxid.firstChild) { 152 boxid.removeChild(boxid.firstChild); 153 } 154 } else { 155 boxid = box; 156 } 157 158 // If attrRenderer is not supplied take the first available renderer 159 if (attrRenderer === undefined || attrRenderer === 'auto') { 160 attrRenderer = this.rendererType; 161 } 162 // create the renderer 163 if (attrRenderer === 'svg') { 164 renderer = new SVGRenderer(boxid, dim); 165 } else if (attrRenderer === 'vml') { 166 renderer = new VMLRenderer(boxid); 167 } else if (attrRenderer === 'canvas') { 168 renderer = new CanvasRenderer(boxid, dim); 169 } else { 170 renderer = new NoRenderer(); 171 } 172 173 return renderer; 174 }, 175 176 /** 177 * Merge the user supplied attributes with the attributes in options.js 178 * 179 * @param {Object} attributes User supplied attributes 180 * @returns {Object} Merged attributes for the board 181 * 182 * @private 183 */ 184 _setAttributes: function (attributes, options) { 185 // merge attributes 186 var attr = Type.copyAttributes(attributes, options, 'board'), 187 188 // These attributes - which are objects - have to be copied separately. 189 list = [ 190 'drag', 'fullscreen', 191 'intl', 192 'keyboard', 'logging', 193 'pan', 'resize', 194 'screenshot', 'selection', 195 'zoom' 196 ], 197 len = list.length, i, key; 198 199 for (i = 0; i < len; i++) { 200 key = list[i]; 201 attr[key] = Type.copyAttributes(attr, options, 'board', key); 202 } 203 attr.navbar = Type.copyAttributes(attr.navbar, options, 'navbar'); 204 205 // Treat moveTarget separately, because deepCopy will not work here. 206 // Reason: moveTarget will be an HTML node and it is prevented that Type.deepCopy will copy it. 207 attr.movetarget = 208 attributes.moveTarget || attributes.movetarget || options.board.moveTarget; 209 210 return attr; 211 }, 212 213 /** 214 * Further initialization of the board. Set some properties from attribute values. 215 * 216 * @param {JXG.Board} board 217 * @param {Object} attr attributes object 218 * @param {Object} dimensions Object containing dimensions of the canvas 219 * 220 * @private 221 */ 222 _fillBoard: function (board, attr, dimensions) { 223 board.initInfobox(attr.infobox); 224 board.maxboundingbox = attr.maxboundingbox; 225 board.resizeContainer(dimensions.width, dimensions.height, true, true); 226 board._createSelectionPolygon(attr); 227 board.renderer.drawNavigationBar(board, attr.navbar); 228 229 JXG.boards[board.id] = board; 230 }, 231 232 /** 233 * 234 * @param {String|Object} container id of or reference to the HTML element in which the board is painted. 235 * @param {Object} attr An object that sets some of the board properties. 236 * 237 * @private 238 */ 239 _setARIA: function (container, attr) { 240 var doc = attr.document, 241 node_jsx; 242 // Unused variables, made obsolete in db3e50f4dfa8b86b1ff619b578e243a97b41151c 243 // doc_glob, 244 // newNode, 245 // parent, 246 // id_label, 247 // id_description; 248 249 if (typeof doc !== 'object') { 250 if (!Env.isBrowser) { 251 return; 252 } 253 doc = document; 254 } 255 256 node_jsx = (Type.isString(container)) ? doc.getElementById(container) : container; 257 node_jsx.setAttribute("role", 'region'); 258 node_jsx.setAttribute("aria-label", attr.title); // set by initBoard( {title:}) 259 260 // doc_glob = node_jsx.ownerDocument; // This is the window.document element, needed below. 261 // parent = node_jsx.parentNode; 262 263 }, 264 265 /** 266 * Remove the two corresponding ARIA divs when freeing a board 267 * 268 * @param {JXG.Board} board 269 * 270 * @private 271 */ 272 _removeARIANodes: function (board) { 273 var node, id, doc; 274 275 doc = board.document || document; 276 if (typeof doc !== 'object') { 277 return; 278 } 279 280 id = board.containerObj.getAttribute("aria-labelledby"); 281 node = doc.getElementById(id); 282 if (node && node.parentNode) { 283 node.parentNode.removeChild(node); 284 } 285 id = board.containerObj.getAttribute("aria-describedby"); 286 node = doc.getElementById(id); 287 if (node && node.parentNode) { 288 node.parentNode.removeChild(node); 289 } 290 }, 291 292 /** 293 * Initialize a new board. 294 * 295 * @param {String|Object} box id of or reference to the HTML element in which the board is painted. 296 * @param {Object} attributes An object that sets some of the board properties. 297 * See {@link JXG.Board} for a list of available attributes of the board. 298 * Most of these attributes can also be set via {@link JXG.Options}, 299 * 300 * @returns {JXG.Board} Reference to the created board. 301 * 302 * @see JXG.AbstractRenderer#drawNavigationBar 303 * @example 304 * var board = JXG.JSXGraph.initBoard('jxgbox', { 305 * boundingbox: [-10, 5, 10, -5], 306 * keepaspectratio: false, 307 * axis: true 308 * }); 309 * 310 * </pre><div id="JXGc0f76e98-20bc-4224-9016-7ffa10770dff" class="jxgbox" style="width: 600px; height: 300px;"></div> 311 * <script type="text/javascript"> 312 * (function() { 313 * var board = JXG.JSXGraph.initBoard('JXGc0f76e98-20bc-4224-9016-7ffa10770dff', { 314 * boundingbox: [-10, 5, 10, -5], 315 * keepaspectratio: false, 316 * axis: true 317 * }); 318 * 319 * })(); 320 * 321 * </script><pre> 322 * 323 * 324 * @example 325 * const board = JXG.JSXGraph.initBoard('jxgbox', { 326 * boundingbox: [-10, 10, 10, -10], 327 * axis: true, 328 * showCopyright: true, 329 * showFullscreen: true, 330 * showScreenshot: false, 331 * showClearTraces: false, 332 * showInfobox: false, 333 * showNavigation: true, 334 * grid: false, 335 * defaultAxes: { 336 * x: { 337 * withLabel: true, 338 * label: { 339 * position: '95% left', 340 * offset: [-10, 10] 341 * }, 342 * lastArrow: { 343 * type: 4, 344 * size: 10 345 * } 346 * }, 347 * y: { 348 * withLabel: true, 349 * label: { 350 * position: '0.90fr right', 351 * offset: [6, -6] 352 * }, 353 * lastArrow: { 354 * type: 4, 355 * size: 10 356 * } 357 * } 358 * } 359 * }); 360 * 361 * </pre><div id="JXG4ced167d-3235-48bc-84e9-1a28fce00f6a" class="jxgbox" style="width: 300px; height: 300px;"></div> 362 * <script type="text/javascript"> 363 * (function() { 364 * var board = JXG.JSXGraph.initBoard('JXG4ced167d-3235-48bc-84e9-1a28fce00f6a', { 365 * boundingbox: [-10, 10, 10, -10], 366 * axis: true, 367 * showCopyright: true, 368 * showFullscreen: true, 369 * showScreenshot: false, 370 * showClearTraces: false, 371 * showInfobox: false, 372 * showNavigation: true, 373 * grid: false, 374 * defaultAxes: { 375 * x: { 376 * withLabel: true, 377 * label: { 378 * position: '95% left', 379 * offset: [0, 0] 380 * }, 381 * lastArrow: { 382 * type: 4, 383 * size: 10 384 * } 385 * }, 386 * y: { 387 * withLabel: true, 388 * label: { 389 * position: '0.90fr right', 390 * offset: [0, 0] 391 * }, 392 * lastArrow: { 393 * type: 4, 394 * size: 10 395 * } 396 * } 397 * } 398 * }); 399 * 400 * })(); 401 * 402 * </script><pre> 403 * @example 404 * const board = JXG.JSXGraph.initBoard('jxgbox', { 405 * boundingbox: [-5, 5, 5, -5], 406 * intl: { 407 * enabled: false, 408 * locale: 'en-EN' 409 * }, 410 * keepaspectratio: true, 411 * axis: true, 412 * defaultAxes: { 413 * x: { 414 * ticks: { 415 * intl: { 416 * enabled: true, 417 * options: { 418 * style: 'unit', 419 * unit: 'kilometer-per-hour', 420 * unitDisplay: 'narrow' 421 * } 422 * } 423 * } 424 * }, 425 * y: { 426 * ticks: { 427 * } 428 * } 429 * }, 430 * infobox: { 431 * fontSize: 20, 432 * intl: { 433 * enabled: true, 434 * options: { 435 * minimumFractionDigits: 4, 436 * maximumFractionDigits: 5 437 * } 438 * } 439 * } 440 * }); 441 * 442 * </pre><div id="JXGdac54e59-f1e8-4fa6-bbcc-7486f7f6f960" class="jxgbox" style="width: 600px; height: 600px;"></div> 443 * <script type="text/javascript"> 444 * (function() { 445 * var board = JXG.JSXGraph.initBoard('JXGdac54e59-f1e8-4fa6-bbcc-7486f7f6f960', { 446 * boundingbox: [-5, 5, 5, -5], 447 * intl: { 448 * enabled: false, 449 * locale: 'en-EN' 450 * }, 451 * keepaspectratio: true, 452 * axis: true, 453 * defaultAxes: { 454 * x: { 455 * ticks: { 456 * intl: { 457 * enabled: true, 458 * options: { 459 * style: 'unit', 460 * unit: 'kilometer-per-hour', 461 * unitDisplay: 'narrow' 462 * } 463 * } 464 * } 465 * }, 466 * y: { 467 * ticks: { 468 * } 469 * } 470 * }, 471 * infobox: { 472 * fontSize: 20, 473 * intl: { 474 * enabled: true, 475 * options: { 476 * minimumFractionDigits: 4, 477 * maximumFractionDigits: 5 478 * } 479 * } 480 * } 481 * }); 482 * 483 * })(); 484 * 485 * </script><pre> 486 * 487 * 488 */ 489 // * 490 // * @param {Array} [attributes.boundingbox=[-5, 5, 5, -5]] An array containing four numbers describing the left, top, right and bottom boundary of the board in user coordinates 491 // * @param {Boolean} [attributes.keepaspectratio=false] If <tt>true</tt>, the bounding box is adjusted to the same aspect ratio as the aspect ratio of the div containing the board. 492 // * @param {Boolean} [attributes.showCopyright=false] Show the copyright string in the top left corner. 493 // * @param {Boolean} [attributes.showNavigation=false] Show the navigation buttons in the bottom right corner. 494 // * @param {Object} [attributes.zoom] Allow the user to zoom with the mouse wheel or the two-fingers-zoom gesture. 495 // * @param {Object} [attributes.pan] Allow the user to pan with shift+drag mouse or two-fingers-pan gesture. 496 // * @param {Object} [attributes.drag] Allow the user to drag objects with a pointer device. 497 // * @param {Object} [attributes.keyboard] Allow the user to drag objects with arrow keys on keyboard. 498 // * @param {Boolean} [attributes.axis=false] If set to true, show the axis. Can also be set to an object that is given to both axes as an attribute object. 499 // * @param {Boolean|Object} [attributes.grid] If set to true, shows the grid. Can also be set to an object that is given to the grid as its attribute object. 500 // * @param {Boolean} [attributes.registerEvents=true] Register mouse / touch events. 501 initBoard: function (box, attributes) { 502 var originX, originY, unitX, unitY, w, h, 503 offX = 0, offY = 0, 504 renderer, dimensions, bbox, 505 attr, axattr, axattr_x, axattr_y, 506 options, 507 theme = {}, 508 board; 509 510 attributes = attributes || {}; // User supplied attributes 511 // Merge a possible theme 512 if (attributes.theme !== 'default' && Type.exists(JXG.themes[attributes.theme])) { 513 theme = JXG.themes[attributes.theme]; 514 } 515 options = Type.deepCopy(Options, theme, true); // Copy global options 516 attr = this._setAttributes(attributes, options); // Merge user supplied attributes into global options 517 518 dimensions = Env.getDimensions(box, attr.document); 519 520 if (attr.unitx || attr.unity) { 521 originX = Type.def(attr.originx, 150); 522 originY = Type.def(attr.originy, 150); 523 unitX = Type.def(attr.unitx, 50); 524 unitY = Type.def(attr.unity, 50); 525 } else { 526 bbox = attr.boundingbox; 527 if (bbox[0] < attr.maxboundingbox[0]) { 528 bbox[0] = attr.maxboundingbox[0]; 529 } 530 if (bbox[1] > attr.maxboundingbox[1]) { 531 bbox[1] = attr.maxboundingbox[1]; 532 } 533 if (bbox[2] > attr.maxboundingbox[2]) { 534 bbox[2] = attr.maxboundingbox[2]; 535 } 536 if (bbox[3] < attr.maxboundingbox[3]) { 537 bbox[3] = attr.maxboundingbox[3]; 538 } 539 540 // Size of HTML div. 541 // If zero, the size is set to a small value to avoid 542 // division by zero. 543 // w = Math.max(parseInt(dimensions.width, 10), Mat.eps); 544 // h = Math.max(parseInt(dimensions.height, 10), Mat.eps); 545 w = parseInt(dimensions.width, 10); 546 h = parseInt(dimensions.height, 10); 547 548 if (Type.exists(bbox) && attr.keepaspectratio) { 549 /* 550 * If the boundingbox attribute is given and the ratio of height and width of the 551 * sides defined by the bounding box and the ratio of the dimensions of the div tag 552 * which contains the board do not coincide, then the smaller side is chosen. 553 */ 554 unitX = w / (bbox[2] - bbox[0]); 555 unitY = h / (bbox[1] - bbox[3]); 556 557 if (Math.abs(unitX) < Math.abs(unitY)) { 558 unitY = (Math.abs(unitX) * unitY) / Math.abs(unitY); 559 // Add the additional units in equal portions above and below 560 offY = (h / unitY - (bbox[1] - bbox[3])) * 0.5; 561 } else { 562 unitX = (Math.abs(unitY) * unitX) / Math.abs(unitX); 563 // Add the additional units in equal portions left and right 564 offX = (w / unitX - (bbox[2] - bbox[0])) * 0.5; 565 } 566 } else { 567 unitX = w / (bbox[2] - bbox[0]); 568 unitY = h / (bbox[1] - bbox[3]); 569 } 570 originX = -unitX * (bbox[0] - offX); 571 originY = unitY * (bbox[1] + offY); 572 } 573 574 renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer); 575 this._setARIA(box, attr); 576 577 // Create the board. 578 // board.options will contain the user supplied board attributes 579 board = new Board( 580 box, 581 renderer, 582 attr.id, 583 [originX, originY], 584 /*attr.zoomfactor * */ attr.zoomx, 585 /*attr.zoomfactor * */ attr.zoomy, 586 unitX, 587 unitY, 588 dimensions.width, 589 dimensions.height, 590 attr 591 ); 592 593 board.keepaspectratio = attr.keepaspectratio; 594 595 this._fillBoard(board, attr, dimensions); 596 597 // Create elements like axes, grid, navigation, ... 598 board.suspendUpdate(); 599 attr = board.attr; 600 if (attr.axis) { 601 axattr = typeof attr.axis === "object" ? attr.axis : {}; 602 603 // The defaultAxes attributes are overwritten by user supplied axis object. 604 axattr_x = Type.deepCopy(options.board.defaultaxes.x, axattr); 605 axattr_y = Type.deepCopy(options.board.defaultaxes.y, axattr); 606 607 // The user supplied defaultAxes attributes are merged in. 608 if (attr.defaultaxes.x) { 609 axattr_x = Type.deepCopy(axattr_x, attr.defaultaxes.x); 610 } 611 if (attr.defaultaxes.y) { 612 axattr_y = Type.deepCopy(axattr_y, attr.defaultaxes.y); 613 } 614 615 board.defaultAxes = {}; 616 board.defaultAxes.x = board.create("axis", [[0, 0], [1, 0]], axattr_x); 617 board.defaultAxes.y = board.create("axis", [[0, 0], [0, 1]], axattr_y); 618 } 619 if (attr.grid) { 620 board.create("grid", [], typeof attr.grid === "object" ? attr.grid : {}); 621 } 622 623 board.sketches = [ 624 board.create('sketchcurve', [], board.attr.sketches[0]), 625 board.create('sketchcurve', [], board.attr.sketches[1]) 626 ]; 627 board.sketches[0].dump = false; 628 board.sketches[1].dump = false; 629 board.sketch = board.sketches[0]; 630 631 board.unsuspendUpdate(); 632 633 // Set CSS styles of JSXGraph div 634 board.setAttribute({cssStyle: attr.cssstyle}, true); 635 636 return board; 637 }, 638 639 /** 640 * Load a board from a file containing a construction made with either GEONExT, 641 * Intergeo, Geogebra, or Cinderella. 642 * @param {String|Object} box id of or reference to the HTML element in which the board is painted. 643 * @param {String} file base64 encoded string. 644 * @param {String} format containing the file format: 'Geonext' or 'Intergeo'. 645 * @param {Object} attributes Attributes for the board and 'encoding'. 646 * Compressed files need encoding 'iso-8859-1'. Otherwise it probably is 'utf-8'. 647 * @param {Function} callback 648 * @returns {JXG.Board} Reference to the created board. 649 * @see JXG.FileReader 650 * @see JXG.GeonextReader 651 * @see JXG.GeogebraReader 652 * @see JXG.IntergeoReader 653 * @see JXG.CinderellaReader 654 * 655 * @example 656 * // Uncompressed file 657 * var board = JXG.JSXGraph.loadBoardFromFile('jxgbox', 'filename', 'geonext', 658 * {encoding: 'utf-8'}, 659 * function (board) { console.log("Done loading"); } 660 * ); 661 * // Compressed file 662 * var board = JXG.JSXGraph.loadBoardFromFile('jxgbox', 'filename', 'geonext', 663 * {encoding: 'iso-8859-1'}, 664 * function (board) { console.log("Done loading"); } 665 * ); 666 * 667 * @example 668 * // From <input type="file" id="localfile" /> 669 * var file = document.getElementById('localfile').files[0]; 670 * JXG.JSXGraph.loadBoardFromFile('jxgbox', file, 'geonext', 671 * {encoding: 'utf-8'}, 672 * function (board) { console.log("Done loading"); } 673 * ); 674 */ 675 loadBoardFromFile: function (box, file, format, attributes, callback) { 676 var attr, renderer, board, dimensions, encoding; 677 678 attributes = attributes || {}; 679 attr = this._setAttributes(attributes); 680 681 dimensions = Env.getDimensions(box, attr.document); 682 renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer); 683 this._setARIA(box, attr); 684 685 /* User default parameters, in parse* the values in the gxt files are submitted to board */ 686 board = new Board( 687 box, 688 renderer, 689 "", 690 [150, 150], 691 1, 692 1, 693 50, 694 50, 695 dimensions.width, 696 dimensions.height, 697 attr 698 ); 699 this._fillBoard(board, attr, dimensions); 700 encoding = attr.encoding || "iso-8859-1"; 701 FileReader.parseFileContent(file, board, format, true, encoding, callback); 702 703 return board; 704 }, 705 706 /** 707 * Load a board from a base64 encoded string containing a construction made with either GEONExT, 708 * Intergeo, Geogebra, or Cinderella. 709 * @param {String|Object} box id of or reference to the HTML element in which the board is painted. 710 * @param {String} string base64 encoded string. 711 * @param {String} format containing the file format: 'Geonext', 'Intergeo', 'Geogebra'. 712 * @param {Object} attributes Attributes for the board and 'encoding'. 713 * Compressed files need encoding 'iso-8859-1'. Otherwise it probably is 'utf-8'. 714 * @param {Function} callback 715 * @returns {JXG.Board} Reference to the created board. 716 * @see JXG.FileReader 717 * @see JXG.GeonextReader 718 * @see JXG.GeogebraReader 719 * @see JXG.IntergeoReader 720 * @see JXG.CinderellaReader 721 */ 722 loadBoardFromString: function (box, string, format, attributes, callback) { 723 var attr, renderer, board, dimensions; 724 725 attributes = attributes || {}; 726 attr = this._setAttributes(attributes); 727 728 dimensions = Env.getDimensions(box, attr.document); 729 renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer); 730 this._setARIA(box, attr); 731 732 /* User default parameters, in parse* the values in the gxt files are submitted to board */ 733 board = new Board( 734 box, 735 renderer, 736 "", 737 [150, 150], 738 1.0, 739 1.0, 740 50, 741 50, 742 dimensions.width, 743 dimensions.height, 744 attr 745 ); 746 this._fillBoard(board, attr, dimensions); 747 FileReader.parseString(string, board, format, true, callback); 748 749 return board; 750 }, 751 752 /** 753 * Delete a board and all its contents. 754 * @param {JXG.Board|String} board id of or reference to the DOM element in which the board is drawn. 755 * 756 */ 757 freeBoard: function (board) { 758 var el; 759 760 if (typeof board === 'string') { 761 board = JXG.boards[board]; 762 } 763 764 this._removeARIANodes(board); 765 board.removeEventHandlers(); 766 board.suspendUpdate(); 767 768 // Remove all objects from the board. 769 for (el in board.objects) { 770 if (board.objects.hasOwnProperty(el)) { 771 board.objects[el].remove(); 772 } 773 } 774 775 // Remove all the other things, left on the board, XHTML save 776 while (board.containerObj.firstChild) { 777 board.containerObj.removeChild(board.containerObj.firstChild); 778 } 779 780 // Tell the browser the objects aren't needed anymore 781 for (el in board.objects) { 782 if (board.objects.hasOwnProperty(el)) { 783 delete board.objects[el]; 784 } 785 } 786 787 // Free the renderer and the algebra object 788 delete board.renderer; 789 790 // clear the creator cache 791 board.jc.creator.clearCache(); 792 delete board.jc; 793 794 // Finally remove the board itself from the boards array 795 delete JXG.boards[board.id]; 796 }, 797 798 /** 799 * @deprecated Use JXG#registerElement 800 * @param element 801 * @param creator 802 */ 803 registerElement: function (element, creator) { 804 JXG.deprecated("JXG.JSXGraph.registerElement()", "JXG.registerElement()"); 805 JXG.registerElement(element, creator); 806 } 807 }; 808 809 // JessieScript/JessieCode startup: 810 // Search for script tags of type text/jessiecode and execute them. 811 if (Env.isBrowser && typeof window === 'object' && typeof document === 'object') { 812 Env.addEvent(window, 'load', 813 function () { 814 var type, i, j, div, id, 815 board, txt, width, height, maxWidth, aspectRatio, 816 cssClasses, bbox, axis, grid, code, src, request, 817 postpone = false, 818 819 scripts = document.getElementsByTagName('script'), 820 init = function (code, type, bbox) { 821 var board = JXG.JSXGraph.initBoard(id, { 822 boundingbox: bbox, 823 keepaspectratio: true, 824 grid: grid, 825 axis: axis, 826 showReload: true 827 }); 828 829 if (type.toLowerCase().indexOf('script') > -1) { 830 board.construct(code); 831 } else { 832 try { 833 board.jc.parse(code); 834 } catch (e2) { 835 JXG.debug(e2); 836 } 837 } 838 839 return board; 840 }, 841 makeReload = function (board, code, type, bbox) { 842 return function () { 843 var newBoard; 844 845 JXG.JSXGraph.freeBoard(board); 846 newBoard = init(code, type, bbox); 847 newBoard.reload = makeReload(newBoard, code, type, bbox); 848 }; 849 }; 850 851 for (i = 0; i < scripts.length; i++) { 852 type = scripts[i].getAttribute("type", false); 853 854 if ( 855 Type.exists(type) && 856 (type.toLowerCase() === "text/jessiescript" || 857 type.toLowerCase() === "jessiescript" || 858 type.toLowerCase() === "text/jessiecode" || 859 type.toLowerCase() === 'jessiecode') 860 ) { 861 cssClasses = scripts[i].getAttribute("class", false) || ""; 862 width = scripts[i].getAttribute("width", false) || ""; 863 height = scripts[i].getAttribute("height", false) || ""; 864 maxWidth = scripts[i].getAttribute("maxwidth", false) || "100%"; 865 aspectRatio = scripts[i].getAttribute("aspectratio", false) || "1/1"; 866 bbox = scripts[i].getAttribute("boundingbox", false) || "-5, 5, 5, -5"; 867 id = scripts[i].getAttribute("container", false); 868 src = scripts[i].getAttribute("src", false); 869 870 bbox = bbox.split(","); 871 if (bbox.length !== 4) { 872 bbox = [-5, 5, 5, -5]; 873 } else { 874 for (j = 0; j < bbox.length; j++) { 875 bbox[j] = parseFloat(bbox[j]); 876 } 877 } 878 axis = Type.str2Bool(scripts[i].getAttribute("axis", false) || 'false'); 879 grid = Type.str2Bool(scripts[i].getAttribute("grid", false) || 'false'); 880 881 if (!Type.exists(id)) { 882 id = "jessiescript_autgen_jxg_" + i; 883 div = document.createElement('div'); 884 div.setAttribute("id", id); 885 886 txt = width !== "" ? "width:" + width + ";" : ""; 887 txt += height !== "" ? "height:" + height + ";" : ""; 888 txt += maxWidth !== "" ? "max-width:" + maxWidth + ";" : ""; 889 txt += aspectRatio !== "" ? "aspect-ratio:" + aspectRatio + ";" : ""; 890 891 div.setAttribute("style", txt); 892 div.setAttribute("class", "jxgbox " + cssClasses); 893 try { 894 document.body.insertBefore(div, scripts[i]); 895 } catch (e) { 896 // there's probably jquery involved... 897 if (Type.exists(jQuery) && typeof jQuery === 'object') { 898 jQuery(div).insertBefore(scripts[i]); 899 } 900 } 901 } else { 902 div = document.getElementById(id); 903 } 904 905 code = ""; 906 907 if (Type.exists(src)) { 908 postpone = true; 909 request = new XMLHttpRequest(); 910 request.open("GET", src); 911 request.overrideMimeType("text/plain; charset=x-user-defined"); 912 /* jshint ignore:start */ 913 request.addEventListener("load", function () { 914 if (this.status < 400) { 915 code = this.responseText + "\n" + code; 916 board = init(code, type, bbox); 917 board.reload = makeReload(board, code, type, bbox); 918 } else { 919 throw new Error( 920 "\nJSXGraph: failed to load file", 921 src, 922 ":", 923 this.responseText 924 ); 925 } 926 }); 927 request.addEventListener("error", function (e) { 928 throw new Error("\nJSXGraph: failed to load file", src, ":", e); 929 }); 930 /* jshint ignore:end */ 931 request.send(); 932 } else { 933 postpone = false; 934 } 935 936 if (document.getElementById(id)) { 937 code = scripts[i].innerHTML; 938 code = code.replace(/<!\[CDATA\[/g, "").replace(/\]\]>/g, ""); 939 scripts[i].innerHTML = code; 940 941 if (!postpone) { 942 // Do no wait for data from "src" attribute 943 board = init(code, type, bbox); 944 board.reload = makeReload(board, code, type, bbox); 945 } 946 } else { 947 JXG.debug( 948 "JSXGraph: Apparently the div injection failed. Can't create a board, sorry." 949 ); 950 } 951 } 952 } 953 }, 954 window 955 ); 956 } 957 958 export default JXG.JSXGraph; 959