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