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             // If len > 3, the element is certainly not a primitive object,
1496             // e.g. copyAttributes(attributes, JXG.Options, 'line', 'point1').
1497             // That is, a later create('point', ...) will be the primitive call.
1498             // This will not yet cover all cases of inheritance.
1499             if (len < 3 || (len === 3 && primitives[s])) {
1500                 // Default options from Options.elements
1501                 a = JXG.deepCopy(options.elements, null, true);
1502             } else {
1503                 a = {};
1504             }
1505 
1506             // Only the layer of the main element is set.
1507             if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
1508                 a.layer = options.layer[s];
1509             }
1510 
1511             // Default options from the specific element like 'line' in
1512             //     copyAttribute(attributes, board.options, 'line')
1513             // but also like in
1514             //     Type.copyAttributes(attributes, board.options, 'view3d', 'az', 'slider');
1515             o = options;
1516             isAvail = true;
1517             for (i = 2; i < len; i++) {
1518                 arg = arguments[i];
1519                 if (this.exists(o[arg])) {
1520                     o = o[arg];
1521                 } else {
1522                     isAvail = false;
1523                     break;
1524                 }
1525             }
1526             if (isAvail) {
1527                 a = JXG.deepCopy(a, o, true);
1528             }
1529 
1530             // Merge the specific options given in the parameter 'attributes'
1531             // into the default options.
1532             // Additionally, we step into a sub-element of attribute like line.point1 -
1533             // in case it is supplied as in
1534             //     copyAttribute(attributes, board.options, 'line', 'point1')
1535             // In this case we would merge attributes.point1 into the global line.point1 attributes.
1536             o = (typeof attributes === 'object') ? this.keysToLowerCase(attributes) : {};
1537             isAvail = true;
1538             for (i = 3; i < len; i++) {
1539                 arg = arguments[i].toLowerCase();
1540                 if (this.exists(o[arg])) {
1541                     o = o[arg];
1542                 } else {
1543                     isAvail = false;
1544                     break;
1545                 }
1546             }
1547             if (isAvail) {
1548                 this.mergeAttr(a, o, true);
1549             }
1550 
1551             if (arguments[2] === 'board') {
1552                 // For board attributes we are done now.
1553                 return a;
1554             }
1555 
1556             // Special treatment of labels
1557             o = options;
1558             isAvail = true;
1559             for (i = 2; i < len; i++) {
1560                 arg = arguments[i];
1561                 if (this.exists(o[arg])) {
1562                     o = o[arg];
1563                 } else {
1564                     isAvail = false;
1565                     break;
1566                 }
1567             }
1568             if (isAvail && this.exists(o.label)) {
1569                 a.label = JXG.deepCopy(o.label, a.label, true);
1570             }
1571             a.label = JXG.deepCopy(options.label, a.label, true);
1572 
1573             return a;
1574         },
1575 
1576         /**
1577          * Copy all prototype methods from object "superObject" to object
1578          * "subObject". The constructor of superObject will be available
1579          * in subObject as subObject.constructor[constructorName].
1580          * @param {Object} subObject A JavaScript object which receives new methods.
1581          * @param {Object} superObject A JavaScript object which lends its prototype methods to subObject
1582          * @param {String} constructorName Under this name the constructor of superObj will be available
1583          * in subObject.
1584          * @private
1585          */
1586         copyPrototypeMethods: function (subObject, superObject, constructorName) {
1587             var key;
1588 
1589             subObject.prototype[constructorName] = superObject.prototype.constructor;
1590             for (key in superObject.prototype) {
1591                 if (superObject.prototype.hasOwnProperty(key)) {
1592                     if (key === 'methodMap') {
1593                         JXG.copyMethodMap(subObject, superObject.prototype.methodMap);
1594                     } else {
1595                         subObject.prototype[key] = superObject.prototype[key];
1596                     }
1597                 }
1598             }
1599         },
1600 
1601         /**
1602          * Create a copy of methodMap in "objectClass.prototype" and optional extend it.
1603          * If objectClass.prototype.methodMap does not exist, it will be initialized.
1604          *
1605          * The methodMap determines which methods can be called from within JessieCode and under which name it
1606          * can be used. The map is saved in an object, the name of a property is the name of the method used in JessieCode,
1607          * the value of a property is the name of the method in JavaScript.
1608          *
1609          * @param {Object} objectClass
1610          * @param {Object} [extension]
1611          * @private
1612          */
1613         copyMethodMap: function (objectClass, extension) {
1614             extension = extension || {};
1615 
1616             objectClass.prototype.methodMap = objectClass.prototype.methodMap || {};
1617             objectClass.prototype.methodMap = this.deepCopy(objectClass.prototype.methodMap, extension);
1618         },
1619 
1620         /**
1621          * Copy methodMap of "object.prototype" to objects instance and optional extend it.
1622          * If extension is of type String and extensionValue is defined, a key-value-pair is added.
1623          *
1624          * The methodMap determines which methods can be called from within JessieCode and under which name it
1625          * can be used. The map is saved in an object, the name of a property is the name of the method used in JessieCode,
1626          * the value of a property is the name of the method in JavaScript.
1627          *
1628          * @param {Object} object
1629          * @param {Object|String} [extension]
1630          * @param {String} [extensionValue]
1631          * @private
1632          */
1633         extendInstanceMethodMap: function (object, extension, extensionValue) {
1634             extension = extension || {};
1635 
1636             // Create own copy only if instance still uses prototype version
1637             if (!object.hasOwnProperty("methodMap")) {
1638                 object.methodMap = Object.assign({}, object.methodMap);
1639             }
1640 
1641             if (this.isObject(extension)) {
1642                 object.methodMap = this.deepCopy(object.methodMap, extension);
1643             } else if (this.isString(extension) && this.exists(extensionValue)) {
1644                 object.methodMap[extension] = extensionValue;
1645             }
1646         },
1647 
1648         /**
1649          * Create a stripped down version of a JSXGraph element for cloning to the background.
1650          * Used in {JXG.GeometryElement#cloneToBackground} for creating traces.
1651          *
1652          * @param {JXG.GeometryElement} el Element to be cloned
1653          * @returns Object Cloned element
1654          * @private
1655          */
1656         getCloneObject: function(el) {
1657             var obj, key,
1658                 copy = {};
1659 
1660             copy.id = el.id + "T" + el.numTraces;
1661             el.numTraces += 1;
1662 
1663             copy.coords = el.coords;
1664             obj = this.deepCopy(el.visProp, el.visProp.traceattributes, true);
1665             copy.visProp = {};
1666             for (key in obj) {
1667                 if (obj.hasOwnProperty(key)) {
1668                     if (
1669                         key.indexOf('aria') !== 0 &&
1670                         key.indexOf('highlight') !== 0 &&
1671                         key.indexOf('attractor') !== 0 &&
1672                         key !== 'label' &&
1673                         key !== 'needsregularupdate' &&
1674                         key !== 'infoboxdigits'
1675                     ) {
1676                         copy.visProp[key] = el.eval(obj[key]);
1677                     }
1678                 }
1679             }
1680             copy.evalVisProp = function(val) {
1681                 return copy.visProp[val];
1682             };
1683             copy.eval = function(val) {
1684                 return val;
1685             };
1686 
1687             copy.visProp.layer = el.board.options.layer.trace;
1688             copy.visProp.tabindex = null;
1689             copy.visProp.highlight = false;
1690             copy.board = el.board;
1691             copy.elementClass = el.elementClass;
1692 
1693             this.clearVisPropOld(copy);
1694             copy.visPropCalc = {
1695                 visible: el.evalVisProp('visible')
1696             };
1697 
1698             return copy;
1699         },
1700 
1701         /**
1702          * Converts a JavaScript object into a JSON string.
1703          * @param {Object} obj A JavaScript object, functions will be ignored.
1704          * @param {Boolean} [noquote=false] No quotes around the name of a property.
1705          * @returns {String} The given object stored in a JSON string.
1706          * @deprecated
1707          */
1708         toJSON: function (obj, noquote) {
1709             var list, prop, i, s, val;
1710 
1711             noquote = JXG.def(noquote, false);
1712 
1713             // check for native JSON support:
1714             if (JSON !== undefined && JSON.stringify && !noquote) {
1715                 try {
1716                     s = JSON.stringify(obj);
1717                     return s;
1718                 } catch (e) {
1719                     // if something goes wrong, e.g. if obj contains functions we won't return
1720                     // and use our own implementation as a fallback
1721                 }
1722             }
1723 
1724             switch (typeof obj) {
1725                 case "object":
1726                     if (obj) {
1727                         list = [];
1728 
1729                         if (this.isArray(obj)) {
1730                             for (i = 0; i < obj.length; i++) {
1731                                 list.push(JXG.toJSON(obj[i], noquote));
1732                             }
1733 
1734                             return "[" + list.join(",") + "]";
1735                         }
1736 
1737                         for (prop in obj) {
1738                             if (obj.hasOwnProperty(prop)) {
1739                                 try {
1740                                     val = JXG.toJSON(obj[prop], noquote);
1741                                 } catch (e2) {
1742                                     val = "";
1743                                 }
1744 
1745                                 if (noquote) {
1746                                     list.push(prop + ":" + val);
1747                                 } else {
1748                                     list.push('"' + prop + '":' + val);
1749                                 }
1750                             }
1751                         }
1752 
1753                         return "{" + list.join(",") + "} ";
1754                     }
1755                     return 'null';
1756                 case "string":
1757                     return "'" + obj.replace(/(["'])/g, "\\$1") + "'";
1758                 case "number":
1759                 case "boolean":
1760                     return obj.toString();
1761             }
1762 
1763             return '0';
1764         },
1765 
1766         /**
1767          * Resets visPropOld.
1768          * @param {JXG.GeometryElement} el
1769          * @returns {GeometryElement}
1770          */
1771         clearVisPropOld: function (el) {
1772             el.visPropOld = {
1773                 cssclass: "",
1774                 cssdefaultstyle: "",
1775                 cssstyle: "",
1776                 fillcolor: "",
1777                 fillopacity: "",
1778                 firstarrow: false,
1779                 fontsize: -1,
1780                 lastarrow: false,
1781                 left: -100000,
1782                 linecap: "",
1783                 shadow: false,
1784                 strokecolor: "",
1785                 strokeopacity: "",
1786                 strokewidth: "",
1787                 tabindex: -100000,
1788                 transitionduration: 0,
1789                 top: -100000,
1790                 visible: null
1791             };
1792 
1793             return el;
1794         },
1795 
1796         /**
1797          * Checks if an object contains a key, whose value equals to val.
1798          * @param {Object} obj
1799          * @param val
1800          * @returns {Boolean}
1801          */
1802         isInObject: function (obj, val) {
1803             var el;
1804 
1805             for (el in obj) {
1806                 if (obj.hasOwnProperty(el)) {
1807                     if (obj[el] === val) {
1808                         return true;
1809                     }
1810                 }
1811             }
1812 
1813             return false;
1814         },
1815 
1816         /**
1817          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
1818          * @param {String} str
1819          * @returns {String}
1820          */
1821         escapeHTML: function (str) {
1822             return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
1823         },
1824 
1825         /**
1826          * Eliminates all substrings enclosed by < and > and replaces all occurences of
1827          * &amp; by &, &gt; by >, and &lt; by <.
1828          * @param {String} str
1829          * @returns {String}
1830          */
1831         unescapeHTML: function (str) {
1832             // This regex is NOT insecure. We are replacing everything found with ''
1833             /*jslint regexp:true*/
1834             return str
1835                 .replace(/<\/?[^>]+>/gi, "")
1836                 .replace(/&/g, "&")
1837                 .replace(/</g, "<")
1838                 .replace(/>/g, ">");
1839         },
1840 
1841         /**
1842          * Makes a string lower case except for the first character which will be upper case.
1843          * @param {String} str Arbitrary string
1844          * @returns {String} The capitalized string.
1845          */
1846         capitalize: function (str) {
1847             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
1848         },
1849 
1850         /**
1851          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
1852          * @param {String} str
1853          * @returns {String}
1854          */
1855         trimNumber: function (str) {
1856             str = str.replace(/^0+/, "");
1857             str = str.replace(/0+$/, "");
1858 
1859             if (str[str.length - 1] === "." || str[str.length - 1] === ",") {
1860                 str = str.slice(0, -1);
1861             }
1862 
1863             if (str[0] === "." || str[0] === ",") {
1864                 str = "0" + str;
1865             }
1866 
1867             return str;
1868         },
1869 
1870         /**
1871          * Filter an array of elements.
1872          * @param {Array} list
1873          * @param {Object|function} filter
1874          * @returns {Array}
1875          */
1876         filterElements: function (list, filter) {
1877             var i,
1878                 f,
1879                 item,
1880                 flower,
1881                 value,
1882                 visPropValue,
1883                 pass,
1884                 l = list.length,
1885                 result = [];
1886 
1887             if (this.exists(filter) && typeof filter !== "function" && typeof filter !== 'object') {
1888                 return result;
1889             }
1890 
1891             for (i = 0; i < l; i++) {
1892                 pass = true;
1893                 item = list[i];
1894 
1895                 if (typeof filter === 'object') {
1896                     for (f in filter) {
1897                         if (filter.hasOwnProperty(f)) {
1898                             flower = f.toLowerCase();
1899 
1900                             if (typeof item[f] === 'function') {
1901                                 value = item[f]();
1902                             } else {
1903                                 value = item[f];
1904                             }
1905 
1906                             if (item.visProp && typeof item.visProp[flower] === 'function') {
1907                                 visPropValue = item.visProp[flower]();
1908                             } else {
1909                                 visPropValue = item.visProp && item.visProp[flower];
1910                             }
1911 
1912                             if (typeof filter[f] === 'function') {
1913                                 pass = filter[f](value) || filter[f](visPropValue);
1914                             } else {
1915                                 pass = value === filter[f] || visPropValue === filter[f];
1916                             }
1917 
1918                             if (!pass) {
1919                                 break;
1920                             }
1921                         }
1922                     }
1923                 } else if (typeof filter === 'function') {
1924                     pass = filter(item);
1925                 }
1926 
1927                 if (pass) {
1928                     result.push(item);
1929                 }
1930             }
1931 
1932             return result;
1933         },
1934 
1935         /**
1936          * Remove all leading and trailing whitespaces from a given string.
1937          * @param {String} str
1938          * @returns {String}
1939          */
1940         trim: function (str) {
1941             // str = str.replace(/^\s+/, '');
1942             // str = str.replace(/\s+$/, '');
1943             //
1944             // return str;
1945             return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
1946         },
1947 
1948         /**
1949          * Convert a floating point number to a string integer + fraction.
1950          * Returns either a string of the form '3 1/3' (in case of useTeX=false)
1951          * or '3 \\frac{1}{3}' (in case of useTeX=true).
1952          *
1953          * @param {Number} x
1954          * @param {Boolean} [useTeX=false]
1955          * @param {Number} [order=0.001]
1956          * @returns {String}
1957          * @see JXG.Math.decToFraction
1958          */
1959         toFraction: function (x, useTeX, order) {
1960             var arr = Mat.decToFraction(x, order),
1961                 str = '';
1962 
1963             if (arr[1] === 0 && arr[2] === 0) {
1964                 // 0
1965                 str += '0';
1966             } else {
1967                 // Sign
1968                 if (arr[0] < 0) {
1969                     str += '-';
1970                 }
1971                 if (arr[2] === 0) {
1972                     // Integer
1973                     str += arr[1];
1974                 } else if (!(arr[2] === 1 && arr[3] === 1)) {
1975                     // Proper fraction
1976                     if (arr[1] !== 0) {
1977                         // Absolute value larger than 1
1978                         str += arr[1] + ' ';
1979                     }
1980                     // Add fractional part
1981                     if (useTeX === true) {
1982                         str += '\\frac{' + arr[2] + '}{' + arr[3] + '}';
1983                     } else {
1984                         str += arr[2] + '/' + arr[3];
1985                     }
1986                 }
1987             }
1988             return str;
1989         },
1990 
1991         /**
1992          * Concat array src to array dest.
1993          * Uses push instead of JavaScript concat, which is much
1994          * faster.
1995          * The array dest is changed in place.
1996          * <p><b>Attention:</b> if "dest" is an anonymous array, the correct result is returned from the function.
1997          *
1998          * @param {Array} dest
1999          * @param {Array} src
2000          * @returns Array
2001          */
2002         concat: function(dest, src) {
2003             var i,
2004                 le = src.length;
2005             for (i = 0; i < le; i++) {
2006                 dest.push(src[i]);
2007             }
2008             return dest;
2009         },
2010 
2011         /**
2012          * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
2013          * @param {String} str
2014          * @param {Boolean} caja
2015          * @returns {String} Sanitized string
2016          */
2017         sanitizeHTML: function (str, caja) {
2018             if (typeof html_sanitize === "function" && caja) {
2019                 return html_sanitize(
2020                     str,
2021                     function () {
2022                         return undefined;
2023                     },
2024                     function (id) {
2025                         return id;
2026                     }
2027                 );
2028             }
2029 
2030             if (str && typeof str === 'string') {
2031                 str = str.replace(/</g, "<").replace(/>/g, ">");
2032             }
2033 
2034             return str;
2035         },
2036 
2037         /**
2038          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
2039          * @param {*} s
2040          * @returns {*} s.Value() if s is an element of type slider, s otherwise
2041          */
2042         evalSlider: function (s) {
2043             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') {
2044                 return s.Value();
2045             }
2046 
2047             return s;
2048         },
2049 
2050         /**
2051          * Convert a string containing a MAXIMA /STACK expression into a JSXGraph / JessieCode string
2052          * or an array of JSXGraph / JessieCode strings.
2053          * <p>
2054          * This function is meanwhile superseded by stack_jxg.stack2jsxgraph.
2055          *
2056          * @deprecated
2057          *
2058          * @example
2059          * console.log( JXG.stack2jsxgraph("%e**x") );
2060          * // Output:
2061          * //    "EULER**x"
2062          *
2063          * @example
2064          * console.log( JXG.stack2jsxgraph("[%pi*(x**2 - 1), %phi*(x - 1), %gamma*(x+1)]") );
2065          * // Output:
2066          * //    [ "PI*(x**2 - 1)", "1.618033988749895*(x - 1)", "0.5772156649015329*(x+1)" ]
2067          *
2068          * @param {String} str
2069          * @returns String
2070          */
2071         stack2jsxgraph: function(str) {
2072             var t;
2073 
2074             t = str.
2075                 replace(/%pi/g, 'PI').
2076                 replace(/%e/g, 'EULER').
2077                 replace(/%phi/g, '1.618033988749895').
2078                 replace(/%gamma/g, '0.5772156649015329').
2079                 trim();
2080 
2081             // String containing array -> array containing strings
2082             if (t[0] === '[' && t[t.length - 1] === ']') {
2083                 t = t.slice(1, -1).split(/\s*,\s*/);
2084             }
2085 
2086             return t;
2087         }
2088     }
2089 );
2090 
2091 export default JXG;
2092