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          * Converts a JavaScript object into a JSON string.
1495          * @param {Object} obj A JavaScript object, functions will be ignored.
1496          * @param {Boolean} [noquote=false] No quotes around the name of a property.
1497          * @returns {String} The given object stored in a JSON string.
1498          * @deprecated
1499          */
1500         toJSON: function (obj, noquote) {
1501             var list, prop, i, s, val;
1502 
1503             noquote = JXG.def(noquote, false);
1504 
1505             // check for native JSON support:
1506             if (JSON !== undefined && JSON.stringify && !noquote) {
1507                 try {
1508                     s = JSON.stringify(obj);
1509                     return s;
1510                 } catch (e) {
1511                     // if something goes wrong, e.g. if obj contains functions we won't return
1512                     // and use our own implementation as a fallback
1513                 }
1514             }
1515 
1516             switch (typeof obj) {
1517                 case "object":
1518                     if (obj) {
1519                         list = [];
1520 
1521                         if (this.isArray(obj)) {
1522                             for (i = 0; i < obj.length; i++) {
1523                                 list.push(JXG.toJSON(obj[i], noquote));
1524                             }
1525 
1526                             return "[" + list.join(",") + "]";
1527                         }
1528 
1529                         for (prop in obj) {
1530                             if (obj.hasOwnProperty(prop)) {
1531                                 try {
1532                                     val = JXG.toJSON(obj[prop], noquote);
1533                                 } catch (e2) {
1534                                     val = "";
1535                                 }
1536 
1537                                 if (noquote) {
1538                                     list.push(prop + ":" + val);
1539                                 } else {
1540                                     list.push('"' + prop + '":' + val);
1541                                 }
1542                             }
1543                         }
1544 
1545                         return "{" + list.join(",") + "} ";
1546                     }
1547                     return "null";
1548                 case "string":
1549                     return "'" + obj.replace(/(["'])/g, "\\$1") + "'";
1550                 case "number":
1551                 case "boolean":
1552                     return obj.toString();
1553             }
1554 
1555             return "0";
1556         },
1557 
1558         /**
1559          * Resets visPropOld.
1560          * @param {JXG.GeometryElement} el
1561          * @returns {GeometryElement}
1562          */
1563         clearVisPropOld: function (el) {
1564             el.visPropOld = {
1565                 cssclass: "",
1566                 cssdefaultstyle: "",
1567                 cssstyle: "",
1568                 fillcolor: "",
1569                 fillopacity: "",
1570                 firstarrow: false,
1571                 fontsize: -1,
1572                 lastarrow: false,
1573                 left: -100000,
1574                 linecap: "",
1575                 shadow: false,
1576                 strokecolor: "",
1577                 strokeopacity: "",
1578                 strokewidth: "",
1579                 tabindex: -100000,
1580                 transitionduration: 0,
1581                 top: -100000,
1582                 visible: null
1583             };
1584 
1585             return el;
1586         },
1587 
1588         /**
1589          * Checks if an object contains a key, whose value equals to val.
1590          * @param {Object} obj
1591          * @param val
1592          * @returns {Boolean}
1593          */
1594         isInObject: function (obj, val) {
1595             var el;
1596 
1597             for (el in obj) {
1598                 if (obj.hasOwnProperty(el)) {
1599                     if (obj[el] === val) {
1600                         return true;
1601                     }
1602                 }
1603             }
1604 
1605             return false;
1606         },
1607 
1608         /**
1609          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
1610          * @param {String} str
1611          * @returns {String}
1612          */
1613         escapeHTML: function (str) {
1614             return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
1615         },
1616 
1617         /**
1618          * Eliminates all substrings enclosed by < and > and replaces all occurences of
1619          * &amp; by &, &gt; by >, and &lt; by <.
1620          * @param {String} str
1621          * @returns {String}
1622          */
1623         unescapeHTML: function (str) {
1624             // This regex is NOT insecure. We are replacing everything found with ''
1625             /*jslint regexp:true*/
1626             return str
1627                 .replace(/<\/?[^>]+>/gi, "")
1628                 .replace(/&/g, "&")
1629                 .replace(/</g, "<")
1630                 .replace(/>/g, ">");
1631         },
1632 
1633         /**
1634          * Makes a string lower case except for the first character which will be upper case.
1635          * @param {String} str Arbitrary string
1636          * @returns {String} The capitalized string.
1637          */
1638         capitalize: function (str) {
1639             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
1640         },
1641 
1642         /**
1643          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
1644          * @param {String} str
1645          * @returns {String}
1646          */
1647         trimNumber: function (str) {
1648             str = str.replace(/^0+/, "");
1649             str = str.replace(/0+$/, "");
1650 
1651             if (str[str.length - 1] === "." || str[str.length - 1] === ",") {
1652                 str = str.slice(0, -1);
1653             }
1654 
1655             if (str[0] === "." || str[0] === ",") {
1656                 str = "0" + str;
1657             }
1658 
1659             return str;
1660         },
1661 
1662         /**
1663          * Filter an array of elements.
1664          * @param {Array} list
1665          * @param {Object|function} filter
1666          * @returns {Array}
1667          */
1668         filterElements: function (list, filter) {
1669             var i,
1670                 f,
1671                 item,
1672                 flower,
1673                 value,
1674                 visPropValue,
1675                 pass,
1676                 l = list.length,
1677                 result = [];
1678 
1679             if (typeof filter !== "function" && typeof filter !== "object") {
1680                 return result;
1681             }
1682 
1683             for (i = 0; i < l; i++) {
1684                 pass = true;
1685                 item = list[i];
1686 
1687                 if (typeof filter === "object") {
1688                     for (f in filter) {
1689                         if (filter.hasOwnProperty(f)) {
1690                             flower = f.toLowerCase();
1691 
1692                             if (typeof item[f] === "function") {
1693                                 value = item[f]();
1694                             } else {
1695                                 value = item[f];
1696                             }
1697 
1698                             if (item.visProp && typeof item.visProp[flower] === "function") {
1699                                 visPropValue = item.visProp[flower]();
1700                             } else {
1701                                 visPropValue = item.visProp && item.visProp[flower];
1702                             }
1703 
1704                             if (typeof filter[f] === "function") {
1705                                 pass = filter[f](value) || filter[f](visPropValue);
1706                             } else {
1707                                 pass = value === filter[f] || visPropValue === filter[f];
1708                             }
1709 
1710                             if (!pass) {
1711                                 break;
1712                             }
1713                         }
1714                     }
1715                 } else if (typeof filter === "function") {
1716                     pass = filter(item);
1717                 }
1718 
1719                 if (pass) {
1720                     result.push(item);
1721                 }
1722             }
1723 
1724             return result;
1725         },
1726 
1727         /**
1728          * Remove all leading and trailing whitespaces from a given string.
1729          * @param {String} str
1730          * @returns {String}
1731          */
1732         trim: function (str) {
1733             // str = str.replace(/^\s+/, '');
1734             // str = str.replace(/\s+$/, '');
1735             //
1736             // return str;
1737             return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
1738         },
1739 
1740         /**
1741          * Convert a floating point number to a string integer + fraction.
1742          * Returns either a string of the form '3 1/3' (in case of useTeX=false)
1743          * or '3 \\frac{1}{3}' (in case of useTeX=true).
1744          *
1745          * @param {Number} x
1746          * @param {Boolean} [useTeX=false]
1747          * @param {Number} [order=0.001]
1748          * @returns {String}
1749          * @see JXG.Math#decToFraction
1750          */
1751         toFraction: function (x, useTeX, order) {
1752             var arr = Mat.decToFraction(x, order),
1753                 str = '';
1754 
1755             if (arr[1] === 0 && arr[2] === 0) {
1756                 // 0
1757                 str += '0';
1758             } else {
1759                 // Sign
1760                 if (arr[0] < 0) {
1761                     str += '-';
1762                 }
1763                 if (arr[2] === 0) {
1764                     // Integer
1765                     str += arr[1];
1766                 } else if (!(arr[2] === 1 && arr[3] === 1)) {
1767                     // Proper fraction
1768                     if (arr[1] !== 0) {
1769                         // Absolute value larger than 1
1770                         str += arr[1] + ' ';
1771                     }
1772                     // Add fractional part
1773                     if (useTeX === true) {
1774                         str += '\\frac{' + arr[2] + '}{' + arr[3] + '}';
1775                     } else {
1776                         str += arr[2] + '/' + arr[3];
1777                     }
1778                 }
1779             }
1780             return str;
1781         },
1782 
1783         /**
1784          * Concat array src to array dest.
1785          * Uses push instead of JavaScript concat, which is much
1786          * faster.
1787          * The array dest is changed in place.
1788          * <p><b>Attention:</b> if "dest" is an anonymous array, the correct result is returned from the function.
1789          *
1790          * @param {Array} dest
1791          * @param {Array} src
1792          * @returns Array
1793          */
1794         concat: function(dest, src) {
1795             var i,
1796                 le = src.length;
1797             for (i = 0; i < le; i++) {
1798                 dest.push(src[i]);
1799             }
1800             return dest;
1801         },
1802 
1803         /**
1804          * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
1805          * @param {String} str
1806          * @param {Boolean} caja
1807          * @returns {String} Sanitized string
1808          */
1809         sanitizeHTML: function (str, caja) {
1810             if (typeof html_sanitize === "function" && caja) {
1811                 return html_sanitize(
1812                     str,
1813                     function () {
1814                         return undefined;
1815                     },
1816                     function (id) {
1817                         return id;
1818                     }
1819                 );
1820             }
1821 
1822             if (str && typeof str === "string") {
1823                 str = str.replace(/</g, "<").replace(/>/g, ">");
1824             }
1825 
1826             return str;
1827         },
1828 
1829         /**
1830          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
1831          * @param {*} s
1832          * @returns {*} s.Value() if s is an element of type slider, s otherwise
1833          */
1834         evalSlider: function (s) {
1835             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === "function") {
1836                 return s.Value();
1837             }
1838 
1839             return s;
1840         },
1841 
1842         /**
1843          * Convert a string containing a MAXIMA /STACK expression into a JSXGraph / JessieCode string
1844          * or an array of JSXGraph / JessieCode strings.
1845          * <p>
1846          * This function is meanwhile superseded by stack_jxg.stack2jsxgraph.
1847          *
1848          * @example
1849          * console.log( JXG.stack2jsxgraph("%e**x") );
1850          * // Output:
1851          * //    "EULER**x"
1852          *
1853          * @example
1854          * console.log( JXG.stack2jsxgraph("[%pi*(x**2 - 1), %phi*(x - 1), %gamma*(x+1)]") );
1855          * // Output:
1856          * //    [ "PI*(x**2 - 1)", "1.618033988749895*(x - 1)", "0.5772156649015329*(x+1)" ]
1857          *
1858          * @param {String} str
1859          * @returns String
1860          */
1861         stack2jsxgraph: function(str) {
1862             var t;
1863 
1864             t = str.
1865                 replace(/%pi/g, 'PI').
1866                 replace(/%e/g, 'EULER').
1867                 replace(/%phi/g, '1.618033988749895').
1868                 replace(/%gamma/g, '0.5772156649015329').
1869                 trim();
1870 
1871             // String containing array -> array containing strings
1872             if (t[0] === '[' && t[t.length - 1] === ']') {
1873                 t = t.slice(1, -1).split(/\s*,\s*/);
1874             }
1875 
1876             return t;
1877         }
1878     }
1879 );
1880 
1881 export default JXG;
1882