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