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