1 /* 2 Copyright 2008-2018 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG: true, document:true, jQuery:true, define: true, window: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 utils/env 39 utils/type 40 base/board 41 reader/file 42 options 43 renderer/svg 44 renderer/vml 45 renderer/canvas 46 renderer/no 47 */ 48 49 /** 50 * @fileoverview The JSXGraph object is defined in this file. JXG.JSXGraph controls all boards. 51 * It has methods to create, save, load and free boards. Additionally some helper functions are 52 * defined in this file directly in the JXG namespace. 53 * @version 0.99 54 */ 55 56 define([ 57 'jxg', 'utils/env', 'utils/type', 'base/board', 'reader/file', 'options', 58 'renderer/svg', 'renderer/vml', 'renderer/canvas', 'renderer/no' 59 ], function (JXG, Env, Type, Board, FileReader, Options, SVGRenderer, VMLRenderer, CanvasRenderer, NoRenderer) { 60 61 "use strict"; 62 63 /** 64 * Constructs a new JSXGraph singleton object. 65 * @class The JXG.JSXGraph singleton stores all properties required 66 * to load, save, create and free a board. 67 */ 68 JXG.JSXGraph = { 69 /** 70 * Stores the renderer that is used to draw the boards. 71 * @type String 72 */ 73 rendererType: (function () { 74 Options.board.renderer = 'no'; 75 76 if (Env.supportsVML()) { 77 Options.board.renderer = 'vml'; 78 // Ok, this is some real magic going on here. IE/VML always was so 79 // terribly slow, except in one place: Examples placed in a moodle course 80 // was almost as fast as in other browsers. So i grabbed all the css and 81 // lib scripts from our moodle, added them to a jsxgraph example and it 82 // worked. next step was to strip all the css/lib code which didn't affect 83 // the VML update speed. The following five lines are what was left after 84 // the last step and yes - it basically does nothing but reads two 85 // properties of document.body on every mouse move. why? we don't know. if 86 // you know, please let us know. 87 // 88 // If we want to use the strict mode we have to refactor this a little bit. Let's 89 // hope the magic isn't gone now. Anywho... it's only useful in old versions of IE 90 // which should not be used anymore. 91 document.onmousemove = function () { 92 var t; 93 94 if (document.body) { 95 t = document.body.scrollLeft; 96 t += document.body.scrollTop; 97 } 98 99 return t; 100 }; 101 } 102 103 if (Env.supportsCanvas()) { 104 Options.board.renderer = 'canvas'; 105 } 106 107 if (Env.supportsSVG()) { 108 Options.board.renderer = 'svg'; 109 } 110 111 // we are inside node 112 if (Env.isNode() && Env.supportsCanvas()) { 113 Options.board.renderer = 'canvas'; 114 } 115 116 if (Env.isNode() || Options.renderer === 'no') { 117 Options.text.display = 'internal'; 118 Options.infobox.display = 'internal'; 119 } 120 121 return Options.board.renderer; 122 }()), 123 124 initRenderer: function (box, dim, doc, attrRenderer) { 125 var boxid, renderer; 126 127 // Former version: 128 // doc = doc || document 129 if ((!Type.exists(doc) || doc === false) && typeof document === 'object') { 130 doc = document; 131 } 132 133 if (typeof doc === 'object' && box !== null) { 134 boxid = doc.getElementById(box); 135 136 // Remove everything from the container before initializing the renderer and the board 137 while (boxid.firstChild) { 138 boxid.removeChild(boxid.firstChild); 139 } 140 } else { 141 boxid = box; 142 } 143 144 // If attrRenderer is not supplied take the first available renderer 145 if (attrRenderer === undefined || attrRenderer === 'auto') { 146 attrRenderer = this.rendererType; 147 } 148 // create the renderer 149 if (attrRenderer === 'svg') { 150 renderer = new SVGRenderer(boxid, dim); 151 } else if (attrRenderer === 'vml') { 152 renderer = new VMLRenderer(boxid); 153 } else if (attrRenderer === 'canvas') { 154 renderer = new CanvasRenderer(boxid, dim); 155 } else { 156 renderer = new NoRenderer(); 157 } 158 159 return renderer; 160 }, 161 162 /** 163 * Initialise a new board. 164 * @param {String} box Html-ID to the Html-element in which the board is painted. 165 * @param {Object} attributes An object that sets some of the board properties. Most of these properties can be set via JXG.Options. 166 * @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 167 * @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. 168 * @param {Boolean} [attributes.showCopyright=false] Show the copyright string in the top left corner. 169 * @param {Boolean} [attributes.showNavigation=false] Show the navigation buttons in the bottom right corner. 170 * @param {Object} [attributes.zoom] Allow the user to zoom with the mouse wheel or the two-fingers-zoom gesture. 171 * @param {Object} [attributes.pan] Allow the user to pan with shift+drag mouse or two-fingers-pan gesture. 172 * @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. 173 * @param {Boolean|Object} [attributes.grid] If set to true, shows the grid. Can also bet set to an object that is given to the grid as its attribute object. 174 * @param {Boolean} [attributes.registerEvents=true] Register mouse / touch events. 175 * @returns {JXG.Board} Reference to the created board. 176 */ 177 initBoard: function (box, attributes) { 178 var originX, originY, unitX, unitY, 179 renderer, 180 w, h, dimensions, 181 bbox, attr, axattr, axattr_x, axattr_y, 182 defaultaxesattr, 183 selectionattr, 184 board; 185 186 attributes = attributes || {}; 187 188 // merge attributes 189 attr = Type.copyAttributes(attributes, Options, 'board'); 190 attr.zoom = Type.copyAttributes(attr, Options, 'board', 'zoom'); 191 attr.pan = Type.copyAttributes(attr, Options, 'board', 'pan'); 192 attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection'); 193 attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar'); 194 195 dimensions = Env.getDimensions(box, attr.document); 196 197 if (attr.unitx || attr.unity) { 198 originX = Type.def(attr.originx, 150); 199 originY = Type.def(attr.originy, 150); 200 unitX = Type.def(attr.unitx, 50); 201 unitY = Type.def(attr.unity, 50); 202 } else { 203 bbox = attr.boundingbox; 204 w = parseInt(dimensions.width, 10); 205 h = parseInt(dimensions.height, 10); 206 207 if (Type.exists(bbox) && attr.keepaspectratio) { 208 /* 209 * If the boundingbox attribute is given and the ratio of height and width of the 210 * sides defined by the bounding box and the ratio of the dimensions of the div tag 211 * which contains the board do not coincide, then the smaller side is chosen. 212 */ 213 unitX = w / (bbox[2] - bbox[0]); 214 unitY = h / (bbox[1] - bbox[3]); 215 216 if (Math.abs(unitX) < Math.abs(unitY)) { 217 unitY = Math.abs(unitX) * unitY / Math.abs(unitY); 218 } else { 219 unitX = Math.abs(unitY) * unitX / Math.abs(unitX); 220 } 221 } else { 222 unitX = w / (bbox[2] - bbox[0]); 223 unitY = h / (bbox[1] - bbox[3]); 224 } 225 originX = -unitX * bbox[0]; 226 originY = unitY * bbox[1]; 227 } 228 229 renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer); 230 231 // create the board 232 board = new Board(box, renderer, attr.id, [originX, originY], 233 attr.zoomfactor * attr.zoomx, 234 attr.zoomfactor * attr.zoomy, 235 unitX, unitY, 236 dimensions.width, dimensions.height, 237 attr); 238 239 JXG.boards[board.id] = board; 240 241 board.keepaspectratio = attr.keepaspectratio; 242 board.resizeContainer(dimensions.width, dimensions.height, true, true); 243 244 // create elements like axes, grid, navigation, ... 245 board.suspendUpdate(); 246 board.initInfobox(); 247 248 if (attr.axis) { 249 axattr = typeof attr.axis === 'object' ? attr.axis : {}; 250 251 // The defaultAxes attributes are overwritten by user supplied axis object. 252 axattr_x = Type.deepCopy(Options.board.defaultAxes.x, axattr); 253 axattr_y = Type.deepCopy(Options.board.defaultAxes.y, axattr); 254 // The user supplied defaultAxes attributes are merged in. 255 if (attr.defaultaxes.x) { 256 axattr_x = Type.deepCopy(axattr_x, attr.defaultaxes.x); 257 } 258 if (attr.defaultaxes.y) { 259 axattr_y = Type.deepCopy(axattr_y, attr.defaultaxes.y); 260 } 261 262 board.defaultAxes = {}; 263 board.defaultAxes.x = board.create('axis', [[0, 0], [1, 0]], axattr_x); 264 board.defaultAxes.y = board.create('axis', [[0, 0], [0, 1]], axattr_y); 265 } 266 267 if (attr.grid) { 268 board.create('grid', [], (typeof attr.grid === 'object' ? attr.grid : {})); 269 } 270 271 board._createSelectionPolygon(attr); 272 /* 273 selectionattr = Type.copyAttributes(attr, Options, 'board', 'selection'); 274 if (selectionattr.enabled === true) { 275 board.selectionPolygon = board.create('polygon', [[0, 0], [0, 0], [0, 0], [0, 0]], selectionattr); 276 } 277 */ 278 279 board.renderer.drawZoomBar(board, attr.navbar); 280 board.unsuspendUpdate(); 281 282 return board; 283 }, 284 285 /** 286 * Load a board from a file containing a construction made with either GEONExT, 287 * Intergeo, Geogebra, or Cinderella. 288 * @param {String} box HTML-ID to the HTML-element in which the board is painted. 289 * @param {String} file base64 encoded string. 290 * @param {String} format containing the file format: 'Geonext' or 'Intergeo'. 291 * @param {Object} [attributes] 292 * @returns {JXG.Board} Reference to the created board. 293 * @see JXG.FileReader 294 * @see JXG.GeonextReader 295 * @see JXG.GeogebraReader 296 * @see JXG.IntergeoReader 297 * @see JXG.CinderellaReader 298 */ 299 loadBoardFromFile: function (box, file, format, attributes, callback) { 300 var attr, renderer, board, dimensions, 301 selectionattr; 302 303 attributes = attributes || {}; 304 305 // merge attributes 306 attr = Type.copyAttributes(attributes, Options, 'board'); 307 attr.zoom = Type.copyAttributes(attributes, Options, 'board', 'zoom'); 308 attr.pan = Type.copyAttributes(attributes, Options, 'board', 'pan'); 309 attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection'); 310 attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar'); 311 312 dimensions = Env.getDimensions(box, attr.document); 313 renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer); 314 315 /* User default parameters, in parse* the values in the gxt files are submitted to board */ 316 board = new Board(box, renderer, '', [150, 150], 1, 1, 50, 50, dimensions.width, dimensions.height, attr); 317 board.initInfobox(); 318 board.resizeContainer(dimensions.width, dimensions.height, true, true); 319 320 FileReader.parseFileContent(file, board, format, true, callback); 321 322 selectionattr = Type.copyAttributes(attr, Options, 'board', 'selection'); 323 board.selectionPolygon = board.create('polygon', [[0, 0], [0, 0], [0, 0], [0, 0]], selectionattr); 324 325 board.renderer.drawZoomBar(board, attr.navbar); 326 JXG.boards[board.id] = board; 327 328 return board; 329 }, 330 331 /** 332 * Load a board from a base64 encoded string containing a construction made with either GEONExT, 333 * Intergeo, Geogebra, or Cinderella. 334 * @param {String} box HTML-ID to the HTML-element in which the board is painted. 335 * @param {String} string base64 encoded string. 336 * @param {String} format containing the file format: 'Geonext' or 'Intergeo'. 337 * @param {Object} [attributes] 338 * @returns {JXG.Board} Reference to the created board. 339 * @see JXG.FileReader 340 * @see JXG.GeonextReader 341 * @see JXG.GeogebraReader 342 * @see JXG.IntergeoReader 343 * @see JXG.CinderellaReader 344 */ 345 loadBoardFromString: function (box, string, format, attributes, callback) { 346 var attr, renderer, dimensions, board, 347 selectionattr; 348 349 attributes = attributes || {}; 350 351 // merge attributes 352 attr = Type.copyAttributes(attributes, Options, 'board'); 353 attr.zoom = Type.copyAttributes(attributes, Options, 'board', 'zoom'); 354 attr.pan = Type.copyAttributes(attributes, Options, 'board', 'pan'); 355 attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection'); 356 attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar'); 357 358 dimensions = Env.getDimensions(box, attr.document); 359 renderer = this.initRenderer(box, dimensions, attr.document); 360 361 /* User default parameters, in parse* the values in the gxt files are submitted to board */ 362 board = new Board(box, renderer, '', [150, 150], 1.0, 1.0, 50, 50, dimensions.width, dimensions.height, attr); 363 board.initInfobox(); 364 board.resizeContainer(dimensions.width, dimensions.height, true, true); 365 366 FileReader.parseString(string, board, format, true, callback); 367 368 selectionattr = Type.copyAttributes(attr, Options, 'board', 'selection'); 369 board.selectionPolygon = board.create('polygon', [[0, 0], [0, 0], [0, 0], [0, 0]], selectionattr); 370 371 board.renderer.drawZoomBar(board, attr.navbar); 372 JXG.boards[board.id] = board; 373 374 return board; 375 }, 376 377 /** 378 * Delete a board and all its contents. 379 * @param {JXG.Board,String} board HTML-ID to the DOM-element in which the board is drawn. 380 */ 381 freeBoard: function (board) { 382 var el; 383 384 if (typeof board === 'string') { 385 board = JXG.boards[board]; 386 } 387 388 board.removeEventHandlers(); 389 board.suspendUpdate(); 390 391 // Remove all objects from the board. 392 for (el in board.objects) { 393 if (board.objects.hasOwnProperty(el)) { 394 board.objects[el].remove(); 395 } 396 } 397 398 // Remove all the other things, left on the board, XHTML save 399 while (board.containerObj.firstChild) { 400 board.containerObj.removeChild(board.containerObj.firstChild); 401 } 402 403 // Tell the browser the objects aren't needed anymore 404 for (el in board.objects) { 405 if (board.objects.hasOwnProperty(el)) { 406 delete board.objects[el]; 407 } 408 } 409 410 // Free the renderer and the algebra object 411 delete board.renderer; 412 413 // clear the creator cache 414 board.jc.creator.clearCache(); 415 delete board.jc; 416 417 // Finally remove the board itself from the boards array 418 delete JXG.boards[board.id]; 419 }, 420 421 /** 422 * @deprecated Use JXG#registerElement 423 * @param element 424 * @param creator 425 */ 426 registerElement: function (element, creator) { 427 JXG.deprecated('JXG.JSXGraph.registerElement()', 'JXG.registerElement()'); 428 JXG.registerElement(element, creator); 429 } 430 }; 431 432 // JessieScript/JessieCode startup: Search for script tags of type text/jessiescript and interprete them. 433 if (Env.isBrowser && typeof window === 'object' && typeof document === 'object') { 434 Env.addEvent(window, 'load', function () { 435 var type, i, j, div, id, board, width, height, bbox, axis, grid, code, 436 scripts = document.getElementsByTagName('script'), 437 init = function (code, type, bbox) { 438 var board = JXG.JSXGraph.initBoard(id, {boundingbox: bbox, keepaspectratio: true, grid: grid, axis: axis, showReload: true}); 439 440 if (type.toLowerCase().indexOf('script') > -1) { 441 board.construct(code); 442 } else { 443 try { 444 board.jc.parse(code); 445 } catch (e2) { 446 JXG.debug(e2); 447 } 448 } 449 450 return board; 451 }, 452 makeReload = function (board, code, type, bbox) { 453 return function () { 454 var newBoard; 455 456 JXG.JSXGraph.freeBoard(board); 457 newBoard = init(code, type, bbox); 458 newBoard.reload = makeReload(newBoard, code, type, bbox); 459 }; 460 }; 461 462 for (i = 0; i < scripts.length; i++) { 463 type = scripts[i].getAttribute('type', false); 464 465 if (Type.exists(type) && 466 (type.toLowerCase() === 'text/jessiescript' || type.toLowerCase() === 'jessiescript' || 467 type.toLowerCase() === 'text/jessiecode' || type.toLowerCase() === 'jessiecode')) { 468 width = scripts[i].getAttribute('width', false) || '500px'; 469 height = scripts[i].getAttribute('height', false) || '500px'; 470 bbox = scripts[i].getAttribute('boundingbox', false) || '-5, 5, 5, -5'; 471 id = scripts[i].getAttribute('container', false); 472 473 bbox = bbox.split(','); 474 if (bbox.length !== 4) { 475 bbox = [-5, 5, 5, -5]; 476 } else { 477 for (j = 0; j < bbox.length; j++) { 478 bbox[j] = parseFloat(bbox[j]); 479 } 480 } 481 axis = Type.str2Bool(scripts[i].getAttribute('axis', false) || 'false'); 482 grid = Type.str2Bool(scripts[i].getAttribute('grid', false) || 'false'); 483 484 if (!Type.exists(id)) { 485 id = 'jessiescript_autgen_jxg_' + i; 486 div = document.createElement('div'); 487 div.setAttribute('id', id); 488 div.setAttribute('style', 'width:' + width + '; height:' + height + '; float:left'); 489 div.setAttribute('class', 'jxgbox'); 490 try { 491 document.body.insertBefore(div, scripts[i]); 492 } catch (e) { 493 // there's probably jquery involved... 494 if (typeof jQuery === 'object') { 495 jQuery(div).insertBefore(scripts[i]); 496 } 497 } 498 } else { 499 div = document.getElementById(id); 500 } 501 502 if (document.getElementById(id)) { 503 code = scripts[i].innerHTML; 504 code = code.replace(/<!\[CDATA\[/g, '').replace(/\]\]>/g, ''); 505 scripts[i].innerHTML = code; 506 507 board = init(code, type, bbox); 508 board.reload = makeReload(board, code, type, bbox); 509 } else { 510 JXG.debug('JSXGraph: Apparently the div injection failed. Can\'t create a board, sorry.'); 511 } 512 } 513 } 514 }, window); 515 } 516 517 return JXG.JSXGraph; 518 }); 519