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