      1 /*
      2     Copyright 2008-2025
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Andreas Walter,
      8         Alfred Wassermann,
      9         Peter Wilfahrt
     10
     11     This file is part of JSXGraph.
     12
     13     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     14
     15     You can redistribute it and/or modify it under the terms of the
     16
     17       * GNU Lesser General Public License as published by
     18         the Free Software Foundation, either version 3 of the License, or
     19         (at your option) any later version
     20       OR
     21       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     22
     23     JSXGraph is distributed in the hope that it will be useful,
     24     but WITHOUT ANY WARRANTY; without even the implied warranty of
     25     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     26     GNU Lesser General Public License for more details.
     27
     28     You should have received a copy of the GNU Lesser General Public License and
     29     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
     30     and <https://opensource.org/licenses/MIT/>.
     31  */
     32
     33 /*global JXG: true, define: true, html_sanitize: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /**
     37  * @fileoverview type.js contains several functions to help deal with javascript's weak types.
     38  * This file mainly consists of detector functions which verify if a variable is or is not of
     39  * a specific type and converter functions that convert variables to another type or normalize
     40  * the type of a variable.
     41  */
     42
     43 import JXG from "../jxg.js";
     44 import Const from "../base/constants.js";
     45 import Mat from "../math/math.js";
     46
     47 JXG.extend(
     48     JXG,
     49     /** @lends JXG */ {
     50         /**
     51          * Checks if the given object is an JSXGraph board.
     52          * @param {Object} v
     53          * @returns {Boolean}
     54          */
     55         isBoard: function (v) {
     56             return v !== null &&
     57                 typeof v === "object" &&
     58                 this.isNumber(v.BOARD_MODE_NONE) &&
     59                 this.isObject(v.objects) &&
     60                 this.isObject(v.jc) &&
     61                 this.isFunction(v.update) &&
     62                 !!v.containerObj &&
     63                 this.isString(v.id);
     64         },
     65
     66         /**
     67          * Checks if the given string is an id within the given board.
     68          * @param {JXG.Board} board
     69          * @param {String} s
     70          * @returns {Boolean}
     71          */
     72         isId: function (board, s) {
     73             return typeof s === "string" && !!board.objects[s];
     74         },
     75
     76         /**
     77          * Checks if the given string is a name within the given board.
     78          * @param {JXG.Board} board
     79          * @param {String} s
     80          * @returns {Boolean}
     81          */
     82         isName: function (board, s) {
     83             return typeof s === "string" && !!board.elementsByName[s];
     84         },
     85
     86         /**
     87          * Checks if the given string is a group id within the given board.
     88          * @param {JXG.Board} board
     89          * @param {String} s
     90          * @returns {Boolean}
     91          */
     92         isGroup: function (board, s) {
     93             return typeof s === "string" && !!board.groups[s];
     94         },
     95
     96         /**
     97          * Checks if the value of a given variable is of type string.
     98          * @param v A variable of any type.
     99          * @returns {Boolean} True, if v is of type string.
    100          */
    101         isString: function (v) {
    102             return typeof v === "string";
    103         },
    104
    105         /**
    106          * Checks if the value of a given variable is of type number.
    107          * @param v A variable of any type.
    108          * @param {Boolean} [acceptStringNumber=false] If set to true, the function returns true for e.g. v='3.1415'.
    109          * @param {Boolean} [acceptNaN=true] If set to false, the function returns false for v=NaN.
    110          * @returns {Boolean} True, if v is of type number.
    111          */
    112         isNumber: function (v, acceptStringNumber, acceptNaN) {
    113             var result = (
    114                 typeof v === 'number' || Object.prototype.toString.call(v) === '[Object Number]'
    115             );
    116             acceptStringNumber = acceptStringNumber || false;
    117             acceptNaN = acceptNaN === undefined ? true : acceptNaN;
    118
    119             if (acceptStringNumber) {
    120                 result = result || ('' + parseFloat(v)) === v;
    121             }
    122             if (!acceptNaN) {
    123                 result = result && !isNaN(v);
    124             }
    125             return result;
    126         },
    127
    128         /**
    129          * Checks if a given variable references a function.
    130          * @param v A variable of any type.
    131          * @returns {Boolean} True, if v is a function.
    132          */
    133         isFunction: function (v) {
    134             return typeof v === "function";
    135         },
    136
    137         /**
    138          * Checks if a given variable references an array.
    139          * @param v A variable of any type.
    140          * @returns {Boolean} True, if v is of type array.
    141          */
    142         isArray: function (v) {
    143             var r;
    144
    145             // use the ES5 isArray() method and if that doesn't exist use a fallback.
    146             if (Array.isArray) {
    147                 r = Array.isArray(v);
    148             } else {
    149                 r =
    150                     v !== null &&
    151                     typeof v === "object" &&
    152                     typeof v.splice === "function" &&
    153                     typeof v.join === "function";
    154             }
    155
    156             return r;
    157         },
    158
    159         /**
    160          * Tests if the input variable is an Object
    161          * @param v
    162          */
    163         isObject: function (v) {
    164             return typeof v === "object" && !this.isArray(v);
    165         },
    166
    167         /**
    168          * Tests if the input variable is a DOM Document or DocumentFragment node
    169          * @param v A variable of any type
    170          */
    171         isDocumentOrFragment: function (v) {
    172             return this.isObject(v) && (
    173                 v.nodeType === 9 || // Node.DOCUMENT_NODE
    174                 v.nodeType === 11   // Node.DOCUMENT_FRAGMENT_NODE
    175             );
    176         },
    177
    178         /**
    179          * Checks if a given variable is a reference of a JSXGraph Point element.
    180          * @param v A variable of any type.
    181          * @returns {Boolean} True, if v is of type JXG.Point.
    182          */
    183         isPoint: function (v) {
    184             if (v !== null && typeof v === "object" && this.exists(v.elementClass)) {
    185                 return v.elementClass === Const.OBJECT_CLASS_POINT;
    186             }
    187
    188             return false;
    189         },
    190
    191         /**
    192          * Checks if a given variable is a reference of a JSXGraph Point3D element.
    193          * @param v A variable of any type.
    194          * @returns {Boolean} True, if v is of type JXG.Point3D.
    195          */
    196         isPoint3D: function (v) {
    197             if (v !== null && typeof v === "object" && this.exists(v.type)) {
    198                 return v.type === Const.OBJECT_TYPE_POINT3D;
    199             }
    200
    201             return false;
    202         },
    203
    204         /**
    205          * Checks if a given variable is a reference of a JSXGraph Point element or an array of length at least two or
    206          * a function returning an array of length two or three.
    207          * @param {JXG.Board} board
    208          * @param v A variable of any type.
    209          * @returns {Boolean} True, if v is of type JXG.Point.
    210          */
    211         isPointType: function (board, v) {
    212             var val, p;
    213
    214             if (this.isArray(v)) {
    215                 return true;
    216             }
    217             if (this.isFunction(v)) {
    218                 val = v();
    219                 if (this.isArray(val) && val.length > 1) {
    220                     return true;
    221                 }
    222             }
    223             p = board.select(v);
    224             return this.isPoint(p);
    225         },
    226
    227         /**
    228          * Checks if a given variable is a reference of a JSXGraph Point3D element or an array of length three
    229          * or a function returning an array of length three.
    230          * @param {JXG.Board} board
    231          * @param v A variable of any type.
    232          * @returns {Boolean} True, if v is of type JXG.Point3D or an array of length at least 3, or a function returning
    233          * such an array.
    234          */
    235         isPointType3D: function (board, v) {
    236             var val, p;
    237
    238             if (this.isArray(v) && v.length >= 3) {
    239                 return true;
    240             }
    241             if (this.isFunction(v)) {
    242                 val = v();
    243                 if (this.isArray(val) && val.length >= 3) {
    244                     return true;
    245                 }
    246             }
    247             p = board.select(v);
    248             return this.isPoint3D(p);
    249         },
    250
    251         /**
    252          * Checks if a given variable is a reference of a JSXGraph transformation element or an array
    253          * of JSXGraph transformation elements.
    254          * @param v A variable of any type.
    255          * @returns {Boolean} True, if v is of type JXG.Transformation.
    256          */
    257         isTransformationOrArray: function (v) {
    258             if (v !== null) {
    259                 if (this.isArray(v) && v.length > 0) {
    260                     return this.isTransformationOrArray(v[0]);
    261                 }
    262                 if (typeof v === "object") {
    263                     return v.type === Const.OBJECT_TYPE_TRANSFORMATION;
    264                 }
    265             }
    266             return false;
    267         },
    268
    269         /**
    270          * Checks if v is an empty object or empty.
    271          * @param v {Object|Array}
    272          * @returns {boolean} True, if v is an empty object or array.
    273          */
    274         isEmpty: function (v) {
    275             return Object.keys(v).length === 0;
    276         },
    277
    278         /**
    279          * Checks if a given variable is neither undefined nor null. You should not use this together with global
    280          * variables!
    281          * @param v A variable of any type.
    282          * @param {Boolean} [checkEmptyString=false] If set to true, it is also checked whether v is not equal to ''.
    283          * @returns {Boolean} True, if v is neither undefined nor null.
    284          */
    285         exists: function (v, checkEmptyString) {
    286             /* eslint-disable eqeqeq */
    287             var result = !(v == undefined || v === null);
    288             /* eslint-enable eqeqeq */
    289             checkEmptyString = checkEmptyString || false;
    290
    291             if (checkEmptyString) {
    292                 return result && v !== "";
    293             }
    294             return result;
    295         },
    296         // exists: (function (undef) {
    297         //     return function (v, checkEmptyString) {
    298         //         var result = !(v === undef || v === null);
    299
    300         //         checkEmptyString = checkEmptyString || false;
    301
    302         //         if (checkEmptyString) {
    303         //             return result && v !== '';
    304         //         }
    305         //         return result;
    306         //     };
    307         // }()),
    308
    309         /**
    310          * Handle default parameters.
    311          * @param v Given value
    312          * @param d Default value
    313          * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null.
    314          */
    315         def: function (v, d) {
    316             if (this.exists(v)) {
    317                 return v;
    318             }
    319
    320             return d;
    321         },
    322
    323         /**
    324          * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
    325          * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
    326          * @returns {Boolean} String typed boolean value converted to boolean.
    327          */
    328         str2Bool: function (s) {
    329             if (!this.exists(s)) {
    330                 return true;
    331             }
    332
    333             if (typeof s === "boolean") {
    334                 return s;
    335             }
    336
    337             if (this.isString(s)) {
    338                 return s.toLowerCase() === "true";
    339             }
    340
    341             return false;
    342         },
    343
    344         /**
    345          * Converts a given CSS style string into a JavaScript object.
    346          * @param {String} styles String containing CSS styles.
    347          * @returns {Object} Object containing CSS styles.
    348          */
    349         cssParse: function (styles) {
    350             var str = styles;
    351             if (!this.isString(str)) return {};
    352
    353             str = str.replace(/\s*;\s*$/g, '');
    354             str = str.replace(/\s*;\s*/g, '","');
    355             str = str.replace(/\s*:\s*/g, '":"');
    356             str = str.trim();
    357             str = '{"' + str + '"}';
    358
    359             return JSON.parse(str);
    360         },
    361
    362         /**
    363          * Converts a given object into a CSS style string.
    364          * @param {Object} styles Object containing CSS styles.
    365          * @returns {String} String containing CSS styles.
    366          */
    367         cssStringify: function (styles) {
    368             var str = '',
    369                 attr, val;
    370             if (!this.isObject(styles)) return '';
    371
    372             for (attr in styles) {
    373                 if (!styles.hasOwnProperty(attr)) continue;
    374                 val = styles[attr];
    375                 if (!this.isString(val) && !this.isNumber(val)) continue;
    376
    377                 str += attr + ':' + val + '; ';
    378             }
    379             str = str.trim();
    380
    381             return str;
    382         },
    383
    384         /**
    385          * Convert a String, a number or a function into a function. This method is used in Transformation.js
    386          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
    387          * by a JessieCode string, thus it must be a valid reference only in case one of the param
    388          * values is of type string.
    389          * @param {Array} param An array containing strings, numbers, or functions.
    390          * @param {Number} n Length of <tt>param</tt>.
    391          * @returns {Function} A function taking one parameter k which specifies the index of the param element
    392          * to evaluate.
    393          */
    394         createEvalFunction: function (board, param, n) {
    395             var f = [], func, i, e,
    396                 deps = {};
    397
    398             for (i = 0; i < n; i++) {
    399                 f[i] = this.createFunction(param[i], board);
    400                 for (e in f[i].deps) {
    401                     deps[e] = f[i].deps;
    402                 }
    403             }
    404
    405             func = function (k) {
    406                 return f[k]();
    407             };
    408             func.deps = deps;
    409
    410             return func;
    411         },
    412
    413         /**
    414          * Convert a String, number or function into a function.
    415          * @param {String|Number|Function} term A variable of type string, function or number.
    416          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
    417          * by a JessieCode/GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
    418          * values is of type string.
    419          * @param {String} variableName Only required if function is supplied as JessieCode string or evalGeonext is set to true.
    420          * Describes the variable name of the variable in a JessieCode/GEONE<sub>X</sub>T string given as term.
    421          * @param {Boolean} [evalGeonext=false] Obsolete and ignored! Set this true
    422          * if term should be treated as a GEONE<sub>X</sub>T string.
    423          * @returns {Function} A function evaluating the value given by term or null if term is not of type string,
    424          * function or number.
    425          */
    426         createFunction: function (term, board, variableName, evalGeonext) {
    427             var f = null;
    428
    429             // if ((!this.exists(evalGeonext) || evalGeonext) && this.isString(term)) {
    430             if (this.isString(term)) {
    431                 // Convert GEONExT syntax into  JavaScript syntax
    432                 //newTerm = JXG.GeonextParser.geonext2JS(term, board);
    433                 //return new Function(variableName,'return ' + newTerm + ';');
    434                 //term = JXG.GeonextParser.replaceNameById(term, board);
    435                 //term = JXG.GeonextParser.geonext2JS(term, board);
    436
    437                 f = board.jc.snippet(term, true, variableName, false);
    438             } else if (this.isFunction(term)) {
    439                 f = term;
    440                 f.deps = (this.isObject(term.deps)) ? term.deps : {};
    441             } else if (this.isNumber(term) || this.isArray(term)) {
    442                 /** @ignore */
    443                 f = function () { return term; };
    444                 f.deps = {};
    445                 // } else if (this.isString(term)) {
    446                 //     // In case of string function like fontsize
    447                 //     /** @ignore */
    448                 //     f = function () { return term; };
    449                 //     f.deps = {};
    450             }
    451
    452             if (f !== null) {
    453                 f.origin = term;
    454             }
    455
    456             return f;
    457         },
    458
    459         /**
    460          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or
    461          *  function returning coordinate arrays
    462          *  free points with these coordinates are created.
    463          *
    464          * @param {JXG.Board} board Board object
    465          * @param {Array} parents Array containing parent elements for a new object. This array may contain
    466          *    <ul>
    467          *      <li> {@link JXG.Point} objects
    468          *      <li> {@link JXG.GeometryElement#name} of {@link JXG.Point} objects
    469          *      <li> {@link JXG.GeometryElement#id} of {@link JXG.Point} objects
    470          *      <li> Coordinates of points given as array of numbers of length two or three, e.g. [2, 3].
    471          *      <li> Coordinates of points given as array of functions of length two or three. Each function returns one coordinate, e.g.
    472          *           [function(){ return 2; }, function(){ return 3; }]
    473          *      <li> Function returning coordinates, e.g. function() { return [2, 3]; }
    474          *    </ul>
    475          *  In the last three cases a new point will be created.
    476          * @param {String} attrClass Main attribute class of newly created points, see {@link JXG#copyAttributes}
    477          * @param {Array} attrArray List of subtype attributes for the newly created points. The list of subtypes is mapped to the list of new points.
    478          * @returns {Array} List of newly created {@link JXG.Point} elements or false if not all returned elements are points.
    479          */
    480         providePoints: function (board, parents, attributes, attrClass, attrArray) {
    481             var i,
    482                 j,
    483                 len,
    484                 lenAttr = 0,
    485                 points = [],
    486                 attr,
    487                 val;
    488
    489             if (!this.isArray(parents)) {
    490                 parents = [parents];
    491             }
    492             len = parents.length;
    493             if (this.exists(attrArray)) {
    494                 lenAttr = attrArray.length;
    495             }
    496             if (lenAttr === 0) {
    497                 attr = this.copyAttributes(attributes, board.options, attrClass);
    498             }
    499
    500             for (i = 0; i < len; ++i) {
    501                 if (lenAttr > 0) {
    502                     j = Math.min(i, lenAttr - 1);
    503                     attr = this.copyAttributes(
    504                         attributes,
    505                         board.options,
    506                         attrClass,
    507                         attrArray[j].toLowerCase()
    508                     );
    509                 }
    510                 if (this.isArray(parents[i]) && parents[i].length > 1) {
    511                     points.push(board.create("point", parents[i], attr));
    512                     points[points.length - 1]._is_new = true;
    513                 } else if (this.isFunction(parents[i])) {
    514                     val = parents[i]();
    515                     if (this.isArray(val) && val.length > 1) {
    516                         points.push(board.create("point", [parents[i]], attr));
    517                         points[points.length - 1]._is_new = true;
    518                     }
    519                 } else {
    520                     points.push(board.select(parents[i]));
    521                 }
    522
    523                 if (!this.isPoint(points[i])) {
    524                     return false;
    525                 }
    526             }
    527
    528             return points;
    529         },
    530
    531         /**
    532          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or
    533          *  function returning coordinate arrays
    534          *  free points with these coordinates are created.
    535          *
    536          * @param {JXG.View3D} view View3D object
    537          * @param {Array} parents Array containing parent elements for a new object. This array may contain
    538          *    <ul>
    539          *      <li> {@link JXG.Point3D} objects
    540          *      <li> {@link JXG.GeometryElement#name} of {@link JXG.Point3D} objects
    541          *      <li> {@link JXG.GeometryElement#id} of {@link JXG.Point3D} objects
    542          *      <li> Coordinates of 3D points given as array of numbers of length three, e.g. [2, 3, 1].
    543          *      <li> Coordinates of 3D points given as array of functions of length three. Each function returns one coordinate, e.g.
    544          *           [function(){ return 2; }, function(){ return 3; }, function(){ return 1; }]
    545          *      <li> Function returning coordinates, e.g. function() { return [2, 3, 1]; }
    546          *    </ul>
    547          *  In the last three cases a new 3D point will be created.
    548          * @param {String} attrClass Main attribute class of newly created 3D points, see {@link JXG#copyAttributes}
    549          * @param {Array} attrArray List of subtype attributes for the newly created 3D points. The list of subtypes is mapped to the list of new 3D points.
    550          * @returns {Array} List of newly created {@link JXG.Point3D} elements or false if not all returned elements are 3D points.
    551          */
    552         providePoints3D: function (view, parents, attributes, attrClass, attrArray) {
    553             var i,
    554                 j,
    555                 len,
    556                 lenAttr = 0,
    557                 points = [],
    558                 attr,
    559                 val;
    560
    561             if (!this.isArray(parents)) {
    562                 parents = [parents];
    563             }
    564             len = parents.length;
    565             if (this.exists(attrArray)) {
    566                 lenAttr = attrArray.length;
    567             }
    568             if (lenAttr === 0) {
    569                 attr = this.copyAttributes(attributes, view.board.options, attrClass);
    570             }
    571
    572             for (i = 0; i < len; ++i) {
    573                 if (lenAttr > 0) {
    574                     j = Math.min(i, lenAttr - 1);
    575                     attr = this.copyAttributes(
    576                         attributes,
    577                         view.board.options,
    578                         attrClass,
    579                         attrArray[j]
    580                     );
    581                 }
    582
    583                 if (this.isArray(parents[i]) && parents[i].length > 0 && parents[i].every((x)=>this.isArray(x) && this.isNumber(x[0]))) {
    584                     // Testing for array-of-arrays-of-numbers, like [[1,2,3],[2,3,4]]
    585                     for (j = 0; j < parents[i].length; j++) {
    586                         points.push(view.create("point3d", parents[i][j], attr));;
    587                         points[points.length - 1]._is_new = true;
    588                     }
    589                 } else if (this.isArray(parents[i]) &&  parents[i].every((x)=> this.isNumber(x) || this.isFunction(x))) {
    590                     // Single array [1,2,3]
    591                     points.push(view.create("point3d", parents[i], attr));
    592                     points[points.length - 1]._is_new = true;
    593
    594                 } else if (this.isPoint3D(parents[i])) {
    595                     points.push(parents[i]);
    596                 } else if (this.isFunction(parents[i])) {
    597                     val = parents[i]();
    598                     if (this.isArray(val) && val.length > 1) {
    599                         points.push(view.create("point3d", [parents[i]], attr));
    600                         points[points.length - 1]._is_new = true;
    601                     }
    602                 } else {
    603                     points.push(view.select(parents[i]));
    604                 }
    605
    606                 if (!this.isPoint3D(points[i])) {
    607                     return false;
    608                 }
    609             }
    610
    611             return points;
    612         },
    613
    614         /**
    615          * Generates a function which calls the function fn in the scope of owner.
    616          * @param {Function} fn Function to call.
    617          * @param {Object} owner Scope in which fn is executed.
    618          * @returns {Function} A function with the same signature as fn.
    619          */
    620         bind: function (fn, owner) {
    621             return function () {
    622                 return fn.apply(owner, arguments);
    623             };
    624         },
    625
    626         /**
    627          * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
    628          * is just returned.
    629          * @param val Could be anything. Preferably a number or a function.
    630          * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
    631          */
    632         evaluate: function (val) {
    633             if (this.isFunction(val)) {
    634                 return val();
    635             }
    636
    637             return val;
    638         },
    639
    640         /**
    641          * Search an array for a given value.
    642          * @param {Array} array
    643          * @param value
    644          * @param {String} [sub] Use this property if the elements of the array are objects.
    645          * @returns {Number} The index of the first appearance of the given value, or
    646          * <tt>-1</tt> if the value was not found.
    647          */
    648         indexOf: function (array, value, sub) {
    649             var i,
    650                 s = this.exists(sub);
    651
    652             if (Array.indexOf && !s) {
    653                 return array.indexOf(value);
    654             }
    655
    656             for (i = 0; i < array.length; i++) {
    657                 if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
    658                     return i;
    659                 }
    660             }
    661
    662             return -1;
    663         },
    664
    665         /**
    666          * Eliminates duplicate entries in an array consisting of numbers and strings.
    667          * @param {Array} a An array of numbers and/or strings.
    668          * @returns {Array} The array with duplicate entries eliminated.
    669          */
    670         eliminateDuplicates: function (a) {
    671             var i,
    672                 len = a.length,
    673                 result = [],
    674                 obj = {};
    675
    676             for (i = 0; i < len; i++) {
    677                 obj[a[i]] = 0;
    678             }
    679
    680             for (i in obj) {
    681                 if (obj.hasOwnProperty(i)) {
    682                     result.push(i);
    683                 }
    684             }
    685
    686             return result;
    687         },
    688
    689         /**
    690          * Swaps to array elements.
    691          * @param {Array} arr
    692          * @param {Number} i
    693          * @param {Number} j
    694          * @returns {Array} Reference to the given array.
    695          */
    696         swap: function (arr, i, j) {
    697             var tmp;
    698
    699             tmp = arr[i];
    700             arr[i] = arr[j];
    701             arr[j] = tmp;
    702
    703             return arr;
    704         },
    705
    706         /**
    707          * Generates a copy of an array and removes the duplicate entries. The original
    708          * Array will be altered.
    709          * @param {Array} arr
    710          * @returns {Array}
    711          */
    712         uniqueArray: function (arr) {
    713             var i,
    714                 j,
    715                 isArray,
    716                 ret = [];
    717
    718             if (arr.length === 0) {
    719                 return [];
    720             }
    721
    722             for (i = 0; i < arr.length; i++) {
    723                 isArray = this.isArray(arr[i]);
    724
    725                 if (!this.exists(arr[i])) {
    726                     arr[i] = "";
    727                     continue;
    728                 }
    729                 for (j = i + 1; j < arr.length; j++) {
    730                     if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
    731                         arr[i] = [];
    732                     } else if (!isArray && arr[i] === arr[j]) {
    733                         arr[i] = "";
    734                     }
    735                 }
    736             }
    737
    738             j = 0;
    739
    740             for (i = 0; i < arr.length; i++) {
    741                 isArray = this.isArray(arr[i]);
    742
    743                 if (!isArray && arr[i] !== "") {
    744                     ret[j] = arr[i];
    745                     j++;
    746                 } else if (isArray && arr[i].length !== 0) {
    747                     ret[j] = arr[i].slice(0);
    748                     j++;
    749                 }
    750             }
    751
    752             arr = ret;
    753             return ret;
    754         },
    755
    756         /**
    757          * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
    758          * @param {Array} arr
    759          * @param val
    760          * @returns {Boolean}
    761          */
    762         isInArray: function (arr, val) {
    763             return JXG.indexOf(arr, val) > -1;
    764         },
    765
    766         /**
    767          * Converts an array of {@link JXG.Coords} objects into a coordinate matrix.
    768          * @param {Array} coords
    769          * @param {Boolean} split
    770          * @returns {Array}
    771          */
    772         coordsArrayToMatrix: function (coords, split) {
    773             var i,
    774                 x = [],
    775                 m = [];
    776
    777             for (i = 0; i < coords.length; i++) {
    778                 if (split) {
    779                     x.push(coords[i].usrCoords[1]);
    780                     m.push(coords[i].usrCoords[2]);
    781                 } else {
    782                     m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]);
    783                 }
    784             }
    785
    786             if (split) {
    787                 m = [x, m];
    788             }
    789
    790             return m;
    791         },
    792
    793         /**
    794          * Compare two arrays.
    795          * @param {Array} a1
    796          * @param {Array} a2
    797          * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
    798          */
    799         cmpArrays: function (a1, a2) {
    800             var i;
    801
    802             // trivial cases
    803             if (a1 === a2) {
    804                 return true;
    805             }
    806
    807             if (a1.length !== a2.length) {
    808                 return false;
    809             }
    810
    811             for (i = 0; i < a1.length; i++) {
    812                 if (this.isArray(a1[i]) && this.isArray(a2[i])) {
    813                     if (!this.cmpArrays(a1[i], a2[i])) {
    814                         return false;
    815                     }
    816                 } else if (a1[i] !== a2[i]) {
    817                     return false;
    818                 }
    819             }
    820
    821             return true;
    822         },
    823
    824         /**
    825          * Removes an element from the given array
    826          * @param {Array} ar
    827          * @param el
    828          * @returns {Array}
    829          */
    830         removeElementFromArray: function (ar, el) {
    831             var i;
    832
    833             for (i = 0; i < ar.length; i++) {
    834                 if (ar[i] === el) {
    835                     ar.splice(i, 1);
    836                     return ar;
    837                 }
    838             }
    839
    840             return ar;
    841         },
    842
    843         /**
    844          * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
    845          * @param {Number} n
    846          * @param {Number} p
    847          * @returns {Number}
    848          */
    849         trunc: function (n, p) {
    850             p = JXG.def(p, 0);
    851
    852             return this.toFixed(n, p);
    853         },
    854
    855         /**
    856          * Decimal adjustment of a number.
    857          * From https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Math/round
    858          *
    859          * @param    {String}    type    The type of adjustment.
    860          * @param    {Number}    value    The number.
    861          * @param    {Number}    exp        The exponent (the 10 logarithm of the adjustment base).
    862          * @returns    {Number}            The adjusted value.
    863          *
    864          * @private
    865          */
    866         _decimalAdjust: function (type, value, exp) {
    867             // If the exp is undefined or zero...
    868             if (exp === undefined || +exp === 0) {
    869                 return Math[type](value);
    870             }
    871
    872             value = +value;
    873             exp = +exp;
    874             // If the value is not a number or the exp is not an integer...
    875             if (isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) {
    876                 return NaN;
    877             }
    878
    879             // Shift
    880             value = value.toString().split("e");
    881             value = Math[type](+(value[0] + "e" + (value[1] ? +value[1] - exp : -exp)));
    882
    883             // Shift back
    884             value = value.toString().split("e");
    885             return +(value[0] + "e" + (value[1] ? +value[1] + exp : exp));
    886         },
    887
    888         /**
    889          * Round a number to given number of decimal digits.
    890          *
    891          * Example: JXG._toFixed(3.14159, -2) gives 3.14
    892          * @param  {Number} value Number to be rounded
    893          * @param  {Number} exp   Number of decimal digits given as negative exponent
    894          * @return {Number}       Rounded number.
    895          *
    896          * @private
    897          */
    898         _round10: function (value, exp) {
    899             return this._decimalAdjust("round", value, exp);
    900         },
    901
    902         /**
    903          * "Floor" a number to given number of decimal digits.
    904          *
    905          * Example: JXG._toFixed(3.14159, -2) gives 3.14
    906          * @param  {Number} value Number to be floored
    907          * @param  {Number} exp   Number of decimal digits given as negative exponent
    908          * @return {Number}       "Floored" number.
    909          *
    910          * @private
    911          */
    912         _floor10: function (value, exp) {
    913             return this._decimalAdjust("floor", value, exp);
    914         },
    915
    916         /**
    917          * "Ceil" a number to given number of decimal digits.
    918          *
    919          * Example: JXG._toFixed(3.14159, -2) gives 3.15
    920          * @param  {Number} value Number to be ceiled
    921          * @param  {Number} exp   Number of decimal digits given as negative exponent
    922          * @return {Number}       "Ceiled" number.
    923          *
    924          * @private
    925          */
    926         _ceil10: function (value, exp) {
    927             return this._decimalAdjust("ceil", value, exp);
    928         },
    929
    930         /**
    931          * Replacement of the default toFixed() method.
    932          * It does a correct rounding (independent of the browser) and
    933          * returns "0.00" for toFixed(-0.000001, 2) instead of "-0.00" which
    934          * is returned by JavaScript's toFixed()
    935          *
    936          * @memberOf JXG
    937          * @param  {Number} num    Number tp be rounded
    938          * @param  {Number} digits Decimal digits
    939          * @return {String}        Rounded number is returned as string
    940          */
    941         toFixed: function (num, digits) {
    942             return this._round10(num, -digits).toFixed(digits);
    943         },
    944
    945         /**
    946          * Truncate a number <tt>val</tt> automatically.
    947          * @memberOf JXG
    948          * @param val
    949          * @returns {Number}
    950          */
    951         autoDigits: function (val) {
    952             var x = Math.abs(val),
    953                 str;
    954
    955             if (x >= 0.1) {
    956                 str = this.toFixed(val, 2);
    957             } else if (x >= 0.01) {
    958                 str = this.toFixed(val, 4);
    959             } else if (x >= 0.0001) {
    960                 str = this.toFixed(val, 6);
    961             } else {
    962                 str = val;
    963             }
    964             return str;
    965         },
    966
    967         /**
    968          * Convert value v. If v has the form
    969          * <ul>
    970          * <li> 'x%': return floating point number x * percentOfWhat * 0.01
    971          * <li> 'xfr': return floating point number x * percentOfWhat
    972          * <li> 'xpx': return x * convertPx or convertPx(x) or x
    973          * <li> x or 'x': return floating point number x
    974          * </ul>
    975          * @param {String|Number} v
    976          * @param {Number} percentOfWhat
    977          * @param {Function|Number|*} convertPx
    978          * @returns {String|Number}
    979          */
    980         parseNumber: function(v, percentOfWhat, convertPx) {
    981             var str;
    982
    983             if (this.isString(v) && v.indexOf('%') > -1) {
    984                 str = v.replace(/\s+%\s+/, '');
    985                 return parseFloat(str) * percentOfWhat * 0.01;
    986             }
    987             if (this.isString(v) && v.indexOf('fr') > -1) {
    988                 str = v.replace(/\s+fr\s+/, '');
    989                 return parseFloat(str) * percentOfWhat;
    990             }
    991             if (this.isString(v) && v.indexOf('px') > -1) {
    992                 str = v.replace(/\s+px\s+/, '');
    993                 str = parseFloat(str);
    994                 if(this.isFunction(convertPx)) {
    995                     return convertPx(str);
    996                 } else if(this.isNumber(convertPx)) {
    997                     return str * convertPx;
    998                 } else {
    999                     return str;
    1000                 }
    1001             }
    1002             // Number or String containing no unit
    1003             return parseFloat(v);
    1004         },
    1005
    1006         /**
    1007          * Parse a string for label positioning of the form 'left pos' or 'pos right'
    1008          * and return e.g.
    1009          * <tt>{ side: 'left', pos: 'pos' }</tt>.
    1010          * @param {String} str
    1011          * @returns {Obj}  <tt>{ side, pos }</tt>
    1012          */
    1013         parsePosition: function(str) {
    1014             var a, i,
    1015                 side = '',
    1016                 pos = '';
    1017
    1018             str = str.trim();
    1019             if (str !== '') {
    1020                 a = str.split(/[ ,]+/);
    1021                 for (i = 0; i < a.length; i++) {
    1022                     if (a[i] === 'left' || a[i] === 'right') {
    1023                         side = a[i];
    1024                     } else {
    1025                         pos = a[i];
    1026                     }
    1027                 }
    1028             }
    1029
    1030             return {
    1031                 side: side,
    1032                 pos: pos
    1033             };
    1034         },
    1035
    1036         /**
    1037          * Extracts the keys of a given object.
    1038          * @param object The object the keys are to be extracted
    1039          * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
    1040          * the object owns itself and not some other object in the prototype chain.
    1041          * @returns {Array} All keys of the given object.
    1042          */
    1043         keys: function (object, onlyOwn) {
    1044             var keys = [],
    1045                 property;
    1046
    1047             // the caller decides if we use hasOwnProperty
    1048             /*jslint forin:true*/
    1049             for (property in object) {
    1050                 if (onlyOwn) {
    1051                     if (object.hasOwnProperty(property)) {
    1052                         keys.push(property);
    1053                     }
    1054                 } else {
    1055                     keys.push(property);
    1056                 }
    1057             }
    1058             /*jslint forin:false*/
    1059
    1060             return keys;
    1061         },
    1062
    1063         /**
    1064          * This outputs an object with a base class reference to the given object. This is useful if
    1065          * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
    1066          * without changing the original object.
    1067          * @param {Object} obj Object to be embedded.
    1068          * @returns {Object} An object with a base class reference to <tt>obj</tt>.
    1069          */
    1070         clone: function (obj) {
    1071             var cObj = {};
    1072
    1073             cObj.prototype = obj;
    1074
    1075             return cObj;
    1076         },
    1077
    1078         /**
    1079          * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
    1080          * to the new one. Warning: The copied properties of obj2 are just flat copies.
    1081          * @param {Object} obj Object to be copied.
    1082          * @param {Object} obj2 Object with data that is to be copied to the new one as well.
    1083          * @returns {Object} Copy of given object including some new/overwritten data from obj2.
    1084          */
    1085         cloneAndCopy: function (obj, obj2) {
    1086             var r,
    1087                 cObj = function () {
    1088                     return undefined;
    1089                 };
    1090
    1091             cObj.prototype = obj;
    1092
    1093             // no hasOwnProperty on purpose
    1094             /*jslint forin:true*/
    1095             /*jshint forin:true*/
    1096
    1097             for (r in obj2) {
    1098                 cObj[r] = obj2[r];
    1099             }
    1100
    1101             /*jslint forin:false*/
    1102             /*jshint forin:false*/
    1103
    1104             return cObj;
    1105         },
    1106
    1107         /**
    1108          * Recursively merges obj2 into obj1 in-place. Contrary to {@link JXG#deepCopy} this won't create a new object
    1109          * but instead will overwrite obj1.
    1110          * <p>
    1111          * In contrast to method JXG.mergeAttr, merge recurses into any kind of object, e.g. DOM object and JSXGraph objects.
    1112          * So, please be careful.
    1113          * @param {Object} obj1
    1114          * @param {Object} obj2
    1115          * @returns {Object}
    1116          * @see JXG.mergeAttr
    1117          *
    1118          * @example
    1119          * JXG.Options = JXG.merge(JXG.Options, {
    1120          *     board: {
    1121          *         showNavigation: false,
    1122          *         showInfobox: true
    1123          *     },
    1124          *     point: {
    1125          *         face: 'o',
    1126          *         size: 4,
    1127          *         fillColor: '#eeeeee',
    1128          *         highlightFillColor: '#eeeeee',
    1129          *         strokeColor: 'white',
    1130          *         highlightStrokeColor: 'white',
    1131          *         showInfobox: 'inherit'
    1132          *     }
    1133          * });
    1134          *
    1135          * </pre><div id="JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1136          * <script type="text/javascript">
    1137          *     (function() {
    1138          *         var board = JXG.JSXGraph.initBoard('JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b',
    1139          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1140          *     JXG.Options = JXG.merge(JXG.Options, {
    1141          *         board: {
    1142          *             showNavigation: false,
    1143          *             showInfobox: true
    1144          *         },
    1145          *         point: {
    1146          *             face: 'o',
    1147          *             size: 4,
    1148          *             fillColor: '#eeeeee',
    1149          *             highlightFillColor: '#eeeeee',
    1150          *             strokeColor: 'white',
    1151          *             highlightStrokeColor: 'white',
    1152          *             showInfobox: 'inherit'
    1153          *         }
    1154          *     });
    1155          *
    1156          *
    1157          *     })();
    1158          *
    1159          * </script><pre>
    1160          */
    1161         merge: function (obj1, obj2) {
    1162             var i, j, o, oo;
    1163
    1164             for (i in obj2) {
    1165                 if (obj2.hasOwnProperty(i)) {
    1166                     o = obj2[i];
    1167                     if (this.isArray(o)) {
    1168                         if (!obj1[i]) {
    1169                             obj1[i] = [];
    1170                         }
    1171
    1172                         for (j = 0; j < o.length; j++) {
    1173                             oo = obj2[i][j];
    1174                             if (typeof obj2[i][j] === 'object') {
    1175                                 obj1[i][j] = this.merge(obj1[i][j], oo);
    1176                             } else {
    1177                                 obj1[i][j] = obj2[i][j];
    1178                             }
    1179                         }
    1180                     } else if (typeof o === 'object') {
    1181                         if (!obj1[i]) {
    1182                             obj1[i] = {};
    1183                         }
    1184
    1185                         obj1[i] = this.merge(obj1[i], o);
    1186                     } else {
    1187                         if (typeof obj1 === 'boolean') {
    1188                             // This is necessary in the following scenario:
    1189                             //   lastArrow == false
    1190                             // and call of
    1191                             //   setAttribute({lastArrow: {type: 7}})
    1192                             obj1 = {};
    1193                         }
    1194                         obj1[i] = o;
    1195                     }
    1196                 }
    1197             }
    1198
    1199             return obj1;
    1200         },
    1201
    1202         /**
    1203          * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
    1204          * element-wise instead of just copying the reference. If a second object is supplied, the two objects
    1205          * are merged into one object. The properties of the second object have priority.
    1206          * @param {Object} obj This object will be copied.
    1207          * @param {Object} obj2 This object will merged into the newly created object
    1208          * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
    1209          * @returns {Object} copy of obj or merge of obj and obj2.
    1210          */
    1211         deepCopy: function (obj, obj2, toLower) {
    1212             var c, i, prop, i2;
    1213
    1214             toLower = toLower || false;
    1215             if (typeof obj !== 'object' || obj === null) {
    1216                 return obj;
    1217             }
    1218
    1219             // Missing hasOwnProperty is on purpose in this function
    1220             if (this.isArray(obj)) {
    1221                 c = [];
    1222                 for (i = 0; i < obj.length; i++) {
    1223                     prop = obj[i];
    1224                     // Attention: typeof null === 'object'
    1225                     if (prop !== null && typeof prop === "object") {
    1226                         // We certainly do not want to recurse into a JSXGraph object.
    1227                         // This would for sure result in an infinite recursion.
    1228                         // As alternative we copy the id of the object.
    1229                         if (this.exists(prop.board)) {
    1230                             c[i] = prop.id;
    1231                         } else {
    1232                             c[i] = this.deepCopy(prop, {}, toLower);
    1233                         }
    1234                     } else {
    1235                         c[i] = prop;
    1236                     }
    1237                 }
    1238             } else {
    1239                 c = {};
    1240                 for (i in obj) {
    1241                     if (obj.hasOwnProperty(i)) {
    1242                         i2 = toLower ? i.toLowerCase() : i;
    1243                         prop = obj[i];
    1244                         if (prop !== null && typeof prop === "object") {
    1245                             if (this.exists(prop.board)) {
    1246                                 c[i2] = prop.id;
    1247                             } else {
    1248                                 c[i2] = this.deepCopy(prop, {}, toLower);
    1249                             }
    1250                         } else {
    1251                             c[i2] = prop;
    1252                         }
    1253                     }
    1254                 }
    1255
    1256                 for (i in obj2) {
    1257                     if (obj2.hasOwnProperty(i)) {
    1258                         i2 = toLower ? i.toLowerCase() : i;
    1259
    1260                         prop = obj2[i];
    1261                         if (prop !== null && typeof prop === "object") {
    1262                             if (this.isArray(prop) || !this.exists(c[i2])) {
    1263                                 c[i2] = this.deepCopy(prop, {}, toLower);
    1264                             } else {
    1265                                 c[i2] = this.deepCopy(c[i2], prop, toLower);
    1266                             }
    1267                         } else {
    1268                             c[i2] = prop;
    1269                         }
    1270                     }
    1271                 }
    1272             }
    1273
    1274             return c;
    1275         },
    1276
    1277         /**
    1278          * In-place (deep) merging of attributes. Allows attributes like `{shadow: {enabled: true...}}`
    1279          * <p>
    1280          * In contrast to method JXG.merge, mergeAttr does not recurse into DOM objects and JSXGraph objects. Instead
    1281          * handles (pointers) to these objects are used.
    1282          *
    1283          * @param {Object} attr Object with attributes - usually containing default options - that will be changed in-place.
    1284          * @param {Object} special Special option values which overwrite (recursively) the default options
    1285          * @param {Boolean} [toLower=true] If true the keys are converted to lower case.
    1286          * @param {Boolean} [ignoreUndefinedSpecials=false] If true the values in special that are undefined are not used.
    1287          *
    1288          * @see JXG.merge
    1289          *
    1290          */
    1291         mergeAttr: function (attr, special, toLower, ignoreUndefinedSpecials) {
    1292             var e, e2, o;
    1293
    1294             toLower = toLower || true;
    1295             ignoreUndefinedSpecials = ignoreUndefinedSpecials || false;
    1296
    1297             for (e in special) {
    1298                 if (special.hasOwnProperty(e)) {
    1299                     e2 = (toLower) ? e.toLowerCase(): e;
    1300                     // Key already exists, but not in lower case
    1301                     if (e2 !== e && attr.hasOwnProperty(e)) {
    1302                         if (attr.hasOwnProperty(e2)) {
    1303                             // Lower case key already exists - this should not happen
    1304                             // We have to unify the two key-value pairs
    1305                             // It is not clear which has precedence.
    1306                             this.mergeAttr(attr[e2], attr[e], toLower);
    1307                         } else {
    1308                             attr[e2] = attr[e];
    1309                         }
    1310                         delete attr[e];
    1311                     }
    1312
    1313                     o = special[e];
    1314                     if (this.isObject(o) && o !== null &&
    1315                         // Do not recurse into a document object or a JSXGraph object
    1316                         !this.isDocumentOrFragment(o) && !this.exists(o.board) &&
    1317                         // Do not recurse if a string is provided as "new String(...)"
    1318                         typeof o.valueOf() !== 'string') {
    1319                         if (attr[e2] === undefined || attr[e2] === null || !this.isObject(attr[e2])) {
    1320                             // The last test handles the case:
    1321                             //   attr.draft = false;
    1322                             //   special.draft = { strokewidth: 4}
    1323                             attr[e2] = {};
    1324                         }
    1325                         this.mergeAttr(attr[e2], o, toLower);
    1326                     } else if(!ignoreUndefinedSpecials || this.exists(o)) {
    1327                         // Flat copy
    1328                         // This is also used in the cases
    1329                         //   attr.shadow = { enabled: true ...}
    1330                         //   special.shadow = false;
    1331                         // and
    1332                         //   special.anchor is a JSXGraph element
    1333                         attr[e2] = o;
    1334                     }
    1335                 }
    1336             }
    1337         },
    1338
    1339         /**
    1340          * Convert a n object to a new object containing only
    1341          * lower case properties.
    1342          *
    1343          * @param {Object} obj
    1344          * @returns Object
    1345          * @example
    1346          * var attr = JXG.keysToLowerCase({radiusPoint: {visible: false}});
    1347          *
    1348          * // return {radiuspoint: {visible: false}}
    1349          */
    1350         keysToLowerCase: function (obj) {
    1351             var key, val,
    1352                 keys = Object.keys(obj),
    1353                 n = keys.length,
    1354                 newObj = {};
    1355
    1356             if (typeof obj !== 'object') {
    1357                 return obj;
    1358             }
    1359
    1360             while (n--) {
    1361                 key = keys[n];
    1362                 if (obj.hasOwnProperty(key)) {
    1363                     // We recurse into an object only if it is
    1364                     // neither a DOM node nor an JSXGraph object
    1365                     val = obj[key];
    1366                     if (typeof val === 'object' &&
    1367                         val !== null &&
    1368                         !this.isArray(val) &&
    1369                         !this.exists(val.nodeType) &&
    1370                         !this.exists(val.board)) {
    1371                         newObj[key.toLowerCase()] = this.keysToLowerCase(val);
    1372                     } else {
    1373                         newObj[key.toLowerCase()] = val;
    1374                     }
    1375                 }
    1376             }
    1377             return newObj;
    1378         },
    1379
    1380         /**
    1381          * Generates an attributes object that is filled with default values from the Options object
    1382          * and overwritten by the user specified attributes.
    1383          * @param {Object} attributes user specified attributes
    1384          * @param {Object} options defaults options
    1385          * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'. Must be provided in lower case!
    1386          * @returns {Object} The resulting attributes object
    1387          */
    1388         copyAttributes: function (attributes, options, s) {
    1389             var a, arg, i, len, o, isAvail,
    1390                 primitives = {
    1391                     circle: 1,
    1392                     curve: 1,
    1393                     foreignobject: 1,
    1394                     image: 1,
    1395                     line: 1,
    1396                     point: 1,
    1397                     polygon: 1,
    1398                     text: 1,
    1399                     ticks: 1,
    1400                     integral: 1
    1401                 };
    1402
    1403             len = arguments.length;
    1404             if (len < 3 || primitives[s]) {
    1405                 // Default options from Options.elements
    1406                 a = JXG.deepCopy(options.elements, null, true);
    1407             } else {
    1408                 a = {};
    1409             }
    1410
    1411             // Only the layer of the main element is set.
    1412             if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
    1413                 a.layer = options.layer[s];
    1414             }
    1415
    1416             // Default options from the specific element like 'line' in
    1417             //     copyAttribute(attributes, board.options, 'line')
    1418             // but also like in
    1419             //     Type.copyAttributes(attributes, board.options, 'view3d', 'az', 'slider');
    1420             o = options;
    1421             isAvail = true;
    1422             for (i = 2; i < len; i++) {
    1423                 arg = arguments[i];
    1424                 if (this.exists(o[arg])) {
    1425                     o = o[arg];
    1426                 } else {
    1427                     isAvail = false;
    1428                     break;
    1429                 }
    1430             }
    1431             if (isAvail) {
    1432                 a = JXG.deepCopy(a, o, true);
    1433             }
    1434
    1435             // Merge the specific options given in the parameter 'attributes'
    1436             // into the default options.
    1437             // Additionally, we step into a sub-element of attribute like line.point1 -
    1438             // in case it is supplied as in
    1439             //     copyAttribute(attributes, board.options, 'line', 'point1')
    1440             // In this case we would merge attributes.point1 into the global line.point1 attributes.
    1441             o = (typeof attributes === 'object') ? this.keysToLowerCase(attributes) : {};
    1442             isAvail = true;
    1443             for (i = 3; i < len; i++) {
    1444                 arg = arguments[i].toLowerCase();
    1445                 if (this.exists(o[arg])) {
    1446                     o = o[arg];
    1447                 } else {
    1448                     isAvail = false;
    1449                     break;
    1450                 }
    1451             }
    1452             if (isAvail) {
    1453                 this.mergeAttr(a, o, true);
    1454             }
    1455
    1456             if (arguments[2] === "board") {
    1457                 // For board attributes we are done now.
    1458                 return a;
    1459             }
    1460
    1461             // Special treatment of labels
    1462             o = options;
    1463             isAvail = true;
    1464             for (i = 2; i < len; i++) {
    1465                 arg = arguments[i];
    1466                 if (this.exists(o[arg])) {
    1467                     o = o[arg];
    1468                 } else {
    1469                     isAvail = false;
    1470                     break;
    1471                 }
    1472             }
    1473             if (isAvail && this.exists(o.label)) {
    1474                 a.label = JXG.deepCopy(o.label, a.label, true);
    1475             }
    1476             a.label = JXG.deepCopy(options.label, a.label, true);
    1477
    1478             return a;
    1479         },
    1480
    1481         /**
    1482          * Copy all prototype methods from object "superObject" to object
    1483          * "subObject". The constructor of superObject will be available
    1484          * in subObject as subObject.constructor[constructorName].
    1485          * @param {Object} subObj A JavaScript object which receives new methods.
    1486          * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject
    1487          * @returns {String} constructorName Under this name the constructor of superObj will be available
    1488          * in subObject.
    1489          * @private
    1490          */
    1491         copyPrototypeMethods: function (subObject, superObject, constructorName) {
    1492             var key;
    1493
    1494             subObject.prototype[constructorName] = superObject.prototype.constructor;
    1495             for (key in superObject.prototype) {
    1496                 if (superObject.prototype.hasOwnProperty(key)) {
    1497                     subObject.prototype[key] = superObject.prototype[key];
    1498                 }
    1499             }
    1500         },
    1501
    1502         /**
    1503          * Create a stripped down version of a JSXGraph element for cloning to the background.
    1504          * Used in {JXG.GeometryElement#cloneToBackground} for creating traces.
    1505          *
    1506          * @param {JXG.GeometryElement} el Element to be cloned
    1507          * @returns Object Cloned element
    1508          * @private
    1509          */
    1510         getCloneObject: function(el) {
    1511             var obj, key,
    1512                 copy = {};
    1513
    1514             copy.id = el.id + "T" + el.numTraces;
    1515             el.numTraces += 1;
    1516
    1517             copy.coords = el.coords;
    1518             obj = this.deepCopy(el.visProp, el.visProp.traceattributes, true);
    1519             copy.visProp = {};
    1520             for (key in obj) {
    1521                 if (obj.hasOwnProperty(key)) {
    1522                     if (
    1523                         key.indexOf('aria') !== 0 &&
    1524                         key.indexOf('highlight') !== 0 &&
    1525                         key.indexOf('attractor') !== 0 &&
    1526                         key !== 'label' &&
    1527                         key !== 'needsregularupdate' &&
    1528                         key !== 'infoboxdigits'
    1529                     ) {
    1530                         copy.visProp[key] = el.eval(obj[key]);
    1531                     }
    1532                 }
    1533             }
    1534             copy.evalVisProp = function(val) {
    1535                 return copy.visProp[val];
    1536             };
    1537             copy.eval = function(val) {
    1538                 return val;
    1539             };
    1540
    1541             copy.visProp.layer = el.board.options.layer.trace;
    1542             copy.visProp.tabindex = null;
    1543             copy.visProp.highlight = false;
    1544             copy.board = el.board;
    1545             copy.elementClass = el.elementClass;
    1546
    1547             this.clearVisPropOld(copy);
    1548             copy.visPropCalc = {
    1549                 visible: el.evalVisProp('visible')
    1550             };
    1551
    1552             return copy;
    1553         },
    1554
    1555         /**
    1556          * Converts a JavaScript object into a JSON string.
    1557          * @param {Object} obj A JavaScript object, functions will be ignored.
    1558          * @param {Boolean} [noquote=false] No quotes around the name of a property.
    1559          * @returns {String} The given object stored in a JSON string.
    1560          * @deprecated
    1561          */
    1562         toJSON: function (obj, noquote) {
    1563             var list, prop, i, s, val;
    1564
    1565             noquote = JXG.def(noquote, false);
    1566
    1567             // check for native JSON support:
    1568             if (JSON !== undefined && JSON.stringify && !noquote) {
    1569                 try {
    1570                     s = JSON.stringify(obj);
    1571                     return s;
    1572                 } catch (e) {
    1573                     // if something goes wrong, e.g. if obj contains functions we won't return
    1574                     // and use our own implementation as a fallback
    1575                 }
    1576             }
    1577
    1578             switch (typeof obj) {
    1579                 case "object":
    1580                     if (obj) {
    1581                         list = [];
    1582
    1583                         if (this.isArray(obj)) {
    1584                             for (i = 0; i < obj.length; i++) {
    1585                                 list.push(JXG.toJSON(obj[i], noquote));
    1586                             }
    1587
    1588                             return "[" + list.join(",") + "]";
    1589                         }
    1590
    1591                         for (prop in obj) {
    1592                             if (obj.hasOwnProperty(prop)) {
    1593                                 try {
    1594                                     val = JXG.toJSON(obj[prop], noquote);
    1595                                 } catch (e2) {
    1596                                     val = "";
    1597                                 }
    1598
    1599                                 if (noquote) {
    1600                                     list.push(prop + ":" + val);
    1601                                 } else {
    1602                                     list.push('"' + prop + '":' + val);
    1603                                 }
    1604                             }
    1605                         }
    1606
    1607                         return "{" + list.join(",") + "} ";
    1608                     }
    1609                     return "null";
    1610                 case "string":
    1611                     return "'" + obj.replace(/(["'])/g, "\\$1") + "'";
    1612                 case "number":
    1613                 case "boolean":
    1614                     return obj.toString();
    1615             }
    1616
    1617             return "0";
    1618         },
    1619
    1620         /**
    1621          * Resets visPropOld.
    1622          * @param {JXG.GeometryElement} el
    1623          * @returns {GeometryElement}
    1624          */
    1625         clearVisPropOld: function (el) {
    1626             el.visPropOld = {
    1627                 cssclass: "",
    1628                 cssdefaultstyle: "",
    1629                 cssstyle: "",
    1630                 fillcolor: "",
    1631                 fillopacity: "",
    1632                 firstarrow: false,
    1633                 fontsize: -1,
    1634                 lastarrow: false,
    1635                 left: -100000,
    1636                 linecap: "",
    1637                 shadow: false,
    1638                 strokecolor: "",
    1639                 strokeopacity: "",
    1640                 strokewidth: "",
    1641                 tabindex: -100000,
    1642                 transitionduration: 0,
    1643                 top: -100000,
    1644                 visible: null
    1645             };
    1646
    1647             return el;
    1648         },
    1649
    1650         /**
    1651          * Checks if an object contains a key, whose value equals to val.
    1652          * @param {Object} obj
    1653          * @param val
    1654          * @returns {Boolean}
    1655          */
    1656         isInObject: function (obj, val) {
    1657             var el;
    1658
    1659             for (el in obj) {
    1660                 if (obj.hasOwnProperty(el)) {
    1661                     if (obj[el] === val) {
    1662                         return true;
    1663                     }
    1664                 }
    1665             }
    1666
    1667             return false;
    1668         },
    1669
    1670         /**
    1671          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
    1672          * @param {String} str
    1673          * @returns {String}
    1674          */
    1675         escapeHTML: function (str) {
    1676             return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
    1677         },
    1678
    1679         /**
    1680          * Eliminates all substrings enclosed by < and > and replaces all occurences of
    1681          * &amp; by &, &gt; by >, and &lt; by <.
    1682          * @param {String} str
    1683          * @returns {String}
    1684          */
    1685         unescapeHTML: function (str) {
    1686             // This regex is NOT insecure. We are replacing everything found with ''
    1687             /*jslint regexp:true*/
    1688             return str
    1689                 .replace(/<\/?[^>]+>/gi, "")
    1690                 .replace(/&/g, "&")
    1691                 .replace(/</g, "<")
    1692                 .replace(/>/g, ">");
    1693         },
    1694
    1695         /**
    1696          * Makes a string lower case except for the first character which will be upper case.
    1697          * @param {String} str Arbitrary string
    1698          * @returns {String} The capitalized string.
    1699          */
    1700         capitalize: function (str) {
    1701             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
    1702         },
    1703
    1704         /**
    1705          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
    1706          * @param {String} str
    1707          * @returns {String}
    1708          */
    1709         trimNumber: function (str) {
    1710             str = str.replace(/^0+/, "");
    1711             str = str.replace(/0+$/, "");
    1712
    1713             if (str[str.length - 1] === "." || str[str.length - 1] === ",") {
    1714                 str = str.slice(0, -1);
    1715             }
    1716
    1717             if (str[0] === "." || str[0] === ",") {
    1718                 str = "0" + str;
    1719             }
    1720
    1721             return str;
    1722         },
    1723
    1724         /**
    1725          * Filter an array of elements.
    1726          * @param {Array} list
    1727          * @param {Object|function} filter
    1728          * @returns {Array}
    1729          */
    1730         filterElements: function (list, filter) {
    1731             var i,
    1732                 f,
    1733                 item,
    1734                 flower,
    1735                 value,
    1736                 visPropValue,
    1737                 pass,
    1738                 l = list.length,
    1739                 result = [];
    1740
    1741             if (typeof filter !== "function" && typeof filter !== "object") {
    1742                 return result;
    1743             }
    1744
    1745             for (i = 0; i < l; i++) {
    1746                 pass = true;
    1747                 item = list[i];
    1748
    1749                 if (typeof filter === "object") {
    1750                     for (f in filter) {
    1751                         if (filter.hasOwnProperty(f)) {
    1752                             flower = f.toLowerCase();
    1753
    1754                             if (typeof item[f] === "function") {
    1755                                 value = item[f]();
    1756                             } else {
    1757                                 value = item[f];
    1758                             }
    1759
    1760                             if (item.visProp && typeof item.visProp[flower] === "function") {
    1761                                 visPropValue = item.visProp[flower]();
    1762                             } else {
    1763                                 visPropValue = item.visProp && item.visProp[flower];
    1764                             }
    1765
    1766                             if (typeof filter[f] === "function") {
    1767                                 pass = filter[f](value) || filter[f](visPropValue);
    1768                             } else {
    1769                                 pass = value === filter[f] || visPropValue === filter[f];
    1770                             }
    1771
    1772                             if (!pass) {
    1773                                 break;
    1774                             }
    1775                         }
    1776                     }
    1777                 } else if (typeof filter === "function") {
    1778                     pass = filter(item);
    1779                 }
    1780
    1781                 if (pass) {
    1782                     result.push(item);
    1783                 }
    1784             }
    1785
    1786             return result;
    1787         },
    1788
    1789         /**
    1790          * Remove all leading and trailing whitespaces from a given string.
    1791          * @param {String} str
    1792          * @returns {String}
    1793          */
    1794         trim: function (str) {
    1795             // str = str.replace(/^\s+/, '');
    1796             // str = str.replace(/\s+$/, '');
    1797             //
    1798             // return str;
    1799             return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
    1800         },
    1801
    1802         /**
    1803          * Convert a floating point number to a string integer + fraction.
    1804          * Returns either a string of the form '3 1/3' (in case of useTeX=false)
    1805          * or '3 \\frac{1}{3}' (in case of useTeX=true).
    1806          *
    1807          * @param {Number} x
    1808          * @param {Boolean} [useTeX=false]
    1809          * @param {Number} [order=0.001]
    1810          * @returns {String}
    1811          * @see JXG.Math.decToFraction
    1812          */
    1813         toFraction: function (x, useTeX, order) {
    1814             var arr = Mat.decToFraction(x, order),
    1815                 str = '';
    1816
    1817             if (arr[1] === 0 && arr[2] === 0) {
    1818                 // 0
    1819                 str += '0';
    1820             } else {
    1821                 // Sign
    1822                 if (arr[0] < 0) {
    1823                     str += '-';
    1824                 }
    1825                 if (arr[2] === 0) {
    1826                     // Integer
    1827                     str += arr[1];
    1828                 } else if (!(arr[2] === 1 && arr[3] === 1)) {
    1829                     // Proper fraction
    1830                     if (arr[1] !== 0) {
    1831                         // Absolute value larger than 1
    1832                         str += arr[1] + ' ';
    1833                     }
    1834                     // Add fractional part
    1835                     if (useTeX === true) {
    1836                         str += '\\frac{' + arr[2] + '}{' + arr[3] + '}';
    1837                     } else {
    1838                         str += arr[2] + '/' + arr[3];
    1839                     }
    1840                 }
    1841             }
    1842             return str;
    1843         },
    1844
    1845         /**
    1846          * Concat array src to array dest.
    1847          * Uses push instead of JavaScript concat, which is much
    1848          * faster.
    1849          * The array dest is changed in place.
    1850          * <p><b>Attention:</b> if "dest" is an anonymous array, the correct result is returned from the function.
    1851          *
    1852          * @param {Array} dest
    1853          * @param {Array} src
    1854          * @returns Array
    1855          */
    1856         concat: function(dest, src) {
    1857             var i,
    1858                 le = src.length;
    1859             for (i = 0; i < le; i++) {
    1860                 dest.push(src[i]);
    1861             }
    1862             return dest;
    1863         },
    1864
    1865         /**
    1866          * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
    1867          * @param {String} str
    1868          * @param {Boolean} caja
    1869          * @returns {String} Sanitized string
    1870          */
    1871         sanitizeHTML: function (str, caja) {
    1872             if (typeof html_sanitize === "function" && caja) {
    1873                 return html_sanitize(
    1874                     str,
    1875                     function () {
    1876                         return undefined;
    1877                     },
    1878                     function (id) {
    1879                         return id;
    1880                     }
    1881                 );
    1882             }
    1883
    1884             if (str && typeof str === "string") {
    1885                 str = str.replace(/</g, "<").replace(/>/g, ">");
    1886             }
    1887
    1888             return str;
    1889         },
    1890
    1891         /**
    1892          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
    1893          * @param {*} s
    1894          * @returns {*} s.Value() if s is an element of type slider, s otherwise
    1895          */
    1896         evalSlider: function (s) {
    1897             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === "function") {
    1898                 return s.Value();
    1899             }
    1900
    1901             return s;
    1902         },
    1903
    1904         /**
    1905          * Convert a string containing a MAXIMA /STACK expression into a JSXGraph / JessieCode string
    1906          * or an array of JSXGraph / JessieCode strings.
    1907          * <p>
    1908          * This function is meanwhile superseded by stack_jxg.stack2jsxgraph.
    1909          *
    1910          * @deprecated
    1911          *
    1912          * @example
    1913          * console.log( JXG.stack2jsxgraph("%e**x") );
    1914          * // Output:
    1915          * //    "EULER**x"
    1916          *
    1917          * @example
    1918          * console.log( JXG.stack2jsxgraph("[%pi*(x**2 - 1), %phi*(x - 1), %gamma*(x+1)]") );
    1919          * // Output:
    1920          * //    [ "PI*(x**2 - 1)", "1.618033988749895*(x - 1)", "0.5772156649015329*(x+1)" ]
    1921          *
    1922          * @param {String} str
    1923          * @returns String
    1924          */
    1925         stack2jsxgraph: function(str) {
    1926             var t;
    1927
    1928             t = str.
    1929                 replace(/%pi/g, 'PI').
    1930                 replace(/%e/g, 'EULER').
    1931                 replace(/%phi/g, '1.618033988749895').
    1932                 replace(/%gamma/g, '0.5772156649015329').
    1933                 trim();
    1934
    1935             // String containing array -> array containing strings
    1936             if (t[0] === '[' && t[t.length - 1] === ']') {
    1937                 t = t.slice(1, -1).split(/\s*,\s*/);
    1938             }
    1939
    1940             return t;
    1941         }
    1942     }
    1943 );
    1944
    1945 export default JXG;
    1946