      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