1 /* 2 Copyright 2008-2016 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 <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG: true, define: true, window: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 base/constants 39 base/coords 40 base/element 41 parser/geonext 42 math/statistics 43 utils/env 44 utils/type 45 */ 46 47 /** 48 * @fileoverview In this file the Text element is defined. 49 */ 50 51 define([ 52 'jxg', 'base/constants', 'base/coords', 'base/element', 'parser/geonext', 'math/statistics', 53 'utils/env', 'utils/type', 'math/math', 'base/coordselement' 54 ], function (JXG, Const, Coords, GeometryElement, GeonextParser, Statistics, Env, Type, Mat, CoordsElement) { 55 56 "use strict"; 57 58 var priv = { 59 HTMLSliderInputEventHandler: function () { 60 this._val = parseFloat(this.rendNodeRange.value); 61 this.rendNodeOut.value = this.rendNodeRange.value; 62 this.board.update(); 63 } 64 }; 65 66 /** 67 * Construct and handle texts. 68 * 69 * The coordinates can be relative to the coordinates of an element 70 * given in {@link JXG.Options#text.anchor}. 71 * 72 * MathJax, HTML and GEONExT syntax can be handled. 73 * @class Creates a new text object. Do not use this constructor to create a text. Use {@link JXG.Board#create} with 74 * type {@link Text} instead. 75 * @augments JXG.GeometryElement 76 * @augments JXG.CoordsElement 77 * @param {string|JXG.Board} board The board the new text is drawn on. 78 * @param {Array} coordinates An array with the user coordinates of the text. 79 * @param {Object} attributes An object containing visual properties and optional a name and a id. 80 * @param {string|function} content A string or a function returning a string. 81 * 82 */ 83 JXG.Text = function (board, coords, attributes, content) { 84 this.constructor(board, attributes, Const.OBJECT_TYPE_TEXT, Const.OBJECT_CLASS_TEXT); 85 86 this.element = this.board.select(attributes.anchor); 87 this.coordsConstructor(coords, this.visProp.islabel); 88 89 this.content = ''; 90 this.plaintext = ''; 91 this.plaintextOld = null; 92 this.orgText = ''; 93 94 this.needsSizeUpdate = false; 95 this.hiddenByParent = false; 96 97 this.size = [1.0, 1.0]; 98 this.id = this.board.setId(this, 'T'); 99 100 // Set text before drawing 101 this._setUpdateText(content); 102 this.updateText(); 103 104 this.board.renderer.drawText(this); 105 this.board.finalizeAdding(this); 106 107 if (Type.isString(this.content)) { 108 this.notifyParents(this.content); 109 } 110 this.elType = 'text'; 111 112 this.methodMap = Type.deepCopy(this.methodMap, { 113 setText: 'setTextJessieCode', 114 // free: 'free', 115 move: 'setCoords' 116 }); 117 }; 118 119 JXG.Text.prototype = new GeometryElement(); 120 Type.copyPrototypeMethods(JXG.Text, CoordsElement, 'coordsConstructor'); 121 122 JXG.extend(JXG.Text.prototype, /** @lends JXG.Text.prototype */ { 123 /** 124 * @private 125 * Test if the the screen coordinates (x,y) are in a small stripe 126 * at the left side or at the right side of the text. 127 * Sensitivity is set in this.board.options.precision.hasPoint. 128 * If dragarea is set to 'all' (default), tests if the the screen 129 * coordinates (x,y) are in within the text boundary. 130 * @param {Number} x 131 * @param {Number} y 132 * @returns {Boolean} 133 */ 134 hasPoint: function (x, y) { 135 var lft, rt, top, bot, 136 r = this.board.options.precision.hasPoint; 137 138 if (this.transformations.length > 0) { 139 //Transform the mouse/touch coordinates 140 // back to the original position of the text. 141 lft = Mat.matVecMult(Mat.inverse(this.board.renderer.joinTransforms(this, this.transformations)), [1, x, y]); 142 x = lft[1]; 143 y = lft[2]; 144 } 145 146 if (this.visProp.anchorx === 'right') { 147 lft = this.coords.scrCoords[1] - this.size[0]; 148 } else if (this.visProp.anchorx === 'middle') { 149 lft = this.coords.scrCoords[1] - 0.5 * this.size[0]; 150 } else { 151 lft = this.coords.scrCoords[1]; 152 } 153 rt = lft + this.size[0]; 154 155 if (this.visProp.anchory === 'top') { 156 bot = this.coords.scrCoords[2] + this.size[1]; 157 } else if (this.visProp.anchory === 'middle') { 158 bot = this.coords.scrCoords[2] + 0.5 * this.size[1]; 159 } else { 160 bot = this.coords.scrCoords[2]; 161 } 162 top = bot - this.size[1]; 163 164 if (this.visProp.dragarea === 'all') { 165 return x >= lft - r && x < rt + r && y >= top - r && y <= bot + r; 166 } 167 168 return (y >= top - r && y <= bot + r) && 169 ((x >= lft - r && x <= lft + 2 * r) || 170 (x >= rt - 2 * r && x <= rt + r)); 171 }, 172 173 /** 174 * This sets the updateText function of this element that depending on the type of text content passed. 175 * Used by {@link JXG.Text#_setText} and {@link JXG.Text} constructor. 176 * @param {String|Function|Number} text 177 * @private 178 */ 179 _setUpdateText: function (text) { 180 var updateText; 181 182 this.orgText = text; 183 if (Type.isFunction(text)) { 184 this.updateText = function () { 185 if (this.visProp.parse && !this.visProp.usemathjax) { 186 this.plaintext = this.replaceSub(this.replaceSup(this.convertGeonext2CSS(text()))); 187 } else { 188 this.plaintext = text(); 189 } 190 }; 191 } else if (Type.isString(text) && !this.visProp.parse) { 192 this.updateText = function () { 193 this.plaintext = text; 194 }; 195 } else { 196 if (Type.isNumber(text)) { 197 this.content = text.toFixed(this.visProp.digits); 198 } else { 199 if (this.visProp.useasciimathml) { 200 // Convert via ASCIIMathML 201 this.content = "'`" + text + "`'"; 202 } else if (this.visProp.usemathjax) { 203 this.content = "'" + text + "'"; 204 } else { 205 // Converts GEONExT syntax into JavaScript string 206 // Short math is allowed 207 this.content = this.generateTerm(text, true); 208 } 209 } 210 updateText = this.board.jc.snippet(this.content, true, '', false); 211 this.updateText = function () { 212 this.plaintext = updateText(); 213 }; 214 } 215 }, 216 217 /** 218 * Defines new content. This is used by {@link JXG.Text#setTextJessieCode} and {@link JXG.Text#setText}. This is required because 219 * JessieCode needs to filter all Texts inserted into the DOM and thus has to replace setText by setTextJessieCode. 220 * @param {String|Function|Number} text 221 * @returns {JXG.Text} 222 * @private 223 */ 224 _setText: function (text) { 225 this._setUpdateText(text); 226 227 // First evaluation of the string. 228 // We need this for display='internal' and Canvas 229 this.updateText(); 230 this.prepareUpdate().update().updateRenderer(); 231 232 // We do not call updateSize for the infobox to speed up rendering 233 if (!this.board.infobox || this.id !== this.board.infobox.id) { 234 this.updateSize(); // updateSize() is called at least once. 235 } 236 237 return this; 238 }, 239 240 /** 241 * Defines new content but converts < and > to HTML entities before updating the DOM. 242 * @param {String|function} text 243 */ 244 setTextJessieCode: function (text) { 245 var s; 246 247 this.visProp.castext = text; 248 249 if (Type.isFunction(text)) { 250 s = function () { 251 return Type.sanitizeHTML(text()); 252 }; 253 } else { 254 if (Type.isNumber(text)) { 255 s = text; 256 } else { 257 s = Type.sanitizeHTML(text); 258 } 259 } 260 261 return this._setText(s); 262 }, 263 264 /** 265 * Defines new content. 266 * @param {String|function} text 267 * @returns {JXG.Text} Reference to the text object. 268 */ 269 setText: function (text) { 270 return this._setText(text); 271 }, 272 273 /** 274 * Recompute the width and the height of the text box. 275 * Update array this.size with pixel values. 276 * The result may differ from browser to browser 277 * by some pixels. 278 * In canvas an old IEs we use a very crude estimation of the dimensions of 279 * the textbox. 280 * In JSXGraph this.size is necessary for applying rotations in IE and 281 * for aligning text. 282 */ 283 updateSize: function () { 284 var tmp, s, that, node; 285 286 if (!Env.isBrowser || this.board.renderer.type === 'no') { 287 return this; 288 } 289 290 node = this.rendNode; 291 292 /** 293 * offsetWidth and offsetHeight seem to be supported for internal vml elements by IE10+ in IE8 mode. 294 */ 295 if (this.visProp.display === 'html' || this.board.renderer.type === 'vml') { 296 if (JXG.exists(node.offsetWidth)) { 297 s = [node.offsetWidth, node.offsetHeight]; 298 if (s[0] === 0 && s[1] === 0) { // Some browsers need some time to set offsetWidth and offsetHeight 299 that = this; 300 window.setTimeout(function () { 301 that.size = [node.offsetWidth, node.offsetHeight]; 302 }, 0); 303 } else { 304 this.size = s; 305 } 306 } else { 307 this.size = this.crudeSizeEstimate(); 308 } 309 } else if (this.visProp.display === 'internal') { 310 if (this.board.renderer.type === 'svg') { 311 try { 312 tmp = node.getBBox(); 313 this.size = [tmp.width, tmp.height]; 314 } catch (e) {} 315 } else if (this.board.renderer.type === 'canvas') { 316 this.size = this.crudeSizeEstimate(); 317 } 318 } 319 320 return this; 321 }, 322 323 /** 324 * A very crude estimation of the dimensions of the textbox in case nothing else is available. 325 * @returns {Array} 326 */ 327 crudeSizeEstimate: function () { 328 return [parseFloat(this.visProp.fontsize) * this.plaintext.length * 0.45, parseFloat(this.visProp.fontsize) * 0.9]; 329 }, 330 331 /** 332 * Decode unicode entities into characters. 333 * @param {String} string 334 * @returns {String} 335 */ 336 utf8_decode : function (string) { 337 return string.replace(/(\w+);/g, function (m, p1) { 338 return String.fromCharCode(parseInt(p1, 16)); 339 }); 340 }, 341 342 /** 343 * Replace _{} by <sub> 344 * @param {String} te String containing _{}. 345 * @returns {String} Given string with _{} replaced by <sub>. 346 */ 347 replaceSub: function (te) { 348 if (!te.indexOf) { 349 return te; 350 } 351 352 var j, 353 i = te.indexOf('_{'); 354 355 // the regexp in here are not used for filtering but to provide some kind of sugar for label creation, 356 // i.e. replacing _{...} with <sub>...</sub>. What is passed would get out anyway. 357 /*jslint regexp: true*/ 358 359 while (i >= 0) { 360 te = te.substr(0, i) + te.substr(i).replace(/_\{/, '<sub>'); 361 j = te.substr(i).indexOf('}'); 362 if (j >= 0) { 363 te = te.substr(0, j) + te.substr(j).replace(/\}/, '</sub>'); 364 } 365 i = te.indexOf('_{'); 366 } 367 368 i = te.indexOf('_'); 369 while (i >= 0) { 370 te = te.substr(0, i) + te.substr(i).replace(/_(.?)/, '<sub>$1</sub>'); 371 i = te.indexOf('_'); 372 } 373 374 return te; 375 }, 376 377 /** 378 * Replace ^{} by <sup> 379 * @param {String} te String containing ^{}. 380 * @returns {String} Given string with ^{} replaced by <sup>. 381 */ 382 replaceSup: function (te) { 383 if (!te.indexOf) { 384 return te; 385 } 386 387 var j, 388 i = te.indexOf('^{'); 389 390 // the regexp in here are not used for filtering but to provide some kind of sugar for label creation, 391 // i.e. replacing ^{...} with <sup>...</sup>. What is passed would get out anyway. 392 /*jslint regexp: true*/ 393 394 while (i >= 0) { 395 te = te.substr(0, i) + te.substr(i).replace(/\^\{/, '<sup>'); 396 j = te.substr(i).indexOf('}'); 397 if (j >= 0) { 398 te = te.substr(0, j) + te.substr(j).replace(/\}/, '</sup>'); 399 } 400 i = te.indexOf('^{'); 401 } 402 403 i = te.indexOf('^'); 404 while (i >= 0) { 405 te = te.substr(0, i) + te.substr(i).replace(/\^(.?)/, '<sup>$1</sup>'); 406 i = te.indexOf('^'); 407 } 408 409 return te; 410 }, 411 412 /** 413 * Return the width of the text element. 414 * @returns {Array} [width, height] in pixel 415 */ 416 getSize: function () { 417 return this.size; 418 }, 419 420 /** 421 * Move the text to new coordinates. 422 * @param {number} x 423 * @param {number} y 424 * @returns {object} reference to the text object. 425 */ 426 setCoords: function (x, y) { 427 var coordsAnchor, dx, dy; 428 if (Type.isArray(x) && x.length > 1) { 429 y = x[1]; 430 x = x[0]; 431 } 432 433 if (this.visProp.islabel && Type.exists(this.element)) { 434 coordsAnchor = this.element.getLabelAnchor(); 435 dx = (x - coordsAnchor.usrCoords[1]) * this.board.unitX; 436 dy = -(y - coordsAnchor.usrCoords[2]) * this.board.unitY; 437 438 this.relativeCoords.setCoordinates(Const.COORDS_BY_SCREEN, [dx, dy]); 439 } else { 440 /* 441 this.X = function () { 442 return x; 443 }; 444 445 this.Y = function () { 446 return y; 447 }; 448 */ 449 this.coords.setCoordinates(Const.COORDS_BY_USER, [x, y]); 450 } 451 452 // this should be a local update, otherwise there might be problems 453 // with the tick update routine resulting in orphaned tick labels 454 this.prepareUpdate().update().updateRenderer(); 455 456 return this; 457 }, 458 459 /** 460 * Evaluates the text. 461 * Then, the update function of the renderer 462 * is called. 463 */ 464 update: function (fromParent) { 465 if (!this.needsUpdate) { 466 return this; 467 } 468 469 this.updateCoords(fromParent); 470 this.updateText(); 471 472 if (this.visProp.display === 'internal') { 473 this.plaintext = this.utf8_decode(this.plaintext); 474 } 475 476 this.checkForSizeUpdate(); 477 if (this.needsSizeUpdate) { 478 this.updateSize(); 479 } 480 481 return this; 482 }, 483 484 /** 485 * Used to save updateSize() calls. 486 * Called in JXG.Text.update 487 * That means this.update() has been called. 488 * More tests are in JXG.Renderer.updateTextStyle. The latter tests 489 * are one update off. But this should pose not too many problems, since 490 * it affects fontSize and cssClass changes. 491 * 492 * @private 493 */ 494 checkForSizeUpdate: function () { 495 if (this.board.infobox && this.id === this.board.infobox.id) { 496 this.needsSizeUpdate = false; 497 } else { 498 // For some magic reason it is more efficient on the iPad to 499 // call updateSize() for EVERY text element EVERY time. 500 this.needsSizeUpdate = (this.plaintextOld !== this.plaintext); 501 502 if (this.needsSizeUpdate) { 503 this.plaintextOld = this.plaintext; 504 } 505 } 506 507 }, 508 509 /** 510 * The update function of the renderert 511 * is called. 512 * @private 513 */ 514 updateRenderer: function () { 515 return this.updateRendererGeneric('updateText'); 516 }, 517 518 /** 519 * Converts shortened math syntax into correct syntax: 3x instead of 3*x or 520 * (a+b)(3+1) instead of (a+b)*(3+1). 521 * 522 * @private 523 * @param{String} expr Math term 524 * @returns {string} expanded String 525 */ 526 expandShortMath: function(expr) { 527 var re = /([\)0-9\.])\s*([\(a-zA-Z_])/g; 528 return expr.replace(re, '$1*$2'); 529 }, 530 531 /** 532 * Converts the GEONExT syntax of the <value> terms into JavaScript. 533 * Also, all Objects whose name appears in the term are searched and 534 * the text is added as child to these objects. 535 * 536 * @param{String} contentStr String to be parsed 537 * @param{Boolean} [expand] Optional flag if shortened math syntax is allowed (e.g. 3x instead of 3*x). 538 * @private 539 * @see JXG.GeonextParser.geonext2JS. 540 */ 541 generateTerm: function (contentStr, expand) { 542 var res, term, i, j, 543 plaintext = '""'; 544 545 // revert possible jc replacement 546 contentStr = contentStr || ''; 547 contentStr = contentStr.replace(/\r/g, ''); 548 contentStr = contentStr.replace(/\n/g, ''); 549 contentStr = contentStr.replace(/"/g, '\''); 550 contentStr = contentStr.replace(/'/g, "\\'"); 551 552 contentStr = contentStr.replace(/&arc;/g, '∠'); 553 contentStr = contentStr.replace(/<arc\s*\/>/g, '∠'); 554 contentStr = contentStr.replace(/<arc\s*\/>/g, '∠'); 555 contentStr = contentStr.replace(/<sqrt\s*\/>/g, '√'); 556 557 contentStr = contentStr.replace(/<value>/g, '<value>'); 558 contentStr = contentStr.replace(/<\/value>/g, '</value>'); 559 560 // Convert GEONExT syntax into JavaScript syntax 561 i = contentStr.indexOf('<value>'); 562 j = contentStr.indexOf('</value>'); 563 if (i >= 0) { 564 while (i >= 0) { 565 plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr.slice(0, i))) + '"'; 566 term = contentStr.slice(i + 7, j); 567 term = term.replace(/\s+/g, ''); // Remove all whitespace 568 if (expand === true) { 569 term = this.expandShortMath(term); 570 } 571 res = GeonextParser.geonext2JS(term, this.board); 572 res = res.replace(/\\"/g, "'"); 573 res = res.replace(/\\'/g, "'"); 574 575 // GEONExT-Hack: apply rounding once only. 576 if (res.indexOf('toFixed') < 0) { 577 // output of a value tag 578 if (Type.isNumber((Type.bind(this.board.jc.snippet(res, true, '', false), this))())) { 579 // may also be a string 580 plaintext += '+(' + res + ').toFixed(' + (this.visProp.digits) + ')'; 581 } else { 582 plaintext += '+(' + res + ')'; 583 } 584 } else { 585 plaintext += '+(' + res + ')'; 586 } 587 588 contentStr = contentStr.slice(j + 8); 589 i = contentStr.indexOf('<value>'); 590 j = contentStr.indexOf('</value>'); 591 } 592 } 593 594 plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr)) + '"'; 595 plaintext = this.convertGeonext2CSS(plaintext); 596 597 // This should replace π by π 598 plaintext = plaintext.replace(/&/g, '&'); 599 plaintext = plaintext.replace(/"/g, "'"); 600 601 return plaintext; 602 }, 603 604 /** 605 * Converts the GEONExT tags <overline> and <arrow> to 606 * HTML span tags with proper CSS formating. 607 * @private 608 * @see JXG.Text.generateTerm @see JXG.Text._setText 609 */ 610 convertGeonext2CSS: function (s) { 611 if (Type.isString(s)) { 612 s = s.replace(/<overline>/g, '<span style=text-decoration:overline>'); 613 s = s.replace(/<overline>/g, '<span style=text-decoration:overline>'); 614 s = s.replace(/<\/overline>/g, '</span>'); 615 s = s.replace(/<\/overline>/g, '</span>'); 616 s = s.replace(/<arrow>/g, '<span style=text-decoration:overline>'); 617 s = s.replace(/<arrow>/g, '<span style=text-decoration:overline>'); 618 s = s.replace(/<\/arrow>/g, '</span>'); 619 s = s.replace(/<\/arrow>/g, '</span>'); 620 } 621 622 return s; 623 }, 624 625 /** 626 * Finds dependencies in a given term and notifies the parents by adding the 627 * dependent object to the found objects child elements. 628 * @param {String} content String containing dependencies for the given object. 629 * @private 630 */ 631 notifyParents: function (content) { 632 var search, 633 res = null; 634 635 // revert possible jc replacement 636 content = content.replace(/<value>/g, '<value>'); 637 content = content.replace(/<\/value>/g, '</value>'); 638 639 do { 640 search = /<value>([\w\s\*\/\^\-\+\(\)\[\],<>=!]+)<\/value>/; 641 res = search.exec(content); 642 643 if (res !== null) { 644 GeonextParser.findDependencies(this, res[1], this.board); 645 content = content.substr(res.index); 646 content = content.replace(search, ''); 647 } 648 } while (res !== null); 649 650 return this; 651 }, 652 653 // documented in element.js 654 getParents: function () { 655 var p; 656 if (this.relativeCoords !== undefined) { // Texts with anchor elements, excluding labels 657 p = [this.relativeCoords.usrCoords[1], this.relativeCoords.usrCoords[2], this.orgText]; 658 } else { // Other texts 659 p = [this.Z(), this.X(), this.Y(), this.orgText]; 660 } 661 662 if (this.parents.length !== 0) { 663 p = this.parents; 664 } 665 666 return p; 667 }, 668 669 bounds: function () { 670 var c = this.coords.usrCoords; 671 672 if (this.visProp.islabel || this.board.unitY === 0 || this.board.unitX === 0) { 673 return [0, 0, 0, 0]; 674 } else { 675 return [c[1], c[2] + this.size[1] / this.board.unitY, c[1] + this.size[0] / this.board.unitX, c[2]]; 676 } 677 } 678 }); 679 680 /** 681 * @class Construct and handle texts. 682 * 683 * The coordinates can be relative to the coordinates of an element 684 * given in {@link JXG.Options#text.anchor}. 685 * 686 * MathJaX, HTML and GEONExT syntax can be handled. 687 * @pseudo 688 * @description 689 * @name Text 690 * @augments JXG.Text 691 * @constructor 692 * @type JXG.Text 693 * 694 * @param {number,function_number,function_number,function_String,function} z_,x,y,str Parent elements for text elements. 695 * <p> 696 * Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T 697 * constraint, or a function which takes no parameter and returns a number. Every parent element determines one coordinate. If a coordinate is 698 * 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 699 * 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 700 * 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 701 * parent elements are given they will be interpreted as homogeneous coordinates. 702 * <p> 703 * The text to display may be given as string or as function returning a string. 704 * 705 * There is the attribute 'display' which takes the values 'html' or 'internal'. In case of 'html' a HTML division tag is created to display 706 * the text. In this case it is also possible to use ASCIIMathML. Incase of 'internal', a SVG or VML text element is used to display the text. 707 * @see JXG.Text 708 * @example 709 * // Create a fixed text at position [0,1]. 710 * var t1 = board.create('text',[0,1,"Hello World"]); 711 * </pre><div class="jxgbox"id="896013aa-f24e-4e83-ad50-7bc7df23f6b7" style="width: 300px; height: 300px;"></div> 712 * <script type="text/javascript"> 713 * var t1_board = JXG.JSXGraph.initBoard('896013aa-f24e-4e83-ad50-7bc7df23f6b7', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 714 * var t1 = t1_board.create('text',[0,1,"Hello World"]); 715 * </script><pre> 716 * @example 717 * // Create a variable text at a variable position. 718 * var s = board.create('slider',[[0,4],[3,4],[-2,0,2]]); 719 * var graph = board.create('text', 720 * [function(x){ return s.Value();}, 1, 721 * function(){return "The value of s is"+s.Value().toFixed(2);} 722 * ] 723 * ); 724 * </pre><div class="jxgbox"id="5441da79-a48d-48e8-9e53-75594c384a1c" style="width: 300px; height: 300px;"></div> 725 * <script type="text/javascript"> 726 * var t2_board = JXG.JSXGraph.initBoard('5441da79-a48d-48e8-9e53-75594c384a1c', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 727 * var s = t2_board.create('slider',[[0,4],[3,4],[-2,0,2]]); 728 * var t2 = t2_board.create('text',[function(x){ return s.Value();}, 1, function(){return "The value of s is "+s.Value().toFixed(2);}]); 729 * </script><pre> 730 * @example 731 * // Create a text bound to the point A 732 * var p = board.create('point',[0, 1]), 733 * t = board.create('text',[0, -1,"Hello World"], {anchor: p}); 734 * 735 * </pre><div class="jxgbox"id="ff5a64b2-2b9a-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 736 * <script type="text/javascript"> 737 * (function() { 738 * var board = JXG.JSXGraph.initBoard('ff5a64b2-2b9a-11e5-8dd9-901b0e1b8723', 739 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 740 * var p = board.create('point',[0, 1]), 741 * t = board.create('text',[0, -1,"Hello World"], {anchor: p}); 742 * 743 * })(); 744 * 745 * </script><pre> 746 * 747 */ 748 JXG.createText = function (board, parents, attributes) { 749 var t, 750 attr = Type.copyAttributes(attributes, board.options, 'text'), 751 coords = parents.slice(0, -1), 752 content = parents[parents.length - 1]; 753 754 // downwards compatibility 755 attr.anchor = attr.parent || attr.anchor; 756 t = CoordsElement.create(JXG.Text, board, coords, attr, content); 757 758 if (!t) { 759 throw new Error("JSXGraph: Can't create text with parent types '" + 760 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 761 "\nPossible parent types: [x,y], [z,x,y], [element,transformation]"); 762 } 763 764 if (Type.evaluate(attr.rotate) !== 0 && attr.display === 'internal') { 765 t.addRotation(Type.evaluate(attr.rotate)); 766 } 767 768 return t; 769 }; 770 771 JXG.registerElement('text', JXG.createText); 772 773 /** 774 * [[x,y], [w px, h px], [range] 775 */ 776 JXG.createHTMLSlider = function (board, parents, attributes) { 777 var t, par, 778 attr = Type.copyAttributes(attributes, board.options, 'htmlslider'); 779 780 if (parents.length !== 2 || parents[0].length !== 2 || parents[1].length !== 3) { 781 throw new Error("JSXGraph: Can't create htmlslider with parent types '" + 782 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 783 "\nPossible parents are: [[x,y], [min, start, max]]"); 784 } 785 786 // backwards compatibility 787 attr.anchor = attr.parent || attr.anchor; 788 attr.fixed = attr.fixed || true; 789 790 par = [parents[0][0], parents[0][1], 791 '<form style="display:inline">' + 792 '<input type="range" /><span></span><input type="text" />' + 793 '</form>']; 794 795 t = JXG.createText(board, par, attr); 796 t.type = Type.OBJECT_TYPE_HTMLSLIDER; 797 798 t.rendNodeForm = t.rendNode.childNodes[0]; 799 t.rendNodeForm.id = t.rendNode.id + '_form'; 800 801 t.rendNodeRange = t.rendNodeForm.childNodes[0]; 802 t.rendNodeRange.id = t.rendNode.id + '_range'; 803 t.rendNodeRange.min = parents[1][0]; 804 t.rendNodeRange.max = parents[1][2]; 805 t.rendNodeRange.step = attr.step; 806 t.rendNodeRange.value = parents[1][1]; 807 808 t.rendNodeLabel = t.rendNodeForm.childNodes[1]; 809 t.rendNodeLabel.id = t.rendNode.id + '_label'; 810 811 if (attr.withlabel) { 812 t.rendNodeLabel.innerHTML = t.name + '='; 813 } 814 815 t.rendNodeOut = t.rendNodeForm.childNodes[2]; 816 t.rendNodeOut.id = t.rendNode.id + '_out'; 817 t.rendNodeOut.value = parents[1][1]; 818 819 t.rendNodeRange.style.width = attr.widthrange + 'px'; 820 t.rendNodeRange.style.verticalAlign = 'middle'; 821 t.rendNodeOut.style.width = attr.widthout + 'px'; 822 823 t._val = parents[1][1]; 824 825 if (JXG.supportsVML()) { 826 /* 827 * OnChange event is used for IE browsers 828 * The range element is supported since IE10 829 */ 830 Env.addEvent(t.rendNodeForm, 'change', priv.HTMLSliderInputEventHandler, t); 831 } else { 832 /* 833 * OnInput event is used for non-IE browsers 834 */ 835 Env.addEvent(t.rendNodeForm, 'input', priv.HTMLSliderInputEventHandler, t); 836 } 837 838 t.Value = function () { 839 return this._val; 840 }; 841 842 return t; 843 }; 844 845 JXG.registerElement('htmlslider', JXG.createHTMLSlider); 846 847 return { 848 Text: JXG.Text, 849 createText: JXG.createText, 850 createHTMLSlider: JXG.createHTMLSlider 851 }; 852 }); 853