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