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