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.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 } else { 1257 // Version 2 1258 MathJax.Hub.Queue(["Typeset", MathJax.Hub, el.rendNode]); 1259 } 1260 1261 // Obsolete: 1262 // // Restore the transformation necessary for fullscreen mode 1263 // // MathJax removes it when handling dynamic content 1264 // id = el.board.container; 1265 // wrap_id = "fullscreenwrap_" + id; 1266 // if (document.getElementById(wrap_id)) { 1267 // scale = el.board.containerObj._cssFullscreenStore.scale; 1268 // vshift = el.board.containerObj._cssFullscreenStore.vshift; 1269 // Env.scaleJSXGraphDiv( 1270 // "#" + wrap_id, 1271 // "#" + id, 1272 // scale, 1273 // vshift 1274 // ); 1275 // } 1276 } catch (e) { 1277 JXG.debug("MathJax (not yet) loaded"); 1278 } 1279 } else if (Type.evaluate(el.visProp.usekatex)) { 1280 try { 1281 // Checkboxes et. al. do not possess rendNodeLabel during the first update. 1282 // In this case node will be undefined and not rendered by KaTeX. 1283 if (el.rendNode.innerHTML.indexOf('<span') === 0 && 1284 el.rendNode.innerHTML.indexOf('<label') > 0 && 1285 ( 1286 el.rendNode.innerHTML.indexOf('<checkbox') > 0 || 1287 el.rendNode.innerHTML.indexOf('<input') > 0 1288 ) 1289 ) { 1290 node = el.rendNodeLabel; 1291 } else if (el.rendNode.innerHTML.indexOf('<button') === 0) { 1292 node = el.rendNodeButton; 1293 } else { 1294 node = el.rendNode; 1295 } 1296 1297 if (node) { 1298 /* eslint-disable no-undef */ 1299 katex.render(content, node, { 1300 macros: Type.evaluate(el.visProp.katexmacros), 1301 throwOnError: false 1302 }); 1303 /* eslint-enable no-undef */ 1304 } 1305 } catch (e) { 1306 JXG.debug("KaTeX not loaded (yet)"); 1307 } 1308 } else if (Type.evaluate(el.visProp.useasciimathml)) { 1309 // This is not a constructor. 1310 // See http://asciimath.org/ for more information 1311 // about AsciiMathML and the project's source code. 1312 try { 1313 AMprocessNode(el.rendNode, false); 1314 } catch (e) { 1315 JXG.debug("AsciiMathML not loaded (yet)"); 1316 } 1317 } 1318 } 1319 1320 angle = Type.evaluate(el.visProp.rotate); 1321 if (angle !== 0) { 1322 // Don't forget to convert to rad 1323 angle *= (Math.PI / 180); 1324 co = Math.cos(angle); 1325 si = Math.sin(angle); 1326 1327 el.rendNode.style['transform'] = 'matrix(' + 1328 [co, -1 * si, si, co, 0, 0].join(',') + 1329 ')'; 1330 el.rendNode.style['transform-origin'] = to_h + ' ' + to_v; 1331 } 1332 this.transformImage(el, el.transformations); 1333 } else { 1334 this.updateInternalText(el); 1335 } 1336 } 1337 }, 1338 1339 /** 1340 * Converts string containing CSS properties into 1341 * array with key-value pair objects. 1342 * 1343 * @example 1344 * "color:blue; background-color:yellow" is converted to 1345 * [{'color': 'blue'}, {'backgroundColor': 'yellow'}] 1346 * 1347 * @param {String} cssString String containing CSS properties 1348 * @return {Array} Array of CSS key-value pairs 1349 */ 1350 _css2js: function (cssString) { 1351 var pairs = [], 1352 i, 1353 len, 1354 key, 1355 val, 1356 s, 1357 list = Type.trim(cssString).replace(/;$/, "").split(";"); 1358 1359 len = list.length; 1360 for (i = 0; i < len; ++i) { 1361 if (Type.trim(list[i]) !== "") { 1362 s = list[i].split(":"); 1363 key = Type.trim( 1364 s[0].replace(/-([a-z])/gi, function (match, char) { 1365 return char.toUpperCase(); 1366 }) 1367 ); 1368 val = Type.trim(s[1]); 1369 pairs.push({ key: key, val: val }); 1370 } 1371 } 1372 return pairs; 1373 }, 1374 1375 /** 1376 * Updates font-size, color and opacity propertiey and CSS style properties of a {@link JXG.Text} node. 1377 * This function is also called by highlight() and nohighlight(). 1378 * @param {JXG.Text} el Reference to the {@link JXG.Text} object, that has to be updated. 1379 * @param {Boolean} doHighlight 1380 * @see Text 1381 * @see JXG.Text 1382 * @see JXG.AbstractRenderer#drawText 1383 * @see JXG.AbstractRenderer#drawInternalText 1384 * @see JXG.AbstractRenderer#updateText 1385 * @see JXG.AbstractRenderer#updateInternalText 1386 * @see JXG.AbstractRenderer#updateInternalTextStyle 1387 */ 1388 updateTextStyle: function (el, doHighlight) { 1389 var fs, 1390 so, 1391 sc, 1392 css, 1393 node, 1394 ev = el.visProp, 1395 display = Env.isBrowser ? ev.display : "internal", 1396 nodeList = ["rendNode", "rendNodeTag", "rendNodeLabel"], 1397 lenN = nodeList.length, 1398 fontUnit = Type.evaluate(ev.fontunit), 1399 cssList, 1400 prop, 1401 style, 1402 cssString, 1403 styleList = ["cssdefaultstyle", "cssstyle"], 1404 lenS = styleList.length; 1405 1406 if (doHighlight) { 1407 sc = ev.highlightstrokecolor; 1408 so = ev.highlightstrokeopacity; 1409 css = ev.highlightcssclass; 1410 } else { 1411 sc = ev.strokecolor; 1412 so = ev.strokeopacity; 1413 css = ev.cssclass; 1414 } 1415 1416 // This part is executed for all text elements except internal texts in canvas. 1417 // HTML-texts or internal texts in SVG or VML. 1418 // HTML internal 1419 // SVG + + 1420 // VML + + 1421 // canvas + - 1422 // no - - 1423 if (this.type !== "no" && (display === "html" || this.type !== "canvas")) { 1424 for (style = 0; style < lenS; style++) { 1425 // First set cssString to 1426 // ev.cssdefaultstyle of ev.highlightcssdefaultstyle, 1427 // then to 1428 // ev.cssstyle of ev.highlightcssstyle 1429 cssString = Type.evaluate( 1430 ev[(doHighlight ? "highlight" : "") + styleList[style]] 1431 ); 1432 if (cssString !== "" && el.visPropOld[styleList[style]] !== cssString) { 1433 cssList = this._css2js(cssString); 1434 for (node = 0; node < lenN; node++) { 1435 if (Type.exists(el[nodeList[node]])) { 1436 for (prop in cssList) { 1437 if (cssList.hasOwnProperty(prop)) { 1438 el[nodeList[node]].style[cssList[prop].key] = 1439 cssList[prop].val; 1440 } 1441 } 1442 } 1443 } 1444 el.visPropOld[styleList[style]] = cssString; 1445 } 1446 } 1447 1448 fs = Type.evaluate(ev.fontsize); 1449 if (el.visPropOld.fontsize !== fs) { 1450 el.needsSizeUpdate = true; 1451 try { 1452 for (node = 0; node < lenN; node++) { 1453 if (Type.exists(el[nodeList[node]])) { 1454 el[nodeList[node]].style.fontSize = fs + fontUnit; 1455 } 1456 } 1457 } catch (e) { 1458 // IE needs special treatment. 1459 for (node = 0; node < lenN; node++) { 1460 if (Type.exists(el[nodeList[node]])) { 1461 el[nodeList[node]].style.fontSize = fs; 1462 } 1463 } 1464 } 1465 el.visPropOld.fontsize = fs; 1466 } 1467 } 1468 1469 this.setTabindex(el); 1470 1471 this.setObjectTransition(el); 1472 if (display === "html" && this.type !== "no") { 1473 this.setObjectViewport(el, true); 1474 // Set new CSS class 1475 if (el.visPropOld.cssclass !== css) { 1476 el.rendNode.className = css; 1477 el.visPropOld.cssclass = css; 1478 el.needsSizeUpdate = true; 1479 } 1480 this.setObjectStrokeColor(el, sc, so); 1481 } else { 1482 this.updateInternalTextStyle(el, sc, so); 1483 } 1484 1485 return this; 1486 }, 1487 1488 /** 1489 * Set color and opacity of internal texts. 1490 * This method is used for Canvas and VML. 1491 * SVG needs its own version. 1492 * @private 1493 * @see JXG.AbstractRenderer#updateTextStyle 1494 * @see JXG.SVGRenderer#updateInternalTextStyle 1495 */ 1496 updateInternalTextStyle: function (el, strokeColor, strokeOpacity) { 1497 this.setObjectStrokeColor(el, strokeColor, strokeOpacity); 1498 }, 1499 1500 /* ************************** 1501 * Image related stuff 1502 * **************************/ 1503 1504 /** 1505 * Draws an {@link JXG.Image} on a board; This is just a template that has to be implemented by special 1506 * renderers. 1507 * @param {JXG.Image} element Reference to the image object that is to be drawn 1508 * @see Image 1509 * @see JXG.Image 1510 * @see JXG.AbstractRenderer#updateImage 1511 */ 1512 drawImage: function (element) { 1513 /* stub */ 1514 }, 1515 1516 /** 1517 * Updates the properties of an {@link JXG.Image} element. 1518 * @param {JXG.Image} el Reference to an {@link JXG.Image} object, that has to be updated. 1519 * @see Image 1520 * @see JXG.Image 1521 * @see JXG.AbstractRenderer#drawImage 1522 */ 1523 updateImage: function (el) { 1524 this.updateRectPrim( 1525 el.rendNode, 1526 el.coords.scrCoords[1], 1527 el.coords.scrCoords[2] - el.size[1], 1528 el.size[0], 1529 el.size[1] 1530 ); 1531 1532 this.updateImageURL(el); 1533 this.transformImage(el, el.transformations); 1534 this._updateVisual(el, { stroke: true, dash: true }, true); 1535 }, 1536 1537 /** 1538 * Multiplication of transformations without updating. That means, at that point it is expected that the 1539 * matrices contain numbers only. First, the origin in user coords is translated to <tt>(0,0)</tt> in screen 1540 * coords. Then, the stretch factors are divided out. After the transformations in user coords, the stretch 1541 * factors are multiplied in again, and the origin in user coords is translated back to its position. This 1542 * method does not have to be implemented in a new renderer. 1543 * @param {JXG.GeometryElement} el A JSXGraph element. We only need its board property. 1544 * @param {Array} transformations An array of JXG.Transformations. 1545 * @returns {Array} A matrix represented by a two dimensional array of numbers. 1546 * @see JXG.AbstractRenderer#transformImage 1547 */ 1548 joinTransforms: function (el, transformations) { 1549 var i, 1550 ox = el.board.origin.scrCoords[1], 1551 oy = el.board.origin.scrCoords[2], 1552 ux = el.board.unitX, 1553 uy = el.board.unitY, 1554 // Translate to 0,0 in screen coords 1555 /* 1556 m = [[1, 0, 0], [0, 1, 0], [0, 0, 1]], 1557 mpre1 = [[1, 0, 0], 1558 [-ox, 1, 0], 1559 [-oy, 0, 1]], 1560 // Scale 1561 mpre2 = [[1, 0, 0], 1562 [0, 1 / ux, 0], 1563 [0, 0, -1 / uy]], 1564 // Scale back 1565 mpost2 = [[1, 0, 0], 1566 [0, ux, 0], 1567 [0, 0, -uy]], 1568 // Translate back 1569 mpost1 = [[1, 0, 0], 1570 [ox, 1, 0], 1571 [oy, 0, 1]], 1572 */ 1573 len = transformations.length, 1574 // Translate to 0,0 in screen coords and then scale 1575 m = [ 1576 [1, 0, 0], 1577 [-ox / ux, 1 / ux, 0], 1578 [oy / uy, 0, -1 / uy] 1579 ]; 1580 1581 for (i = 0; i < len; i++) { 1582 //m = Mat.matMatMult(mpre1, m); 1583 //m = Mat.matMatMult(mpre2, m); 1584 m = Mat.matMatMult(transformations[i].matrix, m); 1585 //m = Mat.matMatMult(mpost2, m); 1586 //m = Mat.matMatMult(mpost1, m); 1587 } 1588 // Scale back and then translate back 1589 m = Mat.matMatMult( 1590 [ 1591 [1, 0, 0], 1592 [ox, ux, 0], 1593 [oy, 0, -uy] 1594 ], 1595 m 1596 ); 1597 return m; 1598 }, 1599 1600 /** 1601 * Applies transformations on images and text elements. This method has to implemented in 1602 * all descendant classes where text and image transformations are to be supported. 1603 * <p> 1604 * Only affine transformation are supported, no proper projective transformations. This means, the 1605 * respective entries of the transformation matrix are simply ignored. 1606 * 1607 * @param {JXG.Image|JXG.Text} element A {@link JXG.Image} or {@link JXG.Text} object. 1608 * @param {Array} transformations An array of {@link JXG.Transformation} objects. This is usually the 1609 * transformations property of the given element <tt>el</tt>. 1610 */ 1611 transformImage: function (element, transformations) { 1612 /* stub */ 1613 }, 1614 1615 /** 1616 * If the URL of the image is provided by a function the URL has to be updated during updateImage() 1617 * @param {JXG.Image} element Reference to an image object. 1618 * @see JXG.AbstractRenderer#updateImage 1619 */ 1620 updateImageURL: function (element) { 1621 /* stub */ 1622 }, 1623 1624 /** 1625 * Updates CSS style properties of a {@link JXG.Image} node. 1626 * In SVGRenderer opacity is the only available style element. 1627 * This function is called by highlight() and nohighlight(). 1628 * This function works for VML. 1629 * It does not work for Canvas. 1630 * SVGRenderer overwrites this method. 1631 * @param {JXG.Text} el Reference to the {@link JXG.Image} object, that has to be updated. 1632 * @param {Boolean} doHighlight 1633 * @see Image 1634 * @see JXG.Image 1635 * @see JXG.AbstractRenderer#highlight 1636 * @see JXG.AbstractRenderer#noHighlight 1637 */ 1638 updateImageStyle: function (el, doHighlight) { 1639 el.rendNode.className = Type.evaluate( 1640 doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass 1641 ); 1642 }, 1643 1644 drawForeignObject: function (el) { 1645 /* stub */ 1646 }, 1647 1648 updateForeignObject: function (el) { 1649 /* stub */ 1650 }, 1651 1652 /* ************************** 1653 * Render primitive objects 1654 * **************************/ 1655 1656 /** 1657 * Appends a node to a specific layer level. This is just an abstract method and has to be implemented 1658 * in all renderers that want to use the <tt>createPrim</tt> model to draw. 1659 * @param {Node} node A DOM tree node. 1660 * @param {Number} level The layer the node is attached to. This is the index of the layer in 1661 * {@link JXG.SVGRenderer#layer} or the <tt>z-index</tt> style property of the node in VMLRenderer. 1662 */ 1663 appendChildPrim: function (node, level) { 1664 /* stub */ 1665 }, 1666 1667 /** 1668 * Stores the rendering nodes. This is an abstract method which has to be implemented in all renderers that use 1669 * the <tt>createPrim</tt> method. 1670 * @param {JXG.GeometryElement} element A JSXGraph element. 1671 * @param {String} type The XML node name. Only used in VMLRenderer. 1672 */ 1673 appendNodesToElement: function (element, type) { 1674 /* stub */ 1675 }, 1676 1677 /** 1678 * Creates a node of a given type with a given id. 1679 * @param {String} type The type of the node to create. 1680 * @param {String} id Set the id attribute to this. 1681 * @returns {Node} Reference to the created node. 1682 */ 1683 createPrim: function (type, id) { 1684 /* stub */ 1685 return null; 1686 }, 1687 1688 /** 1689 * Removes an element node. Just a stub. 1690 * @param {Node} node The node to remove. 1691 */ 1692 remove: function (node) { 1693 /* stub */ 1694 }, 1695 1696 /** 1697 * Can be used to create the nodes to display arrows. This is an abstract method which has to be implemented 1698 * in any descendant renderer. 1699 * @param {JXG.GeometryElement} element The element the arrows are to be attached to. 1700 * @param {Object} arrowData Data concerning possible arrow heads 1701 * 1702 */ 1703 makeArrows: function (element, arrowData) { 1704 /* stub */ 1705 }, 1706 1707 /** 1708 * Updates width of an arrow DOM node. Used in 1709 * @param {Node} node The arrow node. 1710 * @param {Number} width 1711 * @param {Node} parentNode Used in IE only 1712 */ 1713 _setArrowWidth: function (node, width, parentNode) { 1714 /* stub */ 1715 }, 1716 1717 /** 1718 * Updates an ellipse node primitive. This is an abstract method which has to be implemented in all renderers 1719 * that use the <tt>createPrim</tt> method. 1720 * @param {Node} node Reference to the node. 1721 * @param {Number} x Centre X coordinate 1722 * @param {Number} y Centre Y coordinate 1723 * @param {Number} rx The x-axis radius. 1724 * @param {Number} ry The y-axis radius. 1725 */ 1726 updateEllipsePrim: function (node, x, y, rx, ry) { 1727 /* stub */ 1728 }, 1729 1730 /** 1731 * Refreshes a line node. This is an abstract method which has to be implemented in all renderers that use 1732 * the <tt>createPrim</tt> method. 1733 * @param {Node} node The node to be refreshed. 1734 * @param {Number} p1x The first point's x coordinate. 1735 * @param {Number} p1y The first point's y coordinate. 1736 * @param {Number} p2x The second point's x coordinate. 1737 * @param {Number} p2y The second point's y coordinate. 1738 * @param {JXG.Board} board 1739 */ 1740 updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { 1741 /* stub */ 1742 }, 1743 1744 /** 1745 * Updates a path element. This is an abstract method which has to be implemented in all renderers that use 1746 * the <tt>createPrim</tt> method. 1747 * @param {Node} node The path node. 1748 * @param {String} pathString A string formatted like e.g. <em>'M 1,2 L 3,1 L5,5'</em>. The format of the string 1749 * depends on the rendering engine. 1750 * @param {JXG.Board} board Reference to the element's board. 1751 */ 1752 updatePathPrim: function (node, pathString, board) { 1753 /* stub */ 1754 }, 1755 1756 /** 1757 * Builds a path data string to draw a point with a face other than <em>rect</em> and <em>circle</em>. Since 1758 * the format of such a string usually depends on the renderer this method 1759 * is only an abstract method. Therefore, it has to be implemented in the descendant renderer itself unless 1760 * the renderer does not use the createPrim interface but the draw* interfaces to paint. 1761 * @param {JXG.Point} element The point element 1762 * @param {Number} size A positive number describing the size. Usually the half of the width and height of 1763 * the drawn point. 1764 * @param {String} type A string describing the point's face. This method only accepts the shortcut version of 1765 * each possible face: <tt>x, +, |, -, [], <>, <<>>,^, v, >, < </tt> 1766 */ 1767 updatePathStringPoint: function (element, size, type) { 1768 /* stub */ 1769 }, 1770 1771 /** 1772 * Builds a path data string from a {@link JXG.Curve} element. Since the path data strings heavily depend on the 1773 * underlying rendering technique this method is just a stub. Although such a path string is of no use for the 1774 * CanvasRenderer, this method is used there to draw a path directly. 1775 * @param element 1776 */ 1777 updatePathStringPrim: function (element) { 1778 /* stub */ 1779 }, 1780 1781 /** 1782 * Builds a path data string from a {@link JXG.Curve} element such that the curve looks like hand drawn. Since 1783 * the path data strings heavily depend on the underlying rendering technique this method is just a stub. 1784 * Although such a path string is of no use for the CanvasRenderer, this method is used there to draw a path 1785 * directly. 1786 * @param element 1787 */ 1788 updatePathStringBezierPrim: function (element) { 1789 /* stub */ 1790 }, 1791 1792 /** 1793 * Update a polygon primitive. 1794 * @param {Node} node 1795 * @param {JXG.Polygon} element A JSXGraph element of type {@link JXG.Polygon} 1796 */ 1797 updatePolygonPrim: function (node, element) { 1798 /* stub */ 1799 }, 1800 1801 /** 1802 * Update a rectangle primitive. This is used only for points with face of type 'rect'. 1803 * @param {Node} node The node yearning to be updated. 1804 * @param {Number} x x coordinate of the top left vertex. 1805 * @param {Number} y y coordinate of the top left vertex. 1806 * @param {Number} w Width of the rectangle. 1807 * @param {Number} h The rectangle's height. 1808 */ 1809 updateRectPrim: function (node, x, y, w, h) { 1810 /* stub */ 1811 }, 1812 1813 /* ************************** 1814 * Set Attributes 1815 * **************************/ 1816 1817 /** 1818 * Sets a node's attribute. 1819 * @param {Node} node The node that is to be updated. 1820 * @param {String} key Name of the attribute. 1821 * @param {String} val New value for the attribute. 1822 */ 1823 setPropertyPrim: function (node, key, val) { 1824 /* stub */ 1825 }, 1826 1827 setTabindex: function (element) { 1828 var val; 1829 if (element.board.attr.keyboard.enabled && Type.exists(element.rendNode)) { 1830 val = Type.evaluate(element.visProp.tabindex); 1831 if (!element.visPropCalc.visible || Type.evaluate(element.visProp.fixed)) { 1832 val = null; 1833 } 1834 if (val !== element.visPropOld.tabindex) { 1835 element.rendNode.setAttribute("tabindex", val); 1836 element.visPropOld.tabindex = val; 1837 } 1838 } 1839 }, 1840 1841 /** 1842 * Shows or hides an element on the canvas; Only a stub, requires implementation in the derived renderer. 1843 * @param {JXG.GeometryElement} element Reference to the object that has to appear. 1844 * @param {Boolean} value true to show the element, false to hide the element. 1845 */ 1846 display: function (element, value) { 1847 if (element) { 1848 element.visPropOld.visible = value; 1849 } 1850 }, 1851 1852 /** 1853 * Shows a hidden element on the canvas; Only a stub, requires implementation in the derived renderer. 1854 * 1855 * Please use JXG.AbstractRenderer#display instead 1856 * @param {JXG.GeometryElement} element Reference to the object that has to appear. 1857 * @see JXG.AbstractRenderer#hide 1858 * @deprecated 1859 */ 1860 show: function (element) { 1861 /* stub */ 1862 }, 1863 1864 /** 1865 * Hides an element on the canvas; Only a stub, requires implementation in the derived renderer. 1866 * 1867 * Please use JXG.AbstractRenderer#display instead 1868 * @param {JXG.GeometryElement} element Reference to the geometry element that has to disappear. 1869 * @see JXG.AbstractRenderer#show 1870 * @deprecated 1871 */ 1872 hide: function (element) { 1873 /* stub */ 1874 }, 1875 1876 /** 1877 * Sets the buffering as recommended by SVGWG. Until now only Opera supports this and will be ignored by other 1878 * browsers. Although this feature is only supported by SVG we have this method in {@link JXG.AbstractRenderer} 1879 * because it is called from outside the renderer. 1880 * @param {Node} node The SVG DOM Node which buffering type to update. 1881 * @param {String} type Either 'auto', 'dynamic', or 'static'. For an explanation see 1882 * {@link https://www.w3.org/TR/SVGTiny12/painting.html#BufferedRenderingProperty}. 1883 */ 1884 setBuffering: function (node, type) { 1885 /* stub */ 1886 }, 1887 1888 /** 1889 * Sets an element's dash style. 1890 * @param {JXG.GeometryElement} element An JSXGraph element. 1891 */ 1892 setDashStyle: function (element) { 1893 /* stub */ 1894 }, 1895 1896 /** 1897 * Puts an object into draft mode, i.e. it's visual appearance will be changed. For GEONE<sub>x</sub>T backwards 1898 * compatibility. 1899 * @param {JXG.GeometryElement} el Reference of the object that is in draft mode. 1900 */ 1901 setDraft: function (el) { 1902 if (!Type.evaluate(el.visProp.draft)) { 1903 return; 1904 } 1905 var draftColor = el.board.options.elements.draft.color, 1906 draftOpacity = el.board.options.elements.draft.opacity; 1907 1908 this.setObjectViewport(el); 1909 this.setObjectTransition(el); 1910 if (el.type === Const.OBJECT_TYPE_POLYGON) { 1911 this.setObjectFillColor(el, draftColor, draftOpacity); 1912 } else { 1913 if (el.elementClass === Const.OBJECT_CLASS_POINT) { 1914 this.setObjectFillColor(el, draftColor, draftOpacity); 1915 } else { 1916 this.setObjectFillColor(el, "none", 0); 1917 } 1918 this.setObjectStrokeColor(el, draftColor, draftOpacity); 1919 this.setObjectStrokeWidth(el, el.board.options.elements.draft.strokeWidth); 1920 } 1921 }, 1922 1923 /** 1924 * Puts an object from draft mode back into normal mode. 1925 * @param {JXG.GeometryElement} el Reference of the object that no longer is in draft mode. 1926 */ 1927 removeDraft: function (el) { 1928 this.setObjectViewport(el); 1929 this.setObjectTransition(el); 1930 if (el.type === Const.OBJECT_TYPE_POLYGON) { 1931 this.setObjectFillColor(el, el.visProp.fillcolor, el.visProp.fillopacity); 1932 } else { 1933 if (el.type === Const.OBJECT_CLASS_POINT) { 1934 this.setObjectFillColor(el, el.visProp.fillcolor, el.visProp.fillopacity); 1935 } 1936 this.setObjectStrokeColor(el, el.visProp.strokecolor, el.visProp.strokeopacity); 1937 this.setObjectStrokeWidth(el, el.visProp.strokewidth); 1938 } 1939 }, 1940 1941 /** 1942 * Sets up nodes for rendering a gradient fill. 1943 * @param element 1944 */ 1945 setGradient: function (element) { 1946 /* stub */ 1947 }, 1948 1949 /** 1950 * Updates the gradient fill. 1951 * @param {JXG.GeometryElement} element An JSXGraph element with an area that can be filled. 1952 */ 1953 updateGradient: function (element) { 1954 /* stub */ 1955 }, 1956 1957 /** 1958 * Sets the transition duration (in milliseconds) for fill color and stroke 1959 * color and opacity. 1960 * @param {JXG.GeometryElement} element Reference of the object that wants a 1961 * new transition duration. 1962 * @param {Number} duration (Optional) duration in milliseconds. If not given, 1963 * element.visProp.transitionDuration is taken. This is the default. 1964 */ 1965 setObjectTransition: function (element, duration) { 1966 /* stub */ 1967 }, 1968 1969 /** 1970 * 1971 * @param {*} element 1972 * @param {*} isHTML 1973 */ 1974 setObjectViewport: function (element, isHTML) { 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, 2031 ev = el.visProp, 2032 sw; 2033 2034 this.setObjectViewport(el); 2035 this.setObjectTransition(el); 2036 if (!ev.draft) { 2037 if (el.type === Const.OBJECT_TYPE_POLYGON) { 2038 this.setObjectFillColor(el, ev.highlightfillcolor, ev.highlightfillopacity); 2039 do_hl = Type.evaluate(ev.highlightbystrokewidth); 2040 for (i = 0; i < el.borders.length; i++) { 2041 this.highlight(el.borders[i], !do_hl); 2042 } 2043 /* 2044 for (i = 0; i < el.borders.length; i++) { 2045 this.setObjectStrokeColor( 2046 el.borders[i], 2047 el.borders[i].visProp.highlightstrokecolor, 2048 el.borders[i].visProp.highlightstrokeopacity 2049 ); 2050 } 2051 */ 2052 } else { 2053 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 2054 this.updateTextStyle(el, true); 2055 } else if (el.type === Const.OBJECT_TYPE_IMAGE) { 2056 this.updateImageStyle(el, true); 2057 this.setObjectFillColor( 2058 el, 2059 ev.highlightfillcolor, 2060 ev.highlightfillopacity 2061 ); 2062 } else { 2063 this.setObjectStrokeColor( 2064 el, 2065 ev.highlightstrokecolor, 2066 ev.highlightstrokeopacity 2067 ); 2068 this.setObjectFillColor( 2069 el, 2070 ev.highlightfillcolor, 2071 ev.highlightfillopacity 2072 ); 2073 } 2074 } 2075 2076 // Highlight strokeWidth is suppressed if 2077 // parameter suppressHighlightStrokeWidth is false or undefined. 2078 // suppressHighlightStrokeWidth is false if polygon attribute 2079 // highlightbystrokewidth is true. 2080 if (ev.highlightstrokewidth && !suppressHighlightStrokeWidth) { 2081 sw = Math.max( 2082 Type.evaluate(ev.highlightstrokewidth), 2083 Type.evaluate(ev.strokewidth) 2084 ); 2085 this.setObjectStrokeWidth(el, sw); 2086 if ( 2087 el.elementClass === Const.OBJECT_CLASS_LINE || 2088 el.elementClass === Const.OBJECT_CLASS_CURVE 2089 ) { 2090 this.updatePathWithArrowHeads(el, true); 2091 } 2092 } 2093 } 2094 2095 return this; 2096 }, 2097 2098 /** 2099 * Uses the normal colors of an object, i.e. the opposite of {@link JXG.AbstractRenderer#highlight}. 2100 * @param {JXG.GeometryElement} el Reference of the object that will get its normal colors. 2101 * @returns {JXG.AbstractRenderer} Reference to the renderer 2102 * @see JXG.AbstractRenderer#updateTextStyle 2103 */ 2104 noHighlight: function (el) { 2105 var i, 2106 ev = el.visProp, 2107 sw; 2108 2109 this.setObjectViewport(el); 2110 this.setObjectTransition(el); 2111 if (!Type.evaluate(el.visProp.draft)) { 2112 if (el.type === Const.OBJECT_TYPE_POLYGON) { 2113 this.setObjectFillColor(el, ev.fillcolor, ev.fillopacity); 2114 for (i = 0; i < el.borders.length; i++) { 2115 this.noHighlight(el.borders[i]); 2116 } 2117 // for (i = 0; i < el.borders.length; i++) { 2118 // this.setObjectStrokeColor( 2119 // el.borders[i], 2120 // el.borders[i].visProp.strokecolor, 2121 // el.borders[i].visProp.strokeopacity 2122 // ); 2123 // } 2124 } else { 2125 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 2126 this.updateTextStyle(el, false); 2127 } else if (el.type === Const.OBJECT_TYPE_IMAGE) { 2128 this.updateImageStyle(el, false); 2129 this.setObjectFillColor(el, ev.fillcolor, ev.fillopacity); 2130 } else { 2131 this.setObjectStrokeColor(el, ev.strokecolor, ev.strokeopacity); 2132 this.setObjectFillColor(el, ev.fillcolor, ev.fillopacity); 2133 } 2134 } 2135 2136 sw = Type.evaluate(ev.strokewidth); 2137 this.setObjectStrokeWidth(el, sw); 2138 if ( 2139 el.elementClass === Const.OBJECT_CLASS_LINE || 2140 el.elementClass === Const.OBJECT_CLASS_CURVE 2141 ) { 2142 this.updatePathWithArrowHeads(el, false); 2143 } 2144 } 2145 2146 return this; 2147 }, 2148 2149 /* ************************** 2150 * renderer control 2151 * **************************/ 2152 2153 /** 2154 * Stop redraw. This method is called before every update, so a non-vector-graphics based renderer can use this 2155 * method to delete the contents of the drawing panel. This is an abstract method every descendant renderer 2156 * should implement, if appropriate. 2157 * @see JXG.AbstractRenderer#unsuspendRedraw 2158 */ 2159 suspendRedraw: function () { 2160 /* stub */ 2161 }, 2162 2163 /** 2164 * Restart redraw. This method is called after updating all the rendering node attributes. 2165 * @see JXG.AbstractRenderer#suspendRedraw 2166 */ 2167 unsuspendRedraw: function () { 2168 /* stub */ 2169 }, 2170 2171 /** 2172 * The tiny zoom bar shown on the bottom of a board (if board attribute "showNavigation" is true). 2173 * It is a div element and gets the CSS class "JXG_navigation" and the id {board id}_navigationbar. 2174 * <p> 2175 * The buttons get the CSS class "JXG_navigation_button" and the id {board_id}_name where name is 2176 * one of [top, down, left, right, out, 100, in, fullscreen, screenshot, reload, cleartraces]. 2177 * <p> 2178 * The symbols for zoom, navigation and reload are hard-coded. 2179 * 2180 * @param {JXG.Board} board Reference to a JSXGraph board. 2181 * @param {Object} attr Attributes of the navigation bar 2182 * @private 2183 */ 2184 drawNavigationBar: function (board, attr) { 2185 var doc, 2186 node, 2187 cancelbubble = function (e) { 2188 if (!e) { 2189 e = window.event; 2190 } 2191 2192 if (e.stopPropagation) { 2193 // Non IE<=8 2194 e.stopPropagation(); 2195 } else { 2196 e.cancelBubble = true; 2197 } 2198 }, 2199 createButton = function (label, handler, board_id, type) { 2200 var button; 2201 2202 board_id = board_id || ""; 2203 2204 button = doc.createElement("span"); 2205 button.innerHTML = label; // button.appendChild(doc.createTextNode(label)); 2206 2207 // Style settings are superseded by adding the CSS class below 2208 button.style.paddingLeft = "7px"; 2209 button.style.paddingRight = "7px"; 2210 2211 if (button.classList !== undefined) { 2212 // classList not available in IE 9 2213 button.classList.add("JXG_navigation_button"); 2214 button.classList.add("JXG_navigation_button_" + type); 2215 } 2216 // button.setAttribute('tabindex', 0); 2217 2218 button.setAttribute("id", board_id + '_navigation_' + type); 2219 node.appendChild(button); 2220 2221 Env.addEvent( 2222 button, 2223 "click", 2224 function (e) { 2225 Type.bind(handler, board)(); 2226 return false; 2227 }, 2228 board 2229 ); 2230 // prevent the click from bubbling down to the board 2231 Env.addEvent(button, "pointerup", cancelbubble, board); 2232 Env.addEvent(button, "pointerdown", cancelbubble, board); 2233 Env.addEvent(button, "pointerleave", cancelbubble, board); 2234 Env.addEvent(button, "mouseup", cancelbubble, board); 2235 Env.addEvent(button, "mousedown", cancelbubble, board); 2236 Env.addEvent(button, "touchend", cancelbubble, board); 2237 Env.addEvent(button, "touchstart", cancelbubble, board); 2238 }; 2239 2240 if (Env.isBrowser && this.type !== "no") { 2241 doc = board.containerObj.ownerDocument; 2242 node = doc.createElement("div"); 2243 2244 node.setAttribute("id", board.container + "_navigationbar"); 2245 2246 // Style settings are superseded by adding the CSS class below 2247 node.style.color = attr.strokecolor; 2248 node.style.backgroundColor = attr.fillcolor; 2249 node.style.padding = attr.padding; 2250 node.style.position = attr.position; 2251 node.style.fontSize = attr.fontsize; 2252 node.style.cursor = attr.cursor; 2253 node.style.zIndex = attr.zindex; 2254 board.containerObj.appendChild(node); 2255 node.style.right = attr.right; 2256 node.style.bottom = attr.bottom; 2257 2258 if (node.classList !== undefined) { 2259 // classList not available in IE 9 2260 node.classList.add("JXG_navigation"); 2261 } 2262 // For XHTML we need unicode instead of HTML entities 2263 2264 if (board.attr.showfullscreen) { 2265 createButton( 2266 board.attr.fullscreen.symbol, 2267 function () { 2268 board.toFullscreen(board.attr.fullscreen.id); 2269 }, 2270 board.container, "fullscreen" 2271 ); 2272 } 2273 2274 if (board.attr.showscreenshot) { 2275 createButton( 2276 board.attr.screenshot.symbol, 2277 function () { 2278 window.setTimeout(function () { 2279 board.renderer.screenshot(board, "", false); 2280 }, 330); 2281 }, 2282 board.container, "screenshot" 2283 ); 2284 } 2285 2286 if (board.attr.showreload) { 2287 // full reload circle: \u27F2 2288 // the board.reload() method does not exist during the creation 2289 // of this button. That's why this anonymous function wrapper is required. 2290 createButton( 2291 "\u21BB", 2292 function () { 2293 board.reload(); 2294 }, 2295 board.container, "reload" 2296 ); 2297 } 2298 2299 if (board.attr.showcleartraces) { 2300 // clear traces symbol (otimes): \u27F2 2301 createButton("\u2297", 2302 function () { 2303 board.clearTraces(); 2304 }, 2305 board.container, "cleartraces" 2306 ); 2307 } 2308 2309 if (board.attr.shownavigation) { 2310 if (board.attr.showzoom) { 2311 createButton("\u2013", board.zoomOut, board.container, "out"); 2312 createButton("o", board.zoom100, board.container, "100"); 2313 createButton("+", board.zoomIn, board.container, "in"); 2314 } 2315 createButton("\u2190", board.clickLeftArrow, board.container, "left"); 2316 createButton("\u2193", board.clickUpArrow, board.container, "down"); // Down arrow 2317 createButton("\u2191", board.clickDownArrow, board.container, "up"); // Up arrow 2318 createButton("\u2192", board.clickRightArrow, board.container, "right"); 2319 } 2320 } 2321 }, 2322 2323 /** 2324 * Wrapper for getElementById for maybe other renderers which elements are not directly accessible by DOM 2325 * methods like document.getElementById(). 2326 * @param {String} id Unique identifier for element. 2327 * @returns {Object} Reference to a JavaScript object. In case of SVG/VMLRenderer it's a reference to a SVG/VML node. 2328 */ 2329 getElementById: function (id) { 2330 var str; 2331 if (Type.exists(this.container)) { 2332 // Use querySelector over getElementById for compatibility with both 'regular' document 2333 // and ShadowDOM fragments. 2334 str = this.container.id + '_' + id; 2335 // Mask special symbols like '/' and '\' in id 2336 if (Type.exists(CSS) && Type.exists(CSS.escape)) { 2337 str = CSS.escape(str); 2338 } 2339 return this.container.querySelector('#' + str); 2340 } 2341 return ""; 2342 }, 2343 2344 /** 2345 * Remove an element and provide a function that inserts it into its original position. This method 2346 * is taken from this article {@link https://developers.google.com/speed/articles/javascript-dom}. 2347 * @author KeeKim Heng, Google Web Developer 2348 * @param {Element} el The element to be temporarily removed 2349 * @returns {Function} A function that inserts the element into its original position 2350 */ 2351 removeToInsertLater: function (el) { 2352 var parentNode = el.parentNode, 2353 nextSibling = el.nextSibling; 2354 2355 if (parentNode === null) { 2356 return; 2357 } 2358 parentNode.removeChild(el); 2359 2360 return function () { 2361 if (nextSibling) { 2362 parentNode.insertBefore(el, nextSibling); 2363 } else { 2364 parentNode.appendChild(el); 2365 } 2366 }; 2367 }, 2368 2369 /** 2370 * Resizes the rendering element 2371 * @param {Number} w New width 2372 * @param {Number} h New height 2373 */ 2374 resize: function (w, h) { 2375 /* stub */ 2376 }, 2377 2378 /** 2379 * Create crosshair elements (Fadenkreuz) for presentations. 2380 * @param {Number} n Number of crosshairs. 2381 */ 2382 createTouchpoints: function (n) {}, 2383 2384 /** 2385 * Show a specific crosshair. 2386 * @param {Number} i Number of the crosshair to show 2387 */ 2388 showTouchpoint: function (i) {}, 2389 2390 /** 2391 * Hide a specific crosshair. 2392 * @param {Number} i Number of the crosshair to show 2393 */ 2394 hideTouchpoint: function (i) {}, 2395 2396 /** 2397 * Move a specific crosshair. 2398 * @param {Number} i Number of the crosshair to show 2399 * @param {Array} pos New positon in screen coordinates 2400 */ 2401 updateTouchpoint: function (i, pos) {}, 2402 2403 /** 2404 * Convert SVG construction to base64 encoded SVG data URL. 2405 * Only available on SVGRenderer. 2406 * 2407 * @see JXG.SVGRenderer#dumpToDataURI 2408 */ 2409 dumpToDataURI: function (_ignoreTexts) {}, 2410 2411 /** 2412 * Convert SVG construction to canvas. 2413 * Only available on SVGRenderer. 2414 * 2415 * @see JXG.SVGRenderer#dumpToCanvas 2416 */ 2417 dumpToCanvas: function (canvasId, w, h, _ignoreTexts) {}, 2418 2419 /** 2420 * Display SVG image in html img-tag which enables 2421 * easy download for the user. 2422 * 2423 * See JXG.SVGRenderer#screenshot 2424 */ 2425 screenshot: function (board) {}, 2426 2427 /** 2428 * Move element into new layer. This is trivial for canvas, but needs more effort in SVG. 2429 * Does not work dynamically, i.e. if level is a function. 2430 * 2431 * @param {JXG.GeometryElement} el Element which is put into different layer 2432 * @param {Number} value Layer number 2433 * @private 2434 */ 2435 setLayer: function (el, level) {} 2436 } 2437 ); 2438 2439 export default JXG.AbstractRenderer; 2440