      1 /*
      2     Copyright 2008-2024
      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] = JXG.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)) {
    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 > 1) {
    584                     points.push(view.create("point3d", parents[i], attr));
    585                     points[points.length - 1]._is_new = true;
    586                 } else if (this.isFunction(parents[i])) {
    587                     val = parents[i]();
    588                     if (this.isArray(val) && val.length > 1) {
    589                         points.push(view.create("point3d", [parents[i]], attr));
    590                         points[points.length - 1]._is_new = true;
    591                     }
    592                 } else {
    593                     points.push(view.select(parents[i]));
    594                 }
    595
    596                 if (!this.isPoint3D(points[i])) {
    597                     return false;
    598                 }
    599             }
    600
    601             return points;
    602         },
    603
    604         /**
    605          * Generates a function which calls the function fn in the scope of owner.
    606          * @param {Function} fn Function to call.
    607          * @param {Object} owner Scope in which fn is executed.
    608          * @returns {Function} A function with the same signature as fn.
    609          */
    610         bind: function (fn, owner) {
    611             return function () {
    612                 return fn.apply(owner, arguments);
    613             };
    614         },
    615
    616         /**
    617          * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
    618          * is just returned.
    619          * @param val Could be anything. Preferably a number or a function.
    620          * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
    621          */
    622         evaluate: function (val) {
    623             if (this.isFunction(val)) {
    624                 return val();
    625             }
    626
    627             return val;
    628         },
    629
    630         /**
    631          * Search an array for a given value.
    632          * @param {Array} array
    633          * @param value
    634          * @param {String} [sub] Use this property if the elements of the array are objects.
    635          * @returns {Number} The index of the first appearance of the given value, or
    636          * <tt>-1</tt> if the value was not found.
    637          */
    638         indexOf: function (array, value, sub) {
    639             var i,
    640                 s = this.exists(sub);
    641
    642             if (Array.indexOf && !s) {
    643                 return array.indexOf(value);
    644             }
    645
    646             for (i = 0; i < array.length; i++) {
    647                 if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
    648                     return i;
    649                 }
    650             }
    651
    652             return -1;
    653         },
    654
    655         /**
    656          * Eliminates duplicate entries in an array consisting of numbers and strings.
    657          * @param {Array} a An array of numbers and/or strings.
    658          * @returns {Array} The array with duplicate entries eliminated.
    659          */
    660         eliminateDuplicates: function (a) {
    661             var i,
    662                 len = a.length,
    663                 result = [],
    664                 obj = {};
    665
    666             for (i = 0; i < len; i++) {
    667                 obj[a[i]] = 0;
    668             }
    669
    670             for (i in obj) {
    671                 if (obj.hasOwnProperty(i)) {
    672                     result.push(i);
    673                 }
    674             }
    675
    676             return result;
    677         },
    678
    679         /**
    680          * Swaps to array elements.
    681          * @param {Array} arr
    682          * @param {Number} i
    683          * @param {Number} j
    684          * @returns {Array} Reference to the given array.
    685          */
    686         swap: function (arr, i, j) {
    687             var tmp;
    688
    689             tmp = arr[i];
    690             arr[i] = arr[j];
    691             arr[j] = tmp;
    692
    693             return arr;
    694         },
    695
    696         /**
    697          * Generates a copy of an array and removes the duplicate entries. The original
    698          * Array will be altered.
    699          * @param {Array} arr
    700          * @returns {Array}
    701          */
    702         uniqueArray: function (arr) {
    703             var i,
    704                 j,
    705                 isArray,
    706                 ret = [];
    707
    708             if (arr.length === 0) {
    709                 return [];
    710             }
    711
    712             for (i = 0; i < arr.length; i++) {
    713                 isArray = this.isArray(arr[i]);
    714
    715                 if (!this.exists(arr[i])) {
    716                     arr[i] = "";
    717                     continue;
    718                 }
    719                 for (j = i + 1; j < arr.length; j++) {
    720                     if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
    721                         arr[i] = [];
    722                     } else if (!isArray && arr[i] === arr[j]) {
    723                         arr[i] = "";
    724                     }
    725                 }
    726             }
    727
    728             j = 0;
    729
    730             for (i = 0; i < arr.length; i++) {
    731                 isArray = this.isArray(arr[i]);
    732
    733                 if (!isArray && arr[i] !== "") {
    734                     ret[j] = arr[i];
    735                     j++;
    736                 } else if (isArray && arr[i].length !== 0) {
    737                     ret[j] = arr[i].slice(0);
    738                     j++;
    739                 }
    740             }
    741
    742             arr = ret;
    743             return ret;
    744         },
    745
    746         /**
    747          * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
    748          * @param {Array} arr
    749          * @param val
    750          * @returns {Boolean}
    751          */
    752         isInArray: function (arr, val) {
    753             return JXG.indexOf(arr, val) > -1;
    754         },
    755
    756         /**
    757          * Converts an array of {@link JXG.Coords} objects into a coordinate matrix.
    758          * @param {Array} coords
    759          * @param {Boolean} split
    760          * @returns {Array}
    761          */
    762         coordsArrayToMatrix: function (coords, split) {
    763             var i,
    764                 x = [],
    765                 m = [];
    766
    767             for (i = 0; i < coords.length; i++) {
    768                 if (split) {
    769                     x.push(coords[i].usrCoords[1]);
    770                     m.push(coords[i].usrCoords[2]);
    771                 } else {
    772                     m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]);
    773                 }
    774             }
    775
    776             if (split) {
    777                 m = [x, m];
    778             }
    779
    780             return m;
    781         },
    782
    783         /**
    784          * Compare two arrays.
    785          * @param {Array} a1
    786          * @param {Array} a2
    787          * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
    788          */
    789         cmpArrays: function (a1, a2) {
    790             var i;
    791
    792             // trivial cases
    793             if (a1 === a2) {
    794                 return true;
    795             }
    796
    797             if (a1.length !== a2.length) {
    798                 return false;
    799             }
    800
    801             for (i = 0; i < a1.length; i++) {
    802                 if (this.isArray(a1[i]) && this.isArray(a2[i])) {
    803                     if (!this.cmpArrays(a1[i], a2[i])) {
    804                         return false;
    805                     }
    806                 } else if (a1[i] !== a2[i]) {
    807                     return false;
    808                 }
    809             }
    810
    811             return true;
    812         },
    813
    814         /**
    815          * Removes an element from the given array
    816          * @param {Array} ar
    817          * @param el
    818          * @returns {Array}
    819          */
    820         removeElementFromArray: function (ar, el) {
    821             var i;
    822
    823             for (i = 0; i < ar.length; i++) {
    824                 if (ar[i] === el) {
    825                     ar.splice(i, 1);
    826                     return ar;
    827                 }
    828             }
    829
    830             return ar;
    831         },
    832
    833         /**
    834          * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
    835          * @param {Number} n
    836          * @param {Number} p
    837          * @returns {Number}
    838          */
    839         trunc: function (n, p) {
    840             p = JXG.def(p, 0);
    841
    842             return this.toFixed(n, p);
    843         },
    844
    845         /**
    846          * Decimal adjustment of a number.
    847          * From https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Math/round
    848          *
    849          * @param    {String}    type    The type of adjustment.
    850          * @param    {Number}    value    The number.
    851          * @param    {Number}    exp        The exponent (the 10 logarithm of the adjustment base).
    852          * @returns    {Number}            The adjusted value.
    853          *
    854          * @private
    855          */
    856         _decimalAdjust: function (type, value, exp) {
    857             // If the exp is undefined or zero...
    858             if (exp === undefined || +exp === 0) {
    859                 return Math[type](value);
    860             }
    861
    862             value = +value;
    863             exp = +exp;
    864             // If the value is not a number or the exp is not an integer...
    865             if (isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) {
    866                 return NaN;
    867             }
    868
    869             // Shift
    870             value = value.toString().split("e");
    871             value = Math[type](+(value[0] + "e" + (value[1] ? +value[1] - exp : -exp)));
    872
    873             // Shift back
    874             value = value.toString().split("e");
    875             return +(value[0] + "e" + (value[1] ? +value[1] + exp : exp));
    876         },
    877
    878         /**
    879          * Round a number to given number of decimal digits.
    880          *
    881          * Example: JXG._toFixed(3.14159, -2) gives 3.14
    882          * @param  {Number} value Number to be rounded
    883          * @param  {Number} exp   Number of decimal digits given as negative exponent
    884          * @return {Number}       Rounded number.
    885          *
    886          * @private
    887          */
    888         _round10: function (value, exp) {
    889             return this._decimalAdjust("round", value, exp);
    890         },
    891
    892         /**
    893          * "Floor" a number to given number of decimal digits.
    894          *
    895          * Example: JXG._toFixed(3.14159, -2) gives 3.14
    896          * @param  {Number} value Number to be floored
    897          * @param  {Number} exp   Number of decimal digits given as negative exponent
    898          * @return {Number}       "Floored" number.
    899          *
    900          * @private
    901          */
    902         _floor10: function (value, exp) {
    903             return this._decimalAdjust("floor", value, exp);
    904         },
    905
    906         /**
    907          * "Ceil" a number to given number of decimal digits.
    908          *
    909          * Example: JXG._toFixed(3.14159, -2) gives 3.15
    910          * @param  {Number} value Number to be ceiled
    911          * @param  {Number} exp   Number of decimal digits given as negative exponent
    912          * @return {Number}       "Ceiled" number.
    913          *
    914          * @private
    915          */
    916         _ceil10: function (value, exp) {
    917             return this._decimalAdjust("ceil", value, exp);
    918         },
    919
    920         /**
    921          * Replacement of the default toFixed() method.
    922          * It does a correct rounding (independent of the browser) and
    923          * returns "0.00" for toFixed(-0.000001, 2) instead of "-0.00" which
    924          * is returned by JavaScript's toFixed()
    925          *
    926          * @memberOf JXG
    927          * @param  {Number} num    Number tp be rounded
    928          * @param  {Number} digits Decimal digits
    929          * @return {String}        Rounded number is returned as string
    930          */
    931         toFixed: function (num, digits) {
    932             return this._round10(num, -digits).toFixed(digits);
    933         },
    934
    935         /**
    936          * Truncate a number <tt>val</tt> automatically.
    937          * @memberOf JXG
    938          * @param val
    939          * @returns {Number}
    940          */
    941         autoDigits: function (val) {
    942             var x = Math.abs(val),
    943                 str;
    944
    945             if (x >= 0.1) {
    946                 str = this.toFixed(val, 2);
    947             } else if (x >= 0.01) {
    948                 str = this.toFixed(val, 4);
    949             } else if (x >= 0.0001) {
    950                 str = this.toFixed(val, 6);
    951             } else {
    952                 str = val;
    953             }
    954             return str;
    955         },
    956
    957         /**
    958          * Convert value v. If v has the form
    959          * <ul>
    960          * <li> 'x%': return floating point number x * percentOfWhat * 0.01
    961          * <li> 'xfr': return floating point number x * percentOfWhat
    962          * <li> 'xpx': return x * convertPx or convertPx(x) or x
    963          * <li> x or 'x': return floating point number x
    964          * </ul>
    965          * @param {String|Number} v
    966          * @param {Number} percentOfWhat
    967          * @param {Function|Number|*} convertPx
    968          * @returns {String|Number}
    969          */
    970         parseNumber: function(v, percentOfWhat, convertPx) {
    971             var str;
    972
    973             if (this.isString(v) && v.indexOf('%') > -1) {
    974                 str = v.replace(/\s+%\s+/, '');
    975                 return parseFloat(str) * percentOfWhat * 0.01;
    976             }
    977             if (this.isString(v) && v.indexOf('fr') > -1) {
    978                 str = v.replace(/\s+fr\s+/, '');
    979                 return parseFloat(str) * percentOfWhat;
    980             }
    981             if (this.isString(v) && v.indexOf('px') > -1) {
    982                 str = v.replace(/\s+px\s+/, '');
    983                 str = parseFloat(str);
    984                 if(this.isFunction(convertPx)) {
    985                     return convertPx(str);
    986                 } else if(this.isNumber(convertPx)) {
    987                     return str * convertPx;
    988                 } else {
    989                     return str;
    990                 }
    991             }
    992             // Number or String containing no unit
    993             return parseFloat(v);
    994         },
    995
    996         /**
    997          * Parse a string for label positioning of the form 'left pos' or 'pos right'
    998          * and return e.g.
    999          * <tt>{ side: 'left', pos: 'pos' }</tt>.
    1000          * @param {String} str
    1001          * @returns {Obj}  <tt>{ side, pos }</tt>
    1002          */
    1003         parsePosition: function(str) {
    1004             var a, i,
    1005                 side = '',
    1006                 pos = '';
    1007
    1008             str = str.trim();
    1009             if (str !== '') {
    1010                 a = str.split(/[ ,]+/);
    1011                 for (i = 0; i < a.length; i++) {
    1012                     if (a[i] === 'left' || a[i] === 'right') {
    1013                         side = a[i];
    1014                     } else {
    1015                         pos = a[i];
    1016                     }
    1017                 }
    1018             }
    1019
    1020             return {
    1021                 side: side,
    1022                 pos: pos
    1023             };
    1024         },
    1025
    1026         /**
    1027          * Extracts the keys of a given object.
    1028          * @param object The object the keys are to be extracted
    1029          * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
    1030          * the object owns itself and not some other object in the prototype chain.
    1031          * @returns {Array} All keys of the given object.
    1032          */
    1033         keys: function (object, onlyOwn) {
    1034             var keys = [],
    1035                 property;
    1036
    1037             // the caller decides if we use hasOwnProperty
    1038             /*jslint forin:true*/
    1039             for (property in object) {
    1040                 if (onlyOwn) {
    1041                     if (object.hasOwnProperty(property)) {
    1042                         keys.push(property);
    1043                     }
    1044                 } else {
    1045                     keys.push(property);
    1046                 }
    1047             }
    1048             /*jslint forin:false*/
    1049
    1050             return keys;
    1051         },
    1052
    1053         /**
    1054          * This outputs an object with a base class reference to the given object. This is useful if
    1055          * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
    1056          * without changing the original object.
    1057          * @param {Object} obj Object to be embedded.
    1058          * @returns {Object} An object with a base class reference to <tt>obj</tt>.
    1059          */
    1060         clone: function (obj) {
    1061             var cObj = {};
    1062
    1063             cObj.prototype = obj;
    1064
    1065             return cObj;
    1066         },
    1067
    1068         /**
    1069          * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
    1070          * to the new one. Warning: The copied properties of obj2 are just flat copies.
    1071          * @param {Object} obj Object to be copied.
    1072          * @param {Object} obj2 Object with data that is to be copied to the new one as well.
    1073          * @returns {Object} Copy of given object including some new/overwritten data from obj2.
    1074          */
    1075         cloneAndCopy: function (obj, obj2) {
    1076             var r,
    1077                 cObj = function () {
    1078                     return undefined;
    1079                 };
    1080
    1081             cObj.prototype = obj;
    1082
    1083             // no hasOwnProperty on purpose
    1084             /*jslint forin:true*/
    1085             /*jshint forin:true*/
    1086
    1087             for (r in obj2) {
    1088                 cObj[r] = obj2[r];
    1089             }
    1090
    1091             /*jslint forin:false*/
    1092             /*jshint forin:false*/
    1093
    1094             return cObj;
    1095         },
    1096
    1097         /**
    1098          * Recursively merges obj2 into obj1 in-place. Contrary to {@link JXG#deepCopy} this won't create a new object
    1099          * but instead will overwrite obj1.
    1100          * <p>
    1101          * In contrast to method JXG.mergeAttr, merge recurses into any kind of object, e.g. DOM object and JSXGraph objects.
    1102          * So, please be careful.
    1103          * @param {Object} obj1
    1104          * @param {Object} obj2
    1105          * @returns {Object}
    1106          * @see JXG#mergeAttr
    1107          *
    1108          * @example
    1109          * JXG.Options = JXG.merge(JXG.Options, {
    1110          *     board: {
    1111          *         showNavigation: false,
    1112          *         showInfobox: true
    1113          *     },
    1114          *     point: {
    1115          *         face: 'o',
    1116          *         size: 4,
    1117          *         fillColor: '#eeeeee',
    1118          *         highlightFillColor: '#eeeeee',
    1119          *         strokeColor: 'white',
    1120          *         highlightStrokeColor: 'white',
    1121          *         showInfobox: 'inherit'
    1122          *     }
    1123          * });
    1124          *
    1125          * </pre><div id="JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1126          * <script type="text/javascript">
    1127          *     (function() {
    1128          *         var board = JXG.JSXGraph.initBoard('JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b',
    1129          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1130          *     JXG.Options = JXG.merge(JXG.Options, {
    1131          *         board: {
    1132          *             showNavigation: false,
    1133          *             showInfobox: true
    1134          *         },
    1135          *         point: {
    1136          *             face: 'o',
    1137          *             size: 4,
    1138          *             fillColor: '#eeeeee',
    1139          *             highlightFillColor: '#eeeeee',
    1140          *             strokeColor: 'white',
    1141          *             highlightStrokeColor: 'white',
    1142          *             showInfobox: 'inherit'
    1143          *         }
    1144          *     });
    1145          *
    1146          *
    1147          *     })();
    1148          *
    1149          * </script><pre>
    1150          */
    1151         merge: function (obj1, obj2) {
    1152             var i, j, o, oo;
    1153
    1154             for (i in obj2) {
    1155                 if (obj2.hasOwnProperty(i)) {
    1156                     o = obj2[i];
    1157                     if (this.isArray(o)) {
    1158                         if (!obj1[i]) {
    1159                             obj1[i] = [];
    1160                         }
    1161
    1162                         for (j = 0; j < o.length; j++) {
    1163                             oo = obj2[i][j];
    1164                             if (typeof obj2[i][j] === 'object') {
    1165                                 obj1[i][j] = this.merge(obj1[i][j], oo);
    1166                             } else {
    1167                                 obj1[i][j] = obj2[i][j];
    1168                             }
    1169                         }
    1170                     } else if (typeof o === 'object') {
    1171                         if (!obj1[i]) {
    1172                             obj1[i] = {};
    1173                         }
    1174
    1175                         obj1[i] = this.merge(obj1[i], o);
    1176                     } else {
    1177                         if (typeof obj1 === 'boolean') {
    1178                             // This is necessary in the following scenario:
    1179                             //   lastArrow == false
    1180                             // and call of
    1181                             //   setAttribute({lastArrow: {type: 7}})
    1182                             obj1 = {};
    1183                         }
    1184                         obj1[i] = o;
    1185                     }
    1186                 }
    1187             }
    1188
    1189             return obj1;
    1190         },
    1191
    1192         /**
    1193          * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
    1194          * element-wise instead of just copying the reference. If a second object is supplied, the two objects
    1195          * are merged into one object. The properties of the second object have priority.
    1196          * @param {Object} obj This object will be copied.
    1197          * @param {Object} obj2 This object will merged into the newly created object
    1198          * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
    1199          * @returns {Object} copy of obj or merge of obj and obj2.
    1200          */
    1201         deepCopy: function (obj, obj2, toLower) {
    1202             var c, i, prop, i2;
    1203
    1204             toLower = toLower || false;
    1205             if (typeof obj !== 'object' || obj === null) {
    1206                 return obj;
    1207             }
    1208
    1209             // Missing hasOwnProperty is on purpose in this function
    1210             if (this.isArray(obj)) {
    1211                 c = [];
    1212                 for (i = 0; i < obj.length; i++) {
    1213                     prop = obj[i];
    1214                     // Attention: typeof null === 'object'
    1215                     if (prop !== null && typeof prop === "object") {
    1216                         // We certainly do not want to recurse into a JSXGraph object.
    1217                         // This would for sure result in an infinite recursion.
    1218                         // As alternative we copy the id of the object.
    1219                         if (this.exists(prop.board)) {
    1220                             c[i] = prop.id;
    1221                         } else {
    1222                             c[i] = this.deepCopy(prop, {}, toLower);
    1223                         }
    1224                     } else {
    1225                         c[i] = prop;
    1226                     }
    1227                 }
    1228             } else {
    1229                 c = {};
    1230                 for (i in obj) {
    1231                     if (obj.hasOwnProperty(i)) {
    1232                         i2 = toLower ? i.toLowerCase() : i;
    1233                         prop = obj[i];
    1234                         if (prop !== null && typeof prop === "object") {
    1235                             if (this.exists(prop.board)) {
    1236                                 c[i2] = prop.id;
    1237                             } else {
    1238                                 c[i2] = this.deepCopy(prop, {}, toLower);
    1239                             }
    1240                         } else {
    1241                             c[i2] = prop;
    1242                         }
    1243                     }
    1244                 }
    1245
    1246                 for (i in obj2) {
    1247                     if (obj2.hasOwnProperty(i)) {
    1248                         i2 = toLower ? i.toLowerCase() : i;
    1249
    1250                         prop = obj2[i];
    1251                         if (prop !== null && typeof prop === "object") {
    1252                             if (this.isArray(prop) || !this.exists(c[i2])) {
    1253                                 c[i2] = this.deepCopy(prop, {}, toLower);
    1254                             } else {
    1255                                 c[i2] = this.deepCopy(c[i2], prop, toLower);
    1256                             }
    1257                         } else {
    1258                             c[i2] = prop;
    1259                         }
    1260                     }
    1261                 }
    1262             }
    1263
    1264             return c;
    1265         },
    1266
    1267         /**
    1268          * In-place (deep) merging of attributes. Allows attributes like `{shadow: {enabled: true...}}`
    1269          * <p>
    1270          * In contrast to method JXG.merge, mergeAttr does not recurse into DOM objects and JSXGraph objects. Instead
    1271          * handles (pointers) to these objects are used.
    1272          *
    1273          * @param {Object} attr Object with attributes - usually containing default options - that will be changed in-place.
    1274          * @param {Object} special Special option values which overwrite (recursively) the default options
    1275          * @param {Boolean} [toLower=true] If true the keys are converted to lower case.
    1276          * @param {Boolean} [ignoreUndefinedSpecials=false] If true the values in special that are undefined are not used.
    1277          *
    1278          * @see JXG#merge
    1279          *
    1280          */
    1281         mergeAttr: function (attr, special, toLower, ignoreUndefinedSpecials) {
    1282             var e, e2, o;
    1283
    1284             toLower = toLower || true;
    1285             ignoreUndefinedSpecials = ignoreUndefinedSpecials || false;
    1286
    1287             for (e in special) {
    1288                 if (special.hasOwnProperty(e)) {
    1289                     e2 = (toLower) ? e.toLowerCase(): e;
    1290
    1291                     // Key already exists, but not in lower case
    1292                     if (e2 !== e && attr.hasOwnProperty(e)) {
    1293                         if (attr.hasOwnProperty(e2)) {
    1294                             // Lower case key already exists - this should not happen
    1295                             // We have to unify the two key-value pairs
    1296                             // It is not clear which has precedence.
    1297                             this.mergeAttr(attr[e2], attr[e], toLower);
    1298                         } else {
    1299                             attr[e2] = attr[e];
    1300                         }
    1301                         delete attr[e];
    1302                     }
    1303
    1304                     o = special[e];
    1305                     if (this.isObject(o) && o !== null &&
    1306                         // Do not recurse into a document object or a JSXGraph object
    1307                         !this.isDocumentOrFragment(o) && !this.exists(o.board) &&
    1308                         // Do not recurse if a string is provided as "new String(...)"
    1309                         typeof o.valueOf() !== 'string') {
    1310                         if (attr[e2] === undefined || attr[e2] === null || !this.isObject(attr[e2])) {
    1311                             // The last test handles the case:
    1312                             //   attr.draft = false;
    1313                             //   special.draft = { strokewidth: 4}
    1314                             attr[e2] = {};
    1315                         }
    1316                         this.mergeAttr(attr[e2], o, toLower);
    1317                     } else if(!ignoreUndefinedSpecials || this.exists(o)) {
    1318                         // Flat copy
    1319                         // This is also used in the cases
    1320                         //   attr.shadow = { enabled: true ...}
    1321                         //   special.shadow = false;
    1322                         // and
    1323                         //   special.anchor is a JSXGraph element
    1324                         attr[e2] = o;
    1325                     }
    1326                 }
    1327             }
    1328         },
    1329
    1330         /**
    1331          * Convert a n object to a new object containing only
    1332          * lower case properties.
    1333          *
    1334          * @param {Object} obj
    1335          * @returns Object
    1336          * @example
    1337          * var attr = JXG.keysToLowerCase({radiusPoint: {visible: false}});
    1338          *
    1339          * // return {radiuspoint: {visible: false}}
    1340          */
    1341         keysToLowerCase: function (obj) {
    1342             var key, val,
    1343                 keys = Object.keys(obj),
    1344                 n = keys.length,
    1345                 newObj = {};
    1346
    1347             if (typeof obj !== 'object') {
    1348                 return obj;
    1349             }
    1350
    1351             while (n--) {
    1352                 key = keys[n];
    1353                 if (obj.hasOwnProperty(key)) {
    1354                     // We recurse into an object only if it is
    1355                     // neither a DOM node nor an JSXGraph object
    1356                     val = obj[key];
    1357                     if (typeof val === 'object' &&
    1358                         val !== null &&
    1359                         !this.isArray(val) &&
    1360                         !this.exists(val.nodeType) &&
    1361                         !this.exists(val.board)) {
    1362                         newObj[key.toLowerCase()] = this.keysToLowerCase(val);
    1363                     } else {
    1364                         newObj[key.toLowerCase()] = val;
    1365                     }
    1366                 }
    1367             }
    1368             return newObj;
    1369         },
    1370
    1371         /**
    1372          * Generates an attributes object that is filled with default values from the Options object
    1373          * and overwritten by the user specified attributes.
    1374          * @param {Object} attributes user specified attributes
    1375          * @param {Object} options defaults options
    1376          * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'. Must be provided in lower case!
    1377          * @returns {Object} The resulting attributes object
    1378          */
    1379         copyAttributes: function (attributes, options, s) {
    1380             var a, arg, i, len, o, isAvail,
    1381                 primitives = {
    1382                     circle: 1,
    1383                     curve: 1,
    1384                     foreignobject: 1,
    1385                     image: 1,
    1386                     line: 1,
    1387                     point: 1,
    1388                     polygon: 1,
    1389                     text: 1,
    1390                     ticks: 1,
    1391                     integral: 1
    1392                 };
    1393
    1394             len = arguments.length;
    1395             if (len < 3 || primitives[s]) {
    1396                 // Default options from Options.elements
    1397                 a = JXG.deepCopy(options.elements, null, true);
    1398             } else {
    1399                 a = {};
    1400             }
    1401
    1402             // Only the layer of the main element is set.
    1403             if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
    1404                 a.layer = options.layer[s];
    1405             }
    1406
    1407             // Default options from the specific element like 'line' in
    1408             //     copyAttribute(attributes, board.options, 'line')
    1409             // but also like in
    1410             //     Type.copyAttributes(attributes, board.options, 'view3d', 'az', 'slider');
    1411             o = options;
    1412             isAvail = true;
    1413             for (i = 2; i < len; i++) {
    1414                 arg = arguments[i];
    1415                 if (this.exists(o[arg])) {
    1416                     o = o[arg];
    1417                 } else {
    1418                     isAvail = false;
    1419                     break;
    1420                 }
    1421             }
    1422             if (isAvail) {
    1423                 a = JXG.deepCopy(a, o, true);
    1424             }
    1425
    1426             // Merge the specific options given in the parameter 'attributes'
    1427             // into the default options.
    1428             // Additionally, we step into a sub-element of attribute like line.point1 -
    1429             // in case it is supplied as in
    1430             //     copyAttribute(attributes, board.options, 'line', 'point1')
    1431             // In this case we would merge attributes.point1 into the global line.point1 attributes.
    1432             o = (typeof attributes === 'object') ? this.keysToLowerCase(attributes) : {};
    1433             isAvail = true;
    1434             for (i = 3; i < len; i++) {
    1435                 arg = arguments[i].toLowerCase();
    1436                 if (this.exists(o[arg])) {
    1437                     o = o[arg];
    1438                 } else {
    1439                     isAvail = false;
    1440                     break;
    1441                 }
    1442             }
    1443             if (isAvail) {
    1444                 this.mergeAttr(a, o, true);
    1445             }
    1446
    1447             if (arguments[2] === "board") {
    1448                 // For board attributes we are done now.
    1449                 return a;
    1450             }
    1451
    1452             // Special treatment of labels
    1453             o = options;
    1454             isAvail = true;
    1455             for (i = 2; i < len; i++) {
    1456                 arg = arguments[i];
    1457                 if (this.exists(o[arg])) {
    1458                     o = o[arg];
    1459                 } else {
    1460                     isAvail = false;
    1461                     break;
    1462                 }
    1463             }
    1464             if (isAvail && this.exists(o.label)) {
    1465                 a.label = JXG.deepCopy(o.label, a.label, true);
    1466             }
    1467             a.label = JXG.deepCopy(options.label, a.label, true);
    1468
    1469             return a;
    1470         },
    1471
    1472         /**
    1473          * Copy all prototype methods from object "superObject" to object
    1474          * "subObject". The constructor of superObject will be available
    1475          * in subObject as subObject.constructor[constructorName].
    1476          * @param {Object} subObj A JavaScript object which receives new methods.
    1477          * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject
    1478          * @returns {String} constructorName Under this name the constructor of superObj will be available
    1479          * in subObject.
    1480          * @private
    1481          */
    1482         copyPrototypeMethods: function (subObject, superObject, constructorName) {
    1483             var key;
    1484
    1485             subObject.prototype[constructorName] = superObject.prototype.constructor;
    1486             for (key in superObject.prototype) {
    1487                 if (superObject.prototype.hasOwnProperty(key)) {
    1488                     subObject.prototype[key] = superObject.prototype[key];
    1489                 }
    1490             }
    1491         },
    1492
    1493         /**
    1494          * Converts a JavaScript object into a JSON string.
    1495          * @param {Object} obj A JavaScript object, functions will be ignored.
    1496          * @param {Boolean} [noquote=false] No quotes around the name of a property.
    1497          * @returns {String} The given object stored in a JSON string.
    1498          * @deprecated
    1499          */
    1500         toJSON: function (obj, noquote) {
    1501             var list, prop, i, s, val;
    1502
    1503             noquote = JXG.def(noquote, false);
    1504
    1505             // check for native JSON support:
    1506             if (JSON !== undefined && JSON.stringify && !noquote) {
    1507                 try {
    1508                     s = JSON.stringify(obj);
    1509                     return s;
    1510                 } catch (e) {
    1511                     // if something goes wrong, e.g. if obj contains functions we won't return
    1512                     // and use our own implementation as a fallback
    1513                 }
    1514             }
    1515
    1516             switch (typeof obj) {
    1517                 case "object":
    1518                     if (obj) {
    1519                         list = [];
    1520
    1521                         if (this.isArray(obj)) {
    1522                             for (i = 0; i < obj.length; i++) {
    1523                                 list.push(JXG.toJSON(obj[i], noquote));
    1524                             }
    1525
    1526                             return "[" + list.join(",") + "]";
    1527                         }
    1528
    1529                         for (prop in obj) {
    1530                             if (obj.hasOwnProperty(prop)) {
    1531                                 try {
    1532                                     val = JXG.toJSON(obj[prop], noquote);
    1533                                 } catch (e2) {
    1534                                     val = "";
    1535                                 }
    1536
    1537                                 if (noquote) {
    1538                                     list.push(prop + ":" + val);
    1539                                 } else {
    1540                                     list.push('"' + prop + '":' + val);
    1541                                 }
    1542                             }
    1543                         }
    1544
    1545                         return "{" + list.join(",") + "} ";
    1546                     }
    1547                     return "null";
    1548                 case "string":
    1549                     return "'" + obj.replace(/(["'])/g, "\\$1") + "'";
    1550                 case "number":
    1551                 case "boolean":
    1552                     return obj.toString();
    1553             }
    1554
    1555             return "0";
    1556         },
    1557
    1558         /**
    1559          * Resets visPropOld.
    1560          * @param {JXG.GeometryElement} el
    1561          * @returns {GeometryElement}
    1562          */
    1563         clearVisPropOld: function (el) {
    1564             el.visPropOld = {
    1565                 cssclass: "",
    1566                 cssdefaultstyle: "",
    1567                 cssstyle: "",
    1568                 fillcolor: "",
    1569                 fillopacity: "",
    1570                 firstarrow: false,
    1571                 fontsize: -1,
    1572                 lastarrow: false,
    1573                 left: -100000,
    1574                 linecap: "",
    1575                 shadow: false,
    1576                 strokecolor: "",
    1577                 strokeopacity: "",
    1578                 strokewidth: "",
    1579                 tabindex: -100000,
    1580                 transitionduration: 0,
    1581                 top: -100000,
    1582                 visible: null
    1583             };
    1584
    1585             return el;
    1586         },
    1587
    1588         /**
    1589          * Checks if an object contains a key, whose value equals to val.
    1590          * @param {Object} obj
    1591          * @param val
    1592          * @returns {Boolean}
    1593          */
    1594         isInObject: function (obj, val) {
    1595             var el;
    1596
    1597             for (el in obj) {
    1598                 if (obj.hasOwnProperty(el)) {
    1599                     if (obj[el] === val) {
    1600                         return true;
    1601                     }
    1602                 }
    1603             }
    1604
    1605             return false;
    1606         },
    1607
    1608         /**
    1609          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
    1610          * @param {String} str
    1611          * @returns {String}
    1612          */
    1613         escapeHTML: function (str) {
    1614             return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
    1615         },
    1616
    1617         /**
    1618          * Eliminates all substrings enclosed by < and > and replaces all occurences of
    1619          * &amp; by &, &gt; by >, and &lt; by <.
    1620          * @param {String} str
    1621          * @returns {String}
    1622          */
    1623         unescapeHTML: function (str) {
    1624             // This regex is NOT insecure. We are replacing everything found with ''
    1625             /*jslint regexp:true*/
    1626             return str
    1627                 .replace(/<\/?[^>]+>/gi, "")
    1628                 .replace(/&/g, "&")
    1629                 .replace(/</g, "<")
    1630                 .replace(/>/g, ">");
    1631         },
    1632
    1633         /**
    1634          * Makes a string lower case except for the first character which will be upper case.
    1635          * @param {String} str Arbitrary string
    1636          * @returns {String} The capitalized string.
    1637          */
    1638         capitalize: function (str) {
    1639             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
    1640         },
    1641
    1642         /**
    1643          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
    1644          * @param {String} str
    1645          * @returns {String}
    1646          */
    1647         trimNumber: function (str) {
    1648             str = str.replace(/^0+/, "");
    1649             str = str.replace(/0+$/, "");
    1650
    1651             if (str[str.length - 1] === "." || str[str.length - 1] === ",") {
    1652                 str = str.slice(0, -1);
    1653             }
    1654
    1655             if (str[0] === "." || str[0] === ",") {
    1656                 str = "0" + str;
    1657             }
    1658
    1659             return str;
    1660         },
    1661
    1662         /**
    1663          * Filter an array of elements.
    1664          * @param {Array} list
    1665          * @param {Object|function} filter
    1666          * @returns {Array}
    1667          */
    1668         filterElements: function (list, filter) {
    1669             var i,
    1670                 f,
    1671                 item,
    1672                 flower,
    1673                 value,
    1674                 visPropValue,
    1675                 pass,
    1676                 l = list.length,
    1677                 result = [];
    1678
    1679             if (typeof filter !== "function" && typeof filter !== "object") {
    1680                 return result;
    1681             }
    1682
    1683             for (i = 0; i < l; i++) {
    1684                 pass = true;
    1685                 item = list[i];
    1686
    1687                 if (typeof filter === "object") {
    1688                     for (f in filter) {
    1689                         if (filter.hasOwnProperty(f)) {
    1690                             flower = f.toLowerCase();
    1691
    1692                             if (typeof item[f] === "function") {
    1693                                 value = item[f]();
    1694                             } else {
    1695                                 value = item[f];
    1696                             }
    1697
    1698                             if (item.visProp && typeof item.visProp[flower] === "function") {
    1699                                 visPropValue = item.visProp[flower]();
    1700                             } else {
    1701                                 visPropValue = item.visProp && item.visProp[flower];
    1702                             }
    1703
    1704                             if (typeof filter[f] === "function") {
    1705                                 pass = filter[f](value) || filter[f](visPropValue);
    1706                             } else {
    1707                                 pass = value === filter[f] || visPropValue === filter[f];
    1708                             }
    1709
    1710                             if (!pass) {
    1711                                 break;
    1712                             }
    1713                         }
    1714                     }
    1715                 } else if (typeof filter === "function") {
    1716                     pass = filter(item);
    1717                 }
    1718
    1719                 if (pass) {
    1720                     result.push(item);
    1721                 }
    1722             }
    1723
    1724             return result;
    1725         },
    1726
    1727         /**
    1728          * Remove all leading and trailing whitespaces from a given string.
    1729          * @param {String} str
    1730          * @returns {String}
    1731          */
    1732         trim: function (str) {
    1733             // str = str.replace(/^\s+/, '');
    1734             // str = str.replace(/\s+$/, '');
    1735             //
    1736             // return str;
    1737             return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
    1738         },
    1739
    1740         /**
    1741          * Convert a floating point number to a string integer + fraction.
    1742          * Returns either a string of the form '3 1/3' (in case of useTeX=false)
    1743          * or '3 \\frac{1}{3}' (in case of useTeX=true).
    1744          *
    1745          * @param {Number} x
    1746          * @param {Boolean} [useTeX=false]
    1747          * @param {Number} [order=0.001]
    1748          * @returns {String}
    1749          * @see JXG.Math#decToFraction
    1750          */
    1751         toFraction: function (x, useTeX, order) {
    1752             var arr = Mat.decToFraction(x, order),
    1753                 str = '';
    1754
    1755             if (arr[1] === 0 && arr[2] === 0) {
    1756                 // 0
    1757                 str += '0';
    1758             } else {
    1759                 // Sign
    1760                 if (arr[0] < 0) {
    1761                     str += '-';
    1762                 }
    1763                 if (arr[2] === 0) {
    1764                     // Integer
    1765                     str += arr[1];
    1766                 } else if (!(arr[2] === 1 && arr[3] === 1)) {
    1767                     // Proper fraction
    1768                     if (arr[1] !== 0) {
    1769                         // Absolute value larger than 1
    1770                         str += arr[1] + ' ';
    1771                     }
    1772                     // Add fractional part
    1773                     if (useTeX === true) {
    1774                         str += '\\frac{' + arr[2] + '}{' + arr[3] + '}';
    1775                     } else {
    1776                         str += arr[2] + '/' + arr[3];
    1777                     }
    1778                 }
    1779             }
    1780             return str;
    1781         },
    1782
    1783         /**
    1784          * Concat array src to array dest.
    1785          * Uses push instead of JavaScript concat, which is much
    1786          * faster.
    1787          * The array dest is changed in place.
    1788          * <p><b>Attention:</b> if "dest" is an anonymous array, the correct result is returned from the function.
    1789          *
    1790          * @param {Array} dest
    1791          * @param {Array} src
    1792          * @returns Array
    1793          */
    1794         concat: function(dest, src) {
    1795             var i,
    1796                 le = src.length;
    1797             for (i = 0; i < le; i++) {
    1798                 dest.push(src[i]);
    1799             }
    1800             return dest;
    1801         },
    1802
    1803         /**
    1804          * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
    1805          * @param {String} str
    1806          * @param {Boolean} caja
    1807          * @returns {String} Sanitized string
    1808          */
    1809         sanitizeHTML: function (str, caja) {
    1810             if (typeof html_sanitize === "function" && caja) {
    1811                 return html_sanitize(
    1812                     str,
    1813                     function () {
    1814                         return undefined;
    1815                     },
    1816                     function (id) {
    1817                         return id;
    1818                     }
    1819                 );
    1820             }
    1821
    1822             if (str && typeof str === "string") {
    1823                 str = str.replace(/</g, "<").replace(/>/g, ">");
    1824             }
    1825
    1826             return str;
    1827         },
    1828
    1829         /**
    1830          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
    1831          * @param {*} s
    1832          * @returns {*} s.Value() if s is an element of type slider, s otherwise
    1833          */
    1834         evalSlider: function (s) {
    1835             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === "function") {
    1836                 return s.Value();
    1837             }
    1838
    1839             return s;
    1840         },
    1841
    1842         /**
    1843          * Convert a string containing a MAXIMA /STACK expression into a JSXGraph / JessieCode string
    1844          * or an array of JSXGraph / JessieCode strings.
    1845          * <p>
    1846          * This function is meanwhile superseded by stack_jxg.stack2jsxgraph.
    1847          *
    1848          * @example
    1849          * console.log( JXG.stack2jsxgraph("%e**x") );
    1850          * // Output:
    1851          * //    "EULER**x"
    1852          *
    1853          * @example
    1854          * console.log( JXG.stack2jsxgraph("[%pi*(x**2 - 1), %phi*(x - 1), %gamma*(x+1)]") );
    1855          * // Output:
    1856          * //    [ "PI*(x**2 - 1)", "1.618033988749895*(x - 1)", "0.5772156649015329*(x+1)" ]
    1857          *
    1858          * @param {String} str
    1859          * @returns String
    1860          */
    1861         stack2jsxgraph: function(str) {
    1862             var t;
    1863
    1864             t = str.
    1865                 replace(/%pi/g, 'PI').
    1866                 replace(/%e/g, 'EULER').
    1867                 replace(/%phi/g, '1.618033988749895').
    1868                 replace(/%gamma/g, '0.5772156649015329').
    1869                 trim();
    1870
    1871             // String containing array -> array containing strings
    1872             if (t[0] === '[' && t[t.length - 1] === ']') {
    1873                 t = t.slice(1, -1).split(/\s*,\s*/);
    1874             }
    1875
    1876             return t;
    1877         }
    1878     }
    1879 );
    1880
    1881 export default JXG;
    1882