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