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