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