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