      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