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