1 /* 2 Copyright 2008-2023 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true, window: true */ 33 34 /* 35 nomen: Allow underscores to indicate private class members. Might be replaced by local variables. 36 plusplus: Only allowed in for-loops 37 newcap: AsciiMathMl exposes non-constructor functions beginning with upper case letters 38 */ 39 /*jslint nomen: true, plusplus: true, newcap: true, unparam: true*/ 40 /*eslint no-unused-vars: "off"*/ 41 42 /** 43 * @fileoverview JSXGraph can use various technologies to render the contents of a construction, e.g. 44 * SVG, VML, and HTML5 Canvas. To accomplish this, The rendering and the logic and control mechanisms 45 * are completely separated from each other. Every rendering technology has it's own class, called 46 * Renderer, e.g. SVGRenderer for SVG, the same for VML and Canvas. The common base for all available 47 * renderers is the class AbstractRenderer defined in this file. 48 */ 49 50 import JXG from "../jxg"; 51 import Options from "../options"; 52 import Coords from "../base/coords"; 53 import Const from "../base/constants"; 54 import Mat from "../math/math"; 55 import Geometry from "../math/geometry"; 56 import Type from "../utils/type"; 57 import Env from "../utils/env"; 58 59 /** 60 * <p>This class defines the interface to the graphics part of JSXGraph. This class is an abstract class, it 61 * actually does not render anything. This is up to the {@link JXG.SVGRenderer}, {@link JXG.VMLRenderer}, 62 * and {@link JXG.CanvasRenderer} classes. We strongly discourage you from using the methods in these classes 63 * directly. Only the methods which are defined in this class and are not marked as private are guaranteed 64 * to exist in any renderer instance you can access via {@link JXG.Board#renderer}. But not all methods may 65 * work as expected.</p> 66 * <p>The methods of this renderer can be divided into different categories: 67 * <dl> 68 * <dt>Draw basic elements</dt> 69 * <dd>In this category we find methods to draw basic elements like {@link JXG.Point}, {@link JXG.Line}, 70 * and {@link JXG.Curve} as well as assisting methods tightly bound to these basic painters. You do not 71 * need to implement these methods in a descendant renderer but instead implement the primitive drawing 72 * methods described below. This approach is encouraged when you're using a XML based rendering engine 73 * like VML and SVG. If you want to use a bitmap based rendering technique you are supposed to override 74 * these methods instead of the primitive drawing methods.</dd> 75 * <dt>Draw primitives</dt> 76 * <dd>This category summarizes methods to handle primitive nodes. As creation and management of these nodes 77 * is different among different the rendering techniques most of these methods are purely virtual and need 78 * proper implementation if you choose to not overwrite the basic element drawing methods.</dd> 79 * <dt>Attribute manipulation</dt> 80 * <dd>In XML based renders you have to manipulate XML nodes and their attributes to change the graphics. 81 * For that purpose attribute manipulation methods are defined to set the color, opacity, and other things. 82 * Please note that some of these methods are required in bitmap based renderers, too, because some elements 83 * like {@link JXG.Text} can be HTML nodes floating over the construction.</dd> 84 * <dt>Renderer control</dt> 85 * <dd>Methods to clear the drawing board or to stop and to resume the rendering engine.</dd> 86 * </dl></p> 87 * @class JXG.AbstractRenderer 88 * @constructor 89 * @see JXG.SVGRenderer 90 * @see JXG.VMLRenderer 91 * @see JXG.CanvasRenderer 92 */ 93 JXG.AbstractRenderer = function () { 94 // WHY THIS IS A CLASS INSTEAD OF A SINGLETON OBJECT: 95 // 96 // The renderers need to keep track of some stuff which is not always the same on different boards, 97 // like enhancedRendering, reference to the container object, and resolution in VML. Sure, those 98 // things could be stored in board. But they are rendering related and JXG.Board is already very 99 // very big. 100 // 101 // And we can't save the rendering related data in {SVG,VML,Canvas}Renderer and make only the 102 // JXG.AbstractRenderer a singleton because of that: 103 // 104 // Given an object o with property a set to true 105 // var o = {a: true}; 106 // and a class c doing nothing 107 // c = function() {}; 108 // Set c's prototype to o 109 // c.prototype = o; 110 // and create an instance of c we get i.a to be true 111 // i = new c(); 112 // i.a; 113 // > true 114 // But we can overwrite this property via 115 // c.prototype.a = false; 116 // i.a; 117 // > false 118 119 /** 120 * The vertical offset for {@link Text} elements. Every {@link Text} element will 121 * be placed this amount of pixels below the user given coordinates. 122 * @type Number 123 * @default 0 124 */ 125 this.vOffsetText = 0; 126 127 /** 128 * If this property is set to <tt>true</tt> the visual properties of the elements are updated 129 * on every update. Visual properties means: All the stuff stored in the 130 * {@link JXG.GeometryElement#visProp} property won't be set if enhancedRendering is <tt>false</tt> 131 * @type Boolean 132 * @default true 133 */ 134 this.enhancedRendering = true; 135 136 /** 137 * The HTML element that stores the JSXGraph board in it. 138 * @type Node 139 */ 140 this.container = null; 141 142 /** 143 * This is used to easily determine which renderer we are using 144 * @example if (board.renderer.type === 'vml') { 145 * // do something 146 * } 147 * @type String 148 */ 149 this.type = ""; 150 151 /** 152 * True if the browsers' SVG engine supports foreignObject. 153 * Not supported browsers are IE 9 - 11. 154 * It is tested in svg renderer. 155 * 156 * @type Boolean 157 * @private 158 */ 159 this.supportsForeignObject = false; 160 }; 161 162 JXG.extend( 163 JXG.AbstractRenderer.prototype, 164 /** @lends JXG.AbstractRenderer.prototype */ { 165 /* ******************************** * 166 * private methods * 167 * should not be called from * 168 * outside AbstractRenderer * 169 * ******************************** */ 170 171 /** 172 * Update visual properties, but only if {@link JXG.AbstractRenderer#enhancedRendering} or <tt>enhanced</tt> is set to true. 173 * @param {JXG.GeometryElement} el The element to update 174 * @param {Object} [not={}] Select properties you don't want to be updated: <tt>{fill: true, dash: true}</tt> updates 175 * everything except for fill and dash. Possible values are <tt>stroke, fill, dash, shadow, gradient</tt>. 176 * @param {Boolean} [enhanced=false] If true, {@link JXG.AbstractRenderer#enhancedRendering} is assumed to be true. 177 * @private 178 */ 179 _updateVisual: function (el, not, enhanced) { 180 if (enhanced || this.enhancedRendering) { 181 not = not || {}; 182 183 this.setObjectTransition(el); 184 if (!Type.evaluate(el.visProp.draft)) { 185 if (!not.stroke) { 186 if (el.highlighted) { 187 this.setObjectStrokeColor( 188 el, 189 el.visProp.highlightstrokecolor, 190 el.visProp.highlightstrokeopacity 191 ); 192 this.setObjectStrokeWidth(el, el.visProp.highlightstrokewidth); 193 } else { 194 this.setObjectStrokeColor( 195 el, 196 el.visProp.strokecolor, 197 el.visProp.strokeopacity 198 ); 199 this.setObjectStrokeWidth(el, el.visProp.strokewidth); 200 } 201 } 202 203 if (!not.fill) { 204 if (el.highlighted) { 205 this.setObjectFillColor( 206 el, 207 el.visProp.highlightfillcolor, 208 el.visProp.highlightfillopacity 209 ); 210 } else { 211 this.setObjectFillColor( 212 el, 213 el.visProp.fillcolor, 214 el.visProp.fillopacity 215 ); 216 } 217 } 218 219 if (!not.dash) { 220 this.setDashStyle(el, el.visProp); 221 } 222 223 if (!not.shadow) { 224 this.setShadow(el); 225 } 226 227 if (!not.gradient) { 228 this.setShadow(el); 229 } 230 231 if (!not.tabindex) { 232 this.setTabindex(el); 233 } 234 } else { 235 this.setDraft(el); 236 } 237 } 238 }, 239 240 /** 241 * Get information if element is highlighted. 242 * @param {JXG.GeometryElement} el The element which is tested for being highlighted. 243 * @returns {String} 'highlight' if highlighted, otherwise the ampty string '' is returned. 244 * @private 245 */ 246 _getHighlighted: function (el) { 247 var isTrace = false, 248 hl; 249 250 if (!Type.exists(el.board) || !Type.exists(el.board.highlightedObjects)) { 251 // This case handles trace elements. 252 // To make them work, we simply neglect highlighting. 253 isTrace = true; 254 } 255 256 if (!isTrace && Type.exists(el.board.highlightedObjects[el.id])) { 257 hl = "highlight"; 258 } else { 259 hl = ""; 260 } 261 return hl; 262 }, 263 264 /* ******************************** * 265 * Point drawing and updating * 266 * ******************************** */ 267 268 /** 269 * Draws a point on the {@link JXG.Board}. 270 * @param {JXG.Point} el Reference to a {@link JXG.Point} object that has to be drawn. 271 * @see Point 272 * @see JXG.Point 273 * @see JXG.AbstractRenderer#updatePoint 274 * @see JXG.AbstractRenderer#changePointStyle 275 */ 276 drawPoint: function (el) { 277 var prim, 278 // sometimes el is not a real point and lacks the methods of a JXG.Point instance, 279 // in these cases to not use el directly. 280 face = Options.normalizePointFace(Type.evaluate(el.visProp.face)); 281 282 // determine how the point looks like 283 if (face === "o") { 284 prim = "ellipse"; 285 } else if (face === "[]") { 286 prim = "rect"; 287 } else { 288 // cross/x, diamond/<>, triangleup/a/^, triangledown/v, triangleleft/<, 289 // triangleright/>, plus/+, |, - 290 prim = "path"; 291 } 292 293 el.rendNode = this.appendChildPrim( 294 this.createPrim(prim, el.id), 295 Type.evaluate(el.visProp.layer) 296 ); 297 this.appendNodesToElement(el, prim); 298 299 // adjust visual propertys 300 this._updateVisual(el, { dash: true, shadow: true }, true); 301 302 // By now we only created the xml nodes and set some styles, in updatePoint 303 // the attributes are filled with data. 304 this.updatePoint(el); 305 }, 306 307 /** 308 * Updates visual appearance of the renderer element assigned to the given {@link JXG.Point}. 309 * @param {JXG.Point} el Reference to a {@link JXG.Point} object, that has to be updated. 310 * @see Point 311 * @see JXG.Point 312 * @see JXG.AbstractRenderer#drawPoint 313 * @see JXG.AbstractRenderer#changePointStyle 314 */ 315 updatePoint: function (el) { 316 var size = Type.evaluate(el.visProp.size), 317 // sometimes el is not a real point and lacks the methods of a JXG.Point instance, 318 // in these cases to not use el directly. 319 face = Options.normalizePointFace(Type.evaluate(el.visProp.face)), 320 unit = Type.evaluate(el.visProp.sizeunit), 321 zoom = Type.evaluate(el.visProp.zoom), 322 s1; 323 324 if (!isNaN(el.coords.scrCoords[2] + el.coords.scrCoords[1])) { 325 if (unit === "user") { 326 size *= Math.sqrt(el.board.unitX * el.board.unitY); 327 } 328 size *= !el.board || !zoom ? 1.0 : Math.sqrt(el.board.zoomX * el.board.zoomY); 329 s1 = size === 0 ? 0 : size + 1; 330 331 if (face === "o") { 332 // circle 333 this.updateEllipsePrim( 334 el.rendNode, 335 el.coords.scrCoords[1], 336 el.coords.scrCoords[2], 337 s1, 338 s1 339 ); 340 } else if (face === "[]") { 341 // rectangle 342 this.updateRectPrim( 343 el.rendNode, 344 el.coords.scrCoords[1] - size, 345 el.coords.scrCoords[2] - size, 346 size * 2, 347 size * 2 348 ); 349 } else { 350 // x, +, <>, ^, v, <, > 351 this.updatePathPrim( 352 el.rendNode, 353 this.updatePathStringPoint(el, size, face), 354 el.board 355 ); 356 } 357 this._updateVisual(el, { dash: false, shadow: false }); 358 this.setShadow(el); 359 } 360 }, 361 362 /** 363 * Changes the style of a {@link JXG.Point}. This is required because the point styles differ in what 364 * elements have to be drawn, e.g. if the point is marked by a "x" or a "+" two lines are drawn, if 365 * it's marked by spot a circle is drawn. This method removes the old renderer element(s) and creates 366 * the new one(s). 367 * @param {JXG.Point} el Reference to a {@link JXG.Point} object, that's style is changed. 368 * @see Point 369 * @see JXG.Point 370 * @see JXG.AbstractRenderer#updatePoint 371 * @see JXG.AbstractRenderer#drawPoint 372 */ 373 changePointStyle: function (el) { 374 var node = this.getElementById(el.id); 375 376 // remove the existing point rendering node 377 if (Type.exists(node)) { 378 this.remove(node); 379 } 380 381 // and make a new one 382 this.drawPoint(el); 383 Type.clearVisPropOld(el); 384 385 if (!el.visPropCalc.visible) { 386 this.display(el, false); 387 } 388 389 if (Type.evaluate(el.visProp.draft)) { 390 this.setDraft(el); 391 } 392 }, 393 394 /* ******************************** * 395 * Lines * 396 * ******************************** */ 397 398 /** 399 * Draws a line on the {@link JXG.Board}. 400 * @param {JXG.Line} el Reference to a line object, that has to be drawn. 401 * @see Line 402 * @see JXG.Line 403 * @see JXG.AbstractRenderer#updateLine 404 */ 405 drawLine: function (el) { 406 el.rendNode = this.appendChildPrim( 407 this.createPrim("line", el.id), 408 Type.evaluate(el.visProp.layer) 409 ); 410 this.appendNodesToElement(el, "lines"); 411 this.updateLine(el); 412 }, 413 414 /** 415 * Updates visual appearance of the renderer element assigned to the given {@link JXG.Line}. 416 * @param {JXG.Line} el Reference to the {@link JXG.Line} object that has to be updated. 417 * @see Line 418 * @see JXG.Line 419 * @see JXG.AbstractRenderer#drawLine 420 */ 421 updateLine: function (el) { 422 this._updateVisual(el); 423 this.updatePathWithArrowHeads(el); // Calls the renderer primitive 424 this.setLineCap(el); 425 }, 426 427 /* ************************** 428 * Curves 429 * **************************/ 430 431 /** 432 * Draws a {@link JXG.Curve} on the {@link JXG.Board}. 433 * @param {JXG.Curve} el Reference to a graph object, that has to be plotted. 434 * @see Curve 435 * @see JXG.Curve 436 * @see JXG.AbstractRenderer#updateCurve 437 */ 438 drawCurve: function (el) { 439 el.rendNode = this.appendChildPrim( 440 this.createPrim("path", el.id), 441 Type.evaluate(el.visProp.layer) 442 ); 443 this.appendNodesToElement(el, "path"); 444 this.updateCurve(el); 445 }, 446 447 /** 448 * Updates visual appearance of the renderer element assigned to the given {@link JXG.Curve}. 449 * @param {JXG.Curve} el Reference to a {@link JXG.Curve} object, that has to be updated. 450 * @see Curve 451 * @see JXG.Curve 452 * @see JXG.AbstractRenderer#drawCurve 453 */ 454 updateCurve: function (el) { 455 this._updateVisual(el); 456 this.updatePathWithArrowHeads(el); // Calls the renderer primitive 457 this.setLineCap(el); 458 }, 459 460 /* ************************** 461 * Arrow heads and related stuff 462 * **************************/ 463 464 /** 465 * Handles arrow heads of a line or curve element and calls the renderer primitive. 466 * 467 * @param {JXG.GeometryElement} el Reference to a line or curve object that has to be drawn. 468 * @param {Boolean} doHighlight 469 * 470 * @private 471 * @see Line 472 * @see JXG.Line 473 * @see Curve 474 * @see JXG.Curve 475 * @see JXG.AbstractRenderer#updateLine 476 * @see JXG.AbstractRenderer#updateCurve 477 * @see JXG.AbstractRenderer#makeArrows 478 * @see JXG.AbstractRenderer#getArrowHeadData 479 */ 480 updatePathWithArrowHeads: function (el, doHighlight) { 481 var ev = el.visProp, 482 hl = doHighlight ? "highlight" : "", 483 w, 484 arrowData; 485 486 if (doHighlight && ev.highlightstrokewidth) { 487 w = Math.max( 488 Type.evaluate(ev.highlightstrokewidth), 489 Type.evaluate(ev.strokewidth) 490 ); 491 } else { 492 w = Type.evaluate(ev.strokewidth); 493 } 494 495 // Get information if there are arrow heads and how large they are. 496 arrowData = this.getArrowHeadData(el, w, hl); 497 498 // Create the SVG nodes if neccessary 499 this.makeArrows(el, arrowData); 500 501 // Draw the paths with arrow heads 502 if (el.elementClass === Const.OBJECT_CLASS_LINE) { 503 this.updateLineWithEndings(el, arrowData); 504 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) { 505 this.updatePath(el); 506 } 507 508 this.setArrowSize(el, arrowData); 509 }, 510 511 /** 512 * This method determines some data about the line endings of this element. 513 * If there are arrow heads, the offset is determined so that no parts of the line stroke 514 * lap over the arrow head. 515 * <p> 516 * The returned object also contains the types of the arrow heads. 517 * 518 * @param {JXG.GeometryElement} el JSXGraph line or curve element 519 * @param {Number} strokewidth strokewidth of the element 520 * @param {String} hl Ither 'highlight' or empty string 521 * @returns {Object} object containing the data 522 * 523 * @private 524 */ 525 getArrowHeadData: function (el, strokewidth, hl) { 526 var minlen = Mat.eps, 527 typeFirst, 528 typeLast, 529 offFirst = 0, 530 offLast = 0, 531 sizeFirst = 0, 532 sizeLast = 0, 533 ev_fa = Type.evaluate(el.visProp.firstarrow), 534 ev_la = Type.evaluate(el.visProp.lastarrow), 535 off, 536 size; 537 538 /* 539 Handle arrow heads. 540 541 The default arrow head is an isosceles triangle with base length 10 units and height 10 units. 542 These 10 units are scaled to strokeWidth * arrowSize pixels. 543 */ 544 if (ev_fa || ev_la) { 545 if (Type.exists(ev_fa.type)) { 546 typeFirst = Type.evaluate(ev_fa.type); 547 } else { 548 if (el.elementClass === Const.OBJECT_CLASS_LINE) { 549 typeFirst = 1; 550 } else { 551 typeFirst = 7; 552 } 553 } 554 if (Type.exists(ev_la.type)) { 555 typeLast = Type.evaluate(ev_la.type); 556 } else { 557 if (el.elementClass === Const.OBJECT_CLASS_LINE) { 558 typeLast = 1; 559 } else { 560 typeLast = 7; 561 } 562 } 563 564 if (ev_fa) { 565 size = 6; 566 if (Type.exists(ev_fa.size)) { 567 size = Type.evaluate(ev_fa.size); 568 } 569 if (hl !== "" && Type.exists(ev_fa[hl + "size"])) { 570 size = Type.evaluate(ev_fa[hl + "size"]); 571 } 572 573 off = strokewidth * size; 574 if (typeFirst === 2) { 575 off *= 0.5; 576 minlen += strokewidth * size; 577 } else if (typeFirst === 3) { 578 off = (strokewidth * size) / 3; 579 minlen += strokewidth; 580 } else if (typeFirst === 4 || typeFirst === 5 || typeFirst === 6) { 581 off = (strokewidth * size) / 1.5; 582 minlen += strokewidth * size; 583 } else if (typeFirst === 7) { 584 off = 0; 585 size = 10; 586 minlen += strokewidth; 587 } else { 588 minlen += strokewidth * size; 589 } 590 offFirst += off; 591 sizeFirst = size; 592 } 593 594 if (ev_la) { 595 size = 6; 596 if (Type.exists(ev_la.size)) { 597 size = Type.evaluate(ev_la.size); 598 } 599 if (hl !== "" && Type.exists(ev_la[hl + "size"])) { 600 size = Type.evaluate(ev_la[hl + "size"]); 601 } 602 off = strokewidth * size; 603 if (typeLast === 2) { 604 off *= 0.5; 605 minlen += strokewidth * size; 606 } else if (typeLast === 3) { 607 off = (strokewidth * size) / 3; 608 minlen += strokewidth; 609 } else if (typeLast === 4 || typeLast === 5 || typeLast === 6) { 610 off = (strokewidth * size) / 1.5; 611 minlen += strokewidth * size; 612 } else if (typeLast === 7) { 613 off = 0; 614 size = 10; 615 minlen += strokewidth; 616 } else { 617 minlen += strokewidth * size; 618 } 619 offLast += off; 620 sizeLast = size; 621 } 622 } 623 el.visPropCalc.typeFirst = typeFirst; 624 el.visPropCalc.typeLast = typeLast; 625 626 return { 627 evFirst: ev_fa, 628 evLast: ev_la, 629 typeFirst: typeFirst, 630 typeLast: typeLast, 631 offFirst: offFirst, 632 offLast: offLast, 633 sizeFirst: sizeFirst, 634 sizeLast: sizeLast, 635 showFirst: 1, // Show arrow head. 0 if the distance is too small 636 showLast: 1, // Show arrow head. 0 if the distance is too small 637 minLen: minlen, 638 strokeWidth: strokewidth 639 }; 640 }, 641 642 /** 643 * Corrects the line length if there are arrow heads, such that 644 * the arrow ends exactly at the intended position. 645 * Calls the renderer method to draw the line. 646 * 647 * @param {JXG.Line} el Reference to a line object, that has to be drawn 648 * @param {Object} arrowData Data concerning possible arrow heads 649 * 650 * @returns {JXG.AbstractRenderer} Reference to the renderer 651 * 652 * @private 653 * @see Line 654 * @see JXG.Line 655 * @see JXG.AbstractRenderer#updateLine 656 * @see JXG.AbstractRenderer#getPositionArrowHead 657 * 658 */ 659 updateLineWithEndings: function (el, arrowData) { 660 var c1, 661 c2, 662 // useTotalLength = true, 663 margin = null; 664 665 c1 = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords, el.board); 666 c2 = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords, el.board); 667 margin = Type.evaluate(el.visProp.margin); 668 Geometry.calcStraight(el, c1, c2, margin); 669 670 this.handleTouchpoints(el, c1, c2, arrowData); 671 this.getPositionArrowHead(el, c1, c2, arrowData); 672 673 this.updateLinePrim( 674 el.rendNode, 675 c1.scrCoords[1], 676 c1.scrCoords[2], 677 c2.scrCoords[1], 678 c2.scrCoords[2], 679 el.board 680 ); 681 682 return this; 683 }, 684 685 /** 686 * 687 * Calls the renderer method to draw a curve. 688 * 689 * @param {JXG.GeometryElement} el Reference to a line object, that has to be drawn. 690 * @returns {JXG.AbstractRenderer} Reference to the renderer 691 * 692 * @private 693 * @see Curve 694 * @see JXG.Curve 695 * @see JXG.AbstractRenderer#updateCurve 696 * 697 */ 698 updatePath: function (el) { 699 if (Type.evaluate(el.visProp.handdrawing)) { 700 this.updatePathPrim(el.rendNode, this.updatePathStringBezierPrim(el), el.board); 701 } else { 702 this.updatePathPrim(el.rendNode, this.updatePathStringPrim(el), el.board); 703 } 704 705 return this; 706 }, 707 708 /** 709 * Shorten the length of a line element such that the arrow head touches 710 * the start or end point and such that the arrow head ends exactly 711 * at the start / end position of the line. 712 * 713 * @param {JXG.Line} el Reference to the line object that gets arrow heads. 714 * @param {JXG.Coords} c1 Coords of the first point of the line (after {@link JXG.Math.Geometry#calcStraight}). 715 * @param {JXG.Coords} c2 Coords of the second point of the line (after {@link JXG.Math.Geometry#calcStraight}). 716 * @param {Object} a 717 * @return {object} Object containing how much the line has to be shortened. 718 * Data structure: {c1, c2, d1x, d1y, d2x, d2y, sFirst, sLast}. sFirst and sLast is the length by which 719 * firstArrow and lastArrow have to shifted such that there is no gap between arrow head and line. 720 * Additionally, if one of these values is zero, the arrow is not displayed. This is the case, if the 721 * line length is very short. 722 */ 723 getPositionArrowHead: function (el, c1, c2, a) { 724 var d, d1x, d1y, d2x, d2y; 725 726 /* 727 Handle arrow heads. 728 729 The default arrow head (type==1) is an isosceles triangle with base length 10 units and height 10 units. 730 These 10 units are scaled to strokeWidth * arrowSize pixels. 731 */ 732 if (a.evFirst || a.evLast) { 733 // Correct the position of the arrow heads 734 d1x = d1y = d2x = d2y = 0.0; 735 d = c1.distance(Const.COORDS_BY_SCREEN, c2); 736 737 if (a.evFirst && el.board.renderer.type !== "vml") { 738 if (d >= a.minLen) { 739 d1x = ((c2.scrCoords[1] - c1.scrCoords[1]) * a.offFirst) / d; 740 d1y = ((c2.scrCoords[2] - c1.scrCoords[2]) * a.offFirst) / d; 741 } else { 742 a.showFirst = 0; 743 } 744 } 745 746 if (a.evLast && el.board.renderer.type !== "vml") { 747 if (d >= a.minLen) { 748 d2x = ((c2.scrCoords[1] - c1.scrCoords[1]) * a.offLast) / d; 749 d2y = ((c2.scrCoords[2] - c1.scrCoords[2]) * a.offLast) / d; 750 } else { 751 a.showLast = 0; 752 } 753 } 754 c1.setCoordinates( 755 Const.COORDS_BY_SCREEN, 756 [c1.scrCoords[1] + d1x, c1.scrCoords[2] + d1y], 757 false, 758 true 759 ); 760 c2.setCoordinates( 761 Const.COORDS_BY_SCREEN, 762 [c2.scrCoords[1] - d2x, c2.scrCoords[2] - d2y], 763 false, 764 true 765 ); 766 } 767 768 return this; 769 }, 770 771 /** 772 * Handle touchlastpoint / touchfirstpoint 773 * 774 * @param {JXG.GeometryElement} el 775 * @param {JXG.Coords} c1 Coordinates of the start of the line. The coordinates are changed in place. 776 * @param {JXG.Coords} c2 Coordinates of the end of the line. The coordinates are changed in place. 777 * @param {Object} a 778 */ 779 handleTouchpoints: function (el, c1, c2, a) { 780 var s1, s2, d, d1x, d1y, d2x, d2y; 781 782 if (a.evFirst || a.evLast) { 783 d = d1x = d1y = d2x = d2y = 0.0; 784 785 s1 = 786 Type.evaluate(el.point1.visProp.size) + 787 Type.evaluate(el.point1.visProp.strokewidth); 788 s2 = 789 Type.evaluate(el.point2.visProp.size) + 790 Type.evaluate(el.point2.visProp.strokewidth); 791 792 // Handle touchlastpoint /touchfirstpoint 793 if (a.evFirst && Type.evaluate(el.visProp.touchfirstpoint)) { 794 d = c1.distance(Const.COORDS_BY_SCREEN, c2); 795 //if (d > s) { 796 d1x = ((c2.scrCoords[1] - c1.scrCoords[1]) * s1) / d; 797 d1y = ((c2.scrCoords[2] - c1.scrCoords[2]) * s1) / d; 798 //} 799 } 800 if (a.evLast && Type.evaluate(el.visProp.touchlastpoint)) { 801 d = c1.distance(Const.COORDS_BY_SCREEN, c2); 802 //if (d > s) { 803 d2x = ((c2.scrCoords[1] - c1.scrCoords[1]) * s2) / d; 804 d2y = ((c2.scrCoords[2] - c1.scrCoords[2]) * s2) / d; 805 //} 806 } 807 c1.setCoordinates( 808 Const.COORDS_BY_SCREEN, 809 [c1.scrCoords[1] + d1x, c1.scrCoords[2] + d1y], 810 false, 811 true 812 ); 813 c2.setCoordinates( 814 Const.COORDS_BY_SCREEN, 815 [c2.scrCoords[1] - d2x, c2.scrCoords[2] - d2y], 816 false, 817 true 818 ); 819 } 820 821 return this; 822 }, 823 824 /** 825 * Set the arrow head size. 826 * 827 * @param {JXG.GeometryElement} el Reference to a line or curve object that has to be drawn. 828 * @param {Object} arrowData Data concerning possible arrow heads 829 * @returns {JXG.AbstractRenderer} Reference to the renderer 830 * 831 * @private 832 * @see Line 833 * @see JXG.Line 834 * @see Curve 835 * @see JXG.Curve 836 * @see JXG.AbstractRenderer#updatePathWithArrowHeads 837 * @see JXG.AbstractRenderer#getArrowHeadData 838 */ 839 setArrowSize: function (el, a) { 840 if (a.evFirst) { 841 this._setArrowWidth( 842 el.rendNodeTriangleStart, 843 a.showFirst * a.strokeWidth, 844 el.rendNode, 845 a.sizeFirst 846 ); 847 } 848 if (a.evLast) { 849 this._setArrowWidth( 850 el.rendNodeTriangleEnd, 851 a.showLast * a.strokeWidth, 852 el.rendNode, 853 a.sizeLast 854 ); 855 } 856 return this; 857 }, 858 859 /** 860 * Update the line endings (linecap) of a straight line from its attribute 861 * 'linecap'. 862 * Possible values for the attribute 'linecap' are: 'butt', 'round', 'square'. 863 * The default value is 'butt'. Not available for VML renderer. 864 * 865 * @param {JXG.Line} element A arbitrary line. 866 * @see Line 867 * @see JXG.Line 868 * @see JXG.AbstractRenderer#updateLine 869 */ 870 setLineCap: function (el) { 871 /* stub */ 872 }, 873 874 /* ************************** 875 * Ticks related stuff 876 * **************************/ 877 878 /** 879 * Creates a rendering node for ticks added to a line. 880 * @param {JXG.Line} el A arbitrary line. 881 * @see Line 882 * @see Ticks 883 * @see JXG.Line 884 * @see JXG.Ticks 885 * @see JXG.AbstractRenderer#updateTicks 886 */ 887 drawTicks: function (el) { 888 el.rendNode = this.appendChildPrim( 889 this.createPrim("path", el.id), 890 Type.evaluate(el.visProp.layer) 891 ); 892 this.appendNodesToElement(el, "path"); 893 }, 894 895 /** 896 * Update {@link Ticks} on a {@link JXG.Line}. This method is only a stub and has to be implemented 897 * in any descendant renderer class. 898 * @param {JXG.Ticks} element Reference of a ticks object that has to be updated. 899 * @see Line 900 * @see Ticks 901 * @see JXG.Line 902 * @see JXG.Ticks 903 * @see JXG.AbstractRenderer#drawTicks 904 */ 905 updateTicks: function (element) { 906 /* stub */ 907 }, 908 909 /* ************************** 910 * Circle related stuff 911 * **************************/ 912 913 /** 914 * Draws a {@link JXG.Circle} 915 * @param {JXG.Circle} el Reference to a {@link JXG.Circle} object that has to be drawn. 916 * @see Circle 917 * @see JXG.Circle 918 * @see JXG.AbstractRenderer#updateEllipse 919 */ 920 drawEllipse: function (el) { 921 el.rendNode = this.appendChildPrim( 922 this.createPrim("ellipse", el.id), 923 Type.evaluate(el.visProp.layer) 924 ); 925 this.appendNodesToElement(el, "ellipse"); 926 this.updateEllipse(el); 927 }, 928 929 /** 930 * Updates visual appearance of a given {@link JXG.Circle} on the {@link JXG.Board}. 931 * @param {JXG.Circle} el Reference to a {@link JXG.Circle} object, that has to be updated. 932 * @see Circle 933 * @see JXG.Circle 934 * @see JXG.AbstractRenderer#drawEllipse 935 */ 936 updateEllipse: function (el) { 937 this._updateVisual(el); 938 939 var radius = el.Radius(); 940 941 if ( 942 radius > 0.0 && 943 Math.abs(el.center.coords.usrCoords[0]) > Mat.eps && 944 !isNaN( 945 radius + el.center.coords.scrCoords[1] + el.center.coords.scrCoords[2] 946 ) && 947 radius * el.board.unitX < 2000000 948 ) { 949 this.updateEllipsePrim( 950 el.rendNode, 951 el.center.coords.scrCoords[1], 952 el.center.coords.scrCoords[2], 953 radius * el.board.unitX, 954 radius * el.board.unitY 955 ); 956 } 957 }, 958 959 /* ************************** 960 * Polygon related stuff 961 * **************************/ 962 963 /** 964 * Draws a {@link JXG.Polygon} on the {@link JXG.Board}. 965 * @param {JXG.Polygon} el Reference to a Polygon object, that is to be drawn. 966 * @see Polygon 967 * @see JXG.Polygon 968 * @see JXG.AbstractRenderer#updatePolygon 969 */ 970 drawPolygon: function (el) { 971 el.rendNode = this.appendChildPrim( 972 this.createPrim("polygon", el.id), 973 Type.evaluate(el.visProp.layer) 974 ); 975 this.appendNodesToElement(el, "polygon"); 976 this.updatePolygon(el); 977 }, 978 979 /** 980 * Updates properties of a {@link JXG.Polygon}'s rendering node. 981 * @param {JXG.Polygon} el Reference to a {@link JXG.Polygon} object, that has to be updated. 982 * @see Polygon 983 * @see JXG.Polygon 984 * @see JXG.AbstractRenderer#drawPolygon 985 */ 986 updatePolygon: function (el) { 987 // Here originally strokecolor wasn't updated but strokewidth was. 988 // But if there's no strokecolor i don't see why we should update strokewidth. 989 this._updateVisual(el, { stroke: true, dash: true }); 990 this.updatePolygonPrim(el.rendNode, el); 991 }, 992 993 /* ************************** 994 * Text related stuff 995 * **************************/ 996 997 /** 998 * Shows a small copyright notice in the top left corner of the board. 999 * @param {String} str The copyright notice itself 1000 * @param {Number} fontsize Size of the font the copyright notice is written in 1001 */ 1002 displayCopyright: function (str, fontsize) { 1003 /* stub */ 1004 }, 1005 1006 /** 1007 * An internal text is a {@link JXG.Text} element which is drawn using only 1008 * the given renderer but no HTML. This method is only a stub, the drawing 1009 * is done in the special renderers. 1010 * @param {JXG.Text} element Reference to a {@link JXG.Text} object 1011 * @see Text 1012 * @see JXG.Text 1013 * @see JXG.AbstractRenderer#updateInternalText 1014 * @see JXG.AbstractRenderer#drawText 1015 * @see JXG.AbstractRenderer#updateText 1016 * @see JXG.AbstractRenderer#updateTextStyle 1017 */ 1018 drawInternalText: function (element) { 1019 /* stub */ 1020 }, 1021 1022 /** 1023 * Updates visual properties of an already existing {@link JXG.Text} element. 1024 * @param {JXG.Text} element Reference to an {@link JXG.Text} object, that has to be updated. 1025 * @see Text 1026 * @see JXG.Text 1027 * @see JXG.AbstractRenderer#drawInternalText 1028 * @see JXG.AbstractRenderer#drawText 1029 * @see JXG.AbstractRenderer#updateText 1030 * @see JXG.AbstractRenderer#updateTextStyle 1031 */ 1032 updateInternalText: function (element) { 1033 /* stub */ 1034 }, 1035 1036 /** 1037 * Displays a {@link JXG.Text} on the {@link JXG.Board} by putting a HTML div over it. 1038 * @param {JXG.Text} el Reference to an {@link JXG.Text} object, that has to be displayed 1039 * @see Text 1040 * @see JXG.Text 1041 * @see JXG.AbstractRenderer#drawInternalText 1042 * @see JXG.AbstractRenderer#updateText 1043 * @see JXG.AbstractRenderer#updateInternalText 1044 * @see JXG.AbstractRenderer#updateTextStyle 1045 */ 1046 drawText: function (el) { 1047 var node, z, level, ev_visible; 1048 1049 if ( 1050 Type.evaluate(el.visProp.display) === "html" && 1051 Env.isBrowser && 1052 this.type !== "no" 1053 ) { 1054 node = this.container.ownerDocument.createElement("div"); 1055 //node = this.container.ownerDocument.createElementNS('http://www.w3.org/1999/xhtml', 'div'); // 1056 node.style.position = "absolute"; 1057 node.className = Type.evaluate(el.visProp.cssclass); 1058 1059 level = Type.evaluate(el.visProp.layer); 1060 if (!Type.exists(level)) { 1061 // trace nodes have level not set 1062 level = 0; 1063 } 1064 1065 if (this.container.style.zIndex === "") { 1066 z = 0; 1067 } else { 1068 z = parseInt(this.container.style.zIndex, 10); 1069 } 1070 1071 node.style.zIndex = z + level; 1072 this.container.appendChild(node); 1073 1074 node.setAttribute("id", this.container.id + "_" + el.id); 1075 } else { 1076 node = this.drawInternalText(el); 1077 } 1078 1079 el.rendNode = node; 1080 el.htmlStr = ""; 1081 1082 // Set el.visPropCalc.visible 1083 if (el.visProp.islabel && Type.exists(el.visProp.anchor)) { 1084 ev_visible = Type.evaluate(el.visProp.anchor.visProp.visible); 1085 el.prepareUpdate().updateVisibility(ev_visible); 1086 } else { 1087 el.prepareUpdate().updateVisibility(); 1088 } 1089 this.updateText(el); 1090 }, 1091 1092 /** 1093 * Updates visual properties of an already existing {@link JXG.Text} element. 1094 * @param {JXG.Text} el Reference to an {@link JXG.Text} object, that has to be updated. 1095 * @see Text 1096 * @see JXG.Text 1097 * @see JXG.AbstractRenderer#drawText 1098 * @see JXG.AbstractRenderer#drawInternalText 1099 * @see JXG.AbstractRenderer#updateInternalText 1100 * @see JXG.AbstractRenderer#updateTextStyle 1101 */ 1102 updateText: function (el) { 1103 var content = el.plaintext, 1104 v, 1105 c, 1106 parentNode, 1107 scale, 1108 vshift, 1109 id, 1110 wrap_id, 1111 ax, 1112 ay; 1113 1114 if (el.visPropCalc.visible) { 1115 this.updateTextStyle(el, false); 1116 1117 if (Type.evaluate(el.visProp.display) === "html" && this.type !== "no") { 1118 // Set the position 1119 if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) { 1120 // Horizontal 1121 c = el.coords.scrCoords[1]; 1122 // webkit seems to fail for extremely large values for c. 1123 c = Math.abs(c) < 1000000 ? c : 1000000; 1124 ax = el.getAnchorX(); 1125 1126 if (ax === "right") { 1127 // v = Math.floor(el.board.canvasWidth - c); 1128 v = el.board.canvasWidth - c; 1129 } else if (ax === "middle") { 1130 // v = Math.floor(c - 0.5 * el.size[0]); 1131 v = c - 0.5 * el.size[0]; 1132 } else { 1133 // 'left' 1134 // v = Math.floor(c); 1135 v = c; 1136 } 1137 1138 // This may be useful for foreignObj. 1139 //if (window.devicePixelRatio !== undefined) { 1140 //v *= window.devicePixelRatio; 1141 //} 1142 1143 if (el.visPropOld.left !== ax + v) { 1144 if (ax === "right") { 1145 el.rendNode.style.right = v + "px"; 1146 el.rendNode.style.left = "auto"; 1147 } else { 1148 el.rendNode.style.left = v + "px"; 1149 el.rendNode.style.right = "auto"; 1150 } 1151 el.visPropOld.left = ax + v; 1152 } 1153 1154 // Vertical 1155 c = el.coords.scrCoords[2] + this.vOffsetText; 1156 c = Math.abs(c) < 1000000 ? c : 1000000; 1157 ay = el.getAnchorY(); 1158 1159 if (ay === "bottom") { 1160 // v = Math.floor(el.board.canvasHeight - c); 1161 v = el.board.canvasHeight - c; 1162 } else if (ay === "middle") { 1163 // v = Math.floor(c - 0.5 * el.size[1]); 1164 v = c - 0.5 * el.size[1]; 1165 } else { 1166 // top 1167 // v = Math.floor(c); 1168 v = c; 1169 } 1170 1171 // This may be useful for foreignObj. 1172 //if (window.devicePixelRatio !== undefined) { 1173 //v *= window.devicePixelRatio; 1174 //} 1175 1176 if (el.visPropOld.top !== ay + v) { 1177 if (ay === "bottom") { 1178 el.rendNode.style.top = "auto"; 1179 el.rendNode.style.bottom = v + "px"; 1180 } else { 1181 el.rendNode.style.bottom = "auto"; 1182 el.rendNode.style.top = v + "px"; 1183 } 1184 el.visPropOld.top = ay + v; 1185 } 1186 } 1187 1188 // Set the content 1189 if (el.htmlStr !== content) { 1190 try { 1191 if (el.type === Type.OBJECT_TYPE_BUTTON) { 1192 el.rendNodeButton.innerHTML = content; 1193 } else if ( 1194 el.type === Type.OBJECT_TYPE_CHECKBOX || 1195 el.type === Type.OBJECT_TYPE_INPUT 1196 ) { 1197 el.rendNodeLabel.innerHTML = content; 1198 } else { 1199 el.rendNode.innerHTML = content; 1200 } 1201 } catch (e) { 1202 // Setting innerHTML sometimes fails in IE8. 1203 // A workaround is to take the node off the DOM, assign innerHTML, 1204 // then append back. 1205 // Works for text elements as they are absolutely positioned. 1206 parentNode = el.rendNode.parentNode; 1207 el.rendNode.parentNode.removeChild(el.rendNode); 1208 el.rendNode.innerHTML = content; 1209 parentNode.appendChild(el.rendNode); 1210 } 1211 el.htmlStr = content; 1212 1213 if (Type.evaluate(el.visProp.usemathjax)) { 1214 // Typesetting directly might not work because mathjax was not loaded completely 1215 try { 1216 if (MathJax.typeset) { 1217 // Version 3 1218 MathJax.typeset([el.rendNode]); 1219 } else { 1220 // Version 2 1221 MathJax.Hub.Queue(["Typeset", MathJax.Hub, el.rendNode]); 1222 } 1223 1224 // Restore the transformation necessary for fullscreen mode 1225 // MathJax removes it when handling dynamic content 1226 id = el.board.container; 1227 wrap_id = "fullscreenwrap_" + id; 1228 if (document.getElementById(wrap_id)) { 1229 scale = el.board.containerObj._cssFullscreenStore.scale; 1230 vshift = el.board.containerObj._cssFullscreenStore.vshift; 1231 Env.scaleJSXGraphDiv( 1232 "#" + wrap_id, 1233 "#" + id, 1234 scale, 1235 vshift 1236 ); 1237 } 1238 } catch (e) { 1239 JXG.debug("MathJax (not yet) loaded"); 1240 } 1241 } else if (Type.evaluate(el.visProp.usekatex)) { 1242 try { 1243 /* eslint-disable no-undef */ 1244 katex.render(content, el.rendNode, { 1245 throwOnError: false 1246 }); 1247 /* eslint-enable no-undef */ 1248 } catch (e) { 1249 JXG.debug("KaTeX not loaded (yet)"); 1250 } 1251 } else if (Type.evaluate(el.visProp.useasciimathml)) { 1252 // This is not a constructor. 1253 // See http://asciimath.org/ for more information 1254 // about AsciiMathML and the project's source code. 1255 try { 1256 AMprocessNode(el.rendNode, false); 1257 } catch (e) { 1258 JXG.debug("AsciiMathML not loaded (yet)"); 1259 } 1260 } 1261 } 1262 this.transformImage(el, el.transformations); 1263 } else { 1264 this.updateInternalText(el); 1265 } 1266 } 1267 }, 1268 1269 /** 1270 * Converts string containing CSS properties into 1271 * array with key-value pair objects. 1272 * 1273 * @example 1274 * "color:blue; background-color:yellow" is converted to 1275 * [{'color': 'blue'}, {'backgroundColor': 'yellow'}] 1276 * 1277 * @param {String} cssString String containing CSS properties 1278 * @return {Array} Array of CSS key-value pairs 1279 */ 1280 _css2js: function (cssString) { 1281 var pairs = [], 1282 i, 1283 len, 1284 key, 1285 val, 1286 s, 1287 list = Type.trim(cssString).replace(/;$/, "").split(";"); 1288 1289 len = list.length; 1290 for (i = 0; i < len; ++i) { 1291 if (Type.trim(list[i]) !== "") { 1292 s = list[i].split(":"); 1293 key = Type.trim( 1294 s[0].replace(/-([a-z])/gi, function (match, char) { 1295 return char.toUpperCase(); 1296 }) 1297 ); 1298 val = Type.trim(s[1]); 1299 pairs.push({ key: key, val: val }); 1300 } 1301 } 1302 return pairs; 1303 }, 1304 1305 /** 1306 * Updates font-size, color and opacity propertiey and CSS style properties of a {@link JXG.Text} node. 1307 * This function is also called by highlight() and nohighlight(). 1308 * @param {JXG.Text} el Reference to the {@link JXG.Text} object, that has to be updated. 1309 * @param {Boolean} doHighlight 1310 * @see Text 1311 * @see JXG.Text 1312 * @see JXG.AbstractRenderer#drawText 1313 * @see JXG.AbstractRenderer#drawInternalText 1314 * @see JXG.AbstractRenderer#updateText 1315 * @see JXG.AbstractRenderer#updateInternalText 1316 * @see JXG.AbstractRenderer#updateInternalTextStyle 1317 */ 1318 updateTextStyle: function (el, doHighlight) { 1319 var fs, 1320 so, 1321 sc, 1322 css, 1323 node, 1324 ev = el.visProp, 1325 display = Env.isBrowser ? ev.display : "internal", 1326 nodeList = ["rendNode", "rendNodeTag", "rendNodeLabel"], 1327 lenN = nodeList.length, 1328 fontUnit = Type.evaluate(ev.fontunit), 1329 cssList, 1330 prop, 1331 style, 1332 cssString, 1333 styleList = ["cssdefaultstyle", "cssstyle"], 1334 lenS = styleList.length; 1335 1336 if (doHighlight) { 1337 sc = ev.highlightstrokecolor; 1338 so = ev.highlightstrokeopacity; 1339 css = ev.highlightcssclass; 1340 } else { 1341 sc = ev.strokecolor; 1342 so = ev.strokeopacity; 1343 css = ev.cssclass; 1344 } 1345 1346 // This part is executed for all text elements except internal texts in canvas. 1347 // HTML-texts or internal texts in SVG or VML. 1348 // HTML internal 1349 // SVG + + 1350 // VML + + 1351 // canvas + - 1352 // no - - 1353 if (this.type !== "no" && (display === "html" || this.type !== "canvas")) { 1354 for (style = 0; style < lenS; style++) { 1355 // First set cssString to 1356 // ev.cssdefaultstyle of ev.highlightcssdefaultstyle, 1357 // then to 1358 // ev.cssstyle of ev.highlightcssstyle 1359 cssString = Type.evaluate( 1360 ev[(doHighlight ? "highlight" : "") + styleList[style]] 1361 ); 1362 if (cssString !== "" && el.visPropOld[styleList[style]] !== cssString) { 1363 cssList = this._css2js(cssString); 1364 for (node = 0; node < lenN; node++) { 1365 if (Type.exists(el[nodeList[node]])) { 1366 for (prop in cssList) { 1367 if (cssList.hasOwnProperty(prop)) { 1368 el[nodeList[node]].style[cssList[prop].key] = 1369 cssList[prop].val; 1370 } 1371 } 1372 } 1373 } 1374 el.visPropOld[styleList[style]] = cssString; 1375 } 1376 } 1377 1378 fs = Type.evaluate(ev.fontsize); 1379 if (el.visPropOld.fontsize !== fs) { 1380 el.needsSizeUpdate = true; 1381 try { 1382 for (node = 0; node < lenN; node++) { 1383 if (Type.exists(el[nodeList[node]])) { 1384 el[nodeList[node]].style.fontSize = fs + fontUnit; 1385 } 1386 } 1387 } catch (e) { 1388 // IE needs special treatment. 1389 for (node = 0; node < lenN; node++) { 1390 if (Type.exists(el[nodeList[node]])) { 1391 el[nodeList[node]].style.fontSize = fs; 1392 } 1393 } 1394 } 1395 el.visPropOld.fontsize = fs; 1396 } 1397 } 1398 1399 this.setObjectTransition(el); 1400 if (display === "html" && this.type !== "no") { 1401 // Set new CSS class 1402 if (el.visPropOld.cssclass !== css) { 1403 el.rendNode.className = css; 1404 el.visPropOld.cssclass = css; 1405 el.needsSizeUpdate = true; 1406 } 1407 this.setObjectStrokeColor(el, sc, so); 1408 } else { 1409 this.updateInternalTextStyle(el, sc, so); 1410 } 1411 1412 return this; 1413 }, 1414 1415 /** 1416 * Set color and opacity of internal texts. 1417 * This method is used for Canvas and VML. 1418 * SVG needs its own version. 1419 * @private 1420 * @see JXG.AbstractRenderer#updateTextStyle 1421 * @see JXG.SVGRenderer#updateInternalTextStyle 1422 */ 1423 updateInternalTextStyle: function (el, strokeColor, strokeOpacity) { 1424 this.setObjectStrokeColor(el, strokeColor, strokeOpacity); 1425 }, 1426 1427 /* ************************** 1428 * Image related stuff 1429 * **************************/ 1430 1431 /** 1432 * Draws an {@link JXG.Image} on a board; This is just a template that has to be implemented by special 1433 * renderers. 1434 * @param {JXG.Image} element Reference to the image object that is to be drawn 1435 * @see Image 1436 * @see JXG.Image 1437 * @see JXG.AbstractRenderer#updateImage 1438 */ 1439 drawImage: function (element) { 1440 /* stub */ 1441 }, 1442 1443 /** 1444 * Updates the properties of an {@link JXG.Image} element. 1445 * @param {JXG.Image} el Reference to an {@link JXG.Image} object, that has to be updated. 1446 * @see Image 1447 * @see JXG.Image 1448 * @see JXG.AbstractRenderer#drawImage 1449 */ 1450 updateImage: function (el) { 1451 this.updateRectPrim( 1452 el.rendNode, 1453 el.coords.scrCoords[1], 1454 el.coords.scrCoords[2] - el.size[1], 1455 el.size[0], 1456 el.size[1] 1457 ); 1458 1459 this.updateImageURL(el); 1460 this.transformImage(el, el.transformations); 1461 this._updateVisual(el, { stroke: true, dash: true }, true); 1462 }, 1463 1464 /** 1465 * Multiplication of transformations without updating. That means, at that point it is expected that the 1466 * matrices contain numbers only. First, the origin in user coords is translated to <tt>(0,0)</tt> in screen 1467 * coords. Then, the stretch factors are divided out. After the transformations in user coords, the stretch 1468 * factors are multiplied in again, and the origin in user coords is translated back to its position. This 1469 * method does not have to be implemented in a new renderer. 1470 * @param {JXG.GeometryElement} el A JSXGraph element. We only need its board property. 1471 * @param {Array} transformations An array of JXG.Transformations. 1472 * @returns {Array} A matrix represented by a two dimensional array of numbers. 1473 * @see JXG.AbstractRenderer#transformImage 1474 */ 1475 joinTransforms: function (el, transformations) { 1476 var i, 1477 ox = el.board.origin.scrCoords[1], 1478 oy = el.board.origin.scrCoords[2], 1479 ux = el.board.unitX, 1480 uy = el.board.unitY, 1481 // Translate to 0,0 in screen coords 1482 /* 1483 m = [[1, 0, 0], [0, 1, 0], [0, 0, 1]], 1484 mpre1 = [[1, 0, 0], 1485 [-ox, 1, 0], 1486 [-oy, 0, 1]], 1487 // Scale 1488 mpre2 = [[1, 0, 0], 1489 [0, 1 / ux, 0], 1490 [0, 0, -1 / uy]], 1491 // Scale back 1492 mpost2 = [[1, 0, 0], 1493 [0, ux, 0], 1494 [0, 0, -uy]], 1495 // Translate back 1496 mpost1 = [[1, 0, 0], 1497 [ox, 1, 0], 1498 [oy, 0, 1]], 1499 */ 1500 len = transformations.length, 1501 // Translate to 0,0 in screen coords and then scale 1502 m = [ 1503 [1, 0, 0], 1504 [-ox / ux, 1 / ux, 0], 1505 [oy / uy, 0, -1 / uy] 1506 ]; 1507 1508 for (i = 0; i < len; i++) { 1509 //m = Mat.matMatMult(mpre1, m); 1510 //m = Mat.matMatMult(mpre2, m); 1511 m = Mat.matMatMult(transformations[i].matrix, m); 1512 //m = Mat.matMatMult(mpost2, m); 1513 //m = Mat.matMatMult(mpost1, m); 1514 } 1515 // Scale back and then translate back 1516 m = Mat.matMatMult( 1517 [ 1518 [1, 0, 0], 1519 [ox, ux, 0], 1520 [oy, 0, -uy] 1521 ], 1522 m 1523 ); 1524 return m; 1525 }, 1526 1527 /** 1528 * Applies transformations on images and text elements. This method is just a stub and has to be implemented in 1529 * all descendant classes where text and image transformations are to be supported. 1530 * @param {JXG.Image|JXG.Text} element A {@link JXG.Image} or {@link JXG.Text} object. 1531 * @param {Array} transformations An array of {@link JXG.Transformation} objects. This is usually the 1532 * transformations property of the given element <tt>el</tt>. 1533 */ 1534 transformImage: function (element, transformations) { 1535 /* stub */ 1536 }, 1537 1538 /** 1539 * If the URL of the image is provided by a function the URL has to be updated during updateImage() 1540 * @param {JXG.Image} element Reference to an image object. 1541 * @see JXG.AbstractRenderer#updateImage 1542 */ 1543 updateImageURL: function (element) { 1544 /* stub */ 1545 }, 1546 1547 /** 1548 * Updates CSS style properties of a {@link JXG.Image} node. 1549 * In SVGRenderer opacity is the only available style element. 1550 * This function is called by highlight() and nohighlight(). 1551 * This function works for VML. 1552 * It does not work for Canvas. 1553 * SVGRenderer overwrites this method. 1554 * @param {JXG.Text} el Reference to the {@link JXG.Image} object, that has to be updated. 1555 * @param {Boolean} doHighlight 1556 * @see Image 1557 * @see JXG.Image 1558 * @see JXG.AbstractRenderer#highlight 1559 * @see JXG.AbstractRenderer#noHighlight 1560 */ 1561 updateImageStyle: function (el, doHighlight) { 1562 el.rendNode.className = Type.evaluate( 1563 doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass 1564 ); 1565 }, 1566 1567 drawForeignObject: function (el) { 1568 /* stub */ 1569 }, 1570 1571 updateForeignObject: function (el) { 1572 /* stub */ 1573 }, 1574 1575 /* ************************** 1576 * Render primitive objects 1577 * **************************/ 1578 1579 /** 1580 * Appends a node to a specific layer level. This is just an abstract method and has to be implemented 1581 * in all renderers that want to use the <tt>createPrim</tt> model to draw. 1582 * @param {Node} node A DOM tree node. 1583 * @param {Number} level The layer the node is attached to. This is the index of the layer in 1584 * {@link JXG.SVGRenderer#layer} or the <tt>z-index</tt> style property of the node in VMLRenderer. 1585 */ 1586 appendChildPrim: function (node, level) { 1587 /* stub */ 1588 }, 1589 1590 /** 1591 * Stores the rendering nodes. This is an abstract method which has to be implemented in all renderers that use 1592 * the <tt>createPrim</tt> method. 1593 * @param {JXG.GeometryElement} element A JSXGraph element. 1594 * @param {String} type The XML node name. Only used in VMLRenderer. 1595 */ 1596 appendNodesToElement: function (element, type) { 1597 /* stub */ 1598 }, 1599 1600 /** 1601 * Creates a node of a given type with a given id. 1602 * @param {String} type The type of the node to create. 1603 * @param {String} id Set the id attribute to this. 1604 * @returns {Node} Reference to the created node. 1605 */ 1606 createPrim: function (type, id) { 1607 /* stub */ 1608 return null; 1609 }, 1610 1611 /** 1612 * Removes an element node. Just a stub. 1613 * @param {Node} node The node to remove. 1614 */ 1615 remove: function (node) { 1616 /* stub */ 1617 }, 1618 1619 /** 1620 * Can be used to create the nodes to display arrows. This is an abstract method which has to be implemented 1621 * in any descendant renderer. 1622 * @param {JXG.GeometryElement} element The element the arrows are to be attached to. 1623 * @param {Object} arrowData Data concerning possible arrow heads 1624 * 1625 */ 1626 makeArrows: function (element, arrowData) { 1627 /* stub */ 1628 }, 1629 1630 /** 1631 * Updates width of an arrow DOM node. Used in 1632 * @param {Node} node The arrow node. 1633 * @param {Number} width 1634 * @param {Node} parentNode Used in IE only 1635 */ 1636 _setArrowWidth: function (node, width, parentNode) { 1637 /* stub */ 1638 }, 1639 1640 /** 1641 * Updates an ellipse node primitive. This is an abstract method which has to be implemented in all renderers 1642 * that use the <tt>createPrim</tt> method. 1643 * @param {Node} node Reference to the node. 1644 * @param {Number} x Centre X coordinate 1645 * @param {Number} y Centre Y coordinate 1646 * @param {Number} rx The x-axis radius. 1647 * @param {Number} ry The y-axis radius. 1648 */ 1649 updateEllipsePrim: function (node, x, y, rx, ry) { 1650 /* stub */ 1651 }, 1652 1653 /** 1654 * Refreshes a line node. This is an abstract method which has to be implemented in all renderers that use 1655 * the <tt>createPrim</tt> method. 1656 * @param {Node} node The node to be refreshed. 1657 * @param {Number} p1x The first point's x coordinate. 1658 * @param {Number} p1y The first point's y coordinate. 1659 * @param {Number} p2x The second point's x coordinate. 1660 * @param {Number} p2y The second point's y coordinate. 1661 * @param {JXG.Board} board 1662 */ 1663 updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { 1664 /* stub */ 1665 }, 1666 1667 /** 1668 * Updates a path element. This is an abstract method which has to be implemented in all renderers that use 1669 * the <tt>createPrim</tt> method. 1670 * @param {Node} node The path node. 1671 * @param {String} pathString A string formatted like e.g. <em>'M 1,2 L 3,1 L5,5'</em>. The format of the string 1672 * depends on the rendering engine. 1673 * @param {JXG.Board} board Reference to the element's board. 1674 */ 1675 updatePathPrim: function (node, pathString, board) { 1676 /* stub */ 1677 }, 1678 1679 /** 1680 * Builds a path data string to draw a point with a face other than <em>rect</em> and <em>circle</em>. Since 1681 * the format of such a string usually depends on the renderer this method 1682 * is only an abstract method. Therefore, it has to be implemented in the descendant renderer itself unless 1683 * the renderer does not use the createPrim interface but the draw* interfaces to paint. 1684 * @param {JXG.Point} element The point element 1685 * @param {Number} size A positive number describing the size. Usually the half of the width and height of 1686 * the drawn point. 1687 * @param {String} type A string describing the point's face. This method only accepts the shortcut version of 1688 * each possible face: <tt>x, +, |, -, [], <>, ^, v, >, < </tt> 1689 */ 1690 updatePathStringPoint: function (element, size, type) { 1691 /* stub */ 1692 }, 1693 1694 /** 1695 * Builds a path data string from a {@link JXG.Curve} element. Since the path data strings heavily depend on the 1696 * underlying rendering technique this method is just a stub. Although such a path string is of no use for the 1697 * CanvasRenderer, this method is used there to draw a path directly. 1698 * @param element 1699 */ 1700 updatePathStringPrim: function (element) { 1701 /* stub */ 1702 }, 1703 1704 /** 1705 * Builds a path data string from a {@link JXG.Curve} element such that the curve looks like hand drawn. Since 1706 * the path data strings heavily depend on the underlying rendering technique this method is just a stub. 1707 * Although such a path string is of no use for the CanvasRenderer, this method is used there to draw a path 1708 * directly. 1709 * @param element 1710 */ 1711 updatePathStringBezierPrim: function (element) { 1712 /* stub */ 1713 }, 1714 1715 /** 1716 * Update a polygon primitive. 1717 * @param {Node} node 1718 * @param {JXG.Polygon} element A JSXGraph element of type {@link JXG.Polygon} 1719 */ 1720 updatePolygonPrim: function (node, element) { 1721 /* stub */ 1722 }, 1723 1724 /** 1725 * Update a rectangle primitive. This is used only for points with face of type 'rect'. 1726 * @param {Node} node The node yearning to be updated. 1727 * @param {Number} x x coordinate of the top left vertex. 1728 * @param {Number} y y coordinate of the top left vertex. 1729 * @param {Number} w Width of the rectangle. 1730 * @param {Number} h The rectangle's height. 1731 */ 1732 updateRectPrim: function (node, x, y, w, h) { 1733 /* stub */ 1734 }, 1735 1736 /* ************************** 1737 * Set Attributes 1738 * **************************/ 1739 1740 /** 1741 * Sets a node's attribute. 1742 * @param {Node} node The node that is to be updated. 1743 * @param {String} key Name of the attribute. 1744 * @param {String} val New value for the attribute. 1745 */ 1746 setPropertyPrim: function (node, key, val) { 1747 /* stub */ 1748 }, 1749 1750 setTabindex: function (element) { 1751 var val; 1752 if (element.board.attr.keyboard.enabled && Type.exists(element.rendNode)) { 1753 val = Type.evaluate(element.visProp.tabindex); 1754 if (!element.visPropCalc.visible || Type.evaluate(element.visProp.fixed)) { 1755 val = null; 1756 } 1757 if (val !== element.visPropOld.tabindex) { 1758 element.rendNode.setAttribute("tabindex", val); 1759 element.visPropOld.tabindex = val; 1760 } 1761 } 1762 }, 1763 1764 /** 1765 * Shows or hides an element on the canvas; Only a stub, requires implementation in the derived renderer. 1766 * @param {JXG.GeometryElement} element Reference to the object that has to appear. 1767 * @param {Boolean} value true to show the element, false to hide the element. 1768 */ 1769 display: function (element, value) { 1770 if (element) { 1771 element.visPropOld.visible = value; 1772 } 1773 }, 1774 1775 /** 1776 * Shows a hidden element on the canvas; Only a stub, requires implementation in the derived renderer. 1777 * 1778 * Please use JXG.AbstractRenderer#display instead 1779 * @param {JXG.GeometryElement} element Reference to the object that has to appear. 1780 * @see JXG.AbstractRenderer#hide 1781 * @deprecated 1782 */ 1783 show: function (element) { 1784 /* stub */ 1785 }, 1786 1787 /** 1788 * Hides an element on the canvas; Only a stub, requires implementation in the derived renderer. 1789 * 1790 * Please use JXG.AbstractRenderer#display instead 1791 * @param {JXG.GeometryElement} element Reference to the geometry element that has to disappear. 1792 * @see JXG.AbstractRenderer#show 1793 * @deprecated 1794 */ 1795 hide: function (element) { 1796 /* stub */ 1797 }, 1798 1799 /** 1800 * Sets the buffering as recommended by SVGWG. Until now only Opera supports this and will be ignored by other 1801 * browsers. Although this feature is only supported by SVG we have this method in {@link JXG.AbstractRenderer} 1802 * because it is called from outside the renderer. 1803 * @param {Node} node The SVG DOM Node which buffering type to update. 1804 * @param {String} type Either 'auto', 'dynamic', or 'static'. For an explanation see 1805 * {@link https://www.w3.org/TR/SVGTiny12/painting.html#BufferedRenderingProperty}. 1806 */ 1807 setBuffering: function (node, type) { 1808 /* stub */ 1809 }, 1810 1811 /** 1812 * Sets an element's dash style. 1813 * @param {JXG.GeometryElement} element An JSXGraph element. 1814 */ 1815 setDashStyle: function (element) { 1816 /* stub */ 1817 }, 1818 1819 /** 1820 * Puts an object into draft mode, i.e. it's visual appearance will be changed. For GEONE<sub>x</sub>T backwards 1821 * compatibility. 1822 * @param {JXG.GeometryElement} el Reference of the object that is in draft mode. 1823 */ 1824 setDraft: function (el) { 1825 if (!Type.evaluate(el.visProp.draft)) { 1826 return; 1827 } 1828 var draftColor = el.board.options.elements.draft.color, 1829 draftOpacity = el.board.options.elements.draft.opacity; 1830 1831 this.setObjectTransition(el); 1832 if (el.type === Const.OBJECT_TYPE_POLYGON) { 1833 this.setObjectFillColor(el, draftColor, draftOpacity); 1834 } else { 1835 if (el.elementClass === Const.OBJECT_CLASS_POINT) { 1836 this.setObjectFillColor(el, draftColor, draftOpacity); 1837 } else { 1838 this.setObjectFillColor(el, "none", 0); 1839 } 1840 this.setObjectStrokeColor(el, draftColor, draftOpacity); 1841 this.setObjectStrokeWidth(el, el.board.options.elements.draft.strokeWidth); 1842 } 1843 }, 1844 1845 /** 1846 * Puts an object from draft mode back into normal mode. 1847 * @param {JXG.GeometryElement} el Reference of the object that no longer is in draft mode. 1848 */ 1849 removeDraft: function (el) { 1850 this.setObjectTransition(el); 1851 if (el.type === Const.OBJECT_TYPE_POLYGON) { 1852 this.setObjectFillColor(el, el.visProp.fillcolor, el.visProp.fillopacity); 1853 } else { 1854 if (el.type === Const.OBJECT_CLASS_POINT) { 1855 this.setObjectFillColor(el, el.visProp.fillcolor, el.visProp.fillopacity); 1856 } 1857 this.setObjectStrokeColor(el, el.visProp.strokecolor, el.visProp.strokeopacity); 1858 this.setObjectStrokeWidth(el, el.visProp.strokewidth); 1859 } 1860 }, 1861 1862 /** 1863 * Sets up nodes for rendering a gradient fill. 1864 * @param element 1865 */ 1866 setGradient: function (element) { 1867 /* stub */ 1868 }, 1869 1870 /** 1871 * Updates the gradient fill. 1872 * @param {JXG.GeometryElement} element An JSXGraph element with an area that can be filled. 1873 */ 1874 updateGradient: function (element) { 1875 /* stub */ 1876 }, 1877 1878 /** 1879 * Sets the transition duration (in milliseconds) for fill color and stroke 1880 * color and opacity. 1881 * @param {JXG.GeometryElement} element Reference of the object that wants a 1882 * new transition duration. 1883 * @param {Number} duration (Optional) duration in milliseconds. If not given, 1884 * element.visProp.transitionDuration is taken. This is the default. 1885 */ 1886 setObjectTransition: function (element, duration) { 1887 /* stub */ 1888 }, 1889 1890 /** 1891 * Sets an objects fill color. 1892 * @param {JXG.GeometryElement} element Reference of the object that wants a new fill color. 1893 * @param {String} color Color in a HTML/CSS compatible format. If you don't want any fill color at all, choose 1894 * 'none'. 1895 * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1. 1896 */ 1897 setObjectFillColor: function (element, color, opacity) { 1898 /* stub */ 1899 }, 1900 1901 /** 1902 * Changes an objects stroke color to the given color. 1903 * @param {JXG.GeometryElement} element Reference of the {@link JXG.GeometryElement} that gets a new stroke 1904 * color. 1905 * @param {String} color Color value in a HTML compatible format, e.g. <strong>#00ff00</strong> or 1906 * <strong>green</strong> for green. 1907 * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1. 1908 */ 1909 setObjectStrokeColor: function (element, color, opacity) { 1910 /* stub */ 1911 }, 1912 1913 /** 1914 * Sets an element's stroke width. 1915 * @param {JXG.GeometryElement} element Reference to the geometry element. 1916 * @param {Number} width The new stroke width to be assigned to the element. 1917 */ 1918 setObjectStrokeWidth: function (element, width) { 1919 /* stub */ 1920 }, 1921 1922 /** 1923 * Sets the shadow properties to a geometry element. This method is only a stub, it is implemented in the actual 1924 * renderers. 1925 * @param {JXG.GeometryElement} element Reference to a geometry object, that should get a shadow 1926 */ 1927 setShadow: function (element) { 1928 /* stub */ 1929 }, 1930 1931 /** 1932 * Highlights an object, i.e. changes the current colors of the object to its highlighting colors 1933 * and highlighting stroke width. 1934 * @param {JXG.GeometryElement} el Reference of the object that will be highlighted. 1935 * @returns {JXG.AbstractRenderer} Reference to the renderer 1936 * @see JXG.AbstractRenderer#updateTextStyle 1937 */ 1938 highlight: function (el) { 1939 var i, 1940 ev = el.visProp, 1941 sw; 1942 1943 this.setObjectTransition(el); 1944 if (!ev.draft) { 1945 if (el.type === Const.OBJECT_TYPE_POLYGON) { 1946 this.setObjectFillColor(el, ev.highlightfillcolor, ev.highlightfillopacity); 1947 for (i = 0; i < el.borders.length; i++) { 1948 this.setObjectStrokeColor( 1949 el.borders[i], 1950 el.borders[i].visProp.highlightstrokecolor, 1951 el.borders[i].visProp.highlightstrokeopacity 1952 ); 1953 } 1954 } else { 1955 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 1956 this.updateTextStyle(el, true); 1957 } else if (el.type === Const.OBJECT_TYPE_IMAGE) { 1958 this.updateImageStyle(el, true); 1959 this.setObjectFillColor( 1960 el, 1961 ev.highlightfillcolor, 1962 ev.highlightfillopacity 1963 ); 1964 } else { 1965 this.setObjectStrokeColor( 1966 el, 1967 ev.highlightstrokecolor, 1968 ev.highlightstrokeopacity 1969 ); 1970 this.setObjectFillColor( 1971 el, 1972 ev.highlightfillcolor, 1973 ev.highlightfillopacity 1974 ); 1975 } 1976 } 1977 if (ev.highlightstrokewidth) { 1978 sw = Math.max( 1979 Type.evaluate(ev.highlightstrokewidth), 1980 Type.evaluate(ev.strokewidth) 1981 ); 1982 this.setObjectStrokeWidth(el, sw); 1983 if ( 1984 el.elementClass === Const.OBJECT_CLASS_LINE || 1985 el.elementClass === Const.OBJECT_CLASS_CURVE 1986 ) { 1987 this.updatePathWithArrowHeads(el, true); 1988 } 1989 } 1990 } 1991 1992 return this; 1993 }, 1994 1995 /** 1996 * Uses the normal colors of an object, i.e. the opposite of {@link JXG.AbstractRenderer#highlight}. 1997 * @param {JXG.GeometryElement} el Reference of the object that will get its normal colors. 1998 * @returns {JXG.AbstractRenderer} Reference to the renderer 1999 * @see JXG.AbstractRenderer#updateTextStyle 2000 */ 2001 noHighlight: function (el) { 2002 var i, 2003 ev = el.visProp, 2004 sw; 2005 2006 this.setObjectTransition(el); 2007 if (!Type.evaluate(el.visProp.draft)) { 2008 if (el.type === Const.OBJECT_TYPE_POLYGON) { 2009 this.setObjectFillColor(el, ev.fillcolor, ev.fillopacity); 2010 for (i = 0; i < el.borders.length; i++) { 2011 this.setObjectStrokeColor( 2012 el.borders[i], 2013 el.borders[i].visProp.strokecolor, 2014 el.borders[i].visProp.strokeopacity 2015 ); 2016 } 2017 } else { 2018 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 2019 this.updateTextStyle(el, false); 2020 } else if (el.type === Const.OBJECT_TYPE_IMAGE) { 2021 this.updateImageStyle(el, false); 2022 this.setObjectFillColor(el, ev.fillcolor, ev.fillopacity); 2023 } else { 2024 this.setObjectStrokeColor(el, ev.strokecolor, ev.strokeopacity); 2025 this.setObjectFillColor(el, ev.fillcolor, ev.fillopacity); 2026 } 2027 } 2028 2029 sw = Type.evaluate(ev.strokewidth); 2030 this.setObjectStrokeWidth(el, sw); 2031 if ( 2032 el.elementClass === Const.OBJECT_CLASS_LINE || 2033 el.elementClass === Const.OBJECT_CLASS_CURVE 2034 ) { 2035 this.updatePathWithArrowHeads(el, false); 2036 } 2037 } 2038 2039 return this; 2040 }, 2041 2042 /* ************************** 2043 * renderer control 2044 * **************************/ 2045 2046 /** 2047 * Stop redraw. This method is called before every update, so a non-vector-graphics based renderer can use this 2048 * method to delete the contents of the drawing panel. This is an abstract method every descendant renderer 2049 * should implement, if appropriate. 2050 * @see JXG.AbstractRenderer#unsuspendRedraw 2051 */ 2052 suspendRedraw: function () { 2053 /* stub */ 2054 }, 2055 2056 /** 2057 * Restart redraw. This method is called after updating all the rendering node attributes. 2058 * @see JXG.AbstractRenderer#suspendRedraw 2059 */ 2060 unsuspendRedraw: function () { 2061 /* stub */ 2062 }, 2063 2064 /** 2065 * The tiny zoom bar shown on the bottom of a board (if showNavigation on board creation is true). 2066 * It is a div element and gets the CSS class "JXG_navigation" and the id {board id}_navigationbar. 2067 * 2068 * The buttons get the CSS class "JXG_navigation_button" and the id {board_id}_name where name is 2069 * one of [top, down, left, right, out, 100, in, fullscreen, screenshot, reload, cleartraces]. 2070 * 2071 * The symbols are hard-coded. 2072 * 2073 * @param {JXG.Board} board Reference to a JSXGraph board. 2074 * @param {Object} attr Attributes of the navigation bar 2075 * 2076 */ 2077 drawZoomBar: function (board, attr) { 2078 var doc, 2079 node, 2080 cancelbubble = function (e) { 2081 if (!e) { 2082 e = window.event; 2083 } 2084 2085 if (e.stopPropagation) { 2086 // Non IE<=8 2087 e.stopPropagation(); 2088 } else { 2089 e.cancelBubble = true; 2090 } 2091 }, 2092 createButton = function (label, handler, id) { 2093 var button; 2094 2095 id = id || ""; 2096 2097 button = doc.createElement("span"); 2098 button.innerHTML = label; // button.appendChild(doc.createTextNode(label)); 2099 2100 // Style settings are superseded by adding the CSS class below 2101 button.style.paddingLeft = "7px"; 2102 button.style.paddingRight = "7px"; 2103 2104 if (button.classList !== undefined) { 2105 // classList not available in IE 9 2106 button.classList.add("JXG_navigation_button"); 2107 } 2108 // button.setAttribute('tabindex', 0); 2109 2110 button.setAttribute("id", id); 2111 node.appendChild(button); 2112 2113 // Highlighting is now done with CSS 2114 // Env.addEvent(button, 'mouseover', function () { 2115 // this.style.backgroundColor = attr.highlightfillcolor; 2116 // }, button); 2117 // Env.addEvent(button, 'mouseover', function () { 2118 // this.style.backgroundColor = attr.highlightfillcolor; 2119 // }, button); 2120 // Env.addEvent(button, 'mouseout', function () { 2121 // this.style.backgroundColor = attr.fillcolor; 2122 // }, button); 2123 2124 Env.addEvent( 2125 button, 2126 "click", 2127 function (e) { 2128 Type.bind(handler, board)(); 2129 return false; 2130 }, 2131 board 2132 ); 2133 // prevent the click from bubbling down to the board 2134 Env.addEvent(button, "mouseup", cancelbubble, board); 2135 Env.addEvent(button, "mousedown", cancelbubble, board); 2136 Env.addEvent(button, "touchend", cancelbubble, board); 2137 Env.addEvent(button, "touchstart", cancelbubble, board); 2138 }; 2139 2140 if (Env.isBrowser && this.type !== "no") { 2141 doc = board.containerObj.ownerDocument; 2142 node = doc.createElement("div"); 2143 2144 node.setAttribute("id", board.container + "_navigationbar"); 2145 2146 // Style settings are superseded by adding the CSS class below 2147 node.style.color = attr.strokecolor; 2148 node.style.backgroundColor = attr.fillcolor; 2149 node.style.padding = attr.padding; 2150 node.style.position = attr.position; 2151 node.style.fontSize = attr.fontsize; 2152 node.style.cursor = attr.cursor; 2153 node.style.zIndex = attr.zindex; 2154 board.containerObj.appendChild(node); 2155 node.style.right = attr.right; 2156 node.style.bottom = attr.bottom; 2157 2158 if (node.classList !== undefined) { 2159 // classList not available in IE 9 2160 node.classList.add("JXG_navigation"); 2161 } 2162 // For XHTML we need unicode instead of HTML entities 2163 2164 if (board.attr.showfullscreen) { 2165 createButton( 2166 board.attr.fullscreen.symbol, 2167 function () { 2168 board.toFullscreen(board.attr.fullscreen.id); 2169 }, 2170 board.container + "_navigation_fullscreen" 2171 ); 2172 } 2173 2174 if (board.attr.showscreenshot) { 2175 createButton( 2176 board.attr.screenshot.symbol, 2177 function () { 2178 window.setTimeout(function () { 2179 board.renderer.screenshot(board, "", false); 2180 }, 330); 2181 }, 2182 board.container + "_navigation_screenshot" 2183 ); 2184 } 2185 2186 if (board.attr.showreload) { 2187 // full reload circle: \u27F2 2188 // the board.reload() method does not exist during the creation 2189 // of this button. That's why this anonymous function wrapper is required. 2190 createButton( 2191 "\u21BB", 2192 function () { 2193 board.reload(); 2194 }, 2195 board.container + "_navigation_reload" 2196 ); 2197 } 2198 2199 if (board.attr.showcleartraces) { 2200 // clear traces symbol (otimes): \u27F2 2201 createButton( 2202 "\u2297", 2203 function () { 2204 board.clearTraces(); 2205 }, 2206 board.container + "_navigation_cleartraces" 2207 ); 2208 } 2209 2210 if (board.attr.shownavigation) { 2211 if (board.attr.showzoom) { 2212 createButton( 2213 "\u2013", 2214 board.zoomOut, 2215 board.container + "_navigation_out" 2216 ); 2217 createButton("o", board.zoom100, board.container + "_navigation_100"); 2218 createButton("+", board.zoomIn, board.container + "_navigation_in"); 2219 } 2220 createButton( 2221 "\u2190", 2222 board.clickLeftArrow, 2223 board.container + "_navigation_left" 2224 ); 2225 createButton( 2226 "\u2193", 2227 board.clickUpArrow, 2228 board.container + "_navigation_down" 2229 ); // Down arrow 2230 createButton( 2231 "\u2191", 2232 board.clickDownArrow, 2233 board.container + "_navigation_up" 2234 ); // Up arrow 2235 createButton( 2236 "\u2192", 2237 board.clickRightArrow, 2238 board.container + "_navigation_right" 2239 ); 2240 } 2241 } 2242 }, 2243 2244 /** 2245 * Wrapper for getElementById for maybe other renderers which elements are not directly accessible by DOM 2246 * methods like document.getElementById(). 2247 * @param {String} id Unique identifier for element. 2248 * @returns {Object} Reference to a JavaScript object. In case of SVG/VMLRenderer it's a reference to a SVG/VML node. 2249 */ 2250 getElementById: function (id) { 2251 if (Type.exists(this.container)) { 2252 // Use querySelector over getElementById for compatibility with both 'regular' document 2253 // and ShadowDOM fragments. 2254 return this.container.querySelector('#' + this.container.id + '_' + id); 2255 } 2256 return ""; 2257 }, 2258 2259 /** 2260 * Remove an element and provide a function that inserts it into its original position. This method 2261 * is taken from this article {@link https://developers.google.com/speed/articles/javascript-dom}. 2262 * @author KeeKim Heng, Google Web Developer 2263 * @param {Element} el The element to be temporarily removed 2264 * @returns {Function} A function that inserts the element into its original position 2265 */ 2266 removeToInsertLater: function (el) { 2267 var parentNode = el.parentNode, 2268 nextSibling = el.nextSibling; 2269 2270 if (parentNode === null) { 2271 return; 2272 } 2273 parentNode.removeChild(el); 2274 2275 return function () { 2276 if (nextSibling) { 2277 parentNode.insertBefore(el, nextSibling); 2278 } else { 2279 parentNode.appendChild(el); 2280 } 2281 }; 2282 }, 2283 2284 /** 2285 * Resizes the rendering element 2286 * @param {Number} w New width 2287 * @param {Number} h New height 2288 */ 2289 resize: function (w, h) { 2290 /* stub */ 2291 }, 2292 2293 /** 2294 * Create crosshair elements (Fadenkreuz) for presentations. 2295 * @param {Number} n Number of crosshairs. 2296 */ 2297 createTouchpoints: function (n) {}, 2298 2299 /** 2300 * Show a specific crosshair. 2301 * @param {Number} i Number of the crosshair to show 2302 */ 2303 showTouchpoint: function (i) {}, 2304 2305 /** 2306 * Hide a specific crosshair. 2307 * @param {Number} i Number of the crosshair to show 2308 */ 2309 hideTouchpoint: function (i) {}, 2310 2311 /** 2312 * Move a specific crosshair. 2313 * @param {Number} i Number of the crosshair to show 2314 * @param {Array} pos New positon in screen coordinates 2315 */ 2316 updateTouchpoint: function (i, pos) {}, 2317 2318 /** 2319 * Convert SVG construction to base64 encoded SVG data URL. 2320 * Only available on SVGRenderer. 2321 * 2322 * @see JXG.SVGRenderer#dumpToDataURI 2323 */ 2324 dumpToDataURI: function (_ignoreTexts) {}, 2325 2326 /** 2327 * Convert SVG construction to canvas. 2328 * Only available on SVGRenderer. 2329 * 2330 * @see JXG.SVGRenderer#dumpToCanvas 2331 */ 2332 dumpToCanvas: function (canvasId, w, h, _ignoreTexts) {}, 2333 2334 /** 2335 * Display SVG image in html img-tag which enables 2336 * easy download for the user. 2337 * 2338 * See JXG.SVGRenderer#screenshot 2339 */ 2340 screenshot: function (board) {}, 2341 2342 /** 2343 * Move element into new layer. This is trivial for canvas, but needs more effort in SVG. 2344 * Does not work dynamically, i.e. if level is a function. 2345 * 2346 * @param {JXG.GeometryElement} el Element which is put into different layer 2347 * @param {Number} value Layer number 2348 * @private 2349 */ 2350 setLayer: function (el, level) {} 2351 } 2352 ); 2353 2354 export default JXG.AbstractRenderer; 2355