1 /* 2 Copyright 2008-2025 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] = this.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) || this.isArray(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 > 0 && parents[i].every((x)=>this.isArray(x) && this.isNumber(x[0]))) { 584 // Testing for array-of-arrays-of-numbers, like [[1,2,3],[2,3,4]] 585 for (j = 0; j < parents[i].length; j++) { 586 points.push(view.create("point3d", parents[i][j], attr));; 587 points[points.length - 1]._is_new = true; 588 } 589 } else if (this.isArray(parents[i]) && parents[i].every((x)=> this.isNumber(x) || this.isFunction(x))) { 590 // Single array [1,2,3] 591 points.push(view.create("point3d", parents[i], attr)); 592 points[points.length - 1]._is_new = true; 593 594 } else if (this.isPoint3D(parents[i])) { 595 points.push(parents[i]); 596 } else if (this.isFunction(parents[i])) { 597 val = parents[i](); 598 if (this.isArray(val) && val.length > 1) { 599 points.push(view.create("point3d", [parents[i]], attr)); 600 points[points.length - 1]._is_new = true; 601 } 602 } else { 603 points.push(view.select(parents[i])); 604 } 605 606 if (!this.isPoint3D(points[i])) { 607 return false; 608 } 609 } 610 611 return points; 612 }, 613 614 /** 615 * Generates a function which calls the function fn in the scope of owner. 616 * @param {Function} fn Function to call. 617 * @param {Object} owner Scope in which fn is executed. 618 * @returns {Function} A function with the same signature as fn. 619 */ 620 bind: function (fn, owner) { 621 return function () { 622 return fn.apply(owner, arguments); 623 }; 624 }, 625 626 /** 627 * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value 628 * is just returned. 629 * @param val Could be anything. Preferably a number or a function. 630 * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned. 631 */ 632 evaluate: function (val) { 633 if (this.isFunction(val)) { 634 return val(); 635 } 636 637 return val; 638 }, 639 640 /** 641 * Search an array for a given value. 642 * @param {Array} array 643 * @param value 644 * @param {String} [sub] Use this property if the elements of the array are objects. 645 * @returns {Number} The index of the first appearance of the given value, or 646 * <tt>-1</tt> if the value was not found. 647 */ 648 indexOf: function (array, value, sub) { 649 var i, 650 s = this.exists(sub); 651 652 if (Array.indexOf && !s) { 653 return array.indexOf(value); 654 } 655 656 for (i = 0; i < array.length; i++) { 657 if ((s && array[i][sub] === value) || (!s && array[i] === value)) { 658 return i; 659 } 660 } 661 662 return -1; 663 }, 664 665 /** 666 * Eliminates duplicate entries in an array consisting of numbers and strings. 667 * @param {Array} a An array of numbers and/or strings. 668 * @returns {Array} The array with duplicate entries eliminated. 669 */ 670 eliminateDuplicates: function (a) { 671 var i, 672 len = a.length, 673 result = [], 674 obj = {}; 675 676 for (i = 0; i < len; i++) { 677 obj[a[i]] = 0; 678 } 679 680 for (i in obj) { 681 if (obj.hasOwnProperty(i)) { 682 result.push(i); 683 } 684 } 685 686 return result; 687 }, 688 689 /** 690 * Swaps to array elements. 691 * @param {Array} arr 692 * @param {Number} i 693 * @param {Number} j 694 * @returns {Array} Reference to the given array. 695 */ 696 swap: function (arr, i, j) { 697 var tmp; 698 699 tmp = arr[i]; 700 arr[i] = arr[j]; 701 arr[j] = tmp; 702 703 return arr; 704 }, 705 706 /** 707 * Generates a copy of an array and removes the duplicate entries. The original 708 * Array will be altered. 709 * @param {Array} arr 710 * @returns {Array} 711 */ 712 uniqueArray: function (arr) { 713 var i, 714 j, 715 isArray, 716 ret = []; 717 718 if (arr.length === 0) { 719 return []; 720 } 721 722 for (i = 0; i < arr.length; i++) { 723 isArray = this.isArray(arr[i]); 724 725 if (!this.exists(arr[i])) { 726 arr[i] = ""; 727 continue; 728 } 729 for (j = i + 1; j < arr.length; j++) { 730 if (isArray && JXG.cmpArrays(arr[i], arr[j])) { 731 arr[i] = []; 732 } else if (!isArray && arr[i] === arr[j]) { 733 arr[i] = ""; 734 } 735 } 736 } 737 738 j = 0; 739 740 for (i = 0; i < arr.length; i++) { 741 isArray = this.isArray(arr[i]); 742 743 if (!isArray && arr[i] !== "") { 744 ret[j] = arr[i]; 745 j++; 746 } else if (isArray && arr[i].length !== 0) { 747 ret[j] = arr[i].slice(0); 748 j++; 749 } 750 } 751 752 arr = ret; 753 return ret; 754 }, 755 756 /** 757 * Checks if an array contains an element equal to <tt>val</tt> but does not check the type! 758 * @param {Array} arr 759 * @param val 760 * @returns {Boolean} 761 */ 762 isInArray: function (arr, val) { 763 return JXG.indexOf(arr, val) > -1; 764 }, 765 766 /** 767 * Converts an array of {@link JXG.Coords} objects into a coordinate matrix. 768 * @param {Array} coords 769 * @param {Boolean} split 770 * @returns {Array} 771 */ 772 coordsArrayToMatrix: function (coords, split) { 773 var i, 774 x = [], 775 m = []; 776 777 for (i = 0; i < coords.length; i++) { 778 if (split) { 779 x.push(coords[i].usrCoords[1]); 780 m.push(coords[i].usrCoords[2]); 781 } else { 782 m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]); 783 } 784 } 785 786 if (split) { 787 m = [x, m]; 788 } 789 790 return m; 791 }, 792 793 /** 794 * Compare two arrays. 795 * @param {Array} a1 796 * @param {Array} a2 797 * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value. 798 */ 799 cmpArrays: function (a1, a2) { 800 var i; 801 802 // trivial cases 803 if (a1 === a2) { 804 return true; 805 } 806 807 if (a1.length !== a2.length) { 808 return false; 809 } 810 811 for (i = 0; i < a1.length; i++) { 812 if (this.isArray(a1[i]) && this.isArray(a2[i])) { 813 if (!this.cmpArrays(a1[i], a2[i])) { 814 return false; 815 } 816 } else if (a1[i] !== a2[i]) { 817 return false; 818 } 819 } 820 821 return true; 822 }, 823 824 /** 825 * Removes an element from the given array 826 * @param {Array} ar 827 * @param el 828 * @returns {Array} 829 */ 830 removeElementFromArray: function (ar, el) { 831 var i; 832 833 for (i = 0; i < ar.length; i++) { 834 if (ar[i] === el) { 835 ar.splice(i, 1); 836 return ar; 837 } 838 } 839 840 return ar; 841 }, 842 843 /** 844 * Truncate a number <tt>n</tt> after <tt>p</tt> decimals. 845 * @param {Number} n 846 * @param {Number} p 847 * @returns {Number} 848 */ 849 trunc: function (n, p) { 850 p = JXG.def(p, 0); 851 852 return this.toFixed(n, p); 853 }, 854 855 /** 856 * Decimal adjustment of a number. 857 * From https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Math/round 858 * 859 * @param {String} type The type of adjustment. 860 * @param {Number} value The number. 861 * @param {Number} exp The exponent (the 10 logarithm of the adjustment base). 862 * @returns {Number} The adjusted value. 863 * 864 * @private 865 */ 866 _decimalAdjust: function (type, value, exp) { 867 // If the exp is undefined or zero... 868 if (exp === undefined || +exp === 0) { 869 return Math[type](value); 870 } 871 872 value = +value; 873 exp = +exp; 874 // If the value is not a number or the exp is not an integer... 875 if (isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) { 876 return NaN; 877 } 878 879 // Shift 880 value = value.toString().split("e"); 881 value = Math[type](+(value[0] + "e" + (value[1] ? +value[1] - exp : -exp))); 882 883 // Shift back 884 value = value.toString().split("e"); 885 return +(value[0] + "e" + (value[1] ? +value[1] + exp : exp)); 886 }, 887 888 /** 889 * Round a number to given number of decimal digits. 890 * 891 * Example: JXG._toFixed(3.14159, -2) gives 3.14 892 * @param {Number} value Number to be rounded 893 * @param {Number} exp Number of decimal digits given as negative exponent 894 * @return {Number} Rounded number. 895 * 896 * @private 897 */ 898 _round10: function (value, exp) { 899 return this._decimalAdjust("round", value, exp); 900 }, 901 902 /** 903 * "Floor" a number to given number of decimal digits. 904 * 905 * Example: JXG._toFixed(3.14159, -2) gives 3.14 906 * @param {Number} value Number to be floored 907 * @param {Number} exp Number of decimal digits given as negative exponent 908 * @return {Number} "Floored" number. 909 * 910 * @private 911 */ 912 _floor10: function (value, exp) { 913 return this._decimalAdjust("floor", value, exp); 914 }, 915 916 /** 917 * "Ceil" a number to given number of decimal digits. 918 * 919 * Example: JXG._toFixed(3.14159, -2) gives 3.15 920 * @param {Number} value Number to be ceiled 921 * @param {Number} exp Number of decimal digits given as negative exponent 922 * @return {Number} "Ceiled" number. 923 * 924 * @private 925 */ 926 _ceil10: function (value, exp) { 927 return this._decimalAdjust("ceil", value, exp); 928 }, 929 930 /** 931 * Replacement of the default toFixed() method. 932 * It does a correct rounding (independent of the browser) and 933 * returns "0.00" for toFixed(-0.000001, 2) instead of "-0.00" which 934 * is returned by JavaScript's toFixed() 935 * 936 * @memberOf JXG 937 * @param {Number} num Number tp be rounded 938 * @param {Number} digits Decimal digits 939 * @return {String} Rounded number is returned as string 940 */ 941 toFixed: function (num, digits) { 942 return this._round10(num, -digits).toFixed(digits); 943 }, 944 945 /** 946 * Truncate a number <tt>val</tt> automatically. 947 * @memberOf JXG 948 * @param val 949 * @returns {Number} 950 */ 951 autoDigits: function (val) { 952 var x = Math.abs(val), 953 str; 954 955 if (x >= 0.1) { 956 str = this.toFixed(val, 2); 957 } else if (x >= 0.01) { 958 str = this.toFixed(val, 4); 959 } else if (x >= 0.0001) { 960 str = this.toFixed(val, 6); 961 } else { 962 str = val; 963 } 964 return str; 965 }, 966 967 /** 968 * Convert value v. If v has the form 969 * <ul> 970 * <li> 'x%': return floating point number x * percentOfWhat * 0.01 971 * <li> 'xfr': return floating point number x * percentOfWhat 972 * <li> 'xpx': return x * convertPx or convertPx(x) or x 973 * <li> x or 'x': return floating point number x 974 * </ul> 975 * @param {String|Number} v 976 * @param {Number} percentOfWhat 977 * @param {Function|Number|*} convertPx 978 * @returns {String|Number} 979 */ 980 parseNumber: function(v, percentOfWhat, convertPx) { 981 var str; 982 983 if (this.isString(v) && v.indexOf('%') > -1) { 984 str = v.replace(/\s+%\s+/, ''); 985 return parseFloat(str) * percentOfWhat * 0.01; 986 } 987 if (this.isString(v) && v.indexOf('fr') > -1) { 988 str = v.replace(/\s+fr\s+/, ''); 989 return parseFloat(str) * percentOfWhat; 990 } 991 if (this.isString(v) && v.indexOf('px') > -1) { 992 str = v.replace(/\s+px\s+/, ''); 993 str = parseFloat(str); 994 if(this.isFunction(convertPx)) { 995 return convertPx(str); 996 } else if(this.isNumber(convertPx)) { 997 return str * convertPx; 998 } else { 999 return str; 1000 } 1001 } 1002 // Number or String containing no unit 1003 return parseFloat(v); 1004 }, 1005 1006 /** 1007 * Parse a string for label positioning of the form 'left pos' or 'pos right' 1008 * and return e.g. 1009 * <tt>{ side: 'left', pos: 'pos' }</tt>. 1010 * @param {String} str 1011 * @returns {Obj} <tt>{ side, pos }</tt> 1012 */ 1013 parsePosition: function(str) { 1014 var a, i, 1015 side = '', 1016 pos = ''; 1017 1018 str = str.trim(); 1019 if (str !== '') { 1020 a = str.split(/[ ,]+/); 1021 for (i = 0; i < a.length; i++) { 1022 if (a[i] === 'left' || a[i] === 'right') { 1023 side = a[i]; 1024 } else { 1025 pos = a[i]; 1026 } 1027 } 1028 } 1029 1030 return { 1031 side: side, 1032 pos: pos 1033 }; 1034 }, 1035 1036 /** 1037 * Extracts the keys of a given object. 1038 * @param object The object the keys are to be extracted 1039 * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected 1040 * the object owns itself and not some other object in the prototype chain. 1041 * @returns {Array} All keys of the given object. 1042 */ 1043 keys: function (object, onlyOwn) { 1044 var keys = [], 1045 property; 1046 1047 // the caller decides if we use hasOwnProperty 1048 /*jslint forin:true*/ 1049 for (property in object) { 1050 if (onlyOwn) { 1051 if (object.hasOwnProperty(property)) { 1052 keys.push(property); 1053 } 1054 } else { 1055 keys.push(property); 1056 } 1057 } 1058 /*jslint forin:false*/ 1059 1060 return keys; 1061 }, 1062 1063 /** 1064 * This outputs an object with a base class reference to the given object. This is useful if 1065 * you need a copy of an e.g. attributes object and want to overwrite some of the attributes 1066 * without changing the original object. 1067 * @param {Object} obj Object to be embedded. 1068 * @returns {Object} An object with a base class reference to <tt>obj</tt>. 1069 */ 1070 clone: function (obj) { 1071 var cObj = {}; 1072 1073 cObj.prototype = obj; 1074 1075 return cObj; 1076 }, 1077 1078 /** 1079 * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object 1080 * to the new one. Warning: The copied properties of obj2 are just flat copies. 1081 * @param {Object} obj Object to be copied. 1082 * @param {Object} obj2 Object with data that is to be copied to the new one as well. 1083 * @returns {Object} Copy of given object including some new/overwritten data from obj2. 1084 */ 1085 cloneAndCopy: function (obj, obj2) { 1086 var r, 1087 cObj = function () { 1088 return undefined; 1089 }; 1090 1091 cObj.prototype = obj; 1092 1093 // no hasOwnProperty on purpose 1094 /*jslint forin:true*/ 1095 /*jshint forin:true*/ 1096 1097 for (r in obj2) { 1098 cObj[r] = obj2[r]; 1099 } 1100 1101 /*jslint forin:false*/ 1102 /*jshint forin:false*/ 1103 1104 return cObj; 1105 }, 1106 1107 /** 1108 * Recursively merges obj2 into obj1 in-place. Contrary to {@link JXG#deepCopy} this won't create a new object 1109 * but instead will overwrite obj1. 1110 * <p> 1111 * In contrast to method JXG.mergeAttr, merge recurses into any kind of object, e.g. DOM object and JSXGraph objects. 1112 * So, please be careful. 1113 * @param {Object} obj1 1114 * @param {Object} obj2 1115 * @returns {Object} 1116 * @see JXG.mergeAttr 1117 * 1118 * @example 1119 * JXG.Options = JXG.merge(JXG.Options, { 1120 * board: { 1121 * showNavigation: false, 1122 * showInfobox: true 1123 * }, 1124 * point: { 1125 * face: 'o', 1126 * size: 4, 1127 * fillColor: '#eeeeee', 1128 * highlightFillColor: '#eeeeee', 1129 * strokeColor: 'white', 1130 * highlightStrokeColor: 'white', 1131 * showInfobox: 'inherit' 1132 * } 1133 * }); 1134 * 1135 * </pre><div id="JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b" class="jxgbox" style="width: 300px; height: 300px;"></div> 1136 * <script type="text/javascript"> 1137 * (function() { 1138 * var board = JXG.JSXGraph.initBoard('JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b', 1139 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1140 * JXG.Options = JXG.merge(JXG.Options, { 1141 * board: { 1142 * showNavigation: false, 1143 * showInfobox: true 1144 * }, 1145 * point: { 1146 * face: 'o', 1147 * size: 4, 1148 * fillColor: '#eeeeee', 1149 * highlightFillColor: '#eeeeee', 1150 * strokeColor: 'white', 1151 * highlightStrokeColor: 'white', 1152 * showInfobox: 'inherit' 1153 * } 1154 * }); 1155 * 1156 * 1157 * })(); 1158 * 1159 * </script><pre> 1160 */ 1161 merge: function (obj1, obj2) { 1162 var i, j, o, oo; 1163 1164 for (i in obj2) { 1165 if (obj2.hasOwnProperty(i)) { 1166 o = obj2[i]; 1167 if (this.isArray(o)) { 1168 if (!obj1[i]) { 1169 obj1[i] = []; 1170 } 1171 1172 for (j = 0; j < o.length; j++) { 1173 oo = obj2[i][j]; 1174 if (typeof obj2[i][j] === 'object') { 1175 obj1[i][j] = this.merge(obj1[i][j], oo); 1176 } else { 1177 obj1[i][j] = obj2[i][j]; 1178 } 1179 } 1180 } else if (typeof o === 'object') { 1181 if (!obj1[i]) { 1182 obj1[i] = {}; 1183 } 1184 1185 obj1[i] = this.merge(obj1[i], o); 1186 } else { 1187 if (typeof obj1 === 'boolean') { 1188 // This is necessary in the following scenario: 1189 // lastArrow == false 1190 // and call of 1191 // setAttribute({lastArrow: {type: 7}}) 1192 obj1 = {}; 1193 } 1194 obj1[i] = o; 1195 } 1196 } 1197 } 1198 1199 return obj1; 1200 }, 1201 1202 /** 1203 * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp. 1204 * element-wise instead of just copying the reference. If a second object is supplied, the two objects 1205 * are merged into one object. The properties of the second object have priority. 1206 * @param {Object} obj This object will be copied. 1207 * @param {Object} obj2 This object will merged into the newly created object 1208 * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes 1209 * @returns {Object} copy of obj or merge of obj and obj2. 1210 */ 1211 deepCopy: function (obj, obj2, toLower) { 1212 var c, i, prop, i2; 1213 1214 toLower = toLower || false; 1215 if (typeof obj !== 'object' || obj === null) { 1216 return obj; 1217 } 1218 1219 // Missing hasOwnProperty is on purpose in this function 1220 if (this.isArray(obj)) { 1221 c = []; 1222 for (i = 0; i < obj.length; i++) { 1223 prop = obj[i]; 1224 // Attention: typeof null === 'object' 1225 if (prop !== null && typeof prop === "object") { 1226 // We certainly do not want to recurse into a JSXGraph object. 1227 // This would for sure result in an infinite recursion. 1228 // As alternative we copy the id of the object. 1229 if (this.exists(prop.board)) { 1230 c[i] = prop.id; 1231 } else { 1232 c[i] = this.deepCopy(prop, {}, toLower); 1233 } 1234 } else { 1235 c[i] = prop; 1236 } 1237 } 1238 } else { 1239 c = {}; 1240 for (i in obj) { 1241 if (obj.hasOwnProperty(i)) { 1242 i2 = toLower ? i.toLowerCase() : i; 1243 prop = obj[i]; 1244 if (prop !== null && typeof prop === "object") { 1245 if (this.exists(prop.board)) { 1246 c[i2] = prop.id; 1247 } else { 1248 c[i2] = this.deepCopy(prop, {}, toLower); 1249 } 1250 } else { 1251 c[i2] = prop; 1252 } 1253 } 1254 } 1255 1256 for (i in obj2) { 1257 if (obj2.hasOwnProperty(i)) { 1258 i2 = toLower ? i.toLowerCase() : i; 1259 1260 prop = obj2[i]; 1261 if (prop !== null && typeof prop === "object") { 1262 if (this.isArray(prop) || !this.exists(c[i2])) { 1263 c[i2] = this.deepCopy(prop, {}, toLower); 1264 } else { 1265 c[i2] = this.deepCopy(c[i2], prop, toLower); 1266 } 1267 } else { 1268 c[i2] = prop; 1269 } 1270 } 1271 } 1272 } 1273 1274 return c; 1275 }, 1276 1277 /** 1278 * In-place (deep) merging of attributes. Allows attributes like `{shadow: {enabled: true...}}` 1279 * <p> 1280 * In contrast to method JXG.merge, mergeAttr does not recurse into DOM objects and JSXGraph objects. Instead 1281 * handles (pointers) to these objects are used. 1282 * 1283 * @param {Object} attr Object with attributes - usually containing default options - that will be changed in-place. 1284 * @param {Object} special Special option values which overwrite (recursively) the default options 1285 * @param {Boolean} [toLower=true] If true the keys are converted to lower case. 1286 * @param {Boolean} [ignoreUndefinedSpecials=false] If true the values in special that are undefined are not used. 1287 * 1288 * @see JXG.merge 1289 * 1290 */ 1291 mergeAttr: function (attr, special, toLower, ignoreUndefinedSpecials) { 1292 var e, e2, o; 1293 1294 toLower = toLower || true; 1295 ignoreUndefinedSpecials = ignoreUndefinedSpecials || false; 1296 1297 for (e in special) { 1298 if (special.hasOwnProperty(e)) { 1299 e2 = (toLower) ? e.toLowerCase(): e; 1300 // Key already exists, but not in lower case 1301 if (e2 !== e && attr.hasOwnProperty(e)) { 1302 if (attr.hasOwnProperty(e2)) { 1303 // Lower case key already exists - this should not happen 1304 // We have to unify the two key-value pairs 1305 // It is not clear which has precedence. 1306 this.mergeAttr(attr[e2], attr[e], toLower); 1307 } else { 1308 attr[e2] = attr[e]; 1309 } 1310 delete attr[e]; 1311 } 1312 1313 o = special[e]; 1314 if (this.isObject(o) && o !== null && 1315 // Do not recurse into a document object or a JSXGraph object 1316 !this.isDocumentOrFragment(o) && !this.exists(o.board) && 1317 // Do not recurse if a string is provided as "new String(...)" 1318 typeof o.valueOf() !== 'string') { 1319 if (attr[e2] === undefined || attr[e2] === null || !this.isObject(attr[e2])) { 1320 // The last test handles the case: 1321 // attr.draft = false; 1322 // special.draft = { strokewidth: 4} 1323 attr[e2] = {}; 1324 } 1325 this.mergeAttr(attr[e2], o, toLower); 1326 } else if(!ignoreUndefinedSpecials || this.exists(o)) { 1327 // Flat copy 1328 // This is also used in the cases 1329 // attr.shadow = { enabled: true ...} 1330 // special.shadow = false; 1331 // and 1332 // special.anchor is a JSXGraph element 1333 attr[e2] = o; 1334 } 1335 } 1336 } 1337 }, 1338 1339 /** 1340 * Convert a n object to a new object containing only 1341 * lower case properties. 1342 * 1343 * @param {Object} obj 1344 * @returns Object 1345 * @example 1346 * var attr = JXG.keysToLowerCase({radiusPoint: {visible: false}}); 1347 * 1348 * // return {radiuspoint: {visible: false}} 1349 */ 1350 keysToLowerCase: function (obj) { 1351 var key, val, 1352 keys = Object.keys(obj), 1353 n = keys.length, 1354 newObj = {}; 1355 1356 if (typeof obj !== 'object') { 1357 return obj; 1358 } 1359 1360 while (n--) { 1361 key = keys[n]; 1362 if (obj.hasOwnProperty(key)) { 1363 // We recurse into an object only if it is 1364 // neither a DOM node nor an JSXGraph object 1365 val = obj[key]; 1366 if (typeof val === 'object' && 1367 val !== null && 1368 !this.isArray(val) && 1369 !this.exists(val.nodeType) && 1370 !this.exists(val.board)) { 1371 newObj[key.toLowerCase()] = this.keysToLowerCase(val); 1372 } else { 1373 newObj[key.toLowerCase()] = val; 1374 } 1375 } 1376 } 1377 return newObj; 1378 }, 1379 1380 /** 1381 * Generates an attributes object that is filled with default values from the Options object 1382 * and overwritten by the user specified attributes. 1383 * @param {Object} attributes user specified attributes 1384 * @param {Object} options defaults options 1385 * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'. Must be provided in lower case! 1386 * @returns {Object} The resulting attributes object 1387 */ 1388 copyAttributes: function (attributes, options, s) { 1389 var a, arg, i, len, o, isAvail, 1390 primitives = { 1391 circle: 1, 1392 curve: 1, 1393 foreignobject: 1, 1394 image: 1, 1395 line: 1, 1396 point: 1, 1397 polygon: 1, 1398 text: 1, 1399 ticks: 1, 1400 integral: 1 1401 }; 1402 1403 len = arguments.length; 1404 if (len < 3 || primitives[s]) { 1405 // Default options from Options.elements 1406 a = JXG.deepCopy(options.elements, null, true); 1407 } else { 1408 a = {}; 1409 } 1410 1411 // Only the layer of the main element is set. 1412 if (len < 4 && this.exists(s) && this.exists(options.layer[s])) { 1413 a.layer = options.layer[s]; 1414 } 1415 1416 // Default options from the specific element like 'line' in 1417 // copyAttribute(attributes, board.options, 'line') 1418 // but also like in 1419 // Type.copyAttributes(attributes, board.options, 'view3d', 'az', 'slider'); 1420 o = options; 1421 isAvail = true; 1422 for (i = 2; i < len; i++) { 1423 arg = arguments[i]; 1424 if (this.exists(o[arg])) { 1425 o = o[arg]; 1426 } else { 1427 isAvail = false; 1428 break; 1429 } 1430 } 1431 if (isAvail) { 1432 a = JXG.deepCopy(a, o, true); 1433 } 1434 1435 // Merge the specific options given in the parameter 'attributes' 1436 // into the default options. 1437 // Additionally, we step into a sub-element of attribute like line.point1 - 1438 // in case it is supplied as in 1439 // copyAttribute(attributes, board.options, 'line', 'point1') 1440 // In this case we would merge attributes.point1 into the global line.point1 attributes. 1441 o = (typeof attributes === 'object') ? this.keysToLowerCase(attributes) : {}; 1442 isAvail = true; 1443 for (i = 3; i < len; i++) { 1444 arg = arguments[i].toLowerCase(); 1445 if (this.exists(o[arg])) { 1446 o = o[arg]; 1447 } else { 1448 isAvail = false; 1449 break; 1450 } 1451 } 1452 if (isAvail) { 1453 this.mergeAttr(a, o, true); 1454 } 1455 1456 if (arguments[2] === "board") { 1457 // For board attributes we are done now. 1458 return a; 1459 } 1460 1461 // Special treatment of labels 1462 o = options; 1463 isAvail = true; 1464 for (i = 2; i < len; i++) { 1465 arg = arguments[i]; 1466 if (this.exists(o[arg])) { 1467 o = o[arg]; 1468 } else { 1469 isAvail = false; 1470 break; 1471 } 1472 } 1473 if (isAvail && this.exists(o.label)) { 1474 a.label = JXG.deepCopy(o.label, a.label, true); 1475 } 1476 a.label = JXG.deepCopy(options.label, a.label, true); 1477 1478 return a; 1479 }, 1480 1481 /** 1482 * Copy all prototype methods from object "superObject" to object 1483 * "subObject". The constructor of superObject will be available 1484 * in subObject as subObject.constructor[constructorName]. 1485 * @param {Object} subObj A JavaScript object which receives new methods. 1486 * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject 1487 * @returns {String} constructorName Under this name the constructor of superObj will be available 1488 * in subObject. 1489 * @private 1490 */ 1491 copyPrototypeMethods: function (subObject, superObject, constructorName) { 1492 var key; 1493 1494 subObject.prototype[constructorName] = superObject.prototype.constructor; 1495 for (key in superObject.prototype) { 1496 if (superObject.prototype.hasOwnProperty(key)) { 1497 subObject.prototype[key] = superObject.prototype[key]; 1498 } 1499 } 1500 }, 1501 1502 /** 1503 * Create a stripped down version of a JSXGraph element for cloning to the background. 1504 * Used in {JXG.GeometryElement#cloneToBackground} for creating traces. 1505 * 1506 * @param {JXG.GeometryElement} el Element to be cloned 1507 * @returns Object Cloned element 1508 * @private 1509 */ 1510 getCloneObject: function(el) { 1511 var obj, key, 1512 copy = {}; 1513 1514 copy.id = el.id + "T" + el.numTraces; 1515 el.numTraces += 1; 1516 1517 copy.coords = el.coords; 1518 obj = this.deepCopy(el.visProp, el.visProp.traceattributes, true); 1519 copy.visProp = {}; 1520 for (key in obj) { 1521 if (obj.hasOwnProperty(key)) { 1522 if ( 1523 key.indexOf('aria') !== 0 && 1524 key.indexOf('highlight') !== 0 && 1525 key.indexOf('attractor') !== 0 && 1526 key !== 'label' && 1527 key !== 'needsregularupdate' && 1528 key !== 'infoboxdigits' 1529 ) { 1530 copy.visProp[key] = el.eval(obj[key]); 1531 } 1532 } 1533 } 1534 copy.evalVisProp = function(val) { 1535 return copy.visProp[val]; 1536 }; 1537 copy.eval = function(val) { 1538 return val; 1539 }; 1540 1541 copy.visProp.layer = el.board.options.layer.trace; 1542 copy.visProp.tabindex = null; 1543 copy.visProp.highlight = false; 1544 copy.board = el.board; 1545 copy.elementClass = el.elementClass; 1546 1547 this.clearVisPropOld(copy); 1548 copy.visPropCalc = { 1549 visible: el.evalVisProp('visible') 1550 }; 1551 1552 return copy; 1553 }, 1554 1555 /** 1556 * Converts a JavaScript object into a JSON string. 1557 * @param {Object} obj A JavaScript object, functions will be ignored. 1558 * @param {Boolean} [noquote=false] No quotes around the name of a property. 1559 * @returns {String} The given object stored in a JSON string. 1560 * @deprecated 1561 */ 1562 toJSON: function (obj, noquote) { 1563 var list, prop, i, s, val; 1564 1565 noquote = JXG.def(noquote, false); 1566 1567 // check for native JSON support: 1568 if (JSON !== undefined && JSON.stringify && !noquote) { 1569 try { 1570 s = JSON.stringify(obj); 1571 return s; 1572 } catch (e) { 1573 // if something goes wrong, e.g. if obj contains functions we won't return 1574 // and use our own implementation as a fallback 1575 } 1576 } 1577 1578 switch (typeof obj) { 1579 case "object": 1580 if (obj) { 1581 list = []; 1582 1583 if (this.isArray(obj)) { 1584 for (i = 0; i < obj.length; i++) { 1585 list.push(JXG.toJSON(obj[i], noquote)); 1586 } 1587 1588 return "[" + list.join(",") + "]"; 1589 } 1590 1591 for (prop in obj) { 1592 if (obj.hasOwnProperty(prop)) { 1593 try { 1594 val = JXG.toJSON(obj[prop], noquote); 1595 } catch (e2) { 1596 val = ""; 1597 } 1598 1599 if (noquote) { 1600 list.push(prop + ":" + val); 1601 } else { 1602 list.push('"' + prop + '":' + val); 1603 } 1604 } 1605 } 1606 1607 return "{" + list.join(",") + "} "; 1608 } 1609 return "null"; 1610 case "string": 1611 return "'" + obj.replace(/(["'])/g, "\\$1") + "'"; 1612 case "number": 1613 case "boolean": 1614 return obj.toString(); 1615 } 1616 1617 return "0"; 1618 }, 1619 1620 /** 1621 * Resets visPropOld. 1622 * @param {JXG.GeometryElement} el 1623 * @returns {GeometryElement} 1624 */ 1625 clearVisPropOld: function (el) { 1626 el.visPropOld = { 1627 cssclass: "", 1628 cssdefaultstyle: "", 1629 cssstyle: "", 1630 fillcolor: "", 1631 fillopacity: "", 1632 firstarrow: false, 1633 fontsize: -1, 1634 lastarrow: false, 1635 left: -100000, 1636 linecap: "", 1637 shadow: false, 1638 strokecolor: "", 1639 strokeopacity: "", 1640 strokewidth: "", 1641 tabindex: -100000, 1642 transitionduration: 0, 1643 top: -100000, 1644 visible: null 1645 }; 1646 1647 return el; 1648 }, 1649 1650 /** 1651 * Checks if an object contains a key, whose value equals to val. 1652 * @param {Object} obj 1653 * @param val 1654 * @returns {Boolean} 1655 */ 1656 isInObject: function (obj, val) { 1657 var el; 1658 1659 for (el in obj) { 1660 if (obj.hasOwnProperty(el)) { 1661 if (obj[el] === val) { 1662 return true; 1663 } 1664 } 1665 } 1666 1667 return false; 1668 }, 1669 1670 /** 1671 * Replaces all occurences of & by &, > by >, and < by <. 1672 * @param {String} str 1673 * @returns {String} 1674 */ 1675 escapeHTML: function (str) { 1676 return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); 1677 }, 1678 1679 /** 1680 * Eliminates all substrings enclosed by < and > and replaces all occurences of 1681 * & by &, > by >, and < by <. 1682 * @param {String} str 1683 * @returns {String} 1684 */ 1685 unescapeHTML: function (str) { 1686 // This regex is NOT insecure. We are replacing everything found with '' 1687 /*jslint regexp:true*/ 1688 return str 1689 .replace(/<\/?[^>]+>/gi, "") 1690 .replace(/&/g, "&") 1691 .replace(/</g, "<") 1692 .replace(/>/g, ">"); 1693 }, 1694 1695 /** 1696 * Makes a string lower case except for the first character which will be upper case. 1697 * @param {String} str Arbitrary string 1698 * @returns {String} The capitalized string. 1699 */ 1700 capitalize: function (str) { 1701 return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); 1702 }, 1703 1704 /** 1705 * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes. 1706 * @param {String} str 1707 * @returns {String} 1708 */ 1709 trimNumber: function (str) { 1710 str = str.replace(/^0+/, ""); 1711 str = str.replace(/0+$/, ""); 1712 1713 if (str[str.length - 1] === "." || str[str.length - 1] === ",") { 1714 str = str.slice(0, -1); 1715 } 1716 1717 if (str[0] === "." || str[0] === ",") { 1718 str = "0" + str; 1719 } 1720 1721 return str; 1722 }, 1723 1724 /** 1725 * Filter an array of elements. 1726 * @param {Array} list 1727 * @param {Object|function} filter 1728 * @returns {Array} 1729 */ 1730 filterElements: function (list, filter) { 1731 var i, 1732 f, 1733 item, 1734 flower, 1735 value, 1736 visPropValue, 1737 pass, 1738 l = list.length, 1739 result = []; 1740 1741 if (typeof filter !== "function" && typeof filter !== "object") { 1742 return result; 1743 } 1744 1745 for (i = 0; i < l; i++) { 1746 pass = true; 1747 item = list[i]; 1748 1749 if (typeof filter === "object") { 1750 for (f in filter) { 1751 if (filter.hasOwnProperty(f)) { 1752 flower = f.toLowerCase(); 1753 1754 if (typeof item[f] === "function") { 1755 value = item[f](); 1756 } else { 1757 value = item[f]; 1758 } 1759 1760 if (item.visProp && typeof item.visProp[flower] === "function") { 1761 visPropValue = item.visProp[flower](); 1762 } else { 1763 visPropValue = item.visProp && item.visProp[flower]; 1764 } 1765 1766 if (typeof filter[f] === "function") { 1767 pass = filter[f](value) || filter[f](visPropValue); 1768 } else { 1769 pass = value === filter[f] || visPropValue === filter[f]; 1770 } 1771 1772 if (!pass) { 1773 break; 1774 } 1775 } 1776 } 1777 } else if (typeof filter === "function") { 1778 pass = filter(item); 1779 } 1780 1781 if (pass) { 1782 result.push(item); 1783 } 1784 } 1785 1786 return result; 1787 }, 1788 1789 /** 1790 * Remove all leading and trailing whitespaces from a given string. 1791 * @param {String} str 1792 * @returns {String} 1793 */ 1794 trim: function (str) { 1795 // str = str.replace(/^\s+/, ''); 1796 // str = str.replace(/\s+$/, ''); 1797 // 1798 // return str; 1799 return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ""); 1800 }, 1801 1802 /** 1803 * Convert a floating point number to a string integer + fraction. 1804 * Returns either a string of the form '3 1/3' (in case of useTeX=false) 1805 * or '3 \\frac{1}{3}' (in case of useTeX=true). 1806 * 1807 * @param {Number} x 1808 * @param {Boolean} [useTeX=false] 1809 * @param {Number} [order=0.001] 1810 * @returns {String} 1811 * @see JXG.Math.decToFraction 1812 */ 1813 toFraction: function (x, useTeX, order) { 1814 var arr = Mat.decToFraction(x, order), 1815 str = ''; 1816 1817 if (arr[1] === 0 && arr[2] === 0) { 1818 // 0 1819 str += '0'; 1820 } else { 1821 // Sign 1822 if (arr[0] < 0) { 1823 str += '-'; 1824 } 1825 if (arr[2] === 0) { 1826 // Integer 1827 str += arr[1]; 1828 } else if (!(arr[2] === 1 && arr[3] === 1)) { 1829 // Proper fraction 1830 if (arr[1] !== 0) { 1831 // Absolute value larger than 1 1832 str += arr[1] + ' '; 1833 } 1834 // Add fractional part 1835 if (useTeX === true) { 1836 str += '\\frac{' + arr[2] + '}{' + arr[3] + '}'; 1837 } else { 1838 str += arr[2] + '/' + arr[3]; 1839 } 1840 } 1841 } 1842 return str; 1843 }, 1844 1845 /** 1846 * Concat array src to array dest. 1847 * Uses push instead of JavaScript concat, which is much 1848 * faster. 1849 * The array dest is changed in place. 1850 * <p><b>Attention:</b> if "dest" is an anonymous array, the correct result is returned from the function. 1851 * 1852 * @param {Array} dest 1853 * @param {Array} src 1854 * @returns Array 1855 */ 1856 concat: function(dest, src) { 1857 var i, 1858 le = src.length; 1859 for (i = 0; i < le; i++) { 1860 dest.push(src[i]); 1861 } 1862 return dest; 1863 }, 1864 1865 /** 1866 * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available. 1867 * @param {String} str 1868 * @param {Boolean} caja 1869 * @returns {String} Sanitized string 1870 */ 1871 sanitizeHTML: function (str, caja) { 1872 if (typeof html_sanitize === "function" && caja) { 1873 return html_sanitize( 1874 str, 1875 function () { 1876 return undefined; 1877 }, 1878 function (id) { 1879 return id; 1880 } 1881 ); 1882 } 1883 1884 if (str && typeof str === "string") { 1885 str = str.replace(/</g, "<").replace(/>/g, ">"); 1886 } 1887 1888 return str; 1889 }, 1890 1891 /** 1892 * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value. 1893 * @param {*} s 1894 * @returns {*} s.Value() if s is an element of type slider, s otherwise 1895 */ 1896 evalSlider: function (s) { 1897 if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === "function") { 1898 return s.Value(); 1899 } 1900 1901 return s; 1902 }, 1903 1904 /** 1905 * Convert a string containing a MAXIMA /STACK expression into a JSXGraph / JessieCode string 1906 * or an array of JSXGraph / JessieCode strings. 1907 * <p> 1908 * This function is meanwhile superseded by stack_jxg.stack2jsxgraph. 1909 * 1910 * @deprecated 1911 * 1912 * @example 1913 * console.log( JXG.stack2jsxgraph("%e**x") ); 1914 * // Output: 1915 * // "EULER**x" 1916 * 1917 * @example 1918 * console.log( JXG.stack2jsxgraph("[%pi*(x**2 - 1), %phi*(x - 1), %gamma*(x+1)]") ); 1919 * // Output: 1920 * // [ "PI*(x**2 - 1)", "1.618033988749895*(x - 1)", "0.5772156649015329*(x+1)" ] 1921 * 1922 * @param {String} str 1923 * @returns String 1924 */ 1925 stack2jsxgraph: function(str) { 1926 var t; 1927 1928 t = str. 1929 replace(/%pi/g, 'PI'). 1930 replace(/%e/g, 'EULER'). 1931 replace(/%phi/g, '1.618033988749895'). 1932 replace(/%gamma/g, '0.5772156649015329'). 1933 trim(); 1934 1935 // String containing array -> array containing strings 1936 if (t[0] === '[' && t[t.length - 1] === ']') { 1937 t = t.slice(1, -1).split(/\s*,\s*/); 1938 } 1939 1940 return t; 1941 } 1942 } 1943 ); 1944 1945 export default JXG; 1946