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