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