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