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