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