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 // We need the following two methods "extend" and "shortcut" to create the JXG object via JXG.extend.
 35 
 36 /**
 37  * Copy all properties of the <tt>extension</tt> object to <tt>object</tt>.
 38  * @param {Object} object
 39  * @param {Object} extension
 40  * @param {Boolean} [onlyOwn=false] Only consider properties that belong to extension itself, not any inherited properties.
 41  * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
 42  */
 43 JXG.extend = function (object, extension, onlyOwn, toLower) {
 44     var e, e2;
 45 
 46     onlyOwn = onlyOwn || false;
 47     toLower = toLower || false;
 48 
 49     for(e in extension) {
 50         if (!onlyOwn || (onlyOwn && extension.hasOwnProperty(e))) {
 51             if (toLower) {
 52                 e2 = e.toLowerCase();
 53             } else {
 54                 e2 = e;
 55             }
 56              
 57             object[e2] = extension[e];
 58         }
 59     }
 60 };
 61 
 62 /**
 63  * Creates a shortcut to a method, e.g. {@link JXG.Board#create} is a shortcut to {@link JXG.Board#createElement}.
 64  * Sometimes the target is undefined by the time you want to define the shortcut so we need this little helper.
 65  * @param {Object} object The object the method we want to create a shortcut for belongs to.
 66  * @param {Function} fun The method we want to create a shortcut for.
 67  * @returns {Function} A function that calls the given method.
 68  */
 69 JXG.shortcut = function (object, fun) {
 70     return function () {
 71         return object[fun].apply(this, arguments);
 72     };
 73 };
 74 
 75 
 76 JXG.extend(JXG, /** @lends JXG */ {
 77 
 78     /**
 79      * Detect browser support for VML.
 80      * @returns {Boolean} True, if the browser supports VML.
 81      */
 82     supportsVML: function () {
 83         // From stackoverflow.com
 84         return !!document.namespaces;
 85     },
 86 
 87     /**
 88      * Detect browser support for SVG.
 89      * @returns {Boolean} True, if the browser supports SVG.
 90      */
 91     supportsSVG: function () {
 92         return document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1");
 93     },
 94 
 95     /**
 96      * Detect browser support for Canvas.
 97      * @returns {Boolean} True, if the browser supports HTML canvas.
 98      */
 99     supportsCanvas: function () {
100         return !!document.createElement('canvas').getContext;
101     },
102 
103     /**
104      * Detects if the user is using an Android powered device.
105      * @returns {Boolean}
106      */
107     isAndroid: function () {
108         return navigator.userAgent.toLowerCase().search("android") > -1;
109     },
110 
111     /**
112      * Detects if the user is using the default Webkit browser on an Android powered device.
113      * @returns {Boolean}
114      */
115     isWebkitAndroid: function () {
116         return this.isAndroid() && navigator.userAgent.search(" AppleWebKit/") > -1;
117     },
118 
119     /**
120      * Detects if the user is using a Apple iPad / iPhone.
121      * @returns {Boolean}
122      */
123     isApple: function () {
124         return (navigator.userAgent.search(/iPad/) != -1 || navigator.userAgent.search(/iPhone/) != -1);
125     },
126 
127     /**
128      * Detects if the user is using Safari on an Apple device.
129      * @returns {Boolean}
130      */
131     isWebkitApple: function () {
132         return this.isApple() && (navigator.userAgent.search(/Mobile *.*Safari/) > -1);
133     },
134 
135     /**
136      * Resets visPropOld of <tt>el</tt>
137      * @param {JXG.GeometryElement} el
138      */
139     clearVisPropOld: function (el) {
140         el.visPropOld = {
141             strokecolor: '',
142             strokeopacity: '',
143             strokewidth: '',
144             fillcolor: '',
145             fillopacity: '',
146             shadow: false,
147             firstarrow: false,
148             lastarrow: false
149         };
150     },
151 
152     /**
153      * Internet Explorer version. Works only for IE > 4.
154      * @type Number
155      */
156     ieVersion: (function() {
157         var undef,
158             v = 3,
159             div = document.createElement('div'),
160             all = div.getElementsByTagName('i');
161 
162         while (
163             div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i><' + '/i><![endif]-->',
164                 all[0]
165             );
166 
167         return v > 4 ? v : undef;
168 
169     }()),
170 
171     /**
172      * s may be a string containing the name or id of an element or even a reference
173      * to the element itself. This function returns a reference to the element. Search order: id, name.
174      * @param {JXG.Board} board Reference to the board the element belongs to.
175      * @param {String} s String or reference to a JSXGraph element.
176      * @returns {Object} Reference to the object given in parameter object
177      */
178     getReference: function (board, s) {
179         if (typeof(s) == 'string') {
180             if (JXG.exists(board.objects[s])) { // Search by ID
181                 s = board.objects[s];
182             } else if (JXG.exists(board.elementsByName[s])) { // Search by name
183                 s = board.elementsByName[s];
184             } else if (JXG.exists(board.groups[s])) { // Search by group ID 
185                 s = board.groups[s];
186             }
187         }
188 
189         return s;
190     },
191 
192     /**
193      * This is just a shortcut to {@link JXG.getReference}.
194      */
195     getRef: JXG.shortcut(JXG, 'getReference'),
196 
197     /**
198      * Checks if the given string is an id within the given board.
199      * @param {JXG.Board} board
200      * @param {String} s
201      * @returns {Boolean}
202      */
203     isId: function (board, s) {
204         return typeof(s) == 'string' && !!board.objects[s];
205     },
206 
207     /**
208      * Checks if the given string is a name within the given board.
209      * @param {JXG.Board} board
210      * @param {String} s
211      * @returns {Boolean}
212      */
213     isName: function (board, s) {
214         return typeof(s) == 'string' && !!board.elementsByName[s];
215     },
216 
217     /**
218      * Checks if the given string is a group id within the given board.
219      * @param {JXG.Board} board
220      * @param {String} s
221      * @returns {Boolean}
222      */
223     isGroup: function (board, s) {
224         return typeof(s) == 'string' && !!board.groups[s];
225     },
226 
227     /**
228      * Checks if the value of a given variable is of type string.
229      * @param v A variable of any type.
230      * @returns {Boolean} True, if v is of type string.
231      */
232     isString: function (v) {
233         return typeof v === "string";
234     },
235 
236     /**
237      * Checks if the value of a given variable is of type number.
238      * @param v A variable of any type.
239      * @returns {Boolean} True, if v is of type number.
240      */
241     isNumber: function (v) {
242         return typeof v === "number";
243     },
244 
245     /**
246      * Checks if a given variable references a function.
247      * @param v A variable of any type.
248      * @returns {Boolean} True, if v is a function.
249      */
250     isFunction: function (v) {
251         return typeof v === "function";
252     },
253 
254     /**
255      * Checks if a given variable references an array.
256      * @param v A variable of any type.
257      * @returns {Boolean} True, if v is of type array.
258      */
259     isArray: function (v) {
260         // Borrowed from prototype.js
261         return v !== null && typeof v === "object" && 'splice' in v && 'join' in v;
262     },
263 
264     /**
265      * Checks if a given variable is a reference of a JSXGraph Point element.
266      * @param v A variable of any type.
267      * @returns {Boolean} True, if v is of type JXG.Point.
268      */
269     isPoint: function (v) {
270         if (typeof v == 'object') {
271             return (v.elementClass == JXG.OBJECT_CLASS_POINT);
272         }
273 
274         return false;
275     },
276 
277     /**
278      * Checks if a given variable is neither undefined nor null. You should not use this together with global
279      * variables!
280      * @param v A variable of any type.
281      * @returns {Boolean} True, if v is neither undefined nor null.
282      */
283     exists: (function (undefined) {
284         return function (v) {
285             return !(v === undefined || v === null);
286         }
287     })(),
288 
289     /**
290      * Handle default parameters.
291      * @param {%} v Given value
292      * @param {%} d Default value
293      * @returns {%} <tt>d</tt>, if <tt>v</tt> is undefined or null.
294      */
295     def: function (v, d) {
296         if (JXG.exists(v)) {
297             return v;
298         } else {
299             return d;
300         }
301     },
302 
303     /**
304      * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
305      * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
306      * @returns {Boolean} String typed boolean value converted to boolean.
307      */
308     str2Bool: function (s) {
309         if (!JXG.exists(s)) {
310             return true;
311         }
312         if (typeof s == 'boolean') {
313             return s;
314         }
315         return (s.toLowerCase()=='true');
316     },
317 
318     /**
319      * Shortcut for {@link JXG.JSXGraph.initBoard}.
320      */
321     _board: function (box, attributes) {
322         return JXG.JSXGraph.initBoard(box, attributes);
323     },
324 
325     /**
326      * Convert a String, a number or a function into a function. This method is used in Transformation.js
327      * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
328      * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
329      * values is of type string.
330      * @param {Array} param An array containing strings, numbers, or functions.
331      * @param {Number} n Length of <tt>param</tt>.
332      * @returns {Function} A function taking one parameter k which specifies the index of the param element
333      * to evaluate.
334      */
335     createEvalFunction: function (board, param, n) {
336         // convert GEONExT syntax into function
337         var f = [], i, str;
338 
339         for (i=0;i<n;i++) {
340             if (typeof param[i] == 'string') {
341                 str = JXG.GeonextParser.geonext2JS(param[i],board);
342                 str = str.replace(/this\.board\./g,'board.');
343                 f[i] = new Function('','return ' + (str) + ';');
344             }
345         }
346 
347         return function (k) {
348             var a = param[k];
349             if (typeof a == 'string') {
350                 return f[k]();
351             } else if (typeof a=='function') {
352                 return a();
353             } else if (typeof a=='number') {
354                 return a;
355             }
356             return 0;
357         };
358     },
359 
360     /**
361      * Convert a String, number or function into a function.
362      * @param term A variable of type string, function or number.
363      * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
364      * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
365      * values is of type string.
366      * @param {String} variableName Only required if evalGeonext is set to true. Describes the variable name
367      * of the variable in a GEONE<sub>X</sub>T string given as term.
368      * @param {Boolean} evalGeonext Set this true, if term should be treated as a GEONE<sub>X</sub>T string.
369      * @returns {Function} A function evaluation the value given by term or null if term is not of type string,
370      * function or number.
371      */
372     createFunction: function (term, board, variableName, evalGeonext) {
373         var f = null;
374 
375         if ((!JXG.exists(evalGeonext) || evalGeonext) && JXG.isString(term)) {
376             // Convert GEONExT syntax into  JavaScript syntax
377             //newTerm = JXG.GeonextParser.geonext2JS(term, board);
378             //return new Function(variableName,'return ' + newTerm + ';');
379             f = board.jc.snippet(term, true, variableName, true);
380         } else if (JXG.isFunction(term)) {
381             f = term;
382         } else if (JXG.isNumber(term)) {
383             f = function () { return term; };
384         } else if (JXG.isString(term)) {        // In case of string function like fontsize
385             f = function () { return term; };
386         }
387 
388         if (f !== null) {
389             f.origin = term;
390         }
391 
392         return f;
393     },
394 
395     /**
396      * Checks given parents array against several expectations.
397      * @param {String} element The name of the element to be created
398      * @param {Array} parents A parents array
399      * @param {Array} expects Each possible parents array types combination is given as
400      * an array of element type constants containing the types or names of elements that are
401      * accepted and in what order they are accepted. Strings can be given for basic data types
402      * are <em>number, string, array, function, object</em>. We accept non element JSXGraph
403      * types like <em>coords</em>, too.
404      * @returns {Array} A new parents array prepared for the use within a create* method
405      */
406     checkParents: function (element, parents, expects) {
407         // some running variables
408         var i, j, k, len,
409 
410             // collects the parent elements that already got verified
411             new_parents = [],
412 
413             // in case of multiple parent array type combinations we may have to start over again
414             // so hold the parents array in an temporary array in case we need the original one back
415             tmp_parents = parents.slice(0),
416 
417             // test the given parent element against what we expect
418             is = function (expect, parent) {
419                 // we basically got three cases:
420                 // expect is of type
421                 // number => test for parent.elementClass and parent.type
422                 //     lucky us elementClass and type constants don't overlap \o/
423                 // string => here we have two sub cases depending on the value of expect
424                 //   string, object, function, number => make a simple typeof
425                 //   array => check via isArray
426 
427                 var type_expect = (typeof expect).toLowerCase();
428 
429                 if (type_expect === 'number') {
430                     return parent && ((parent.type && parent.type === expect) || (parent.elementClass && parent.elementClass === expect))
431                 } else {
432                     switch(expect.toLowerCase()) {
433                         case 'string':
434                         case 'object':
435                         case 'function':
436                         case 'number':
437                             return (typeof parent).toLowerCase() === expect.toLowerCase();
438                             break;
439                         case 'array':
440                             return JXG.isArray(parent);
441                             break;
442                     }
443                 }
444 
445 
446                 return false;
447             };
448 
449 
450         for(i = 0; i < expects.length; i++) {
451             // enter the next loop only if parents has enough elements
452             for(j = 0; j < expects[i].length && parents.length >= expects[i].length; j++) {
453                 k = 0;
454                 while (k < tmp_parents.length && !is(expects[i][j], tmp_parents[k]))
455                     k++;
456 
457                 if (k<tmp_parents.length) {
458                     new_parents.push(tmp_parents.splice(len-k-1, 1)[0]);
459                 }
460             }
461 
462             // if there are still elements left in the parents array we need to
463             // rebuild the original parents array and start with the next expect array
464             if (tmp_parents.length) {
465                 tmp_parents = parents.slice(0);
466                 new_parents = [];
467             } else // yay, we found something \o/
468                 return new_parents;
469         }
470     },
471 
472     /**
473      * Reads the configuration parameter of an attribute of an element from a {@link JXG.Options} object
474      * @param {JXG.Options} options Reference to an instance of JXG.Options. You usually want to use the
475      * options property of your board.
476      * @param {String} element The name of the element which options you wish to read, e.g. 'point' or
477      * 'elements' for general attributes.
478      * @param {String} key The name of the attribute to read, e.g. 'strokeColor' or 'withLabel'
479      * @returns The value of the selected configuration parameter.
480      * @see JXG.Options
481      */
482     readOption: function (options, element, key) {
483         var val = options.elements[key];
484 
485         if (JXG.exists(options[element][key]))
486             val = options[element][key];
487 
488         return val;
489     },
490 
491     /**
492      * Checks an attributes object and fills it with default values if there are properties missing.
493      * @param {Object} attributes
494      * @param {Object} defaults
495      * @returns {Object} The given attributes object with missing properties added via the defaults object.
496      * @deprecated replaced by JXG#copyAttributes
497      */
498     checkAttributes: function (attributes, defaults) {
499         var key;
500 
501         // Make sure attributes is an object.
502         if (!JXG.exists(attributes)) {
503             attributes = {};
504         }
505 
506         // Go through all keys of defaults and check for their existence
507         // in attributes. If one doesn't exist, it is created with the
508         // same value as in defaults.
509         for (key in defaults) {
510             if (!JXG.exists(attributes[key])) {
511                 attributes[key] = defaults[key];
512             }
513         }
514 
515         return attributes;
516     },
517 
518     /**
519      * Generates an attributes object that is filled with default values from the Options object
520      * and overwritten by the user speciified attributes.
521      * @param {Object} attributes user specified attributes
522      * @param {Object} options defaults options
523      * @param {String} % variable number of strings, e.g. 'slider', subtype 'point1'.
524      * @returns {Object} The resulting attributes object
525      */
526     copyAttributes: function (attributes, options) {
527         var a, i, len, o, isAvail;
528 
529         a = this.deepCopy(options.elements, null, true);       // default options from Options.elements
530         len = arguments.length;
531         
532         // Only the layer of the main element is set.
533         if (len < 4 && this.exists(arguments[2]) && this.exists(options.layer[arguments[2]])) {
534             a.layer = options.layer[arguments[2]];
535         }
536         
537         o = options;                                                // default options from specific elements
538         isAvail = true;
539         for (i = 2; i < len; i++) {
540             if (JXG.exists(o[arguments[i]])) {
541                 o = o[arguments[i]];
542             } else {
543                 isAvail = false;
544                 break;
545             }
546         }
547         if (isAvail) {
548             a = this.deepCopy(a, o, true);
549         }
550         
551         o = attributes;                                             // options from attributes
552         isAvail = true;
553         for (i=3;i<len;i++) {
554             if (JXG.exists(o[arguments[i]])) {
555                 o = o[arguments[i]];
556             } else {
557                 isAvail = false;
558                 break;
559             }
560         }
561         if (isAvail) {
562             this.extend(a, o, null, true);
563         }
564         
565         /**
566          * Special treatment of labels
567          */
568         o = options;                   
569         isAvail = true;
570         for (i = 2; i < len; i++) {
571             if (JXG.exists(o[arguments[i]])) {
572                 o = o[arguments[i]];
573             } else {
574                 isAvail = false;
575                 break;
576             }
577         }
578         if (isAvail) {
579             a.label =  JXG.deepCopy(o.label, a.label);
580         }
581         a.label = JXG.deepCopy(options.label, a.label);
582         
583         return a;
584     },
585     
586     /**
587      * Reads the width and height of an HTML element.
588      * @param {String} elementId The HTML id of an HTML DOM node.
589      * @returns {Object} An object with the two properties width and height.
590      */
591     getDimensions: function (elementId) {
592         var element, display, els, originalVisibility, originalPosition,
593             originalDisplay, originalWidth, originalHeight;
594 
595         // Borrowed from prototype.js
596         element = document.getElementById(elementId);
597         if (!JXG.exists(element)) {
598             throw new Error("\nJSXGraph: HTML container element '" + (elementId) + "' not found.");
599         }
600 
601         display = element.style.display;
602         if (display != 'none' && display != null) {// Safari bug
603             return {width: element.offsetWidth, height: element.offsetHeight};
604         }
605 
606         // All *Width and *Height properties give 0 on elements with display set to none,
607         // hence we show the element temporarily
608         els = element.style;
609 
610         // save style
611         originalVisibility = els.visibility;
612         originalPosition = els.position;
613         originalDisplay = els.display;
614 
615         // show element
616         els.visibility = 'hidden';
617         els.position = 'absolute';
618         els.display = 'block';
619 
620         // read the dimension
621         originalWidth = element.clientWidth;
622         originalHeight = element.clientHeight;
623 
624         // restore original css values
625         els.display = originalDisplay;
626         els.position = originalPosition;
627         els.visibility = originalVisibility;
628 
629         return {
630             width: originalWidth,
631             height: originalHeight
632         };
633     },
634 
635     /**
636      * Adds an event listener to a DOM element.
637      * @param {Object} obj Reference to a DOM node.
638      * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'.
639      * @param {Function} fn The function to call when the event is triggered.
640      * @param {Object} owner The scope in which the event trigger is called.
641      */
642     addEvent: function (obj, type, fn, owner) {
643         var el = function () {
644                 return fn.apply(owner, arguments);
645             };
646 
647         el.origin = fn;
648         owner['x_internal'+type] = owner['x_internal'+type] || [];
649         owner['x_internal'+type].push(el);
650 
651         if (JXG.exists(obj) && JXG.exists(obj.addEventListener)) { // Non-IE browser
652             obj.addEventListener(type, el, false);
653         } else {  // IE
654             obj.attachEvent('on'+type, el);
655         }
656     },
657 
658     /**
659      * Removes an event listener from a DOM element.
660      * @param {Object} obj Reference to a DOM node.
661      * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'.
662      * @param {Function} fn The function to call when the event is triggered.
663      * @param {Object} owner The scope in which the event trigger is called.
664      */
665     removeEvent: function (obj, type, fn, owner) {
666         var em = 'JSXGraph: Can\'t remove event listener on' + type + ': ' + owner['x_internal' + type],
667             i, j = -1, l;
668 
669         if ((!JXG.exists(owner) || !JXG.exists(owner['x_internal' + type])) && !JXG.isArray(owner['x_internal' + type])) {
670             //JXG.debug(em);
671             return;
672         }
673 
674         l = owner['x_internal' + type].length;
675         for (i = 0; i < l; i++) {
676             if (owner['x_internal' + type][i].origin === fn) {
677                 j = i;
678                 break;
679             }
680         }
681 
682         if (j === -1) {
683             //JXG.debug(em + '; Event listener not found.');
684             return;
685         }
686 
687         try {
688             if (JXG.exists(obj.addEventListener)) { // Non-IE browser
689                 obj.removeEventListener(type, owner['x_internal'+type][j], false);
690             } else {  // IE
691                 obj.detachEvent('on'+type, owner['x_internal'+type][j]);
692             }
693 
694             JXG.removeElementFromArray(owner['x_internal'+type], owner['x_internal'+type][j]);
695         } catch(e) {
696             //JXG.debug('JSXGraph: Can\'t remove event listener on' + type + ': ' + owner['x_internal' + type]);
697         }
698     },
699 
700     /**
701      * Removes all events of the given type from a given DOM node; Use with caution and do not use it on a container div
702      * of a {@link JXG.Board} because this might corrupt the event handling system.
703      * @param {Object} obj Reference to a DOM node.
704      * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'.
705      * @param {Object} owner The scope in which the event trigger is called.
706      */
707     removeAllEvents: function(obj, type, owner) {
708 		if (owner['x_internal' + type]) {
709         	while (owner['x_internal' + type].length > 0) {
710             	JXG.removeEvent(obj, type, owner['x_internal' + type][0].origin, owner);
711             }
712         }
713     },
714 
715     /**
716      * Generates a function which calls the function fn in the scope of owner.
717      * @param {Function} fn Function to call.
718      * @param {Object} owner Scope in which fn is executed.
719      * @returns {Function} A function with the same signature as fn.
720      */
721     bind: function (fn, owner) {
722         return function () {
723             return fn.apply(owner,arguments);
724         };
725     },
726 
727     /**
728      * Removes an element from the given array
729      * @param {Array} ar
730      * @param {%} el
731      */
732     removeElementFromArray: function(ar, el) {
733         var i;
734 
735         for (i = 0; i < ar.length; i++) {
736             if (ar[i] === el) {
737                 ar.splice(i, 1);
738                 return ar;
739             }
740         }
741 
742         return ar;
743     },
744 
745     /**
746      * Cross browser mouse / touch coordinates retrieval relative to the board's top left corner.
747      * @param {Object} [e] The browsers event object. If omitted, <tt>window.event</tt> will be used.
748      * @param {Number} [index] If <tt>e</tt> is a touch event, this provides the index of the touch coordinates, i.e. it determines which finger.
749      * @returns {Array} Contains the position as x,y-coordinates in the first resp. second component.
750      */
751     getPosition: function (e, index) {
752         var posx = 0,
753             posy = 0;
754 
755         if (!e) {
756             e = window.event;
757         }
758 
759         if (JXG.exists(index)) {
760             e = e.targetTouches[index];
761         }
762 
763         if (e.pageX || e.pageY) {
764             posx = e.pageX;
765             posy = e.pageY;
766         } else if (e.clientX || e.clientY)    {
767             posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
768             posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
769         }
770 
771         return [posx,posy];
772     },
773 
774     /**
775      * Calculates recursively the offset of the DOM element in which the board is stored.
776      * @param {Object} obj A DOM element
777      * @returns {Array} An array with the elements left and top offset.
778      */
779     getOffset: function (obj) {
780         var o = obj,
781             o2 = obj,
782             l =o.offsetLeft - o.scrollLeft,
783             t =o.offsetTop - o.scrollTop;
784         
785         /*
786          * In Mozilla and Webkit: offsetParent seems to jump at least to the next iframe,
787          * if not to the body. In IE and if we are in an position:absolute environment 
788          * offsetParent walks up the DOM hierarchy.
789          * In order to walk up the DOM hierarchy also in Mozilla and Webkit
790          * we need the parentNode steps.
791          */
792         while (o=o.offsetParent) {
793             l+=o.offsetLeft;
794             t+=o.offsetTop;
795             if (o.offsetParent) {
796                 l+=o.clientLeft - o.scrollLeft;
797                 t+=o.clientTop - o.scrollTop;
798             }
799             o2 = o2.parentNode;
800             while (o2!=o) {
801                 l += o2.clientLeft - o2.scrollLeft;
802                 t += o2.clientTop - o2.scrollTop;
803                 o2 = o2.parentNode;
804             }
805         }
806         return [l,t];
807     },
808 
809     /**
810      * Access CSS style sheets.
811      * @param {Object} obj A DOM element
812      * @param {String} stylename The CSS property to read.
813      * @returns The value of the CSS property and <tt>undefined</tt> if it is not set.
814      */
815     getStyle: function (obj, stylename) {
816         var r;
817 
818         if (window.getComputedStyle) {
819             // Non-IE
820             r = document.defaultView.getComputedStyle(obj, null).getPropertyValue(stylename);
821         } else if (obj.currentStyle && JXG.ieVersion >= 9) {
822             // IE
823             r = obj.currentStyle[stylename];
824         } else {
825             if (obj.style) {
826                 // make stylename lower camelcase
827                 stylename = stylename.replace(/-([a-z]|[0-9])/ig, function (all, letter) {
828                     return ( letter + "" ).toUpperCase();
829                 });
830                 r = obj.style[stylename]
831             }
832         }
833 
834         return r;
835     },
836 
837     /**
838      * Extracts the keys of a given object.
839      * @param object The object the keys are to be extracted
840      * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
841      * the object owns itself and not some other object in the prototype chain.
842      * @returns {Array} All keys of the given object.
843      */
844     keys: function (object, onlyOwn) {
845         var keys = [], property;
846 
847         for (property in object) {
848             if (onlyOwn) {
849                 if (object.hasOwnProperty(property)) {
850                     keys.push(property);
851                 }
852             } else {
853                 keys.push(property);
854             }
855         }
856         return keys;
857     },
858     
859     /**
860      * Search an array for a given value.
861      * @param {Array} array
862      * @param {%} value
863      * @returns {Number} The index of the first appearance of the given value, or
864      * <tt>-1</tt> if the value was not found.
865      */
866     indexOf: function (array, value) {
867         var i;
868         
869         if (Array.indexOf) {
870             return array.indexOf(value);
871         }
872         
873         for (i = 0; i < array.length; i++) {
874             if (array[i] === value) {
875                 return i;
876             }
877         }
878         
879         return -1;
880     },
881 
882     /**
883      * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
884      * @param str
885      */
886     escapeHTML: function (str) {
887         return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
888     },
889 
890     /**
891      * Eliminates all substrings enclosed by < and > and replaces all occurences of
892      * &amp; by &, &gt; by >, and &lt; by <.
893      * @param str
894      */
895     unescapeHTML: function (str) {
896         return str.replace(/<\/?[^>]+>/gi, '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
897     },
898 
899     /**
900      * This outputs an object with a base class reference to the given object. This is useful if
901      * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
902      * without changing the original object.
903      * @param {Object} obj Object to be embedded.
904      * @returns {Object} An object with a base class reference to <tt>obj</tt>.
905      */
906     clone: function (obj) {
907         var cObj = {};
908         cObj.prototype = obj;
909         return cObj;
910     },
911 
912     /**
913      * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
914      * to the new one. Warning: The copied properties of obj2 are just flat copies.
915      * @param {Object} obj Object to be copied.
916      * @param {Object} obj2 Object with data that is to be copied to the new one as well.
917      * @returns {Object} Copy of given object including some new/overwritten data from obj2.
918      */
919     cloneAndCopy: function (obj, obj2) {
920         var cObj = function(){}, r;
921         cObj.prototype = obj;
922         //cObj = obj;
923         for(r in obj2)
924             cObj[r] = obj2[r];
925 
926         return cObj;
927     },
928 
929     /**
930      * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
931      * element-wise instead of just copying the reference. If a second object is supplied, the two objects
932      * are merged into one object. The properties of the second object have priority.
933      * @param {Object} obj This object will be copied.
934      * @param {Object} obj2 This object will merged into the newly created object
935      * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
936      * @returns {Object} copy of obj or merge of obj and obj2.
937      */
938     deepCopy: function (obj, obj2, toLower) {
939         var c, i, prop, j, i2;
940 
941         toLower = toLower || false;
942         
943         if (typeof obj !== 'object' || obj == null) {
944             return obj;
945         }
946 
947         if (this.isArray(obj)) {
948             c = [];
949             for (i = 0; i < obj.length; i++) {
950                 prop = obj[i];
951                 if (typeof prop == 'object') {
952                     c[i] = this.deepCopy(prop);
953                 } else {
954                     c[i] = prop;
955                 }
956             }
957         } else {
958             c = {};
959             for (i in obj) {
960                 i2 = toLower ? i.toLowerCase() : i;
961 
962                 prop = obj[i];
963                 if (typeof prop == 'object') {
964                     c[i2] = this.deepCopy(prop);
965                 } else {
966                     c[i2] = prop;
967                 }
968             }
969             
970             for (i in obj2) {
971                 i2 = toLower ? i.toLowerCase() : i;
972 
973                 prop = obj2[i];
974                 if (typeof prop == 'object') {
975                     if (JXG.isArray(prop) || !JXG.exists(c[i2])) {
976                         c[i2] = this.deepCopy(prop);
977                     } else {
978                         c[i2] = this.deepCopy(c[i2], prop, toLower);
979                     }
980                 } else {
981                     c[i2] = prop;
982                 }
983             }
984         }
985         return c;
986     },
987 
988     /**
989      * Converts a JavaScript object into a JSON string.
990      * @param {Object} obj A JavaScript object, functions will be ignored.
991      * @param {Boolean} [noquote=false] No quotes around the name of a property.
992      * @returns {String} The given object stored in a JSON string.
993      */
994     toJSON: function (obj, noquote) {
995         var s;
996 
997         if (!JXG.exists(noquote)) {
998             noquote = false;
999         }
1000 
1001         // check for native JSON support:
1002         if (window.JSON && window.JSON.stringify && !noquote) {
1003             try {
1004                 s = JSON.stringify(obj);
1005                 return s;
1006             } catch(e) {
1007                 // if something goes wrong, e.g. if obj contains functions we won't return
1008                 // and use our own implementation as a fallback
1009             }
1010         }
1011 
1012         switch (typeof obj) {
1013             case 'object':
1014                 if (obj) {
1015                     var list = [];
1016                     if (obj instanceof Array) {
1017                         for (var i=0;i < obj.length;i++) {
1018                             list.push(JXG.toJSON(obj[i], noquote));
1019                         }
1020                         return '[' + list.join(',') + ']';
1021                     } else {
1022                         for (var prop in obj) {
1023                             if (noquote) {
1024                                 list.push(prop + ':' + JXG.toJSON(obj[prop], noquote));
1025                             } else {
1026                                 list.push('"' + prop + '":' + JXG.toJSON(obj[prop], noquote));
1027                             }
1028                         }
1029                         return '{' + list.join(',') + '} ';
1030                     }
1031                 } else {
1032                     return 'null';
1033                 }
1034             case 'string':
1035                 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
1036             case 'number':
1037             case 'boolean':
1038                 return new String(obj);
1039         }
1040     },
1041 
1042     /**
1043      * Makes a string lower case except for the first character which will be upper case.
1044      * @param {String} str Arbitrary string
1045      * @returns {String} The capitalized string.
1046      */
1047     capitalize: function (str) {
1048         return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
1049     },
1050 
1051     /**
1052      * Process data in timed chunks. Data which takes long to process, either because it is such
1053      * a huge amount of data or the processing takes some time, causes warnings in browsers about
1054      * irresponsive scripts. To prevent these warnings, the processing is split into smaller pieces
1055      * called chunks which will be processed in serial order.
1056      * Copyright 2009 Nicholas C. Zakas. All rights reserved. MIT Licensed
1057      * @param {Array} items to do
1058      * @param {Function} process Function that is applied for every array item
1059      * @param {Object} context The scope of function process
1060      * @param {Function} callback This function is called after the last array element has been processed.
1061      */
1062     timedChunk: function (items, process, context, callback) {
1063         var todo = items.concat();   //create a clone of the original
1064         setTimeout(function (){
1065             var start = +new Date();
1066             do {
1067                 process.call(context, todo.shift());
1068             } while (todo.length > 0 && (+new Date() - start < 300));
1069             if (todo.length > 0){
1070                 setTimeout(arguments.callee, 1);
1071             } else {
1072                 callback(items);
1073             }
1074         }, 1);
1075     },
1076 
1077     /**
1078      * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
1079      * @param {String} str
1080      * @returns {String}
1081      */
1082     trimNumber: function (str) {
1083         str = str.replace(/^0+/, "");
1084         str = str.replace(/0+$/, "");
1085         if (str[str.length-1] == '.' || str[str.length-1] == ',') {
1086             str = str.slice(0, -1);
1087         }
1088         if (str[0] == '.' || str[0] == ',') {
1089             str = "0" + str;
1090         }
1091 
1092         return str;
1093     },
1094 
1095     /**
1096      * Remove all leading and trailing whitespaces from a given string.
1097      * @param {String} str
1098      * @returns {String}
1099      */
1100     trim: function (str) {
1101         str = str.replace(/^\s+/, "");
1102         str = str.replace(/\s+$/, "");
1103 
1104         return str;
1105     },
1106 
1107     /**
1108      * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
1109      * is just returned.
1110      * @param val Could be anything. Preferably a number or a function.
1111      * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
1112      */
1113     evaluate: function (val) {
1114         if (JXG.isFunction(val)) {
1115             return val();
1116         } else {
1117             return val;
1118         }
1119     },
1120     
1121     /**
1122      * Eliminates duplicate entries in an array.
1123      * @param {Array} a An array
1124      * @returns {Array} The array with duplicate entries eliminated.
1125      */
1126     eliminateDuplicates: function (a) {
1127         var i, len = a.length,
1128             result = [],
1129             obj = {};
1130 
1131         for (i = 0; i < len; i++) {
1132             obj[a[i]] = 0;
1133         }
1134         
1135         for (i in obj) {
1136             if (obj.hasOwnProperty(i)) {
1137                 result.push(i);
1138             }
1139         }
1140         
1141         return result;
1142     },
1143 
1144     /**
1145      * Compare two arrays.
1146      * @param {Array} a1
1147      * @param {Array} a2
1148      * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
1149      */
1150     cmpArrays: function (a1, a2) {
1151         var i;
1152 
1153         // trivial cases
1154         if (a1 === a2) {
1155             return true;
1156         }
1157 
1158         if (a1.length !== a2.length) {
1159             return false;
1160         }
1161 
1162         for (i = 0; i < a1.length; i++) {
1163             if ((typeof a1[i] !== typeof a2[i]) || (a1[i] !== a2[i])) {
1164                 return false;
1165             }
1166         }
1167 
1168         return true;
1169     },
1170 
1171     /**
1172      * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
1173      * @param n
1174      * @param p
1175      * @returns {Number}
1176      */
1177     trunc: function (n, p) {
1178         p = JXG.def(p, 0);
1179 
1180         if (p == 0) {
1181             n = ~~n;
1182         } else {
1183             n = n.toFixed(p);
1184         }
1185 
1186         return n;
1187     },
1188     
1189     /**
1190      * Add something to the debug log. If available a JavaScript debug console is used. Otherwise
1191      * we're looking for a HTML div with id "debug". If this doesn't exist, too, the output is omitted.
1192      * @param {%} An arbitrary number of parameters.
1193      */
1194     debug: function (s) {
1195         var i;
1196 
1197         for(i = 0; i < arguments.length; i++) {
1198             s = arguments[i];
1199             if (window.console && console.log) {
1200                 //if (typeof s === 'string') s = s.replace(/<\S[^><]*>/g, "");
1201                 console.log(s);
1202             } else if (document.getElementById('debug')) {
1203                 document.getElementById('debug').innerHTML += s + "<br/>";
1204             }
1205             // else: do nothing
1206         }
1207     },
1208 
1209     debugWST: function (s) {
1210         var e;
1211         JXG.debug(s);
1212 
1213         if (window.console && console.log) {
1214             e = new Error();
1215             if (e && e.stack) {
1216                 console.log('stacktrace');
1217                 console.log(e.stack.split('\n').slice(1).join('\n'));
1218             }
1219         }
1220     }
1221 });
1222 
1223 // JessieScript startup: Search for script tags of type text/jessiescript and interpret them.
1224 JXG.addEvent(window, 'load', function () {
1225     var scripts = document.getElementsByTagName('script'), type,
1226         i, j, div, board, width, height, bbox, axis, grid;
1227     
1228     for(i=0;i<scripts.length;i++) {
1229         type = scripts[i].getAttribute('type', false);
1230 		if (!JXG.exists(type)) continue;
1231         if (type.toLowerCase() === 'text/jessiescript' || type.toLowerCase === 'jessiescript') {
1232             width = scripts[i].getAttribute('width', false) || '500px';
1233             height = scripts[i].getAttribute('height', false) || '500px';
1234             bbox = scripts[i].getAttribute('boundingbox', false) || '-5, 5, 5, -5';
1235             bbox = bbox.split(',');
1236             if (bbox.length!==4) {
1237                 bbox = [-5, 5, 5, -5];
1238             } else {
1239                 for(j=0;j<bbox.length;j++) {
1240                     bbox[j] = parseFloat(bbox[j]);
1241                 }
1242             }
1243             axis = JXG.str2Bool(scripts[i].getAttribute('axis', false) || 'false');
1244             grid = JXG.str2Bool(scripts[i].getAttribute('grid', false) || 'false');
1245 
1246             div = document.createElement('div');
1247             div.setAttribute('id', 'jessiescript_autgen_jxg_'+i);
1248             div.setAttribute('style', 'width:'+width+'; height:'+height+'; float:left');
1249             div.setAttribute('class', 'jxgbox');
1250             document.body.insertBefore(div, scripts[i]);
1251 
1252             board = JXG.JSXGraph.initBoard('jessiescript_autgen_jxg_'+i, {boundingbox: bbox, keepaspectratio:true, grid: grid, axis: axis});
1253             board.construct(scripts[i].innerHTML);
1254         }
1255     }
1256 }, window);
1257