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