1 /*
  2     Copyright 2008-2012
  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  * Constructs a new JSXGraph singleton object.
 35  * @class The JXG.JSXGraph singleton stores all properties required
 36  * to load, save, create and free a board.
 37  */
 38 JXG.JSXGraph = {
 39 
 40     /**
 41      * The small gray version indicator in the top left corner of every JSXGraph board (if
 42      * showCopyright is not set to false on board creation).
 43      * @type String
 44      */
 45     licenseText: 'JSXGraph v0.96 Copyright (C) see http://jsxgraph.org',
 46 
 47     /**
 48      * Associative array that keeps references to all boards.
 49      * @type Object
 50      */
 51     boards: {},
 52 
 53     /**
 54      * Associative array that keeps track of all constructable elements registered
 55      * via {@link JXG.JSXGraph.registerElement}.
 56      * @type Object
 57      */
 58     elements: {},
 59 
 60     /**
 61      * Stores the renderer that is used to draw the boards.
 62      * @type String
 63      */
 64     rendererType: (function() {
 65         var loadRenderer = function (renderer) {
 66             var arr, i;
 67 
 68             // Load the source files for the renderer
 69             if (JXG.rendererFiles[renderer]) {
 70                 arr = JXG.rendererFiles[renderer].split(',');
 71 
 72                 for (i = 0; i < arr.length; i++) {
 73                     (function(include) {
 74                         JXG.require(JXG.requirePath + include + '.js');
 75                     })(arr[i]);
 76                 }
 77 
 78                 delete(JXG.rendererFiles[renderer]);
 79             }
 80         };
 81 
 82         JXG.Options.renderer = 'no';
 83 
 84         if (JXG.supportsVML()) {
 85             JXG.Options.renderer = 'vml';
 86             // Ok, this is some real magic going on here. IE/VML always was so
 87             // terribly slow, except in one place: Examples placed in a moodle course
 88             // was almost as fast as in other browsers. So i grabbed all the css and
 89             // lib scripts from our moodle, added them to a jsxgraph example and it
 90             // worked. next step was to strip all the css/lib code which didn't affect
 91             // the VML update speed. The following five lines are what was left after
 92             // the last step and yes - it basically does nothing but reads two
 93             // properties of document.body on every mouse move. why? we don't know. if
 94             // you know, please let us know.
 95             function MouseMove() {
 96                 if (document.body) {
 97                     document.body.scrollLeft;
 98                     document.body.scrollTop;
 99                 }
100             }
101             document.onmousemove = MouseMove;
102             loadRenderer('vml');
103         }
104 
105         if (JXG.supportsCanvas()) {
106             JXG.Options.renderer = 'canvas';
107             loadRenderer('canvas');
108         }
109 
110         if (JXG.supportsSVG()) {
111             JXG.Options.renderer = 'svg';
112             loadRenderer('svg');
113         }
114         
115         // we are inside node
116         if (JXG.isNode()) {
117             JXG.Options.renderer = 'canvas';
118             loadRenderer('canvas');
119         }
120 
121         return JXG.Options.renderer;
122     })(),
123 
124     /**
125      * Initialise a new board.
126      * @param {String} box Html-ID to the Html-element in which the board is painted.
127      * @param {Object} attributes An object that sets some of the board properties. Most of these properties can be set via JXG.Options. Valid properties are
128      * <ul>
129      *     <li><b>boundingbox</b>: An array containing four numbers describing the left, top, right and bottom boundary of the board in user coordinates</li>
130      *     <li><b>keepaspectratio</b>: If <tt>true</tt>, the bounding box is adjusted to the same aspect ratio as the aspect ratio of the div containing the board.</li>
131      *     <li><b>showCopyright</b>: Show the copyright string in the top left corner.</li>
132      *     <li><b>showNavigation</b>: Show the navigation buttons in the bottom right corner.</li>
133      *     <li><b>zoom</b>: Allow the user to zoom with the mouse wheel or the two-fingers-zoom gesture.</li>
134      *     <li><b>pan</b>: Allow the user to pan with shift+drag mouse or two-fingers-pan gesture.</li>
135      *     <li><b>axis</b>: If set to true, show the axis. Can also be set to an object that is given to both axes as an attribute object.</li>
136      *     <li><b>grid</b>: If set to true, shows the grid. Can also bet set to an object that is given to the grid as its attribute object.</li>
137      * </ul>
138      * @returns {JXG.Board} Reference to the created board.
139      */
140     initBoard: function (box, attributes) {
141         var renderer,
142             originX, originY, unitX, unitY,
143             w, h, dimensions,
144             bbox,
145             zoomfactor, zoomX, zoomY,
146             showCopyright, showNavi,
147             board,
148             wheelzoom, shiftpan, attr, boxid;
149 
150         dimensions = JXG.getDimensions(box);
151         
152         if (typeof document !== 'undefined' && box !== null) {
153             boxid = document.getElementById(box);
154         } else {
155             boxid = box;
156         }
157 
158         // parse attributes
159         if (typeof attributes == 'undefined') {
160             attributes = {};
161         }
162 
163         if (typeof attributes["boundingbox"] != 'undefined') {
164             bbox = attributes["boundingbox"];
165             w = parseInt(dimensions.width);
166             h = parseInt(dimensions.height);
167 
168             if (attributes["keepaspectratio"]) {
169                 /*
170                  * If the boundingbox attribute is given and the ratio of height and width of the
171                  * sides defined by the bounding box and the ratio of the dimensions of the div tag
172                  * which contains the board do not coincide, then the smaller side is chosen.
173                  */
174                 unitX = w/(bbox[2]-bbox[0]);
175                 unitY = h/(-bbox[3]+bbox[1]);
176                 if (Math.abs(unitX)<Math.abs(unitY)) {
177                     unitY = Math.abs(unitX)*unitY/Math.abs(unitY);
178                 } else {
179                     unitX = Math.abs(unitY)*unitX/Math.abs(unitX);
180                 }
181             } else {
182                 unitX = w/(bbox[2]-bbox[0]);
183                 unitY = h/(-bbox[3]+bbox[1]);
184             }
185             originX = -unitX*bbox[0];
186             originY = unitY*bbox[1];
187         } else {
188             originX = ( (typeof attributes["originX"]) == 'undefined' ? 150 : attributes["originX"]);
189             originY = ( (typeof attributes["originY"]) == 'undefined' ? 150 : attributes["originY"]);
190             unitX = ( (typeof attributes["unitX"]) == 'undefined' ? 50 : attributes["unitX"]);
191             unitY = ( (typeof attributes["unitY"]) == 'undefined' ? 50 : attributes["unitY"]);
192         }
193 
194         zoomfactor = ( (typeof attributes["zoomfactor"]) == 'undefined' ? 1.0 : attributes["zoom"]);
195         zoomX = zoomfactor*( (typeof attributes["zoomX"]) == 'undefined' ? 1.0 : attributes["zoomX"]);
196         zoomY = zoomfactor*( (typeof attributes["zoomY"]) == 'undefined' ? 1.0 : attributes["zoomY"]);
197 
198         showCopyright = ( (typeof attributes["showCopyright"]) == 'undefined' ? JXG.Options.showCopyright : attributes["showCopyright"]);
199 
200         wheelzoom = ( (typeof attributes["zoom"]) == 'undefined' ? JXG.Options.zoom.wheel : attributes["zoom"]);
201         shiftpan = ( (typeof attributes["pan"]) == 'undefined' ? JXG.Options.pan : attributes["pan"]);
202 
203         // create the renderer
204         if(JXG.Options.renderer == 'svg') {
205             renderer = new JXG.SVGRenderer(boxid);
206         } else if(JXG.Options.renderer == 'vml') {
207             renderer = new JXG.VMLRenderer(boxid);
208         } else if(JXG.Options.renderer == 'silverlight') {
209             renderer = new JXG.SilverlightRenderer(boxid, dimensions.width, dimensions.height);
210         } else if (JXG.Options.renderer == 'canvas') {
211             renderer = new JXG.CanvasRenderer(boxid);
212         } else {
213             renderer = new JXG.NoRenderer();
214         }
215 
216         // create the board
217         board = new JXG.Board(box, renderer, '', [originX, originY], zoomX, zoomY, unitX, unitY, dimensions.width, dimensions.height, showCopyright);
218         this.boards[board.id] = board;
219 
220         board.keepaspectratio = attributes.keepaspectratio;
221         board.options.zoom.wheel = wheelzoom;
222         board.options.pan = shiftpan;
223 
224         // create elements like axes, grid, navigation, ...
225         board.suspendUpdate();
226         board.initInfobox();
227         
228         if(attributes["axis"]) {
229             attr = typeof attributes['axis'] === 'object' ? attributes['axis'] : {ticks: {drawZero: true}};
230         	board.defaultAxes = {};
231             board.defaultAxes.x = board.create('axis', [[0,0], [1,0]], attr);
232             board.defaultAxes.y = board.create('axis', [[0,0], [0,1]], attr);
233         }
234 
235         if(attributes["grid"]) {
236             board.create('grid', [], (typeof attributes["grid"] === 'object' ? attributes['grid'] : {}));
237         }
238 
239         if (typeof attributes["shownavigation"] != 'undefined') attributes["showNavigation"] = attributes["shownavigation"];
240         showNavi = ( (typeof attributes["showNavigation"]) == 'undefined' ? board.options.showNavigation : attributes["showNavigation"]);
241         if (showNavi) {
242             board.renderer.drawZoomBar(board);
243         }
244         board.unsuspendUpdate();
245 
246         return board;
247     },
248 
249     /**
250      * Load a board from a file containing a construction made with either GEONExT,
251      * Intergeo, Geogebra, or Cinderella.
252      * @param {String} box HTML-ID to the HTML-element in which the board is painted.
253      * @param {String} file base64 encoded string.
254      * @param {String} format containing the file format: 'Geonext' or 'Intergeo'.
255      * @returns {JXG.Board} Reference to the created board.
256      *
257      * @see JXG.FileReader
258      * @see JXG.GeonextReader
259      * @see JXG.GeogebraReader
260      * @see JXG.IntergeoReader
261      * @see JXG.CinderellaReader
262      */
263     loadBoardFromFile: function (box, file, format) {
264         var renderer, board, dimensions;
265 
266         if(JXG.Options.renderer == 'svg') {
267             renderer = new JXG.SVGRenderer(document.getElementById(box));
268         } else if(JXG.Options.renderer == 'vml') {
269             renderer = new JXG.VMLRenderer(document.getElementById(box));
270         } else if(JXG.Options.renderer == 'silverlight') {
271             renderer = new JXG.SilverlightRenderer(document.getElementById(box), dimensions.width, dimensions.height);
272         } else {
273             renderer = new JXG.CanvasRenderer(document.getElementById(box));
274         }
275         
276         //var dimensions = document.getElementById(box).getDimensions();
277         dimensions = JXG.getDimensions(box);
278 
279         /* User default parameters, in parse* the values in the gxt files are submitted to board */
280         board = new JXG.Board(box, renderer, '', [150, 150], 1.0, 1.0, 50, 50, dimensions.width, dimensions.height);
281         board.initInfobox();
282 
283         JXG.FileReader.parseFileContent(file, board, format);
284         if(board.options.showNavigation) {
285             board.renderer.drawZoomBar(board);
286         }
287         this.boards[board.id] = board;
288         return board;
289     },
290 
291     /**
292      * Load a board from a base64 encoded string containing a construction made with either GEONExT,
293      * Intergeo, Geogebra, or Cinderella.
294      * @param {String} box HTML-ID to the HTML-element in which the board is painted.
295      * @param {String} string base64 encoded string.
296      * @param {String} format containing the file format: 'Geonext' or 'Intergeo'.
297      * @returns {JXG.Board} Reference to the created board.
298      *
299      * @see JXG.FileReader
300      * @see JXG.GeonextReader
301      * @see JXG.GeogebraReader
302      * @see JXG.IntergeoReader
303      * @see JXG.CinderellaReader
304      */
305     loadBoardFromString: function(box, string, format) {
306         var renderer, dimensions, board;
307 
308         if(JXG.Options.renderer == 'svg') {
309             renderer = new JXG.SVGRenderer(document.getElementById(box));
310         } else if(JXG.Options.renderer == 'vml') {
311             renderer = new JXG.VMLRenderer(document.getElementById(box));
312         } else if(JXG.Options.renderer == 'silverlight') {
313             renderer = new JXG.SilverlightRenderer(document.getElementById(box), dimensions.width, dimensions.height);
314         } else {
315             renderer = new JXG.CanvasRenderer(document.getElementById(box));
316         }
317         //var dimensions = document.getElementById(box).getDimensions();
318         dimensions = JXG.getDimensions(box);
319 
320         /* User default parameters, in parse* the values in the gxt files are submitted to board */
321         board = new JXG.Board(box, renderer, '', [150, 150], 1.0, 1.0, 50, 50, dimensions.width, dimensions.height);
322         board.initInfobox();
323 
324         JXG.FileReader.parseString(string, board, format, true);
325         if (board.options.showNavigation) {
326             board.renderer.drawZoomBar(board);
327         }
328 
329         this.boards[board.id] = board;
330         return board;
331     },
332 
333     /**
334      * Delete a board and all its contents.
335      * @param {String} board HTML-ID to the DOM-element in which the board is drawn.
336      */
337     freeBoard: function (board) {
338         var el, i;
339 
340         if (typeof(board) == 'string') {
341             board = this.boards[board];
342         }
343 
344         board.removeEventHandlers();
345         board.suspendUpdate();
346 
347         // Remove all objects from the board.
348         for(el in board.objects) {
349             //board.removeObject(board.objects[el]);
350             board.objects[el].remove();
351         }
352 
353         // Remove all the other things, left on the board, XHTML save
354         while (board.containerObj.firstChild) {
355             board.containerObj.removeChild(board.containerObj.firstChild);
356         }
357         // board.containerObj.innerHTML = '';
358 
359         // Tell the browser the objects aren't needed anymore
360         for(el in board.objects) {
361             delete(board.objects[el]);
362         }
363 
364         // Free the renderer and the algebra object
365         delete(board.renderer);
366         delete(board.algebra);
367 
368         // clear the creator cache
369         board.jc.creator.clearCache();
370         delete(board.jc);
371 
372         // Finally remove the board itself from the boards array
373         delete(this.boards[board.id]);
374     },
375 
376     /**
377      * This registers a new construction element to JSXGraph for the construction via the {@link JXG.Board.create}
378      * interface.
379      * @param {String} element The elements name. This is case-insensitive, existing elements with the same name
380      * will be overwritten.
381      * @param {Function} creator A reference to a function taking three parameters: First the board, the element is
382      * to be created on, a parent element array, and an attributes object. See {@link JXG.createPoint} or any other
383      * <tt>JXG.create...</tt> function for an example.
384      */
385     registerElement: function (element, creator) {
386         element = element.toLowerCase();
387         this.elements[element] = creator;
388 
389         if(JXG.Board.prototype['_' + element])
390         	throw new Error("JSXGraph: Can't create wrapper method in JXG.Board because member '_" + element + "' already exists'");
391         JXG.Board.prototype['_' + element] = function (parents, attributes) {
392         	return this.create(element, parents, attributes);
393         };
394 
395     },
396 
397     /**
398      * The opposite of {@link JXG.JSXGraph.registerElement}, it removes a given element from
399      * the element list. You probably don't need this.
400      * @param {String} element The name of the element which is to be removed from the element list.
401      */
402     unregisterElement: function (element) {
403         delete (this.elements[element.toLowerCase()]);
404         delete (JXG.Board.prototype['_' + element.toLowerCase()]);
405     }
406 };
407