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