      1 /*
      2     Copyright 2008-2022
      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 <http://www.gnu.org/licenses/>
     30     and <http://opensource.org/licenses/MIT/>.
     31  */
     32
     33 /*global JXG: true, define: true, html_sanitize: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /* depends:
     37  jxg
     38  base/constants
     39  */
     40
     41 /**
     42  * @fileoverview type.js contains several functions to help deal with javascript's weak types.
     43  * This file mainly consists of detector functions which verify if a variable is or is not of
     44  * a specific type and converter functions that convert variables to another type or normalize
     45  * the type of a variable.
     46  */
     47
     48 define([
     49     'jxg', 'base/constants'
     50 ], function (JXG, Const) {
     51
     52     'use strict';
     53
     54     JXG.extend(JXG, /** @lends JXG */ {
     55         /**
     56          * Checks if the given string is an id within the given board.
     57          * @param {JXG.Board} board
     58          * @param {String} s
     59          * @returns {Boolean}
     60          */
     61         isId: function (board, s) {
     62             return (typeof s === 'string') && !!board.objects[s];
     63         },
     64
     65         /**
     66          * Checks if the given string is a name within the given board.
     67          * @param {JXG.Board} board
     68          * @param {String} s
     69          * @returns {Boolean}
     70          */
     71         isName: function (board, s) {
     72             return typeof s === 'string' && !!board.elementsByName[s];
     73         },
     74
     75         /**
     76          * Checks if the given string is a group id within the given board.
     77          * @param {JXG.Board} board
     78          * @param {String} s
     79          * @returns {Boolean}
     80          */
     81         isGroup: function (board, s) {
     82             return typeof s === 'string' && !!board.groups[s];
     83         },
     84
     85         /**
     86          * Checks if the value of a given variable is of type string.
     87          * @param v A variable of any type.
     88          * @returns {Boolean} True, if v is of type string.
     89          */
     90         isString: function (v) {
     91             return typeof v === 'string';
     92         },
     93
     94         /**
     95          * Checks if the value of a given variable is of type number.
     96          * @param v A variable of any type.
     97          * @returns {Boolean} True, if v is of type number.
     98          */
     99         isNumber: function (v) {
    100             return typeof v === 'number' || Object.prototype.toString.call(v) === '[Object Number]';
    101         },
    102
    103         /**
    104          * Checks if a given variable references a function.
    105          * @param v A variable of any type.
    106          * @returns {Boolean} True, if v is a function.
    107          */
    108         isFunction: function (v) {
    109             return typeof v === 'function';
    110         },
    111
    112         /**
    113          * Checks if a given variable references an array.
    114          * @param v A variable of any type.
    115          * @returns {Boolean} True, if v is of type array.
    116          */
    117         isArray: function (v) {
    118             var r;
    119
    120             // use the ES5 isArray() method and if that doesn't exist use a fallback.
    121             if (Array.isArray) {
    122                 r = Array.isArray(v);
    123             } else {
    124                 r = (v !== null && typeof v === 'object' && typeof v.splice === 'function' && typeof v.join === 'function');
    125             }
    126
    127             return r;
    128         },
    129
    130         /**
    131          * Tests if the input variable is an Object
    132          * @param v
    133          */
    134         isObject: function (v) {
    135             return typeof v === 'object' && !this.isArray(v);
    136         },
    137
    138         /**
    139          * Checks if a given variable is a reference of a JSXGraph Point element.
    140          * @param v A variable of any type.
    141          * @returns {Boolean} True, if v is of type JXG.Point.
    142          */
    143         isPoint: function (v) {
    144             if (v !== null && typeof v === 'object') {
    145                 return (v.elementClass === Const.OBJECT_CLASS_POINT);
    146             }
    147
    148             return false;
    149         },
    150
    151         /**
    152          * Checks if a given variable is a reference of a JSXGraph Point element or an array of length at least two or
    153          * a function returning an array of length two or three.
    154          * @param {JXG.Board} board
    155          * @param v A variable of any type.
    156          * @returns {Boolean} True, if v is of type JXG.Point.
    157          */
    158         isPointType: function (board, v) {
    159             var val, p;
    160
    161             if (this.isArray(v)) {
    162                 return true;
    163             }
    164             if (this.isFunction(v)) {
    165                 val = v();
    166                 if (this.isArray(val) && val.length > 1) {
    167                     return true;
    168                 }
    169             }
    170             p = board.select(v);
    171             return this.isPoint(p);
    172         },
    173
    174         /**
    175          * Checks if a given variable is a reference of a JSXGraph transformation element or an array
    176          * of JSXGraph transformation elements.
    177          * @param v A variable of any type.
    178          * @returns {Boolean} True, if v is of type JXG.Transformation.
    179          */
    180         isTransformationOrArray: function (v) {
    181             if (v !== null) {
    182                 if (this.isArray(v) && v.length > 0) {
    183                     return this.isTransformationOrArray(v[0]);
    184                 }
    185                 if (typeof v === 'object') {
    186                     return (v.type === Const.OBJECT_TYPE_TRANSFORMATION);
    187                 }
    188             }
    189             return false;
    190         },
    191
    192         /**
    193          * Checks if a given variable is neither undefined nor null. You should not use this together with global
    194          * variables!
    195          * @param v A variable of any type.
    196          * @param {Boolean} [checkEmptyString=false] If set to true, it is also checked whether v is not equal to ''.
    197          * @returns {Boolean} True, if v is neither undefined nor null.
    198          */
    199         exists: function (v, checkEmptyString) {
    200             /* eslint-disable eqeqeq */
    201             var result = !(v == undefined || v === null);
    202             /* eslint-enable eqeqeq */
    203             checkEmptyString = checkEmptyString || false;
    204
    205             if (checkEmptyString) {
    206                 return result && v !== '';
    207             }
    208             return result;
    209         },
    210         // exists: (function (undef) {
    211         //     return function (v, checkEmptyString) {
    212         //         var result = !(v === undef || v === null);
    213
    214         //         checkEmptyString = checkEmptyString || false;
    215
    216         //         if (checkEmptyString) {
    217         //             return result && v !== '';
    218         //         }
    219         //         return result;
    220         //     };
    221         // }()),
    222
    223         /**
    224          * Checks if v is an empty object or empty.
    225          * @param v {Object|Array}
    226          * @returns {boolean} True, if v is an empty object or array.
    227          */
    228         isEmpty: function(v) {
    229             return Object.keys(v).length === 0;
    230         },
    231
    232         /**
    233          * Handle default parameters.
    234          * @param v Given value
    235          * @param d Default value
    236          * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null.
    237          */
    238         def: function (v, d) {
    239             if (this.exists(v)) {
    240                 return v;
    241             }
    242
    243             return d;
    244         },
    245
    246         /**
    247          * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
    248          * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
    249          * @returns {Boolean} String typed boolean value converted to boolean.
    250          */
    251         str2Bool: function (s) {
    252             if (!this.exists(s)) {
    253                 return true;
    254             }
    255
    256             if (typeof s === 'boolean') {
    257                 return s;
    258             }
    259
    260             if (this.isString(s)) {
    261                 return (s.toLowerCase() === 'true');
    262             }
    263
    264             return false;
    265         },
    266
    267         /**
    268          * Convert a String, a number or a function into a function. This method is used in Transformation.js
    269          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
    270          * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
    271          * values is of type string.
    272          * @param {Array} param An array containing strings, numbers, or functions.
    273          * @param {Number} n Length of <tt>param</tt>.
    274          * @returns {Function} A function taking one parameter k which specifies the index of the param element
    275          * to evaluate.
    276          */
    277         createEvalFunction: function (board, param, n) {
    278             var f = [], i;
    279
    280             for (i = 0; i < n; i++) {
    281                 f[i] = JXG.createFunction(param[i], board, '', true);
    282             }
    283
    284             return function (k) {
    285                 return f[k]();
    286             };
    287         },
    288
    289         /**
    290          * Convert a String, number or function into a function.
    291          * @param {String|Number|Function} term A variable of type string, function or number.
    292          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
    293          * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
    294          * values is of type string.
    295          * @param {String} variableName Only required if evalGeonext is set to true. Describes the variable name
    296          * of the variable in a GEONE<sub>X</sub>T string given as term.
    297          * @param {Boolean} [evalGeonext=true] Set this true, if term should be treated as a GEONE<sub>X</sub>T string.
    298          * @returns {Function} A function evaluation the value given by term or null if term is not of type string,
    299          * function or number.
    300          */
    301         createFunction: function (term, board, variableName, evalGeonext) {
    302             var f = null;
    303
    304             if ((!this.exists(evalGeonext) || evalGeonext) && this.isString(term)) {
    305                 // Convert GEONExT syntax into  JavaScript syntax
    306                 //newTerm = JXG.GeonextParser.geonext2JS(term, board);
    307                 //return new Function(variableName,'return ' + newTerm + ';');
    308
    309                 //term = JXG.GeonextParser.replaceNameById(term, board);
    310                 //term = JXG.GeonextParser.geonext2JS(term, board);
    311                 f = board.jc.snippet(term, true, variableName, true);
    312             } else if (this.isFunction(term)) {
    313                 f = term;
    314             } else if (this.isNumber(term)) {
    315                 /** @ignore */
    316                 f = function () {
    317                     return term;
    318                 };
    319             } else if (this.isString(term)) {
    320                 // In case of string function like fontsize
    321                 /** @ignore */
    322                 f = function () {
    323                     return term;
    324                 };
    325             }
    326
    327             if (f !== null) {
    328                 f.origin = term;
    329             }
    330
    331             return f;
    332         },
    333
    334         /**
    335          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or
    336          *  function returning coordinate arrays
    337          *  free points with these coordinates are created.
    338          *
    339          * @param {JXG.Board} board Board object
    340          * @param {Array} parents Array containing parent elements for a new object. This array may contain
    341          *    <ul>
    342          *      <li> {@link JXG.Point} objects
    343          *      <li> {@link JXG.GeometryElement#name} of {@link JXG.Point} objects
    344          *      <li> {@link JXG.GeometryElement#id} of {@link JXG.Point} objects
    345          *      <li> Coordinates of points given as array of numbers of length two or three, e.g. [2, 3].
    346          *      <li> Coordinates of points given as array of functions of length two or three. Each function returns one coordinate, e.g.
    347          *           [function(){ return 2; }, function(){ return 3; }]
    348          *      <li> Function returning coordinates, e.g. function() { return [2, 3]; }
    349          *    </ul>
    350          *  In the last three cases a new point will be created.
    351          * @param {String} attrClass Main attribute class of newly created points, see {@link JXG#copyAttributes}
    352          * @param {Array} attrArray List of subtype attributes for the newly created points. The list of subtypes is mapped to the list of new points.
    353          * @returns {Array} List of newly created {@link JXG.Point} elements or false if not all returned elements are points.
    354          */
    355         providePoints: function (board, parents, attributes, attrClass, attrArray) {
    356             var i, j,
    357                 len,
    358                 lenAttr = 0,
    359                 points = [], attr, val;
    360
    361             if (!this.isArray(parents)) {
    362                 parents = [parents];
    363             }
    364             len = parents.length;
    365             if (this.exists(attrArray)) {
    366                 lenAttr = attrArray.length;
    367             }
    368             if (lenAttr === 0) {
    369                 attr = this.copyAttributes(attributes, board.options, attrClass);
    370             }
    371
    372             for (i = 0; i < len; ++i) {
    373                 if (lenAttr > 0) {
    374                     j = Math.min(i, lenAttr - 1);
    375                     attr = this.copyAttributes(attributes, board.options, attrClass, attrArray[j]);
    376                 }
    377                 if (this.isArray(parents[i]) && parents[i].length > 1) {
    378                     points.push(board.create('point', parents[i], attr));
    379                     points[points.length - 1]._is_new = true;
    380                 } else if (this.isFunction(parents[i])) {
    381                     val = parents[i]();
    382                     if (this.isArray(val) && (val.length > 1)) {
    383                         points.push(board.create('point', [parents[i]], attr));
    384                         points[points.length - 1]._is_new = true;
    385                     }
    386                 } else {
    387                     points.push(board.select(parents[i]));
    388                 }
    389
    390                 if (!this.isPoint(points[i])) {
    391                     return false;
    392                 }
    393             }
    394
    395             return points;
    396         },
    397
    398         /**
    399          * Generates a function which calls the function fn in the scope of owner.
    400          * @param {Function} fn Function to call.
    401          * @param {Object} owner Scope in which fn is executed.
    402          * @returns {Function} A function with the same signature as fn.
    403          */
    404         bind: function (fn, owner) {
    405             return function () {
    406                 return fn.apply(owner, arguments);
    407             };
    408         },
    409
    410         /**
    411          * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
    412          * is just returned.
    413          * @param val Could be anything. Preferably a number or a function.
    414          * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
    415          */
    416         evaluate: function (val) {
    417             if (this.isFunction(val)) {
    418                 return val();
    419             }
    420
    421             return val;
    422         },
    423
    424         /**
    425          * Search an array for a given value.
    426          * @param {Array} array
    427          * @param value
    428          * @param {String} [sub] Use this property if the elements of the array are objects.
    429          * @returns {Number} The index of the first appearance of the given value, or
    430          * <tt>-1</tt> if the value was not found.
    431          */
    432         indexOf: function (array, value, sub) {
    433             var i, s = this.exists(sub);
    434
    435             if (Array.indexOf && !s) {
    436                 return array.indexOf(value);
    437             }
    438
    439             for (i = 0; i < array.length; i++) {
    440                 if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
    441                     return i;
    442                 }
    443             }
    444
    445             return -1;
    446         },
    447
    448         /**
    449          * Eliminates duplicate entries in an array consisting of numbers and strings.
    450          * @param {Array} a An array of numbers and/or strings.
    451          * @returns {Array} The array with duplicate entries eliminated.
    452          */
    453         eliminateDuplicates: function (a) {
    454             var i,
    455                 len = a.length,
    456                 result = [],
    457                 obj = {};
    458
    459             for (i = 0; i < len; i++) {
    460                 obj[a[i]] = 0;
    461             }
    462
    463             for (i in obj) {
    464                 if (obj.hasOwnProperty(i)) {
    465                     result.push(i);
    466                 }
    467             }
    468
    469             return result;
    470         },
    471
    472         /**
    473          * Swaps to array elements.
    474          * @param {Array} arr
    475          * @param {Number} i
    476          * @param {Number} j
    477          * @returns {Array} Reference to the given array.
    478          */
    479         swap: function (arr, i, j) {
    480             var tmp;
    481
    482             tmp = arr[i];
    483             arr[i] = arr[j];
    484             arr[j] = tmp;
    485
    486             return arr;
    487         },
    488
    489         /**
    490          * Generates a copy of an array and removes the duplicate entries. The original
    491          * Array will be altered.
    492          * @param {Array} arr
    493          * @returns {Array}
    494          */
    495         uniqueArray: function (arr) {
    496             var i, j, isArray, ret = [];
    497
    498             if (arr.length === 0) {
    499                 return [];
    500             }
    501
    502             for (i = 0; i < arr.length; i++) {
    503                 isArray = this.isArray(arr[i]);
    504
    505                 if (!this.exists(arr[i])) {
    506                     arr[i] = '';
    507                     continue;
    508                 }
    509                 for (j = i + 1; j < arr.length; j++) {
    510                     if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
    511                         arr[i] = [];
    512                     } else if (!isArray && arr[i] === arr[j]) {
    513                         arr[i] = '';
    514                     }
    515                 }
    516             }
    517
    518             j = 0;
    519
    520             for (i = 0; i < arr.length; i++) {
    521                 isArray = this.isArray(arr[i]);
    522
    523                 if (!isArray && arr[i] !== '') {
    524                     ret[j] = arr[i];
    525                     j++;
    526                 } else if (isArray && arr[i].length !== 0) {
    527                     ret[j] = (arr[i].slice(0));
    528                     j++;
    529                 }
    530             }
    531
    532             arr = ret;
    533             return ret;
    534         },
    535
    536         /**
    537          * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
    538          * @param {Array} arr
    539          * @param val
    540          * @returns {Boolean}
    541          */
    542         isInArray: function (arr, val) {
    543             return JXG.indexOf(arr, val) > -1;
    544         },
    545
    546         /**
    547          * Converts an array of {@link JXG.Coords} objects into a coordinate matrix.
    548          * @param {Array} coords
    549          * @param {Boolean} split
    550          * @returns {Array}
    551          */
    552         coordsArrayToMatrix: function (coords, split) {
    553             var i,
    554                 x = [],
    555                 m = [];
    556
    557             for (i = 0; i < coords.length; i++) {
    558                 if (split) {
    559                     x.push(coords[i].usrCoords[1]);
    560                     m.push(coords[i].usrCoords[2]);
    561                 } else {
    562                     m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]);
    563                 }
    564             }
    565
    566             if (split) {
    567                 m = [x, m];
    568             }
    569
    570             return m;
    571         },
    572
    573         /**
    574          * Compare two arrays.
    575          * @param {Array} a1
    576          * @param {Array} a2
    577          * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
    578          */
    579         cmpArrays: function (a1, a2) {
    580             var i;
    581
    582             // trivial cases
    583             if (a1 === a2) {
    584                 return true;
    585             }
    586
    587             if (a1.length !== a2.length) {
    588                 return false;
    589             }
    590
    591             for (i = 0; i < a1.length; i++) {
    592                 if (this.isArray(a1[i]) && this.isArray(a2[i])) {
    593                     if (!this.cmpArrays(a1[i], a2[i])) {
    594                         return false;
    595                     }
    596                 } else if (a1[i] !== a2[i]) {
    597                     return false;
    598                 }
    599             }
    600
    601             return true;
    602         },
    603
    604         /**
    605          * Removes an element from the given array
    606          * @param {Array} ar
    607          * @param el
    608          * @returns {Array}
    609          */
    610         removeElementFromArray: function (ar, el) {
    611             var i;
    612
    613             for (i = 0; i < ar.length; i++) {
    614                 if (ar[i] === el) {
    615                     ar.splice(i, 1);
    616                     return ar;
    617                 }
    618             }
    619
    620             return ar;
    621         },
    622
    623         /**
    624          * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
    625          * @param {Number} n
    626          * @param {Number} p
    627          * @returns {Number}
    628          */
    629         trunc: function (n, p) {
    630             p = JXG.def(p, 0);
    631
    632             return this.toFixed(n, p);
    633         },
    634
    635         /**
    636          * Decimal adjustment of a number.
    637          * From https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Math/round
    638          *
    639          * @param    {String}    type    The type of adjustment.
    640          * @param    {Number}    value    The number.
    641          * @param    {Number}    exp        The exponent (the 10 logarithm of the adjustment base).
    642          * @returns    {Number}            The adjusted value.
    643          *
    644          * @private
    645          */
    646         _decimalAdjust: function (type, value, exp) {
    647             // If the exp is undefined or zero...
    648             if (exp === undefined || +exp === 0) {
    649                 return Math[type](value);
    650             }
    651
    652             value = +value;
    653             exp = +exp;
    654             // If the value is not a number or the exp is not an integer...
    655             if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
    656                 return NaN;
    657             }
    658
    659             // Shift
    660             value = value.toString().split('e');
    661             value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
    662
    663             // Shift back
    664             value = value.toString().split('e');
    665             return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
    666         },
    667
    668         /**
    669          * Round a number to given number of decimal digits.
    670          *
    671          * Example: JXG._toFixed(3.14159, -2) gives 3.14
    672          * @param  {Number} value Number to be rounded
    673          * @param  {Number} exp   Number of decimal digits given as negative exponent
    674          * @return {Number}       Rounded number.
    675          *
    676          * @private
    677          */
    678         _round10: function (value, exp) {
    679             return this._decimalAdjust('round', value, exp);
    680         },
    681
    682         /**
    683          * "Floor" a number to given number of decimal digits.
    684          *
    685          * Example: JXG._toFixed(3.14159, -2) gives 3.14
    686          * @param  {Number} value Number to be floored
    687          * @param  {Number} exp   Number of decimal digits given as negative exponent
    688          * @return {Number}       "Floored" number.
    689          *
    690          * @private
    691          */
    692         _floor10: function (value, exp) {
    693             return this._decimalAdjust('floor', value, exp);
    694         },
    695
    696         /**
    697          * "Ceil" a number to given number of decimal digits.
    698          *
    699          * Example: JXG._toFixed(3.14159, -2) gives 3.15
    700          * @param  {Number} value Number to be ceiled
    701          * @param  {Number} exp   Number of decimal digits given as negative exponent
    702          * @return {Number}       "Ceiled" number.
    703          *
    704          * @private
    705          */
    706         _ceil10: function (value, exp) {
    707             return this._decimalAdjust('ceil', value, exp);
    708         },
    709
    710         /**
    711          * Replacement of the default toFixed() method.
    712          * It does a correct rounding (independent of the browser) and
    713          * returns "0.00" for toFixed(-0.000001, 2) instead of "-0.00" which
    714          * is returned by JavaScript's toFixed()
    715          *
    716          * @memberOf JXG
    717          * @param  {Number} num    Number tp be rounded
    718          * @param  {Number} digits Decimal digits
    719          * @return {String}        Rounded number is returned as string
    720          */
    721         toFixed: function (num, digits) {
    722             return this._round10(num, -digits).toFixed(digits);
    723         },
    724
    725         /**
    726          * Truncate a number <tt>val</tt> automatically.
    727          * @memberOf JXG
    728          * @param val
    729          * @returns {Number}
    730          */
    731         autoDigits: function (val) {
    732             var x = Math.abs(val),
    733                 str;
    734
    735             if (x > 0.1) {
    736                 str = this.toFixed(val, 2);
    737             } else if (x >= 0.01) {
    738                 str = this.toFixed(val, 4);
    739             } else if (x >= 0.0001) {
    740                 str = this.toFixed(val, 6);
    741             } else {
    742                 str = val;
    743             }
    744             return str;
    745         },
    746
    747         /**
    748          * Extracts the keys of a given object.
    749          * @param object The object the keys are to be extracted
    750          * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
    751          * the object owns itself and not some other object in the prototype chain.
    752          * @returns {Array} All keys of the given object.
    753          */
    754         keys: function (object, onlyOwn) {
    755             var keys = [], property;
    756
    757             // the caller decides if we use hasOwnProperty
    758             /*jslint forin:true*/
    759             for (property in object) {
    760                 if (onlyOwn) {
    761                     if (object.hasOwnProperty(property)) {
    762                         keys.push(property);
    763                     }
    764                 } else {
    765                     keys.push(property);
    766                 }
    767             }
    768             /*jslint forin:false*/
    769
    770             return keys;
    771         },
    772
    773         /**
    774          * This outputs an object with a base class reference to the given object. This is useful if
    775          * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
    776          * without changing the original object.
    777          * @param {Object} obj Object to be embedded.
    778          * @returns {Object} An object with a base class reference to <tt>obj</tt>.
    779          */
    780         clone: function (obj) {
    781             var cObj = {};
    782
    783             cObj.prototype = obj;
    784
    785             return cObj;
    786         },
    787
    788         /**
    789          * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
    790          * to the new one. Warning: The copied properties of obj2 are just flat copies.
    791          * @param {Object} obj Object to be copied.
    792          * @param {Object} obj2 Object with data that is to be copied to the new one as well.
    793          * @returns {Object} Copy of given object including some new/overwritten data from obj2.
    794          */
    795         cloneAndCopy: function (obj, obj2) {
    796             var r,
    797                 cObj = function () { return undefined; };
    798
    799             cObj.prototype = obj;
    800
    801             // no hasOwnProperty on purpose
    802             /*jslint forin:true*/
    803             /*jshint forin:true*/
    804
    805             for (r in obj2) {
    806                 cObj[r] = obj2[r];
    807             }
    808
    809             /*jslint forin:false*/
    810             /*jshint forin:false*/
    811
    812             return cObj;
    813         },
    814
    815         /**
    816          * Recursively merges obj2 into obj1. Contrary to {@link JXG#deepCopy} this won't create a new object
    817          * but instead will overwrite obj1.
    818          * @param {Object} obj1
    819          * @param {Object} obj2
    820          * @returns {Object}
    821          */
    822         merge: function (obj1, obj2) {
    823             var i, j;
    824
    825             for (i in obj2) {
    826                 if (obj2.hasOwnProperty(i)) {
    827                     if (this.isArray(obj2[i])) {
    828                         if (!obj1[i]) {
    829                             obj1[i] = [];
    830                         }
    831
    832                         for (j = 0; j < obj2[i].length; j++) {
    833                             if (typeof obj2[i][j] === 'object') {
    834                                 obj1[i][j] = this.merge(obj1[i][j], obj2[i][j]);
    835                             } else {
    836                                 obj1[i][j] = obj2[i][j];
    837                             }
    838                         }
    839                     } else if (typeof obj2[i] === 'object') {
    840                         if (!obj1[i]) {
    841                             obj1[i] = {};
    842                         }
    843
    844                         obj1[i] = this.merge(obj1[i], obj2[i]);
    845                     } else {
    846                         obj1[i] = obj2[i];
    847                     }
    848                 }
    849             }
    850
    851             return obj1;
    852         },
    853
    854         /**
    855          * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
    856          * element-wise instead of just copying the reference. If a second object is supplied, the two objects
    857          * are merged into one object. The properties of the second object have priority.
    858          * @param {Object} obj This object will be copied.
    859          * @param {Object} obj2 This object will merged into the newly created object
    860          * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
    861          * @returns {Object} copy of obj or merge of obj and obj2.
    862          */
    863         deepCopy: function (obj, obj2, toLower) {
    864             var c, i, prop, i2;
    865
    866             toLower = toLower || false;
    867
    868             if (typeof obj !== 'object' || obj === null) {
    869                 return obj;
    870             }
    871
    872             // missing hasOwnProperty is on purpose in this function
    873             if (this.isArray(obj)) {
    874                 c = [];
    875                 for (i = 0; i < obj.length; i++) {
    876                     prop = obj[i];
    877                     if (typeof prop === 'object') {
    878                         // We certainly do not want to recurse into a JSXGraph object.
    879                         // This would for sure result in an infinite recursion.
    880                         // As alternative we copy the id of the object.
    881                         if (this.exists(prop.board)) {
    882                             c[i] = prop.id;
    883                         } else {
    884                             c[i] = this.deepCopy(prop);
    885                         }
    886                     } else {
    887                         c[i] = prop;
    888                     }
    889                 }
    890             } else {
    891                 c = {};
    892                 for (i in obj) {
    893                     if (obj.hasOwnProperty(i)) {
    894                         i2 = toLower ? i.toLowerCase() : i;
    895                         prop = obj[i];
    896                         if (prop !== null && typeof prop === 'object') {
    897                             if (this.exists(prop.board)) {
    898                                 c[i2] = prop.id;
    899                             } else {
    900                                 c[i2] = this.deepCopy(prop);
    901                             }
    902                         } else {
    903                             c[i2] = prop;
    904                         }
    905                     }
    906                 }
    907
    908                 for (i in obj2) {
    909                     if (obj2.hasOwnProperty(i)) {
    910                         i2 = toLower ? i.toLowerCase() : i;
    911
    912                         prop = obj2[i];
    913                         if (typeof prop === 'object') {
    914                             if (this.isArray(prop) || !this.exists(c[i2])) {
    915                                 c[i2] = this.deepCopy(prop);
    916                             } else {
    917                                 c[i2] = this.deepCopy(c[i2], prop, toLower);
    918                             }
    919                         } else {
    920                             c[i2] = prop;
    921                         }
    922                     }
    923                 }
    924             }
    925
    926             return c;
    927         },
    928
    929         /**
    930          * Generates an attributes object that is filled with default values from the Options object
    931          * and overwritten by the user specified attributes.
    932          * @param {Object} attributes user specified attributes
    933          * @param {Object} options defaults options
    934          * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'.
    935          * @returns {Object} The resulting attributes object
    936          */
    937         copyAttributes: function (attributes, options, s) {
    938             var a, i, len, o, isAvail,
    939                 primitives = {
    940                     'circle': 1,
    941                     'curve': 1,
    942                     'image': 1,
    943                     'line': 1,
    944                     'point': 1,
    945                     'polygon': 1,
    946                     'text': 1,
    947                     'ticks': 1,
    948                     'integral': 1
    949                 };
    950
    951             len = arguments.length;
    952             if (len < 3 || primitives[s]) {
    953                 // default options from Options.elements
    954                 a = JXG.deepCopy(options.elements, null, true);
    955             } else {
    956                 a = {};
    957             }
    958
    959             // Only the layer of the main element is set.
    960             if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
    961                 a.layer = options.layer[s];
    962             }
    963
    964             // default options from specific elements
    965             o = options;
    966             isAvail = true;
    967             for (i = 2; i < len; i++) {
    968                 if (this.exists(o[arguments[i]])) {
    969                     o = o[arguments[i]];
    970                 } else {
    971                     isAvail = false;
    972                     break;
    973                 }
    974             }
    975             if (isAvail) {
    976                 a = JXG.deepCopy(a, o, true);
    977             }
    978
    979             // options from attributes
    980             o = attributes;
    981             isAvail = true;
    982             for (i = 3; i < len; i++) {
    983                 if (this.exists(o[arguments[i]])) {
    984                     o = o[arguments[i]];
    985                 } else {
    986                     isAvail = false;
    987                     break;
    988                 }
    989             }
    990             if (isAvail) {
    991                 this.extend(a, o, null, true);
    992             }
    993
    994             if (arguments[2] === 'board') {
    995                 // For board attributes we are done now.
    996                 return a;
    997             }
    998
    999             // Special treatment of labels
    1000             o = options;
    1001             isAvail = true;
    1002             for (i = 2; i < len; i++) {
    1003                 if (this.exists(o[arguments[i]])) {
    1004                     o = o[arguments[i]];
    1005                 } else {
    1006                     isAvail = false;
    1007                     break;
    1008                 }
    1009             }
    1010             if (isAvail && this.exists(o.label)) {
    1011                 a.label = JXG.deepCopy(o.label, a.label);
    1012             }
    1013             a.label = JXG.deepCopy(options.label, a.label);
    1014
    1015             return a;
    1016         },
    1017
    1018         /**
    1019          * Copy all prototype methods from object "superObject" to object
    1020          * "subObject". The constructor of superObject will be available
    1021          * in subObject as subObject.constructor[constructorName].
    1022          * @param {Object} subObj A JavaScript object which receives new methods.
    1023          * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject
    1024          * @returns {String} constructorName Under this name the constructor of superObj will be available
    1025          * in subObject.
    1026          * @private
    1027          */
    1028         copyPrototypeMethods: function (subObject, superObject, constructorName) {
    1029             var key;
    1030
    1031             subObject.prototype[constructorName] = superObject.prototype.constructor;
    1032             for (key in superObject.prototype) {
    1033                 if (superObject.prototype.hasOwnProperty(key)) {
    1034                     subObject.prototype[key] = superObject.prototype[key];
    1035                 }
    1036             }
    1037         },
    1038
    1039         /**
    1040          * Converts a JavaScript object into a JSON string.
    1041          * @param {Object} obj A JavaScript object, functions will be ignored.
    1042          * @param {Boolean} [noquote=false] No quotes around the name of a property.
    1043          * @returns {String} The given object stored in a JSON string.
    1044          */
    1045         toJSON: function (obj, noquote) {
    1046             var list, prop, i, s, val;
    1047
    1048             noquote = JXG.def(noquote, false);
    1049
    1050             // check for native JSON support:
    1051             if (typeof JSON && JSON.stringify && !noquote) {
    1052                 try {
    1053                     s = JSON.stringify(obj);
    1054                     return s;
    1055                 } catch (e) {
    1056                     // if something goes wrong, e.g. if obj contains functions we won't return
    1057                     // and use our own implementation as a fallback
    1058                 }
    1059             }
    1060
    1061             switch (typeof obj) {
    1062                 case 'object':
    1063                     if (obj) {
    1064                         list = [];
    1065
    1066                         if (this.isArray(obj)) {
    1067                             for (i = 0; i < obj.length; i++) {
    1068                                 list.push(JXG.toJSON(obj[i], noquote));
    1069                             }
    1070
    1071                             return '[' + list.join(',') + ']';
    1072                         }
    1073
    1074                         for (prop in obj) {
    1075                             if (obj.hasOwnProperty(prop)) {
    1076                                 try {
    1077                                     val = JXG.toJSON(obj[prop], noquote);
    1078                                 } catch (e2) {
    1079                                     val = '';
    1080                                 }
    1081
    1082                                 if (noquote) {
    1083                                     list.push(prop + ':' + val);
    1084                                 } else {
    1085                                     list.push('"' + prop + '":' + val);
    1086                                 }
    1087                             }
    1088                         }
    1089
    1090                         return '{' + list.join(',') + '} ';
    1091                     }
    1092                     return 'null';
    1093                 case 'string':
    1094                     return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
    1095                 case 'number':
    1096                 case 'boolean':
    1097                     return obj.toString();
    1098             }
    1099
    1100             return '0';
    1101         },
    1102
    1103         /**
    1104          * Resets visPropOld.
    1105          * @param {JXG.GeometryElement} el
    1106          * @returns {GeometryElement}
    1107          */
    1108         clearVisPropOld: function (el) {
    1109             el.visPropOld = {
    1110                 cssclass: '',
    1111                 cssdefaultstyle: '',
    1112                 cssstyle: '',
    1113                 fillcolor: '',
    1114                 fillopacity: '',
    1115                 firstarrow: false,
    1116                 fontsize: -1,
    1117                 lastarrow: false,
    1118                 left: -100000,
    1119                 linecap: '',
    1120                 shadow: false,
    1121                 strokecolor: '',
    1122                 strokeopacity: '',
    1123                 strokewidth: '',
    1124                 tabindex: -100000,
    1125                 transitionduration: 0,
    1126                 top: -100000,
    1127                 visible: null
    1128             };
    1129
    1130             return el;
    1131         },
    1132
    1133         /**
    1134          * Checks if an object contains a key, whose value equals to val.
    1135          * @param {Object} obj
    1136          * @param val
    1137          * @returns {Boolean}
    1138          */
    1139         isInObject: function (obj, val) {
    1140             var el;
    1141
    1142             for (el in obj) {
    1143                 if (obj.hasOwnProperty(el)) {
    1144                     if (obj[el] === val) {
    1145                         return true;
    1146                     }
    1147                 }
    1148             }
    1149
    1150             return false;
    1151         },
    1152
    1153         /**
    1154          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
    1155          * @param {String} str
    1156          * @returns {String}
    1157          */
    1158         escapeHTML: function (str) {
    1159             return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
    1160         },
    1161
    1162         /**
    1163          * Eliminates all substrings enclosed by < and > and replaces all occurences of
    1164          * &amp; by &, &gt; by >, and &lt; by <.
    1165          * @param {String} str
    1166          * @returns {String}
    1167          */
    1168         unescapeHTML: function (str) {
    1169             // This regex is NOT insecure. We are replacing everything found with ''
    1170             /*jslint regexp:true*/
    1171             return str.replace(/<\/?[^>]+>/gi, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
    1172         },
    1173
    1174         /**
    1175          * Makes a string lower case except for the first character which will be upper case.
    1176          * @param {String} str Arbitrary string
    1177          * @returns {String} The capitalized string.
    1178          */
    1179         capitalize: function (str) {
    1180             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
    1181         },
    1182
    1183         /**
    1184          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
    1185          * @param {String} str
    1186          * @returns {String}
    1187          */
    1188         trimNumber: function (str) {
    1189             str = str.replace(/^0+/, '');
    1190             str = str.replace(/0+$/, '');
    1191
    1192             if (str[str.length - 1] === '.' || str[str.length - 1] === ',') {
    1193                 str = str.slice(0, -1);
    1194             }
    1195
    1196             if (str[0] === '.' || str[0] === ',') {
    1197                 str = '0' + str;
    1198             }
    1199
    1200             return str;
    1201         },
    1202
    1203         /**
    1204          * Filter an array of elements.
    1205          * @param {Array} list
    1206          * @param {Object|function} filter
    1207          * @returns {Array}
    1208          */
    1209         filterElements: function (list, filter) {
    1210             var i, f, item, flower, value, visPropValue, pass,
    1211                 l = list.length,
    1212                 result = [];
    1213
    1214             if (typeof filter !== 'function' && typeof filter !== 'object') {
    1215                 return result;
    1216             }
    1217
    1218             for (i = 0; i < l; i++) {
    1219                 pass = true;
    1220                 item = list[i];
    1221
    1222                 if (typeof filter === 'object') {
    1223                     for (f in filter) {
    1224                         if (filter.hasOwnProperty(f)) {
    1225                             flower = f.toLowerCase();
    1226
    1227                             if (typeof item[f] === 'function') {
    1228                                 value = item[f]();
    1229                             } else {
    1230                                 value = item[f];
    1231                             }
    1232
    1233                             if (item.visProp && typeof item.visProp[flower] === 'function') {
    1234                                 visPropValue = item.visProp[flower]();
    1235                             } else {
    1236                                 visPropValue = item.visProp && item.visProp[flower];
    1237                             }
    1238
    1239                             if (typeof filter[f] === 'function') {
    1240                                 pass = filter[f](value) || filter[f](visPropValue);
    1241                             } else {
    1242                                 pass = (value === filter[f] || visPropValue === filter[f]);
    1243                             }
    1244
    1245                             if (!pass) {
    1246                                 break;
    1247                             }
    1248                         }
    1249                     }
    1250                 } else if (typeof filter === 'function') {
    1251                     pass = filter(item);
    1252                 }
    1253
    1254                 if (pass) {
    1255                     result.push(item);
    1256                 }
    1257             }
    1258
    1259             return result;
    1260         },
    1261
    1262         /**
    1263          * Remove all leading and trailing whitespaces from a given string.
    1264          * @param {String} str
    1265          * @returns {String}
    1266          */
    1267         trim: function (str) {
    1268             // str = str.replace(/^\s+/, '');
    1269             // str = str.replace(/\s+$/, '');
    1270             //
    1271             // return str;
    1272             return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
    1273         },
    1274
    1275         /**

    1277          * @param {String} str
    1278          * @param {Boolean} caja
    1279          * @returns {String} Sanitized string
    1280          */
    1281         sanitizeHTML: function (str, caja) {
    1282             if (typeof html_sanitize === 'function' && caja) {
    1283                 return html_sanitize(str, function () { return undefined; }, function (id) { return id; });
    1284             }
    1285
    1286             if (str) {
    1287                 str = str.replace(/</g, '<').replace(/>/g, '>');
    1288             }
    1289
    1290             return str;
    1291         },
    1292
    1293         /**
    1294          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
    1295          * @param {*} s
    1296          * @returns {*} s.Value() if s is an element of type slider, s otherwise
    1297          */
    1298         evalSlider: function (s) {
    1299             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') {
    1300                 return s.Value();
    1301             }
    1302
    1303             return s;
    1304         }
    1305     });
    1306
    1307     return JXG;
    1308 });
    1309