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 // Key already exists, but not in lower case 1291 if (e2 !== e && attr.hasOwnProperty(e)) { 1292 if (attr.hasOwnProperty(e2)) { 1293 // Lower case key already exists - this should not happen 1294 // We have to unify the two key-value pairs 1295 // It is not clear which has precedence. 1296 this.mergeAttr(attr[e2], attr[e], toLower); 1297 } else { 1298 attr[e2] = attr[e]; 1299 } 1300 delete attr[e]; 1301 } 1302 1303 o = special[e]; 1304 if (this.isObject(o) && o !== null && 1305 // Do not recurse into a document object or a JSXGraph object 1306 !this.isDocumentOrFragment(o) && !this.exists(o.board) && 1307 // Do not recurse if a string is provided as "new String(...)" 1308 typeof o.valueOf() !== 'string') { 1309 if (attr[e2] === undefined || attr[e2] === null || !this.isObject(attr[e2])) { 1310 // The last test handles the case: 1311 // attr.draft = false; 1312 // special.draft = { strokewidth: 4} 1313 attr[e2] = {}; 1314 } 1315 this.mergeAttr(attr[e2], o, toLower); 1316 } else if(!ignoreUndefinedSpecials || this.exists(o)) { 1317 // Flat copy 1318 // This is also used in the cases 1319 // attr.shadow = { enabled: true ...} 1320 // special.shadow = false; 1321 // and 1322 // special.anchor is a JSXGraph element 1323 attr[e2] = o; 1324 } 1325 } 1326 } 1327 }, 1328 1329 /** 1330 * Convert a n object to a new object containing only 1331 * lower case properties. 1332 * 1333 * @param {Object} obj 1334 * @returns Object 1335 * @example 1336 * var attr = JXG.keysToLowerCase({radiusPoint: {visible: false}}); 1337 * 1338 * // return {radiuspoint: {visible: false}} 1339 */ 1340 keysToLowerCase: function (obj) { 1341 var key, val, 1342 keys = Object.keys(obj), 1343 n = keys.length, 1344 newObj = {}; 1345 1346 if (typeof obj !== 'object') { 1347 return obj; 1348 } 1349 1350 while (n--) { 1351 key = keys[n]; 1352 if (obj.hasOwnProperty(key)) { 1353 // We recurse into an object only if it is 1354 // neither a DOM node nor an JSXGraph object 1355 val = obj[key]; 1356 if (typeof val === 'object' && 1357 val !== null && 1358 !this.isArray(val) && 1359 !this.exists(val.nodeType) && 1360 !this.exists(val.board)) { 1361 newObj[key.toLowerCase()] = this.keysToLowerCase(val); 1362 } else { 1363 newObj[key.toLowerCase()] = val; 1364 } 1365 } 1366 } 1367 return newObj; 1368 }, 1369 1370 /** 1371 * Generates an attributes object that is filled with default values from the Options object 1372 * and overwritten by the user specified attributes. 1373 * @param {Object} attributes user specified attributes 1374 * @param {Object} options defaults options 1375 * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'. Must be provided in lower case! 1376 * @returns {Object} The resulting attributes object 1377 */ 1378 copyAttributes: function (attributes, options, s) { 1379 var a, arg, i, len, o, isAvail, 1380 primitives = { 1381 circle: 1, 1382 curve: 1, 1383 foreignobject: 1, 1384 image: 1, 1385 line: 1, 1386 point: 1, 1387 polygon: 1, 1388 text: 1, 1389 ticks: 1, 1390 integral: 1 1391 }; 1392 1393 len = arguments.length; 1394 if (len < 3 || primitives[s]) { 1395 // Default options from Options.elements 1396 a = JXG.deepCopy(options.elements, null, true); 1397 } else { 1398 a = {}; 1399 } 1400 1401 // Only the layer of the main element is set. 1402 if (len < 4 && this.exists(s) && this.exists(options.layer[s])) { 1403 a.layer = options.layer[s]; 1404 } 1405 1406 // Default options from the specific element like 'line' in 1407 // copyAttribute(attributes, board.options, 'line') 1408 // but also like in 1409 // Type.copyAttributes(attributes, board.options, 'view3d', 'az', 'slider'); 1410 o = options; 1411 isAvail = true; 1412 for (i = 2; i < len; i++) { 1413 arg = arguments[i]; 1414 if (this.exists(o[arg])) { 1415 o = o[arg]; 1416 } else { 1417 isAvail = false; 1418 break; 1419 } 1420 } 1421 if (isAvail) { 1422 a = JXG.deepCopy(a, o, true); 1423 } 1424 1425 // Merge the specific options given in the parameter 'attributes' 1426 // into the default options. 1427 // Additionally, we step into a sub-element of attribute like line.point1 - 1428 // in case it is supplied as in 1429 // copyAttribute(attributes, board.options, 'line', 'point1') 1430 // In this case we would merge attributes.point1 into the global line.point1 attributes. 1431 o = (typeof attributes === 'object') ? this.keysToLowerCase(attributes) : {}; 1432 isAvail = true; 1433 for (i = 3; i < len; i++) { 1434 arg = arguments[i].toLowerCase(); 1435 if (this.exists(o[arg])) { 1436 o = o[arg]; 1437 } else { 1438 isAvail = false; 1439 break; 1440 } 1441 } 1442 if (isAvail) { 1443 this.mergeAttr(a, o, true); 1444 } 1445 1446 if (arguments[2] === "board") { 1447 // For board attributes we are done now. 1448 return a; 1449 } 1450 1451 // Special treatment of labels 1452 o = options; 1453 isAvail = true; 1454 for (i = 2; i < len; i++) { 1455 arg = arguments[i]; 1456 if (this.exists(o[arg])) { 1457 o = o[arg]; 1458 } else { 1459 isAvail = false; 1460 break; 1461 } 1462 } 1463 if (isAvail && this.exists(o.label)) { 1464 a.label = JXG.deepCopy(o.label, a.label, true); 1465 } 1466 a.label = JXG.deepCopy(options.label, a.label, true); 1467 1468 return a; 1469 }, 1470 1471 /** 1472 * Copy all prototype methods from object "superObject" to object 1473 * "subObject". The constructor of superObject will be available 1474 * in subObject as subObject.constructor[constructorName]. 1475 * @param {Object} subObj A JavaScript object which receives new methods. 1476 * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject 1477 * @returns {String} constructorName Under this name the constructor of superObj will be available 1478 * in subObject. 1479 * @private 1480 */ 1481 copyPrototypeMethods: function (subObject, superObject, constructorName) { 1482 var key; 1483 1484 subObject.prototype[constructorName] = superObject.prototype.constructor; 1485 for (key in superObject.prototype) { 1486 if (superObject.prototype.hasOwnProperty(key)) { 1487 subObject.prototype[key] = superObject.prototype[key]; 1488 } 1489 } 1490 }, 1491 1492 /** 1493 * Create a stripped down version of a JSXGraph element for cloning to the background. 1494 * Used in {JXG.GeometryElement#cloneToBackground} for creating traces. 1495 * 1496 * @param {JXG.GeometryElement} el Element to be cloned 1497 * @returns Object Cloned element 1498 * @private 1499 */ 1500 getCloneObject: function(el) { 1501 var obj, key, 1502 copy = {}; 1503 1504 copy.id = el.id + "T" + el.numTraces; 1505 el.numTraces += 1; 1506 1507 copy.coords = el.coords; 1508 obj = this.deepCopy(el.visProp, el.visProp.traceattributes, true); 1509 copy.visProp = {}; 1510 for (key in obj) { 1511 if (obj.hasOwnProperty(key)) { 1512 if ( 1513 key.indexOf('aria') !== 0 && 1514 key.indexOf('highlight') !== 0 && 1515 key.indexOf('attractor') !== 0 && 1516 key !== 'label' && 1517 key !== 'needsregularupdate' && 1518 key !== 'infoboxdigits' 1519 ) { 1520 copy.visProp[key] = el.eval(obj[key]); 1521 } 1522 } 1523 } 1524 copy.evalVisProp = function(val) { 1525 return copy.visProp[val]; 1526 }; 1527 copy.eval = function(val) { 1528 return val; 1529 }; 1530 1531 copy.visProp.layer = el.board.options.layer.trace; 1532 copy.visProp.tabindex = null; 1533 copy.visProp.highlight = false; 1534 copy.board = el.board; 1535 copy.elementClass = el.elementClass; 1536 1537 this.clearVisPropOld(copy); 1538 copy.visPropCalc = { 1539 visible: el.evalVisProp('visible') 1540 }; 1541 1542 return copy; 1543 }, 1544 1545 /** 1546 * Converts a JavaScript object into a JSON string. 1547 * @param {Object} obj A JavaScript object, functions will be ignored. 1548 * @param {Boolean} [noquote=false] No quotes around the name of a property. 1549 * @returns {String} The given object stored in a JSON string. 1550 * @deprecated 1551 */ 1552 toJSON: function (obj, noquote) { 1553 var list, prop, i, s, val; 1554 1555 noquote = JXG.def(noquote, false); 1556 1557 // check for native JSON support: 1558 if (JSON !== undefined && JSON.stringify && !noquote) { 1559 try { 1560 s = JSON.stringify(obj); 1561 return s; 1562 } catch (e) { 1563 // if something goes wrong, e.g. if obj contains functions we won't return 1564 // and use our own implementation as a fallback 1565 } 1566 } 1567 1568 switch (typeof obj) { 1569 case "object": 1570 if (obj) { 1571 list = []; 1572 1573 if (this.isArray(obj)) { 1574 for (i = 0; i < obj.length; i++) { 1575 list.push(JXG.toJSON(obj[i], noquote)); 1576 } 1577 1578 return "[" + list.join(",") + "]"; 1579 } 1580 1581 for (prop in obj) { 1582 if (obj.hasOwnProperty(prop)) { 1583 try { 1584 val = JXG.toJSON(obj[prop], noquote); 1585 } catch (e2) { 1586 val = ""; 1587 } 1588 1589 if (noquote) { 1590 list.push(prop + ":" + val); 1591 } else { 1592 list.push('"' + prop + '":' + val); 1593 } 1594 } 1595 } 1596 1597 return "{" + list.join(",") + "} "; 1598 } 1599 return "null"; 1600 case "string": 1601 return "'" + obj.replace(/(["'])/g, "\\$1") + "'"; 1602 case "number": 1603 case "boolean": 1604 return obj.toString(); 1605 } 1606 1607 return "0"; 1608 }, 1609 1610 /** 1611 * Resets visPropOld. 1612 * @param {JXG.GeometryElement} el 1613 * @returns {GeometryElement} 1614 */ 1615 clearVisPropOld: function (el) { 1616 el.visPropOld = { 1617 cssclass: "", 1618 cssdefaultstyle: "", 1619 cssstyle: "", 1620 fillcolor: "", 1621 fillopacity: "", 1622 firstarrow: false, 1623 fontsize: -1, 1624 lastarrow: false, 1625 left: -100000, 1626 linecap: "", 1627 shadow: false, 1628 strokecolor: "", 1629 strokeopacity: "", 1630 strokewidth: "", 1631 tabindex: -100000, 1632 transitionduration: 0, 1633 top: -100000, 1634 visible: null 1635 }; 1636 1637 return el; 1638 }, 1639 1640 /** 1641 * Checks if an object contains a key, whose value equals to val. 1642 * @param {Object} obj 1643 * @param val 1644 * @returns {Boolean} 1645 */ 1646 isInObject: function (obj, val) { 1647 var el; 1648 1649 for (el in obj) { 1650 if (obj.hasOwnProperty(el)) { 1651 if (obj[el] === val) { 1652 return true; 1653 } 1654 } 1655 } 1656 1657 return false; 1658 }, 1659 1660 /** 1661 * Replaces all occurences of & by &, > by >, and < by <. 1662 * @param {String} str 1663 * @returns {String} 1664 */ 1665 escapeHTML: function (str) { 1666 return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); 1667 }, 1668 1669 /** 1670 * Eliminates all substrings enclosed by < and > and replaces all occurences of 1671 * & by &, > by >, and < by <. 1672 * @param {String} str 1673 * @returns {String} 1674 */ 1675 unescapeHTML: function (str) { 1676 // This regex is NOT insecure. We are replacing everything found with '' 1677 /*jslint regexp:true*/ 1678 return str 1679 .replace(/<\/?[^>]+>/gi, "") 1680 .replace(/&/g, "&") 1681 .replace(/</g, "<") 1682 .replace(/>/g, ">"); 1683 }, 1684 1685 /** 1686 * Makes a string lower case except for the first character which will be upper case. 1687 * @param {String} str Arbitrary string 1688 * @returns {String} The capitalized string. 1689 */ 1690 capitalize: function (str) { 1691 return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); 1692 }, 1693 1694 /** 1695 * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes. 1696 * @param {String} str 1697 * @returns {String} 1698 */ 1699 trimNumber: function (str) { 1700 str = str.replace(/^0+/, ""); 1701 str = str.replace(/0+$/, ""); 1702 1703 if (str[str.length - 1] === "." || str[str.length - 1] === ",") { 1704 str = str.slice(0, -1); 1705 } 1706 1707 if (str[0] === "." || str[0] === ",") { 1708 str = "0" + str; 1709 } 1710 1711 return str; 1712 }, 1713 1714 /** 1715 * Filter an array of elements. 1716 * @param {Array} list 1717 * @param {Object|function} filter 1718 * @returns {Array} 1719 */ 1720 filterElements: function (list, filter) { 1721 var i, 1722 f, 1723 item, 1724 flower, 1725 value, 1726 visPropValue, 1727 pass, 1728 l = list.length, 1729 result = []; 1730 1731 if (typeof filter !== "function" && typeof filter !== "object") { 1732 return result; 1733 } 1734 1735 for (i = 0; i < l; i++) { 1736 pass = true; 1737 item = list[i]; 1738 1739 if (typeof filter === "object") { 1740 for (f in filter) { 1741 if (filter.hasOwnProperty(f)) { 1742 flower = f.toLowerCase(); 1743 1744 if (typeof item[f] === "function") { 1745 value = item[f](); 1746 } else { 1747 value = item[f]; 1748 } 1749 1750 if (item.visProp && typeof item.visProp[flower] === "function") { 1751 visPropValue = item.visProp[flower](); 1752 } else { 1753 visPropValue = item.visProp && item.visProp[flower]; 1754 } 1755 1756 if (typeof filter[f] === "function") { 1757 pass = filter[f](value) || filter[f](visPropValue); 1758 } else { 1759 pass = value === filter[f] || visPropValue === filter[f]; 1760 } 1761 1762 if (!pass) { 1763 break; 1764 } 1765 } 1766 } 1767 } else if (typeof filter === "function") { 1768 pass = filter(item); 1769 } 1770 1771 if (pass) { 1772 result.push(item); 1773 } 1774 } 1775 1776 return result; 1777 }, 1778 1779 /** 1780 * Remove all leading and trailing whitespaces from a given string. 1781 * @param {String} str 1782 * @returns {String} 1783 */ 1784 trim: function (str) { 1785 // str = str.replace(/^\s+/, ''); 1786 // str = str.replace(/\s+$/, ''); 1787 // 1788 // return str; 1789 return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ""); 1790 }, 1791 1792 /** 1793 * Convert a floating point number to a string integer + fraction. 1794 * Returns either a string of the form '3 1/3' (in case of useTeX=false) 1795 * or '3 \\frac{1}{3}' (in case of useTeX=true). 1796 * 1797 * @param {Number} x 1798 * @param {Boolean} [useTeX=false] 1799 * @param {Number} [order=0.001] 1800 * @returns {String} 1801 * @see JXG.Math.decToFraction 1802 */ 1803 toFraction: function (x, useTeX, order) { 1804 var arr = Mat.decToFraction(x, order), 1805 str = ''; 1806 1807 if (arr[1] === 0 && arr[2] === 0) { 1808 // 0 1809 str += '0'; 1810 } else { 1811 // Sign 1812 if (arr[0] < 0) { 1813 str += '-'; 1814 } 1815 if (arr[2] === 0) { 1816 // Integer 1817 str += arr[1]; 1818 } else if (!(arr[2] === 1 && arr[3] === 1)) { 1819 // Proper fraction 1820 if (arr[1] !== 0) { 1821 // Absolute value larger than 1 1822 str += arr[1] + ' '; 1823 } 1824 // Add fractional part 1825 if (useTeX === true) { 1826 str += '\\frac{' + arr[2] + '}{' + arr[3] + '}'; 1827 } else { 1828 str += arr[2] + '/' + arr[3]; 1829 } 1830 } 1831 } 1832 return str; 1833 }, 1834 1835 /** 1836 * Concat array src to array dest. 1837 * Uses push instead of JavaScript concat, which is much 1838 * faster. 1839 * The array dest is changed in place. 1840 * <p><b>Attention:</b> if "dest" is an anonymous array, the correct result is returned from the function. 1841 * 1842 * @param {Array} dest 1843 * @param {Array} src 1844 * @returns Array 1845 */ 1846 concat: function(dest, src) { 1847 var i, 1848 le = src.length; 1849 for (i = 0; i < le; i++) { 1850 dest.push(src[i]); 1851 } 1852 return dest; 1853 }, 1854 1855 /** 1856 * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available. 1857 * @param {String} str 1858 * @param {Boolean} caja 1859 * @returns {String} Sanitized string 1860 */ 1861 sanitizeHTML: function (str, caja) { 1862 if (typeof html_sanitize === "function" && caja) { 1863 return html_sanitize( 1864 str, 1865 function () { 1866 return undefined; 1867 }, 1868 function (id) { 1869 return id; 1870 } 1871 ); 1872 } 1873 1874 if (str && typeof str === "string") { 1875 str = str.replace(/</g, "<").replace(/>/g, ">"); 1876 } 1877 1878 return str; 1879 }, 1880 1881 /** 1882 * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value. 1883 * @param {*} s 1884 * @returns {*} s.Value() if s is an element of type slider, s otherwise 1885 */ 1886 evalSlider: function (s) { 1887 if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === "function") { 1888 return s.Value(); 1889 } 1890 1891 return s; 1892 }, 1893 1894 /** 1895 * Convert a string containing a MAXIMA /STACK expression into a JSXGraph / JessieCode string 1896 * or an array of JSXGraph / JessieCode strings. 1897 * <p> 1898 * This function is meanwhile superseded by stack_jxg.stack2jsxgraph. 1899 * 1900 * @deprecated 1901 * 1902 * @example 1903 * console.log( JXG.stack2jsxgraph("%e**x") ); 1904 * // Output: 1905 * // "EULER**x" 1906 * 1907 * @example 1908 * console.log( JXG.stack2jsxgraph("[%pi*(x**2 - 1), %phi*(x - 1), %gamma*(x+1)]") ); 1909 * // Output: 1910 * // [ "PI*(x**2 - 1)", "1.618033988749895*(x - 1)", "0.5772156649015329*(x+1)" ] 1911 * 1912 * @param {String} str 1913 * @returns String 1914 */ 1915 stack2jsxgraph: function(str) { 1916 var t; 1917 1918 t = str. 1919 replace(/%pi/g, 'PI'). 1920 replace(/%e/g, 'EULER'). 1921 replace(/%phi/g, '1.618033988749895'). 1922 replace(/%gamma/g, '0.5772156649015329'). 1923 trim(); 1924 1925 // String containing array -> array containing strings 1926 if (t[0] === '[' && t[t.length - 1] === ']') { 1927 t = t.slice(1, -1).split(/\s*,\s*/); 1928 } 1929 1930 return t; 1931 } 1932 } 1933 ); 1934 1935 export default JXG; 1936