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