1 /* 2 Copyright 2008-2023 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true, window: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview In this file the Text element is defined. 37 */ 38 39 import JXG from "../jxg"; 40 import Const from "./constants"; 41 import GeometryElement from "./element"; 42 import GeonextParser from "../parser/geonext"; 43 import Env from "../utils/env"; 44 import Type from "../utils/type"; 45 import Mat from "../math/math"; 46 import CoordsElement from "./coordselement"; 47 48 var priv = { 49 /** 50 * @class 51 * @ignore 52 */ 53 HTMLSliderInputEventHandler: function () { 54 this._val = parseFloat(this.rendNodeRange.value); 55 this.rendNodeOut.value = this.rendNodeRange.value; 56 this.board.update(); 57 } 58 }; 59 60 /** 61 * Construct and handle texts. 62 * 63 * The coordinates can be relative to the coordinates of an element 64 * given in {@link JXG.Options#text.anchor}. 65 * 66 * MathJax, HTML and GEONExT syntax can be handled. 67 * @class Creates a new text object. Do not use this constructor to create a text. Use {@link JXG.Board#create} with 68 * type {@link Text} instead. 69 * @augments JXG.GeometryElement 70 * @augments JXG.CoordsElement 71 * @param {string|JXG.Board} board The board the new text is drawn on. 72 * @param {Array} coordinates An array with the user coordinates of the text. 73 * @param {Object} attributes An object containing visual properties and optional a name and a id. 74 * @param {string|function} content A string or a function returning a string. 75 * 76 */ 77 JXG.Text = function (board, coords, attributes, content) { 78 var tmp; 79 80 this.constructor(board, attributes, Const.OBJECT_TYPE_TEXT, Const.OBJECT_CLASS_TEXT); 81 82 this.element = this.board.select(attributes.anchor); 83 this.coordsConstructor(coords, Type.evaluate(this.visProp.islabel)); 84 85 this.content = ""; 86 this.plaintext = ""; 87 this.plaintextOld = null; 88 this.orgText = ""; 89 90 this.needsSizeUpdate = false; 91 // Only used by infobox anymore 92 this.hiddenByParent = false; 93 94 /** 95 * Width and height of the text element in pixel. 96 * 97 * @private 98 * @type Array 99 */ 100 this.size = [1.0, 1.0]; 101 this.id = this.board.setId(this, "T"); 102 103 this.board.renderer.drawText(this); 104 this.board.finalizeAdding(this); 105 106 // Set text before drawing 107 // this._createFctUpdateText(content); 108 // this.updateText(); 109 110 // Set attribute visible to true. This is necessary to 111 // create all sub-elements for button, input and checkbox 112 tmp = this.visProp.visible; 113 this.visProp.visible = true; 114 this.setText(content); 115 // Restore the correct attribute visible. 116 this.visProp.visible = tmp; 117 118 if (Type.isString(this.content)) { 119 this.notifyParents(this.content); 120 } 121 this.elType = "text"; 122 123 this.methodMap = Type.deepCopy(this.methodMap, { 124 setText: "setTextJessieCode" 125 // free: 'free', 126 // move: "setCoords" 127 }); 128 }; 129 130 JXG.Text.prototype = new GeometryElement(); 131 Type.copyPrototypeMethods(JXG.Text, CoordsElement, "coordsConstructor"); 132 133 JXG.extend( 134 JXG.Text.prototype, 135 /** @lends JXG.Text.prototype */ { 136 /** 137 * @private 138 * @param {Number} x 139 * @param {Number} y 140 * @returns {Boolean} 141 */ 142 // Test if the screen coordinates (x,y) are in a small stripe 143 // at the left side or at the right side of the text. 144 // Sensitivity is set in this.board.options.precision.hasPoint. 145 // If dragarea is set to 'all' (default), tests if the screen 146 // coordinates (x,y) are in within the text boundary. 147 hasPoint: function (x, y) { 148 var lft, rt, top, bot, ax, ay, type, r; 149 150 if (Type.isObject(Type.evaluate(this.visProp.precision))) { 151 type = this.board._inputDevice; 152 r = Type.evaluate(this.visProp.precision[type]); 153 } else { 154 // 'inherit' 155 r = this.board.options.precision.hasPoint; 156 } 157 if (this.transformations.length > 0) { 158 //Transform the mouse/touch coordinates 159 // back to the original position of the text. 160 lft = Mat.matVecMult( 161 Mat.inverse(this.board.renderer.joinTransforms(this, this.transformations)), 162 [1, x, y] 163 ); 164 x = lft[1]; 165 y = lft[2]; 166 } 167 168 ax = this.getAnchorX(); 169 if (ax === "right") { 170 lft = this.coords.scrCoords[1] - this.size[0]; 171 } else if (ax === "middle") { 172 lft = this.coords.scrCoords[1] - 0.5 * this.size[0]; 173 } else { 174 lft = this.coords.scrCoords[1]; 175 } 176 rt = lft + this.size[0]; 177 178 ay = this.getAnchorY(); 179 if (ay === "top") { 180 bot = this.coords.scrCoords[2] + this.size[1]; 181 } else if (ay === "middle") { 182 bot = this.coords.scrCoords[2] + 0.5 * this.size[1]; 183 } else { 184 bot = this.coords.scrCoords[2]; 185 } 186 top = bot - this.size[1]; 187 188 if (Type.evaluate(this.visProp.dragarea) === "all") { 189 return x >= lft - r && x < rt + r && y >= top - r && y <= bot + r; 190 } 191 // e.g. 'small' 192 return ( 193 y >= top - r && 194 y <= bot + r && 195 ((x >= lft - r && x <= lft + 2 * r) || (x >= rt - 2 * r && x <= rt + r)) 196 ); 197 }, 198 199 /** 200 * This sets the updateText function of this element depending on the type of text content passed. 201 * Used by {@link JXG.Text#_setText}. 202 * @param {String|Function|Number} text 203 * @private 204 * @see JXG.Text#_setText 205 */ 206 _createFctUpdateText: function (text) { 207 var updateText, e, digits, 208 resolvedText, 209 i, that, 210 ev_p = Type.evaluate(this.visProp.parse), 211 ev_um = Type.evaluate(this.visProp.usemathjax), 212 ev_uk = Type.evaluate(this.visProp.usekatex), 213 convertJessieCode = false; 214 215 this.orgText = text; 216 217 if (Type.isFunction(text)) { 218 /** 219 * Dynamically created function to update the content 220 * of a text. Can not be overwritten. 221 * <p> 222 * <value> tags will not be evaluated if text is provided by a function 223 * <p> 224 * Sets the property <tt>plaintext</tt> of the text element. 225 * 226 * @private 227 */ 228 this.updateText = function () { 229 resolvedText = text().toString(); // Evaluate function 230 if (ev_p && !ev_um && !ev_uk) { 231 this.plaintext = this.replaceSub( 232 this.replaceSup( 233 this.convertGeonextAndSketchometry2CSS(resolvedText, false) 234 ) 235 ); 236 } else { 237 this.plaintext = resolvedText; 238 } 239 }; 240 } else { 241 if (Type.isNumber(text)) { 242 digits = Type.evaluate(this.visProp.digits); 243 if (this.useLocale()) { 244 this.content = this.formatNumberLocale(text, digits); 245 } else { 246 this.content = Type.toFixed(text, digits); 247 } 248 } else if (Type.isString(text) && ev_p) { 249 if (Type.evaluate(this.visProp.useasciimathml)) { 250 // ASCIIMathML 251 // value-tags are not supported 252 this.content = "'`" + text + "`'"; 253 } else if (ev_um || ev_uk) { 254 // MathJax or KaTeX 255 // Replace value-tags by functions 256 // sketchofont is ignored 257 this.content = this.valueTagToJessieCode(text); 258 if (!Type.isArray(this.content)) { 259 // For some reason we don't have to mask backslashes in an array of strings 260 // anymore. 261 // 262 // for (i = 0; i < this.content.length; i++) { 263 // this.content[i] = this.content[i].replace(/\\/g, "\\\\"); // Replace single backslash by double 264 // } 265 // } else { 266 this.content = this.content.replace(/\\/g, "\\\\"); // Replace single backslash by double 267 } 268 } else { 269 // No TeX involved. 270 // Converts GEONExT syntax into JavaScript string 271 // Short math is allowed 272 // Replace value-tags by functions 273 // Avoid geonext2JS calls 274 this.content = this.poorMansTeX(this.valueTagToJessieCode(text)); 275 } 276 convertJessieCode = true; 277 } else { 278 this.content = text; 279 } 280 281 // Generate function which returns the text to be displayed 282 if (convertJessieCode) { 283 // Convert JessieCode to JS function 284 if (Type.isArray(this.content)) { 285 // This is the case if the text contained value-tags. 286 // These value-tags consist of JessieCode snippets 287 // which are now replaced by JavaScript functions 288 that = this; 289 for (i = 0; i < this.content.length; i++) { 290 if (this.content[i][0] !== '"') { 291 this.content[i] = this.board.jc.snippet(this.content[i], true, "", false); 292 for (e in this.content[i].deps) { 293 this.addParents(this.content[i].deps[e]); 294 this.content[i].deps[e].addChild(this); 295 } 296 } 297 } 298 299 updateText = function() { 300 var i, t, 301 digits = Type.evaluate(that.visProp.digits), 302 txt = ''; 303 304 for (i = 0; i < that.content.length; i++) { 305 if (Type.isFunction(that.content[i])) { 306 t = that.content[i](); 307 if (that.useLocale()) { 308 t = that.formatNumberLocale(t, digits); 309 } else { 310 t = Type.toFixed(t, digits); 311 } 312 } else { 313 t = that.content[i]; 314 if (t.at(0) === '"' && t.at(-1) === '"') { 315 t = t.slice(1, -1); 316 } 317 } 318 319 txt += t; 320 } 321 return txt; 322 }; 323 } else { 324 updateText = this.board.jc.snippet(this.content, true, "", false); 325 for (e in updateText.deps) { 326 this.addParents(updateText.deps[e]); 327 updateText.deps[e].addChild(this); 328 } 329 } 330 331 // Ticks have been escaped in valueTagToJessieCode 332 this.updateText = function () { 333 this.plaintext = this.unescapeTicks(updateText()); 334 }; 335 } else { 336 this.updateText = function () { 337 this.plaintext = this.content; // text; 338 }; 339 } 340 } 341 }, 342 343 /** 344 * Defines new content. This is used by {@link JXG.Text#setTextJessieCode} and {@link JXG.Text#setText}. This is required because 345 * JessieCode needs to filter all Texts inserted into the DOM and thus has to replace setText by setTextJessieCode. 346 * @param {String|Function|Number} text 347 * @returns {JXG.Text} 348 * @private 349 */ 350 _setText: function (text) { 351 this._createFctUpdateText(text); 352 353 // First evaluation of the string. 354 // We need this for display='internal' and Canvas 355 this.updateText(); 356 this.fullUpdate(); 357 358 // We do not call updateSize for the infobox to speed up rendering 359 if (!this.board.infobox || this.id !== this.board.infobox.id) { 360 this.updateSize(); // updateSize() is called at least once. 361 } 362 363 // This may slow down canvas renderer 364 // if (this.board.renderer.type === 'canvas') { 365 // this.board.fullUpdate(); 366 // } 367 368 return this; 369 }, 370 371 /** 372 * Defines new content but converts < and > to HTML entities before updating the DOM. 373 * @param {String|function} text 374 */ 375 setTextJessieCode: function (text) { 376 var s; 377 378 this.visProp.castext = text; 379 if (Type.isFunction(text)) { 380 s = function () { 381 return Type.sanitizeHTML(text()); 382 }; 383 } else { 384 if (Type.isNumber(text)) { 385 s = text; 386 } else { 387 s = Type.sanitizeHTML(text); 388 } 389 } 390 391 return this._setText(s); 392 }, 393 394 /** 395 * Defines new content. 396 * @param {String|function} text 397 * @returns {JXG.Text} Reference to the text object. 398 */ 399 setText: function (text) { 400 return this._setText(text); 401 }, 402 403 /** 404 * Recompute the width and the height of the text box. 405 * Updates the array {@link JXG.Text#size} with pixel values. 406 * The result may differ from browser to browser 407 * by some pixels. 408 * In canvas an old IEs we use a very crude estimation of the dimensions of 409 * the textbox. 410 * JSXGraph needs {@link JXG.Text#size} for applying rotations in IE and 411 * for aligning text. 412 * 413 * @return {this} [description] 414 */ 415 updateSize: function () { 416 var tmp, 417 that, 418 node, 419 ev_d = Type.evaluate(this.visProp.display); 420 421 if (!Env.isBrowser || this.board.renderer.type === "no") { 422 return this; 423 } 424 node = this.rendNode; 425 426 /** 427 * offsetWidth and offsetHeight seem to be supported for internal vml elements by IE10+ in IE8 mode. 428 */ 429 if (ev_d === "html" || this.board.renderer.type === "vml") { 430 if (Type.exists(node.offsetWidth)) { 431 that = this; 432 window.setTimeout(function () { 433 that.size = [node.offsetWidth, node.offsetHeight]; 434 that.needsUpdate = true; 435 that.updateRenderer(); 436 }, 0); 437 // In case, there is non-zero padding or borders 438 // the following approach does not longer work. 439 // s = [node.offsetWidth, node.offsetHeight]; 440 // if (s[0] === 0 && s[1] === 0) { // Some browsers need some time to set offsetWidth and offsetHeight 441 // that = this; 442 // window.setTimeout(function () { 443 // that.size = [node.offsetWidth, node.offsetHeight]; 444 // that.needsUpdate = true; 445 // that.updateRenderer(); 446 // }, 0); 447 // } else { 448 // this.size = s; 449 // } 450 } else { 451 this.size = this.crudeSizeEstimate(); 452 } 453 } else if (ev_d === "internal") { 454 if (this.board.renderer.type === "svg") { 455 that = this; 456 window.setTimeout(function () { 457 try { 458 tmp = node.getBBox(); 459 that.size = [tmp.width, tmp.height]; 460 that.needsUpdate = true; 461 that.updateRenderer(); 462 } catch (e) {} 463 }, 0); 464 } else if (this.board.renderer.type === "canvas") { 465 this.size = this.crudeSizeEstimate(); 466 } 467 } 468 469 return this; 470 }, 471 472 /** 473 * A very crude estimation of the dimensions of the textbox in case nothing else is available. 474 * @returns {Array} 475 */ 476 crudeSizeEstimate: function () { 477 var ev_fs = parseFloat(Type.evaluate(this.visProp.fontsize)); 478 return [ev_fs * this.plaintext.length * 0.45, ev_fs * 0.9]; 479 }, 480 481 /** 482 * Decode unicode entities into characters. 483 * @param {String} string 484 * @returns {String} 485 */ 486 utf8_decode: function (string) { 487 return string.replace(/(\w+);/g, function (m, p1) { 488 return String.fromCharCode(parseInt(p1, 16)); 489 }); 490 }, 491 492 /** 493 * Replace _{} by <sub> 494 * @param {String} te String containing _{}. 495 * @returns {String} Given string with _{} replaced by <sub>. 496 */ 497 replaceSub: function (te) { 498 if (!te.indexOf) { 499 return te; 500 } 501 502 var j, 503 i = te.indexOf("_{"); 504 505 // The regexp in here are not used for filtering but to provide some kind of sugar for label creation, 506 // i.e. replacing _{...} with <sub>...</sub>. What is passed would get out anyway. 507 /*jslint regexp: true*/ 508 while (i >= 0) { 509 te = te.slice(0, i) + te.slice(i).replace(/_\{/, "<sub>"); 510 j = te.indexOf("}", i + 4); 511 if (j >= 0) { 512 te = te.slice(0, j) + te.slice(j).replace(/\}/, "</sub>"); 513 } 514 i = te.indexOf("_{"); 515 } 516 517 i = te.indexOf("_"); 518 while (i >= 0) { 519 te = te.slice(0, i) + te.slice(i).replace(/_(.?)/, "<sub>$1</sub>"); 520 i = te.indexOf("_"); 521 } 522 523 return te; 524 }, 525 526 /** 527 * Replace ^{} by <sup> 528 * @param {String} te String containing ^{}. 529 * @returns {String} Given string with ^{} replaced by <sup>. 530 */ 531 replaceSup: function (te) { 532 if (!te.indexOf) { 533 return te; 534 } 535 536 var j, 537 i = te.indexOf("^{"); 538 539 // The regexp in here are not used for filtering but to provide some kind of sugar for label creation, 540 // i.e. replacing ^{...} with <sup>...</sup>. What is passed would get out anyway. 541 /*jslint regexp: true*/ 542 while (i >= 0) { 543 te = te.slice(0, i) + te.slice(i).replace(/\^\{/, "<sup>"); 544 j = te.indexOf("}", i + 4); 545 if (j >= 0) { 546 te = te.slice(0, j) + te.slice(j).replace(/\}/, "</sup>"); 547 } 548 i = te.indexOf("^{"); 549 } 550 551 i = te.indexOf("^"); 552 while (i >= 0) { 553 te = te.slice(0, i) + te.slice(i).replace(/\^(.?)/, "<sup>$1</sup>"); 554 i = te.indexOf("^"); 555 } 556 557 return te; 558 }, 559 560 /** 561 * Return the width of the text element. 562 * @returns {Array} [width, height] in pixel 563 */ 564 getSize: function () { 565 return this.size; 566 }, 567 568 /** 569 * Move the text to new coordinates. 570 * @param {number} x 571 * @param {number} y 572 * @returns {object} reference to the text object. 573 */ 574 setCoords: function (x, y) { 575 var coordsAnchor, dx, dy; 576 if (Type.isArray(x) && x.length > 1) { 577 y = x[1]; 578 x = x[0]; 579 } 580 581 if (Type.evaluate(this.visProp.islabel) && Type.exists(this.element)) { 582 coordsAnchor = this.element.getLabelAnchor(); 583 dx = (x - coordsAnchor.usrCoords[1]) * this.board.unitX; 584 dy = -(y - coordsAnchor.usrCoords[2]) * this.board.unitY; 585 586 this.relativeCoords.setCoordinates(Const.COORDS_BY_SCREEN, [dx, dy]); 587 } else { 588 /* 589 this.X = function () { 590 return x; 591 }; 592 593 this.Y = function () { 594 return y; 595 }; 596 */ 597 this.coords.setCoordinates(Const.COORDS_BY_USER, [x, y]); 598 } 599 600 // this should be a local update, otherwise there might be problems 601 // with the tick update routine resulting in orphaned tick labels 602 this.fullUpdate(); 603 604 return this; 605 }, 606 607 /** 608 * Evaluates the text. 609 * Then, the update function of the renderer 610 * is called. 611 */ 612 update: function (fromParent) { 613 if (!this.needsUpdate) { 614 return this; 615 } 616 617 this.updateCoords(fromParent); 618 this.updateText(); 619 620 if (Type.evaluate(this.visProp.display) === "internal") { 621 if (Type.isString(this.plaintext)) { 622 this.plaintext = this.utf8_decode(this.plaintext); 623 } 624 } 625 626 this.checkForSizeUpdate(); 627 if (this.needsSizeUpdate) { 628 this.updateSize(); 629 } 630 631 return this; 632 }, 633 634 /** 635 * Used to save updateSize() calls. 636 * Called in JXG.Text.update 637 * That means this.update() has been called. 638 * More tests are in JXG.Renderer.updateTextStyle. The latter tests 639 * are one update off. But this should pose not too many problems, since 640 * it affects fontSize and cssClass changes. 641 * 642 * @private 643 */ 644 checkForSizeUpdate: function () { 645 if (this.board.infobox && this.id === this.board.infobox.id) { 646 this.needsSizeUpdate = false; 647 } else { 648 // For some magic reason it is more efficient on the iPad to 649 // call updateSize() for EVERY text element EVERY time. 650 this.needsSizeUpdate = this.plaintextOld !== this.plaintext; 651 652 if (this.needsSizeUpdate) { 653 this.plaintextOld = this.plaintext; 654 } 655 } 656 }, 657 658 /** 659 * The update function of the renderer 660 * is called. 661 * @private 662 */ 663 updateRenderer: function () { 664 if ( 665 //this.board.updateQuality === this.board.BOARD_QUALITY_HIGH && 666 Type.evaluate(this.visProp.autoposition) 667 ) { 668 this.setAutoPosition().updateConstraint(); 669 } 670 return this.updateRendererGeneric("updateText"); 671 }, 672 673 /** 674 * Converts shortened math syntax into correct syntax: 3x instead of 3*x or 675 * (a+b)(3+1) instead of (a+b)*(3+1). 676 * 677 * @private 678 * @param{String} expr Math term 679 * @returns {string} expanded String 680 */ 681 expandShortMath: function (expr) { 682 var re = /([)0-9.])\s*([(a-zA-Z_])/g; 683 return expr.replace(re, "$1*$2"); 684 }, 685 686 /** 687 * Converts the GEONExT syntax of the <value> terms into JavaScript. 688 * Also, all Objects whose name appears in the term are searched and 689 * the text is added as child to these objects. 690 * This method is called if the attribute parse==true is set. 691 * 692 * Obsolete, replaced by JXG.Text.valueTagToJessieCode 693 * 694 * @param{String} contentStr String to be parsed 695 * @param{Boolean} [expand] Optional flag if shortened math syntax is allowed (e.g. 3x instead of 3*x). 696 * @param{Boolean} [avoidGeonext2JS] Optional flag if geonext2JS should be called. For backwards compatibility 697 * this has to be set explicitly to true. 698 * @param{Boolean} [outputTeX] Optional flag which has to be true if the resulting term will be sent to MathJax or KaTeX. 699 * If true, "_" and "^" are NOT replaced by HTML tags sub and sup. Default: false, i.e. the replacement is done. 700 * This flag allows the combination of <value> tag containing calculations with TeX output. 701 * 702 * @deprecated 703 * @private 704 * @see JXG.GeonextParser#geonext2JS 705 * @see JXG.Text#valueTagToJessieCode 706 * 707 */ 708 generateTerm: function (contentStr, expand, avoidGeonext2JS) { 709 var res, 710 term, 711 i, 712 j, 713 plaintext = '""'; 714 715 // Revert possible jc replacement 716 contentStr = contentStr || ""; 717 contentStr = contentStr.replace(/\r/g, ""); 718 contentStr = contentStr.replace(/\n/g, ""); 719 contentStr = contentStr.replace(/"/g, "'"); 720 contentStr = contentStr.replace(/'/g, "\\'"); 721 722 // Old GEONExT syntax, not (yet) supported as TeX output. 723 // Otherwise, the else clause should be used. 724 // That means, i.e. the <arc> tag and <sqrt> tag are not 725 // converted into TeX syntax. 726 contentStr = contentStr.replace(/&arc;/g, "∠"); 727 contentStr = contentStr.replace(/<arc\s*\/>/g, "∠"); 728 contentStr = contentStr.replace(/<arc\s*\/>/g, "∠"); 729 contentStr = contentStr.replace(/<sqrt\s*\/>/g, "√"); 730 731 contentStr = contentStr.replace(/<value>/g, "<value>"); 732 contentStr = contentStr.replace(/<\/value>/g, "</value>"); 733 734 // Convert GEONExT syntax into JavaScript syntax 735 i = contentStr.indexOf("<value>"); 736 j = contentStr.indexOf("</value>"); 737 if (i >= 0) { 738 while (i >= 0) { 739 plaintext += 740 ' + "' + this.replaceSub(this.replaceSup(contentStr.slice(0, i))) + '"'; 741 // plaintext += ' + "' + this.replaceSub(contentStr.slice(0, i)) + '"'; 742 743 term = contentStr.slice(i + 7, j); 744 term = term.replace(/\s+/g, ""); // Remove all whitespace 745 if (expand === true) { 746 term = this.expandShortMath(term); 747 } 748 if (avoidGeonext2JS) { 749 res = term; 750 } else { 751 res = GeonextParser.geonext2JS(term, this.board); 752 } 753 res = res.replace(/\\"/g, "'"); 754 res = res.replace(/\\'/g, "'"); 755 756 // GEONExT-Hack: apply rounding once only. 757 if (res.indexOf("toFixed") < 0) { 758 // output of a value tag 759 if ( 760 Type.isNumber( 761 Type.bind(this.board.jc.snippet(res, true, '', false), this)() 762 ) 763 ) { 764 // may also be a string 765 plaintext += '+(' + res + ').toFixed(' + Type.evaluate(this.visProp.digits) + ')'; 766 } else { 767 plaintext += '+(' + res + ')'; 768 } 769 } else { 770 plaintext += '+(' + res + ')'; 771 } 772 773 contentStr = contentStr.slice(j + 8); 774 i = contentStr.indexOf("<value>"); 775 j = contentStr.indexOf("</value>"); 776 } 777 } 778 779 plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr)) + '"'; 780 plaintext = this.convertGeonextAndSketchometry2CSS(plaintext); 781 782 // This should replace e.g. π by π 783 plaintext = plaintext.replace(/&/g, "&"); 784 plaintext = plaintext.replace(/"/g, "'"); 785 786 return plaintext; 787 }, 788 789 /** 790 * Replace value-tags in string by JessieCode functions. 791 * @param {String} contentStr 792 * @returns String 793 * @private 794 * @example 795 * "The x-coordinate of A is <value>X(A)</value>" 796 * 797 */ 798 valueTagToJessieCode: function (contentStr) { 799 var res, term, 800 i, j, 801 expandShortMath = true, 802 textComps = [], 803 tick = '"'; 804 805 contentStr = contentStr || ""; 806 contentStr = contentStr.replace(/\r/g, ""); 807 contentStr = contentStr.replace(/\n/g, ""); 808 809 contentStr = contentStr.replace(/<value>/g, "<value>"); 810 contentStr = contentStr.replace(/<\/value>/g, "</value>"); 811 812 // Convert content of value tag (GEONExT/JessieCode) syntax into JavaScript syntax 813 i = contentStr.indexOf("<value>"); 814 j = contentStr.indexOf("</value>"); 815 if (i >= 0) { 816 while (i >= 0) { 817 // Add string fragment before <value> tag 818 textComps.push(tick + this.escapeTicks(contentStr.slice(0, i)) + tick); 819 820 term = contentStr.slice(i + 7, j); 821 term = term.replace(/\s+/g, ""); // Remove all whitespace 822 if (expandShortMath === true) { 823 term = this.expandShortMath(term); 824 } 825 res = term; 826 res = res.replace(/\\"/g, "'").replace(/\\'/g, "'"); 827 828 // // Hack: apply rounding once only. 829 // if (res.indexOf("toFixed") < 0) { 830 // // Output of a value tag 831 // // Run the JessieCode parser 832 // if ( 833 // Type.isNumber( 834 // Type.bind(this.board.jc.snippet(res, true, "", false), this)() 835 // ) 836 // ) { 837 // // Output is number 838 // // textComps.push( 839 // // '(' + res + ').toFixed(' + Type.evaluate(this.visProp.digits) + ')' 840 // // ); 841 // textComps.push('(' + res + ')'); 842 // } else { 843 // // Output is a string 844 // textComps.push("(" + res + ")"); 845 // } 846 // } else { 847 textComps.push("(" + res + ")"); 848 // } 849 contentStr = contentStr.slice(j + 8); 850 i = contentStr.indexOf("<value>"); 851 j = contentStr.indexOf("</value>"); 852 } 853 } 854 // Add trailing string fragment 855 textComps.push(tick + this.escapeTicks(contentStr) + tick); 856 857 // return textComps.join(" + ").replace(/&/g, "&"); 858 for (i = 0; i < textComps.length; i++) { 859 textComps[i] = textComps[i].replace(/&/g, "&"); 860 } 861 return textComps; 862 }, 863 864 /** 865 * Simple math rendering using HTML / CSS only. In case of array, 866 * handle each entry separately and return array with the 867 * rendering strings. 868 * 869 * @param {String|Array} s 870 * @returns {String|Array} 871 * @see JXG.Text#convertGeonextAndSketchometry2CSS 872 * @private 873 * @see JXG.Text#replaceSub 874 * @see JXG.Text#replaceSup 875 * @see JXG.Text#convertGeonextAndSketchometry2CSS 876 */ 877 poorMansTeX: function (s) { 878 var i, a; 879 if (Type.isArray(s)) { 880 a = []; 881 for (i = 0; i < s.length; i++) { 882 a.push(this.poorMansTeX(s[i])); 883 } 884 return a; 885 } 886 887 s = s 888 .replace(/<arc\s*\/*>/g, "∠") 889 .replace(/<arc\s*\/*>/g, "∠") 890 .replace(/<sqrt\s*\/*>/g, "√") 891 .replace(/<sqrt\s*\/*>/g, "√"); 892 return this.convertGeonextAndSketchometry2CSS(this.replaceSub(this.replaceSup(s)), true); 893 }, 894 895 /** 896 * Replace ticks by URI escape sequences 897 * 898 * @param {String} s 899 * @returns String 900 * @private 901 * 902 */ 903 escapeTicks: function (s) { 904 return s.replace(/"/g, "%22").replace(/'/g, "%27"); 905 }, 906 907 /** 908 * Replace escape sequences for ticks by ticks 909 * 910 * @param {String} s 911 * @returns String 912 * @private 913 */ 914 unescapeTicks: function (s) { 915 return s.replace(/%22/g, '"').replace(/%27/g, "'"); 916 }, 917 918 /** 919 * Converts the GEONExT tags <overline> and <arrow> to 920 * HTML span tags with proper CSS formatting. 921 * @private 922 * @see JXG.Text.poorMansTeX 923 * @see JXG.Text._setText 924 */ 925 convertGeonext2CSS: function (s) { 926 if (Type.isString(s)) { 927 s = s.replace( 928 /(<|<)overline(>|>)/g, 929 "<span style=text-decoration:overline;>" 930 ); 931 s = s.replace(/(<|<)\/overline(>|>)/g, "</span>"); 932 s = s.replace( 933 /(<|<)arrow(>|>)/g, 934 "<span style=text-decoration:overline;>" 935 ); 936 s = s.replace(/(<|<)\/arrow(>|>)/g, "</span>"); 937 } 938 939 return s; 940 }, 941 942 /** 943 * Converts the sketchometry tag <sketchofont> to 944 * HTML span tags with proper CSS formatting. 945 * 946 * @param {String|Function|Number} s Text 947 * @param {Boolean} escape Flag if ticks should be escaped. Escaping is necessary 948 * if s is a text. It has to be avoided if s is a function returning text. 949 * @private 950 * @see JXG.Text._setText 951 * @see JXG.Text.convertGeonextAndSketchometry2CSS 952 * 953 */ 954 convertSketchometry2CSS: function (s, escape) { 955 var t1 = "<span class=\"sketcho sketcho-inherit sketcho-", 956 t2 = "\"></span>"; 957 958 if (Type.isString(s)) { 959 if (escape) { 960 t1 = this.escapeTicks(t1); 961 t2 = this.escapeTicks(t2); 962 } 963 s = s.replace(/(<|<)sketchofont(>|>)/g, t1); 964 s = s.replace(/(<|<)\/sketchofont(>|>)/g, t2); 965 } 966 967 return s; 968 }, 969 970 /** 971 * Alias for convertGeonext2CSS and convertSketchometry2CSS 972 * 973 * @param {String|Function|Number} s Text 974 * @param {Boolean} escape Flag if ticks should be escaped 975 * @private 976 * @see JXG.Text.convertGeonext2CSS 977 * @see JXG.Text.convertSketchometry2CSS 978 */ 979 convertGeonextAndSketchometry2CSS: function (s, escape) { 980 s = this.convertGeonext2CSS(s); 981 s = this.convertSketchometry2CSS(s, escape); 982 return s; 983 }, 984 985 /** 986 * Finds dependencies in a given term and notifies the parents by adding the 987 * dependent object to the found objects child elements. 988 * @param {String} content String containing dependencies for the given object. 989 * @private 990 */ 991 notifyParents: function (content) { 992 var search, 993 res = null; 994 995 // revert possible jc replacement 996 content = content.replace(/<value>/g, "<value>"); 997 content = content.replace(/<\/value>/g, "</value>"); 998 999 do { 1000 search = /<value>([\w\s*/^\-+()[\],<>=!]+)<\/value>/; 1001 res = search.exec(content); 1002 1003 if (res !== null) { 1004 GeonextParser.findDependencies(this, res[1], this.board); 1005 content = content.slice(res.index); 1006 content = content.replace(search, ""); 1007 } 1008 } while (res !== null); 1009 1010 return this; 1011 }, 1012 1013 // documented in element.js 1014 getParents: function () { 1015 var p; 1016 if (this.relativeCoords !== undefined) { 1017 // Texts with anchor elements, excluding labels 1018 p = [ 1019 this.relativeCoords.usrCoords[1], 1020 this.relativeCoords.usrCoords[2], 1021 this.orgText 1022 ]; 1023 } else { 1024 // Other texts 1025 p = [this.Z(), this.X(), this.Y(), this.orgText]; 1026 } 1027 1028 if (this.parents.length !== 0) { 1029 p = this.parents; 1030 } 1031 1032 return p; 1033 }, 1034 1035 /** 1036 * Returns the bounding box of the text element in user coordinates as an 1037 * array of length 4: [upper left x, upper left y, lower right x, lower right y]. 1038 * The method assumes that the lower left corner is at position [el.X(), el.Y()] 1039 * of the text element el, i.e. the attributes anchorX, anchorY are ignored. 1040 * 1041 * <p> 1042 * or labels, [0, 0, 0, 0] is returned. 1043 * 1044 * @returns Array 1045 */ 1046 bounds: function () { 1047 var c = this.coords.usrCoords; 1048 1049 if ( 1050 Type.evaluate(this.visProp.islabel) || 1051 this.board.unitY === 0 || 1052 this.board.unitX === 0 1053 ) { 1054 return [0, 0, 0, 0]; 1055 } 1056 return [ 1057 c[1], 1058 c[2] + this.size[1] / this.board.unitY, 1059 c[1] + this.size[0] / this.board.unitX, 1060 c[2] 1061 ]; 1062 }, 1063 1064 /** 1065 * Returns the value of the attribute "anchorX". If this equals "auto", 1066 * returns "left", "middle", or "right", depending on the 1067 * value of the attribute "position". 1068 * @returns String 1069 */ 1070 getAnchorX: function () { 1071 var a = Type.evaluate(this.visProp.anchorx); 1072 if (a === "auto") { 1073 switch (this.visProp.position) { 1074 case "top": 1075 case "bot": 1076 return "middle"; 1077 case "rt": 1078 case "lrt": 1079 case "urt": 1080 return "left"; 1081 case "lft": 1082 case "llft": 1083 case "ulft": 1084 default: 1085 return "right"; 1086 } 1087 } 1088 return a; 1089 }, 1090 1091 /** 1092 * Returns the value of the attribute "anchorY". If this equals "auto", 1093 * returns "bottom", "middle", or "top", depending on the 1094 * value of the attribute "position". 1095 * @returns String 1096 */ 1097 getAnchorY: function () { 1098 var a = Type.evaluate(this.visProp.anchory); 1099 if (a === "auto") { 1100 switch (this.visProp.position) { 1101 case "top": 1102 case "ulft": 1103 case "urt": 1104 return "bottom"; 1105 case "bot": 1106 case "lrt": 1107 case "llft": 1108 return "top"; 1109 case "rt": 1110 case "lft": 1111 default: 1112 return "middle"; 1113 } 1114 } 1115 return a; 1116 }, 1117 1118 /** 1119 * Computes the number of overlaps of a box of w pixels width, h pixels height 1120 * and center (x, y) 1121 * 1122 * @private 1123 * @param {Number} x x-coordinate of the center (screen coordinates) 1124 * @param {Number} y y-coordinate of the center (screen coordinates) 1125 * @param {Number} w width of the box in pixel 1126 * @param {Number} h width of the box in pixel 1127 * @return {Number} Number of overlapping elements 1128 */ 1129 getNumberOfConflicts: function (x, y, w, h) { 1130 var count = 0, 1131 i, obj, le, 1132 savePointPrecision; 1133 1134 // Set the precision of hasPoint to half the max if label isn't too long 1135 savePointPrecision = this.board.options.precision.hasPoint; 1136 // this.board.options.precision.hasPoint = Math.max(w, h) * 0.5; 1137 this.board.options.precision.hasPoint = (w + h) * 0.25; 1138 // TODO: 1139 // Make it compatible with the objects' visProp.precision attribute 1140 for (i = 0, le = this.board.objectsList.length; i < le; i++) { 1141 obj = this.board.objectsList[i]; 1142 if ( 1143 obj.visPropCalc.visible && 1144 obj.elType !== "axis" && 1145 obj.elType !== "ticks" && 1146 obj !== this.board.infobox && 1147 obj !== this && 1148 obj.hasPoint(x, y) 1149 ) { 1150 count++; 1151 } 1152 } 1153 this.board.options.precision.hasPoint = savePointPrecision; 1154 1155 return count; 1156 }, 1157 1158 /** 1159 * Sets the offset of a label element to the position with the least number 1160 * of overlaps with other elements, while retaining the distance to its 1161 * anchor element. Twelve different angles are possible. 1162 * 1163 * @returns {JXG.Text} Reference to the text object. 1164 */ 1165 setAutoPosition: function () { 1166 var x, y, cx, cy, 1167 anchorCoords, 1168 // anchorX, anchorY, 1169 w = this.size[0], 1170 h = this.size[1], 1171 start_angle, angle, 1172 optimum = { 1173 conflicts: Infinity, 1174 angle: 0, 1175 r: 0 1176 }, 1177 max_r, delta_r, 1178 conflicts, offset, r, 1179 num_positions = 12, 1180 step = (2 * Math.PI) / num_positions, 1181 j, dx, dy, co, si; 1182 1183 if ( 1184 this === this.board.infobox || 1185 !this.visPropCalc.visible || 1186 !Type.evaluate(this.visProp.islabel) || 1187 !this.element 1188 ) { 1189 return this; 1190 } 1191 1192 // anchorX = Type.evaluate(this.visProp.anchorx); 1193 // anchorY = Type.evaluate(this.visProp.anchory); 1194 offset = Type.evaluate(this.visProp.offset); 1195 anchorCoords = this.element.getLabelAnchor(); 1196 cx = anchorCoords.scrCoords[1]; 1197 cy = anchorCoords.scrCoords[2]; 1198 1199 // Set dx, dy as the relative position of the center of the label 1200 // to its anchor element ignoring anchorx and anchory. 1201 dx = offset[0]; 1202 dy = offset[1]; 1203 1204 conflicts = this.getNumberOfConflicts(cx + dx, cy - dy, w, h); 1205 if (conflicts === 0) { 1206 return this; 1207 } 1208 // console.log(this.id, conflicts, w, h); 1209 // r = Geometry.distance([0, 0], offset, 2); 1210 1211 r = Type.evaluate(this.visProp.autopositionmindistance); 1212 max_r = Type.evaluate(this.visProp.autopositionmaxdistance); 1213 delta_r = 0.2 * r; 1214 1215 start_angle = Math.atan2(dy, dx); 1216 1217 optimum.conflicts = conflicts; 1218 optimum.angle = start_angle; 1219 optimum.r = r; 1220 1221 while (optimum.conflicts > 0 && r <= max_r) { 1222 for ( 1223 j = 1, angle = start_angle + step; 1224 j < num_positions && optimum.conflicts > 0; 1225 j++ 1226 ) { 1227 co = Math.cos(angle); 1228 si = Math.sin(angle); 1229 1230 x = cx + r * co; 1231 y = cy - r * si; 1232 1233 conflicts = this.getNumberOfConflicts(x, y, w, h); 1234 if (conflicts < optimum.conflicts) { 1235 optimum.conflicts = conflicts; 1236 optimum.angle = angle; 1237 optimum.r = r; 1238 } 1239 if (optimum.conflicts === 0) { 1240 break; 1241 } 1242 angle += step; 1243 } 1244 r += delta_r; 1245 } 1246 // console.log(this.id, "after", optimum) 1247 r = optimum.r; 1248 co = Math.cos(optimum.angle); 1249 si = Math.sin(optimum.angle); 1250 this.visProp.offset = [r * co, r * si]; 1251 1252 if (co < -0.2) { 1253 this.visProp.anchorx = "right"; 1254 } else if (co > 0.2) { 1255 this.visProp.anchorx = "left"; 1256 } else { 1257 this.visProp.anchorx = "middle"; 1258 } 1259 1260 return this; 1261 } 1262 } 1263 ); 1264 1265 /** 1266 * @class Construct and handle texts. 1267 * 1268 * The coordinates can either be abslute (i.e. respective to the coordinate system of the board) or be relative to the coordinates of an element 1269 * given in {@link Text#anchor}. 1270 * <p> 1271 * HTML, MathJaX, KaTeX and GEONExT syntax can be handled. 1272 * <p> 1273 * There are two ways to display texts: 1274 * <ul> 1275 * <li> using the text element of the renderer (canvas or svg). In most cases this is the suitable approach if speed matters. 1276 * However, advanced rendering like MathJax, KaTeX or HTML/CSS are not possible. 1277 * <li> using HTML <div>. This is the most flexible approach. The drawback is that HTML can only be display "above" the geometry elements. 1278 * If HTML should be displayed in an inbetween layer, conder to use an element of type {@link ForeignObject} (available in svg renderer, only). 1279 * </ul> 1280 * @pseudo 1281 * @name Text 1282 * @augments JXG.Text 1283 * @constructor 1284 * @type JXG.Text 1285 * 1286 * @param {number,function_number,function_number,function_String,function} z_,x,y,str Parent elements for text elements. 1287 * <p> 1288 * Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T 1289 * constraint, or a function which takes no parameter and returns a number. Every parent element beside the last determines one coordinate. 1290 * If a coordinate is 1291 * given by a number, the number determines the initial position of a free text. If given by a string or a function that coordinate will be constrained 1292 * that means the user won't be able to change the texts's position directly by mouse because it will be calculated automatically depending on the string 1293 * or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine Euclidean coordinates, if three such 1294 * parent elements are given they will be interpreted as homogeneous coordinates. 1295 * <p> 1296 * The text to display may be given as string or as function returning a string. 1297 * 1298 * There is the attribute 'display' which takes the values 'html' or 'internal'. In case of 'html' an HTML division tag is created to display 1299 * the text. In this case it is also possible to use MathJax, KaTeX, or ASCIIMathML. If neither of these is used, basic Math rendering is 1300 * applied. 1301 * <p> 1302 * In case of 'internal', an SVG text element is used to display the text. 1303 * @see JXG.Text 1304 * @example 1305 * // Create a fixed text at position [0,1]. 1306 * var t1 = board.create('text',[0,1,"Hello World"]); 1307 * </pre><div class="jxgbox" id="JXG896013aa-f24e-4e83-ad50-7bc7df23f6b7" style="width: 300px; height: 300px;"></div> 1308 * <script type="text/javascript"> 1309 * var t1_board = JXG.JSXGraph.initBoard('JXG896013aa-f24e-4e83-ad50-7bc7df23f6b7', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1310 * var t1 = t1_board.create('text',[0,1,"Hello World"]); 1311 * </script><pre> 1312 * @example 1313 * // Create a variable text at a variable position. 1314 * var s = board.create('slider',[[0,4],[3,4],[-2,0,2]]); 1315 * var graph = board.create('text', 1316 * [function(x){ return s.Value();}, 1, 1317 * function(){return "The value of s is"+JXG.toFixed(s.Value(), 2);} 1318 * ] 1319 * ); 1320 * </pre><div class="jxgbox" id="JXG5441da79-a48d-48e8-9e53-75594c384a1c" style="width: 300px; height: 300px;"></div> 1321 * <script type="text/javascript"> 1322 * var t2_board = JXG.JSXGraph.initBoard('JXG5441da79-a48d-48e8-9e53-75594c384a1c', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1323 * var s = t2_board.create('slider',[[0,4],[3,4],[-2,0,2]]); 1324 * var t2 = t2_board.create('text',[function(x){ return s.Value();}, 1, function(){return "The value of s is "+JXG.toFixed(s.Value(), 2);}]); 1325 * </script><pre> 1326 * @example 1327 * // Create a text bound to the point A 1328 * var p = board.create('point',[0, 1]), 1329 * t = board.create('text',[0, -1,"Hello World"], {anchor: p}); 1330 * 1331 * </pre><div class="jxgbox" id="JXGff5a64b2-2b9a-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1332 * <script type="text/javascript"> 1333 * (function() { 1334 * var board = JXG.JSXGraph.initBoard('JXGff5a64b2-2b9a-11e5-8dd9-901b0e1b8723', 1335 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1336 * var p = board.create('point',[0, 1]), 1337 * t = board.create('text',[0, -1,"Hello World"], {anchor: p}); 1338 * 1339 * })(); 1340 * 1341 * </script><pre> 1342 * 1343 */ 1344 JXG.createText = function (board, parents, attributes) { 1345 var t, 1346 attr = Type.copyAttributes(attributes, board.options, "text"), 1347 coords = parents.slice(0, -1), 1348 content = parents[parents.length - 1]; 1349 1350 // downwards compatibility 1351 attr.anchor = attr.parent || attr.anchor; 1352 t = CoordsElement.create(JXG.Text, board, coords, attr, content); 1353 1354 if (!t) { 1355 throw new Error( 1356 "JSXGraph: Can't create text with parent types '" + 1357 typeof parents[0] + 1358 "' and '" + 1359 typeof parents[1] + 1360 "'." + 1361 "\nPossible parent types: [x,y], [z,x,y], [element,transformation]" 1362 ); 1363 } 1364 1365 if (attr.rotate !== 0) { 1366 // This is the default value, i.e. no rotation 1367 t.addRotation(attr.rotate); 1368 } 1369 1370 return t; 1371 }; 1372 1373 JXG.registerElement("text", JXG.createText); 1374 1375 /** 1376 * @class Labels are text objects tied to other elements like points, lines and curves. 1377 * Labels are handled internally by JSXGraph, only. There is NO constructor "board.create('label', ...)". 1378 * 1379 * @pseudo 1380 * @name Label 1381 * @augments JXG.Text 1382 * @constructor 1383 * @type JXG.Text 1384 */ 1385 // See element.js#createLabel 1386 1387 /** 1388 * [[x,y], [w px, h px], [range] 1389 */ 1390 JXG.createHTMLSlider = function (board, parents, attributes) { 1391 var t, 1392 par, 1393 attr = Type.copyAttributes(attributes, board.options, "htmlslider"); 1394 1395 if (parents.length !== 2 || parents[0].length !== 2 || parents[1].length !== 3) { 1396 throw new Error( 1397 "JSXGraph: Can't create htmlslider with parent types '" + 1398 typeof parents[0] + 1399 "' and '" + 1400 typeof parents[1] + 1401 "'." + 1402 "\nPossible parents are: [[x,y], [min, start, max]]" 1403 ); 1404 } 1405 1406 // backwards compatibility 1407 attr.anchor = attr.parent || attr.anchor; 1408 attr.fixed = attr.fixed || true; 1409 1410 par = [ 1411 parents[0][0], 1412 parents[0][1], 1413 '<form style="display:inline">' + 1414 '<input type="range" /><span></span><input type="text" />' + 1415 "</form>" 1416 ]; 1417 1418 t = JXG.createText(board, par, attr); 1419 t.type = Type.OBJECT_TYPE_HTMLSLIDER; 1420 1421 t.rendNodeForm = t.rendNode.childNodes[0]; 1422 1423 t.rendNodeRange = t.rendNodeForm.childNodes[0]; 1424 t.rendNodeRange.min = parents[1][0]; 1425 t.rendNodeRange.max = parents[1][2]; 1426 t.rendNodeRange.step = attr.step; 1427 t.rendNodeRange.value = parents[1][1]; 1428 1429 t.rendNodeLabel = t.rendNodeForm.childNodes[1]; 1430 t.rendNodeLabel.id = t.rendNode.id + "_label"; 1431 1432 if (attr.withlabel) { 1433 t.rendNodeLabel.innerHTML = t.name + "="; 1434 } 1435 1436 t.rendNodeOut = t.rendNodeForm.childNodes[2]; 1437 t.rendNodeOut.value = parents[1][1]; 1438 1439 try { 1440 t.rendNodeForm.id = t.rendNode.id + "_form"; 1441 t.rendNodeRange.id = t.rendNode.id + "_range"; 1442 t.rendNodeOut.id = t.rendNode.id + "_out"; 1443 } catch (e) { 1444 JXG.debug(e); 1445 } 1446 1447 t.rendNodeRange.style.width = attr.widthrange + "px"; 1448 t.rendNodeRange.style.verticalAlign = "middle"; 1449 t.rendNodeOut.style.width = attr.widthout + "px"; 1450 1451 t._val = parents[1][1]; 1452 1453 if (JXG.supportsVML()) { 1454 /* 1455 * OnChange event is used for IE browsers 1456 * The range element is supported since IE10 1457 */ 1458 Env.addEvent(t.rendNodeForm, "change", priv.HTMLSliderInputEventHandler, t); 1459 } else { 1460 /* 1461 * OnInput event is used for non-IE browsers 1462 */ 1463 Env.addEvent(t.rendNodeForm, "input", priv.HTMLSliderInputEventHandler, t); 1464 } 1465 1466 t.Value = function () { 1467 return this._val; 1468 }; 1469 1470 return t; 1471 }; 1472 1473 JXG.registerElement("htmlslider", JXG.createHTMLSlider); 1474 1475 export default JXG.Text; 1476 // export default { 1477 // Text: JXG.Text, 1478 // createText: JXG.createText, 1479 // createHTMLSlider: JXG.createHTMLSlider 1480 // }; 1481