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 */ 33 /*jslint nomen: true, plusplus: true, newcap:true*/ 34 35 import JXG from "../jxg.js"; 36 import AbstractRenderer from "./abstract.js"; 37 import Const from "../base/constants.js"; 38 import Type from "../utils/type.js"; 39 import Color from "../utils/color.js"; 40 import Mat from "../math/math.js"; 41 import Numerics from "../math/numerics.js"; 42 43 /** 44 * Uses VML to implement the rendering methods defined in {@link JXG.AbstractRenderer}. 45 * VML was used in very old Internet Explorer versions upto IE 8. 46 * 47 * 48 * @class JXG.VMLRenderer 49 * @augments JXG.AbstractRenderer 50 * @param {Node} container Reference to a DOM node containing the board. 51 * @see JXG.AbstractRenderer 52 * @deprecated 53 */ 54 JXG.VMLRenderer = function (container) { 55 this.type = "vml"; 56 57 this.container = container; 58 this.container.style.overflow = "hidden"; 59 if (this.container.style.position === "") { 60 this.container.style.position = "relative"; 61 } 62 this.container.onselectstart = function () { 63 return false; 64 }; 65 66 this.resolution = 10; // Paths are drawn with a resolution of this.resolution/pixel. 67 68 // Add VML includes and namespace 69 // Original: IE <=7 70 //container.ownerDocument.createStyleSheet().addRule("v\\:*", "behavior: url(#default#VML);"); 71 if (!Type.exists(JXG.vmlStylesheet)) { 72 container.ownerDocument.namespaces.add("jxgvml", "urn:schemas-microsoft-com:vml"); 73 JXG.vmlStylesheet = this.container.ownerDocument.createStyleSheet(); 74 JXG.vmlStylesheet.addRule(".jxgvml", "behavior:url(#default#VML)"); 75 } 76 77 try { 78 if (!container.ownerDocument.namespaces.jxgvml) { 79 container.ownerDocument.namespaces.add("jxgvml", "urn:schemas-microsoft-com:vml"); 80 } 81 82 this.createNode = function (tagName) { 83 return container.ownerDocument.createElement( 84 "<jxgvml:" + tagName + ' class="jxgvml">' 85 ); 86 }; 87 } catch (e) { 88 this.createNode = function (tagName) { 89 return container.ownerDocument.createElement( 90 "<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="jxgvml">' 91 ); 92 }; 93 } 94 95 // dash styles 96 this.dashArray = [ 97 "Solid", 98 "1 1", 99 "ShortDash", 100 "Dash", 101 "LongDash", 102 "ShortDashDot", 103 "LongDashDot" 104 ]; 105 }; 106 107 JXG.VMLRenderer.prototype = new AbstractRenderer(); 108 109 JXG.extend( 110 JXG.VMLRenderer.prototype, 111 /** @lends JXG.VMLRenderer.prototype */ { 112 /** 113 * Sets attribute <tt>key</tt> of node <tt>node</tt> to <tt>value</tt>. 114 * @param {Node} node A DOM node. 115 * @param {String} key Name of the attribute. 116 * @param {String} val New value of the attribute. 117 * @param {Boolean} [iFlag=false] If false, the attribute's name is case insensitive. 118 */ 119 _setAttr: function (node, key, val, iFlag) { 120 try { 121 if (this.container.ownerDocument.documentMode === 8) { 122 node[key] = val; 123 } else { 124 node.setAttribute(key, val, iFlag); 125 } 126 } catch (e) { 127 JXG.debug("_setAttr:" /*node.id*/ + " " + key + " " + val + "<br>\n"); 128 } 129 }, 130 131 /* ******************************** * 132 * This renderer does not need to 133 * override draw/update* methods 134 * since it provides draw/update*Prim 135 * methods. 136 * ******************************** */ 137 138 /* ************************** 139 * Lines 140 * **************************/ 141 142 // documented in AbstractRenderer 143 updateTicks: function (ticks) { 144 var i, 145 len, 146 c, 147 x, 148 y, 149 r = this.resolution, 150 tickArr = []; 151 152 len = ticks.ticks.length; 153 for (i = 0; i < len; i++) { 154 c = ticks.ticks[i]; 155 x = c[0]; 156 y = c[1]; 157 158 if (Type.isNumber(x[0]) && Type.isNumber(x[1])) { 159 tickArr.push( 160 " m " + 161 Math.round(r * x[0]) + 162 ", " + 163 Math.round(r * y[0]) + 164 " l " + 165 Math.round(r * x[1]) + 166 ", " + 167 Math.round(r * y[1]) + 168 " " 169 ); 170 } 171 } 172 173 if (!Type.exists(ticks.rendNode)) { 174 ticks.rendNode = this.createPrim("path", ticks.id); 175 this.appendChildPrim(ticks.rendNode, Type.evaluate(ticks.visProp.layer)); 176 } 177 178 this._setAttr(ticks.rendNode, "stroked", "true"); 179 this._setAttr( 180 ticks.rendNode, 181 "strokecolor", 182 Type.evaluate(ticks.visProp.strokecolor), 183 1 184 ); 185 this._setAttr( 186 ticks.rendNode, 187 "strokeweight", 188 Type.evaluate(ticks.visProp.strokewidth) 189 ); 190 this._setAttr( 191 ticks.rendNodeStroke, 192 "opacity", 193 Type.evaluate(ticks.visProp.strokeopacity) * 100 + "%" 194 ); 195 this.updatePathPrim(ticks.rendNode, tickArr, ticks.board); 196 }, 197 198 /* ************************** 199 * Text related stuff 200 * **************************/ 201 202 // Already documented in JXG.AbstractRenderer 203 displayCopyright: function (str, fontsize) { 204 var node, t; 205 206 node = this.createNode("textbox"); 207 node.style.position = "absolute"; 208 this._setAttr(node, "id", this.container.id + "_" + "licenseText"); 209 210 node.style.left = 20; 211 node.style.top = 2; 212 node.style.fontSize = fontsize; 213 node.style.color = "#356AA0"; 214 node.style.fontFamily = "Arial,Helvetica,sans-serif"; 215 this._setAttr(node, "opacity", "30%"); 216 node.style.filter = 217 "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand', enabled = false) progid:DXImageTransform.Microsoft.Alpha(opacity = 30, enabled = true)"; 218 219 t = this.container.ownerDocument.createTextNode(str); 220 node.appendChild(t); 221 this.appendChildPrim(node, 0); 222 }, 223 224 // documented in AbstractRenderer 225 drawInternalText: function (el) { 226 var node; 227 node = this.createNode("textbox"); 228 node.style.position = "absolute"; 229 el.rendNodeText = this.container.ownerDocument.createTextNode(""); 230 node.appendChild(el.rendNodeText); 231 this.appendChildPrim(node, 9); 232 node.style.filter = 233 "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand', enabled = false) progid:DXImageTransform.Microsoft.Alpha(opacity = 100, enabled = false)"; 234 235 return node; 236 }, 237 238 // documented in AbstractRenderer 239 updateInternalText: function (el) { 240 var v, 241 content = el.plaintext, 242 m = this.joinTransforms(el, el.transformations), 243 offset = [0, 0], 244 maxX, 245 maxY, 246 minX, 247 minY, 248 i, 249 node = el.rendNode, 250 p = [], 251 ev_ax = el.getAnchorX(), 252 ev_ay = el.getAnchorY(); 253 254 if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) { 255 // Horizontal 256 if (ev_ax === "right") { 257 offset[0] = 1; 258 } else if (ev_ax === "middle") { 259 offset[0] = 0.5; 260 } // default (ev_ax === 'left') offset[0] = 0; 261 262 // Vertical 263 if (ev_ay === "bottom") { 264 offset[1] = 1; 265 } else if (ev_ay === "middle") { 266 offset[1] = 0.5; 267 } // default (ev_ay === 'top') offset[1] = 0; 268 269 // Compute maxX, maxY, minX, minY 270 p[0] = Mat.matVecMult(m, [ 271 1, 272 el.coords.scrCoords[1] - offset[0] * el.size[0], 273 el.coords.scrCoords[2] + (1 - offset[1]) * el.size[1] + this.vOffsetText 274 ]); 275 p[0][1] /= p[0][0]; 276 p[0][2] /= p[0][0]; 277 p[1] = Mat.matVecMult(m, [ 278 1, 279 el.coords.scrCoords[1] + (1 - offset[0]) * el.size[0], 280 el.coords.scrCoords[2] + (1 - offset[1]) * el.size[1] + this.vOffsetText 281 ]); 282 p[1][1] /= p[1][0]; 283 p[1][2] /= p[1][0]; 284 p[2] = Mat.matVecMult(m, [ 285 1, 286 el.coords.scrCoords[1] + (1 - offset[0]) * el.size[0], 287 el.coords.scrCoords[2] - offset[1] * el.size[1] + this.vOffsetText 288 ]); 289 p[2][1] /= p[2][0]; 290 p[2][2] /= p[2][0]; 291 p[3] = Mat.matVecMult(m, [ 292 1, 293 el.coords.scrCoords[1] - offset[0] * el.size[0], 294 el.coords.scrCoords[2] - offset[1] * el.size[1] + this.vOffsetText 295 ]); 296 p[3][1] /= p[3][0]; 297 p[3][2] /= p[3][0]; 298 maxX = p[0][1]; 299 minX = p[0][1]; 300 maxY = p[0][2]; 301 minY = p[0][2]; 302 303 for (i = 1; i < 4; i++) { 304 maxX = Math.max(maxX, p[i][1]); 305 minX = Math.min(minX, p[i][1]); 306 maxY = Math.max(maxY, p[i][2]); 307 minY = Math.min(minY, p[i][2]); 308 } 309 310 // Horizontal 311 v = 312 offset[0] === 1 313 ? Math.floor(el.board.canvasWidth - maxX) 314 : Math.floor(minX); 315 if (el.visPropOld.left !== ev_ax + v) { 316 if (offset[0] === 1) { 317 el.rendNode.style.right = v + "px"; 318 el.rendNode.style.left = "auto"; 319 } else { 320 el.rendNode.style.left = v + "px"; 321 el.rendNode.style.right = "auto"; 322 } 323 el.visPropOld.left = ev_ax + v; 324 } 325 326 // Vertical 327 v = 328 offset[1] === 1 329 ? Math.floor(el.board.canvasHeight - maxY) 330 : Math.floor(minY); 331 if (el.visPropOld.top !== ev_ay + v) { 332 if (offset[1] === 1) { 333 el.rendNode.style.bottom = v + "px"; 334 el.rendNode.style.top = "auto"; 335 } else { 336 el.rendNode.style.top = v + "px"; 337 el.rendNode.style.bottom = "auto"; 338 } 339 el.visPropOld.top = ev_ay + v; 340 } 341 } 342 343 if (el.htmlStr !== content) { 344 el.rendNodeText.data = content; 345 el.htmlStr = content; 346 } 347 348 //this.transformImage(el, el.transformations); 349 node.filters.item(0).M11 = m[1][1]; 350 node.filters.item(0).M12 = m[1][2]; 351 node.filters.item(0).M21 = m[2][1]; 352 node.filters.item(0).M22 = m[2][2]; 353 node.filters.item(0).enabled = true; 354 }, 355 356 /* ************************** 357 * Image related stuff 358 * **************************/ 359 360 // Already documented in JXG.AbstractRenderer 361 drawImage: function (el) { 362 // IE 8: Bilder ueber data URIs werden bis 32kB unterstuetzt. 363 var node; 364 365 node = this.container.ownerDocument.createElement("img"); 366 node.style.position = "absolute"; 367 this._setAttr(node, "id", this.container.id + "_" + el.id); 368 369 this.container.appendChild(node); 370 this.appendChildPrim(node, Type.evaluate(el.visProp.layer)); 371 372 // Adding the rotation filter. This is always filter item 0: 373 // node.filters.item(0), see transformImage 374 // Also add the alpha filter. This is always filter item 1 375 // node.filters.item(1), see setObjectFillColor and setObjectSTrokeColor 376 //node.style.filter = node.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')"; 377 node.style.filter = 378 "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') progid:DXImageTransform.Microsoft.Alpha(opacity = 100, enabled = false)"; 379 el.rendNode = node; 380 this.updateImage(el); 381 }, 382 383 // Already documented in JXG.AbstractRenderer 384 transformImage: function (el, t) { 385 var m, 386 maxX, 387 maxY, 388 minX, 389 minY, 390 i, 391 node = el.rendNode, 392 p = [], 393 len = t.length; 394 395 if (len > 0) { 396 /* 397 nt = el.rendNode.style.filter.toString(); 398 if (!nt.match(/DXImageTransform/)) { 399 node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') " + nt; 400 } 401 */ 402 403 m = this.joinTransforms(el, t); 404 p[0] = Mat.matVecMult(m, el.coords.scrCoords); 405 p[0][1] /= p[0][0]; 406 p[0][2] /= p[0][0]; 407 p[1] = Mat.matVecMult(m, [ 408 1, 409 el.coords.scrCoords[1] + el.size[0], 410 el.coords.scrCoords[2] 411 ]); 412 p[1][1] /= p[1][0]; 413 p[1][2] /= p[1][0]; 414 p[2] = Mat.matVecMult(m, [ 415 1, 416 el.coords.scrCoords[1] + el.size[0], 417 el.coords.scrCoords[2] - el.size[1] 418 ]); 419 p[2][1] /= p[2][0]; 420 p[2][2] /= p[2][0]; 421 p[3] = Mat.matVecMult(m, [ 422 1, 423 el.coords.scrCoords[1], 424 el.coords.scrCoords[2] - el.size[1] 425 ]); 426 p[3][1] /= p[3][0]; 427 p[3][2] /= p[3][0]; 428 maxX = p[0][1]; 429 minX = p[0][1]; 430 maxY = p[0][2]; 431 minY = p[0][2]; 432 433 for (i = 1; i < 4; i++) { 434 maxX = Math.max(maxX, p[i][1]); 435 minX = Math.min(minX, p[i][1]); 436 maxY = Math.max(maxY, p[i][2]); 437 minY = Math.min(minY, p[i][2]); 438 } 439 node.style.left = Math.floor(minX) + "px"; 440 node.style.top = Math.floor(minY) + "px"; 441 442 node.filters.item(0).M11 = m[1][1]; 443 node.filters.item(0).M12 = m[1][2]; 444 node.filters.item(0).M21 = m[2][1]; 445 node.filters.item(0).M22 = m[2][2]; 446 node.filters.item(0).enabled = true; 447 } 448 }, 449 450 // Already documented in JXG.AbstractRenderer 451 updateImageURL: function (el) { 452 var url = Type.evaluate(el.url); 453 454 this._setAttr(el.rendNode, "src", url); 455 }, 456 457 /* ************************** 458 * Render primitive objects 459 * **************************/ 460 461 // Already documented in JXG.AbstractRenderer 462 appendChildPrim: function (node, level) { 463 // For trace nodes 464 if (!Type.exists(level)) { 465 level = 0; 466 } 467 468 node.style.zIndex = level; 469 this.container.appendChild(node); 470 471 return node; 472 }, 473 474 // Already documented in JXG.AbstractRenderer 475 appendNodesToElement: function (el, type) { 476 if (type === "shape" || type === "path" || type === "polygon") { 477 el.rendNodePath = this.getElementById(el.id + "_path"); 478 } 479 el.rendNodeFill = this.getElementById(el.id + "_fill"); 480 el.rendNodeStroke = this.getElementById(el.id + "_stroke"); 481 el.rendNodeShadow = this.getElementById(el.id + "_shadow"); 482 el.rendNode = this.getElementById(el.id); 483 }, 484 485 // Already documented in JXG.AbstractRenderer 486 createPrim: function (type, id) { 487 var node, 488 pathNode, 489 fillNode = this.createNode("fill"), 490 strokeNode = this.createNode("stroke"), 491 shadowNode = this.createNode("shadow"); 492 493 this._setAttr(fillNode, "id", this.container.id + "_" + id + "_fill"); 494 this._setAttr(strokeNode, "id", this.container.id + "_" + id + "_stroke"); 495 this._setAttr(shadowNode, "id", this.container.id + "_" + id + "_shadow"); 496 497 if (type === "circle" || type === "ellipse") { 498 node = this.createNode("oval"); 499 node.appendChild(fillNode); 500 node.appendChild(strokeNode); 501 node.appendChild(shadowNode); 502 } else if ( 503 type === "polygon" || 504 type === "path" || 505 type === "shape" || 506 type === "line" 507 ) { 508 node = this.createNode("shape"); 509 node.appendChild(fillNode); 510 node.appendChild(strokeNode); 511 node.appendChild(shadowNode); 512 pathNode = this.createNode("path"); 513 this._setAttr(pathNode, "id", this.container.id + "_" + id + "_path"); 514 node.appendChild(pathNode); 515 } else { 516 node = this.createNode(type); 517 node.appendChild(fillNode); 518 node.appendChild(strokeNode); 519 node.appendChild(shadowNode); 520 } 521 522 node.style.position = "absolute"; 523 node.style.left = "0px"; 524 node.style.top = "0px"; 525 this._setAttr(node, "id", this.container.id + "_" + id); 526 527 return node; 528 }, 529 530 // Already documented in JXG.AbstractRenderer 531 remove: function (node) { 532 if (Type.exists(node)) { 533 node.removeNode(true); 534 } 535 }, 536 537 // Already documented in JXG.AbstractRenderer 538 makeArrows: function (el) { 539 var nodeStroke, 540 ev_fa = Type.evaluate(el.visProp.firstarrow), 541 ev_la = Type.evaluate(el.visProp.lastarrow); 542 543 if (el.visPropOld.firstarrow === ev_fa && el.visPropOld.lastarrow === ev_la) { 544 return; 545 } 546 547 if (ev_fa) { 548 nodeStroke = el.rendNodeStroke; 549 this._setAttr(nodeStroke, "startarrow", "block"); 550 this._setAttr(nodeStroke, "startarrowlength", "long"); 551 } else { 552 nodeStroke = el.rendNodeStroke; 553 if (Type.exists(nodeStroke)) { 554 this._setAttr(nodeStroke, "startarrow", "none"); 555 } 556 } 557 558 if (ev_la) { 559 nodeStroke = el.rendNodeStroke; 560 this._setAttr(nodeStroke, "id", this.container.id + "_" + el.id + "stroke"); 561 this._setAttr(nodeStroke, "endarrow", "block"); 562 this._setAttr(nodeStroke, "endarrowlength", "long"); 563 } else { 564 nodeStroke = el.rendNodeStroke; 565 if (Type.exists(nodeStroke)) { 566 this._setAttr(nodeStroke, "endarrow", "none"); 567 } 568 } 569 el.visPropOld.firstarrow = ev_fa; 570 el.visPropOld.lastarrow = ev_la; 571 }, 572 573 // Already documented in JXG.AbstractRenderer 574 updateEllipsePrim: function (node, x, y, rx, ry) { 575 node.style.left = Math.floor(x - rx) + "px"; 576 node.style.top = Math.floor(y - ry) + "px"; 577 node.style.width = Math.floor(Math.abs(rx) * 2) + "px"; 578 node.style.height = Math.floor(Math.abs(ry) * 2) + "px"; 579 }, 580 581 // Already documented in JXG.AbstractRenderer 582 updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { 583 var s, 584 r = this.resolution; 585 586 if (!isNaN(p1x + p1y + p2x + p2y)) { 587 s = [ 588 "m ", 589 Math.floor(r * p1x), 590 ", ", 591 Math.floor(r * p1y), 592 " l ", 593 Math.floor(r * p2x), 594 ", ", 595 Math.floor(r * p2y) 596 ]; 597 this.updatePathPrim(node, s, board); 598 } 599 }, 600 601 // Already documented in JXG.AbstractRenderer 602 updatePathPrim: function (node, pointString, board) { 603 var x = board.canvasWidth, 604 y = board.canvasHeight; 605 if (pointString.length <= 0) { 606 pointString = ["m 0,0"]; 607 } 608 node.style.width = x; 609 node.style.height = y; 610 this._setAttr( 611 node, 612 "coordsize", 613 [Math.floor(this.resolution * x), Math.floor(this.resolution * y)].join(",") 614 ); 615 this._setAttr(node, "path", pointString.join("")); 616 }, 617 618 // Already documented in JXG.AbstractRenderer 619 updatePathStringPoint: function (el, size, type) { 620 var s = [], 621 mround = Math.round, 622 scr = el.coords.scrCoords, 623 sqrt32 = size * Math.sqrt(3) * 0.5, 624 s05 = size * 0.5, 625 r = this.resolution; 626 627 if (type === "x") { 628 s.push( 629 [ 630 " m ", 631 mround(r * (scr[1] - size)), 632 ", ", 633 mround(r * (scr[2] - size)), 634 " l ", 635 mround(r * (scr[1] + size)), 636 ", ", 637 mround(r * (scr[2] + size)), 638 " m ", 639 mround(r * (scr[1] + size)), 640 ", ", 641 mround(r * (scr[2] - size)), 642 " l ", 643 mround(r * (scr[1] - size)), 644 ", ", 645 mround(r * (scr[2] + size)) 646 ].join("") 647 ); 648 } else if (type === "+") { 649 s.push( 650 [ 651 " m ", 652 mround(r * (scr[1] - size)), 653 ", ", 654 mround(r * scr[2]), 655 " l ", 656 mround(r * (scr[1] + size)), 657 ", ", 658 mround(r * scr[2]), 659 " m ", 660 mround(r * scr[1]), 661 ", ", 662 mround(r * (scr[2] - size)), 663 " l ", 664 mround(r * scr[1]), 665 ", ", 666 mround(r * (scr[2] + size)) 667 ].join("") 668 ); 669 } else if (type === "<>" || type === "<<>>") { 670 if (type === "<<>>") { 671 size *= 1.41; 672 } 673 s.push( 674 [ 675 " m ", 676 mround(r * (scr[1] - size)), 677 ", ", 678 mround(r * scr[2]), 679 " l ", 680 mround(r * scr[1]), 681 ", ", 682 mround(r * (scr[2] + size)), 683 " l ", 684 mround(r * (scr[1] + size)), 685 ", ", 686 mround(r * scr[2]), 687 " l ", 688 mround(r * scr[1]), 689 ", ", 690 mround(r * (scr[2] - size)), 691 " x e " 692 ].join("") 693 ); 694 } else if (type === "^") { 695 s.push( 696 [ 697 " m ", 698 mround(r * scr[1]), 699 ", ", 700 mround(r * (scr[2] - size)), 701 " l ", 702 mround(r * (scr[1] - sqrt32)), 703 ", ", 704 mround(r * (scr[2] + s05)), 705 " l ", 706 mround(r * (scr[1] + sqrt32)), 707 ", ", 708 mround(r * (scr[2] + s05)), 709 " x e " 710 ].join("") 711 ); 712 } else if (type === "v") { 713 s.push( 714 [ 715 " m ", 716 mround(r * scr[1]), 717 ", ", 718 mround(r * (scr[2] + size)), 719 " l ", 720 mround(r * (scr[1] - sqrt32)), 721 ", ", 722 mround(r * (scr[2] - s05)), 723 " l ", 724 mround(r * (scr[1] + sqrt32)), 725 ", ", 726 mround(r * (scr[2] - s05)), 727 " x e " 728 ].join("") 729 ); 730 } else if (type === ">") { 731 s.push( 732 [ 733 " m ", 734 mround(r * (scr[1] + size)), 735 ", ", 736 mround(r * scr[2]), 737 " l ", 738 mround(r * (scr[1] - s05)), 739 ", ", 740 mround(r * (scr[2] - sqrt32)), 741 " l ", 742 mround(r * (scr[1] - s05)), 743 ", ", 744 mround(r * (scr[2] + sqrt32)), 745 " l ", 746 mround(r * (scr[1] + size)), 747 ", ", 748 mround(r * scr[2]) 749 ].join("") 750 ); 751 } else if (type === "<") { 752 s.push( 753 [ 754 " m ", 755 mround(r * (scr[1] - size)), 756 ", ", 757 mround(r * scr[2]), 758 " l ", 759 mround(r * (scr[1] + s05)), 760 ", ", 761 mround(r * (scr[2] - sqrt32)), 762 " l ", 763 mround(r * (scr[1] + s05)), 764 ", ", 765 mround(r * (scr[2] + sqrt32)), 766 " x e " 767 ].join("") 768 ); 769 } 770 771 return s; 772 }, 773 774 // Already documented in JXG.AbstractRenderer 775 updatePathStringPrim: function (el) { 776 var i, 777 scr, 778 pStr = [], 779 r = this.resolution, 780 mround = Math.round, 781 symbm = " m ", 782 symbl = " l ", 783 symbc = " c ", 784 nextSymb = symbm, 785 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html 786 787 if (el.numberPoints <= 0) { 788 return ""; 789 } 790 len = Math.min(len, el.points.length); 791 792 if (el.bezierDegree === 1) { 793 /* 794 if (isNotPlot && el.board.options.curve.RDPsmoothing) { 795 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0); 796 } 797 */ 798 799 for (i = 0; i < len; i++) { 800 scr = el.points[i].scrCoords; 801 if (isNaN(scr[1]) || isNaN(scr[2])) { 802 // PenUp 803 nextSymb = symbm; 804 } else { 805 // IE has problems with values being too far away. 806 if (scr[1] > 20000.0) { 807 scr[1] = 20000.0; 808 } else if (scr[1] < -20000.0) { 809 scr[1] = -20000.0; 810 } 811 812 if (scr[2] > 20000.0) { 813 scr[2] = 20000.0; 814 } else if (scr[2] < -20000.0) { 815 scr[2] = -20000.0; 816 } 817 818 pStr.push( 819 [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 820 ); 821 nextSymb = symbl; 822 } 823 } 824 } else if (el.bezierDegree === 3) { 825 i = 0; 826 while (i < len) { 827 scr = el.points[i].scrCoords; 828 if (isNaN(scr[1]) || isNaN(scr[2])) { 829 // PenUp 830 nextSymb = symbm; 831 } else { 832 pStr.push( 833 [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 834 ); 835 if (nextSymb === symbc) { 836 i += 1; 837 scr = el.points[i].scrCoords; 838 pStr.push( 839 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 840 ); 841 i += 1; 842 scr = el.points[i].scrCoords; 843 pStr.push( 844 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 845 ); 846 } 847 nextSymb = symbc; 848 } 849 i += 1; 850 } 851 } 852 pStr.push(" e"); 853 return pStr; 854 }, 855 856 // Already documented in JXG.AbstractRenderer 857 updatePathStringBezierPrim: function (el) { 858 var i, 859 j, 860 k, 861 scr, 862 lx, 863 ly, 864 pStr = [], 865 f = Type.evaluate(el.visProp.strokewidth), 866 r = this.resolution, 867 mround = Math.round, 868 symbm = " m ", 869 symbl = " c ", 870 nextSymb = symbm, 871 isNoPlot = Type.evaluate(el.visProp.curvetype) !== "plot", 872 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html 873 874 if (el.numberPoints <= 0) { 875 return ""; 876 } 877 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 878 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0); 879 } 880 len = Math.min(len, el.points.length); 881 882 for (j = 1; j < 3; j++) { 883 nextSymb = symbm; 884 for (i = 0; i < len; i++) { 885 scr = el.points[i].scrCoords; 886 if (isNaN(scr[1]) || isNaN(scr[2])) { 887 // PenUp 888 nextSymb = symbm; 889 } else { 890 // IE has problems with values being too far away. 891 if (scr[1] > 20000.0) { 892 scr[1] = 20000.0; 893 } else if (scr[1] < -20000.0) { 894 scr[1] = -20000.0; 895 } 896 897 if (scr[2] > 20000.0) { 898 scr[2] = 20000.0; 899 } else if (scr[2] < -20000.0) { 900 scr[2] = -20000.0; 901 } 902 903 if (nextSymb === symbm) { 904 pStr.push( 905 [nextSymb, mround(r * scr[1]), " ", mround(r * scr[2])].join("") 906 ); 907 } else { 908 k = 2 * j; 909 pStr.push( 910 [ 911 nextSymb, 912 mround( 913 r * 914 (lx + 915 (scr[1] - lx) * 0.333 + 916 f * (k * Math.random() - j)) 917 ), 918 " ", 919 mround( 920 r * 921 (ly + 922 (scr[2] - ly) * 0.333 + 923 f * (k * Math.random() - j)) 924 ), 925 " ", 926 mround( 927 r * 928 (lx + 929 (scr[1] - lx) * 0.666 + 930 f * (k * Math.random() - j)) 931 ), 932 " ", 933 mround( 934 r * 935 (ly + 936 (scr[2] - ly) * 0.666 + 937 f * (k * Math.random() - j)) 938 ), 939 " ", 940 mround(r * scr[1]), 941 " ", 942 mround(r * scr[2]) 943 ].join("") 944 ); 945 } 946 nextSymb = symbl; 947 lx = scr[1]; 948 ly = scr[2]; 949 } 950 } 951 } 952 pStr.push(" e"); 953 return pStr; 954 }, 955 956 // Already documented in JXG.AbstractRenderer 957 updatePolygonPrim: function (node, el) { 958 var i, 959 len = el.vertices.length, 960 r = this.resolution, 961 scr, 962 pStr = []; 963 964 this._setAttr(node, "stroked", "false"); 965 scr = el.vertices[0].coords.scrCoords; 966 967 if (isNaN(scr[1] + scr[2])) { 968 return; 969 } 970 971 pStr.push( 972 ["m ", Math.floor(r * scr[1]), ",", Math.floor(r * scr[2]), " l "].join("") 973 ); 974 975 for (i = 1; i < len - 1; i++) { 976 if (el.vertices[i].isReal) { 977 scr = el.vertices[i].coords.scrCoords; 978 979 if (isNaN(scr[1] + scr[2])) { 980 return; 981 } 982 983 pStr.push(Math.floor(r * scr[1]) + "," + Math.floor(r * scr[2])); 984 } else { 985 this.updatePathPrim(node, "", el.board); 986 return; 987 } 988 if (i < len - 2) { 989 pStr.push(", "); 990 } 991 } 992 pStr.push(" x e"); 993 this.updatePathPrim(node, pStr, el.board); 994 }, 995 996 // Already documented in JXG.AbstractRenderer 997 updateRectPrim: function (node, x, y, w, h) { 998 node.style.left = Math.floor(x) + "px"; 999 node.style.top = Math.floor(y) + "px"; 1000 1001 if (w >= 0) { 1002 node.style.width = w + "px"; 1003 } 1004 1005 if (h >= 0) { 1006 node.style.height = h + "px"; 1007 } 1008 }, 1009 1010 /* ************************** 1011 * Set Attributes 1012 * **************************/ 1013 1014 // Already documented in JXG.AbstractRenderer 1015 setPropertyPrim: function (node, key, val) { 1016 var keyVml = "", 1017 v; 1018 1019 switch (key) { 1020 case "stroke": 1021 keyVml = "strokecolor"; 1022 break; 1023 case "stroke-width": 1024 keyVml = "strokeweight"; 1025 break; 1026 case "stroke-dasharray": 1027 keyVml = "dashstyle"; 1028 break; 1029 } 1030 1031 if (keyVml !== "") { 1032 v = Type.evaluate(val); 1033 this._setAttr(node, keyVml, v); 1034 } 1035 }, 1036 1037 // Already documented in JXG.AbstractRenderer 1038 display: function (el, val) { 1039 if (el && el.rendNode) { 1040 el.visPropOld.visible = val; 1041 if (val) { 1042 el.rendNode.style.visibility = "inherit"; 1043 } else { 1044 el.rendNode.style.visibility = "hidden"; 1045 } 1046 } 1047 }, 1048 1049 // Already documented in JXG.AbstractRenderer 1050 show: function (el) { 1051 JXG.deprecated("Board.renderer.show()", "Board.renderer.display()"); 1052 1053 if (el && el.rendNode) { 1054 el.rendNode.style.visibility = "inherit"; 1055 } 1056 }, 1057 1058 // Already documented in JXG.AbstractRenderer 1059 hide: function (el) { 1060 JXG.deprecated("Board.renderer.hide()", "Board.renderer.display()"); 1061 1062 if (el && el.rendNode) { 1063 el.rendNode.style.visibility = "hidden"; 1064 } 1065 }, 1066 1067 // Already documented in JXG.AbstractRenderer 1068 setDashStyle: function (el, visProp) { 1069 var node; 1070 if (visProp.dash >= 0) { 1071 node = el.rendNodeStroke; 1072 this._setAttr(node, "dashstyle", this.dashArray[visProp.dash]); 1073 } 1074 }, 1075 1076 // Already documented in JXG.AbstractRenderer 1077 setGradient: function (el) { 1078 var nodeFill = el.rendNodeFill, 1079 ev_g = Type.evaluate(el.visProp.gradient); 1080 1081 if (ev_g === "linear") { 1082 this._setAttr(nodeFill, "type", "gradient"); 1083 this._setAttr( 1084 nodeFill, 1085 "color2", 1086 Type.evaluate(el.visProp.gradientsecondcolor) 1087 ); 1088 this._setAttr( 1089 nodeFill, 1090 "opacity2", 1091 Type.evaluate(el.visProp.gradientsecondopacity) 1092 ); 1093 this._setAttr(nodeFill, "angle", Type.evaluate(el.visProp.gradientangle)); 1094 } else if (ev_g === "radial") { 1095 this._setAttr(nodeFill, "type", "gradientradial"); 1096 this._setAttr( 1097 nodeFill, 1098 "color2", 1099 Type.evaluate(el.visProp.gradientsecondcolor) 1100 ); 1101 this._setAttr( 1102 nodeFill, 1103 "opacity2", 1104 Type.evaluate(el.visProp.gradientsecondopacity) 1105 ); 1106 this._setAttr( 1107 nodeFill, 1108 "focusposition", 1109 Type.evaluate(el.visProp.gradientpositionx) * 100 + 1110 "%," + 1111 Type.evaluate(el.visProp.gradientpositiony) * 100 + 1112 "%" 1113 ); 1114 this._setAttr(nodeFill, "focussize", "0,0"); 1115 } else { 1116 this._setAttr(nodeFill, "type", "solid"); 1117 } 1118 }, 1119 1120 // Already documented in JXG.AbstractRenderer 1121 setObjectFillColor: function (el, color, opacity) { 1122 var rgba = Type.evaluate(color), 1123 c, 1124 rgbo, 1125 o = Type.evaluate(opacity), 1126 oo, 1127 node = el.rendNode; 1128 1129 o = o > 0 ? o : 0; 1130 1131 if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) { 1132 return; 1133 } 1134 1135 if (Type.exists(rgba) && rgba !== false) { 1136 // RGB, not RGBA 1137 if (rgba.length !== 9) { 1138 c = rgba; 1139 oo = o; 1140 // True RGBA, not RGB 1141 } else { 1142 rgbo = Color.rgba2rgbo(rgba); 1143 c = rgbo[0]; 1144 oo = o * rgbo[1]; 1145 } 1146 if (c === "none" || c === false) { 1147 this._setAttr(el.rendNode, "filled", "false"); 1148 } else { 1149 this._setAttr(el.rendNode, "filled", "true"); 1150 this._setAttr(el.rendNode, "fillcolor", c); 1151 1152 if (Type.exists(oo) && el.rendNodeFill) { 1153 this._setAttr(el.rendNodeFill, "opacity", oo * 100 + "%"); 1154 } 1155 } 1156 if (el.type === Const.OBJECT_TYPE_IMAGE) { 1157 /* 1158 t = el.rendNode.style.filter.toString(); 1159 if (t.match(/alpha/)) { 1160 el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')'); 1161 } else { 1162 el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) + ')'; 1163 } 1164 */ 1165 if (node.filters.length > 1) { 1166 // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]? 1167 // Setting axes:true shows text labels! 1168 node.filters.item(1).opacity = Math.round(oo * 100); // Why does setObjectFillColor not use Math.round? 1169 node.filters.item(1).enabled = true; 1170 } 1171 } 1172 } 1173 el.visPropOld.fillcolor = rgba; 1174 el.visPropOld.fillopacity = o; 1175 }, 1176 1177 // Already documented in JXG.AbstractRenderer 1178 setObjectStrokeColor: function (el, color, opacity) { 1179 var rgba = Type.evaluate(color), 1180 c, 1181 rgbo, 1182 o = Type.evaluate(opacity), 1183 oo, 1184 node = el.rendNode, 1185 nodeStroke; 1186 1187 o = o > 0 ? o : 0; 1188 1189 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 1190 return; 1191 } 1192 1193 // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor 1194 1195 if (Type.exists(rgba) && rgba !== false) { 1196 // RGB, not RGBA 1197 if (rgba.length !== 9) { 1198 c = rgba; 1199 oo = o; 1200 // True RGBA, not RGB 1201 } else { 1202 rgbo = color.rgba2rgbo(rgba); 1203 c = rgbo[0]; 1204 oo = o * rgbo[1]; 1205 } 1206 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 1207 //node.style.filter = ' alpha(opacity = ' + oo + ')'; 1208 /* 1209 t = node.style.filter.toString(); 1210 if (t.match(/alpha/)) { 1211 node.style.filter = 1212 t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + oo + ')'); 1213 } else { 1214 node.style.filter += ' alpha(opacity = ' + oo + ')'; 1215 } 1216 */ 1217 if (node.filters.length > 1) { 1218 // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]? 1219 // Setting axes:true shows text labels! 1220 node.filters.item(1).opacity = Math.round(oo * 100); 1221 node.filters.item(1).enabled = true; 1222 } 1223 1224 node.style.color = c; 1225 } else { 1226 if (c !== false) { 1227 this._setAttr(node, "stroked", "true"); 1228 this._setAttr(node, "strokecolor", c); 1229 } 1230 1231 nodeStroke = el.rendNodeStroke; 1232 if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) { 1233 this._setAttr(nodeStroke, "opacity", oo * 100 + "%"); 1234 } 1235 } 1236 } 1237 el.visPropOld.strokecolor = rgba; 1238 el.visPropOld.strokeopacity = o; 1239 }, 1240 1241 // Already documented in JXG.AbstractRenderer 1242 setObjectStrokeWidth: function (el, width) { 1243 var w = Type.evaluate(width), 1244 node; 1245 1246 if (isNaN(w) || el.visPropOld.strokewidth === w) { 1247 return; 1248 } 1249 1250 node = el.rendNode; 1251 this.setPropertyPrim(node, "stroked", "true"); 1252 1253 if (Type.exists(w)) { 1254 this.setPropertyPrim(node, "stroke-width", w); 1255 if (w === 0 && Type.exists(el.rendNodeStroke)) { 1256 this._setAttr(node, "stroked", "false"); 1257 } 1258 } 1259 1260 el.visPropOld.strokewidth = w; 1261 }, 1262 1263 // Already documented in JXG.AbstractRenderer 1264 setShadow: function (el) { 1265 var nodeShadow = el.rendNodeShadow, 1266 ev_s = Type.evaluate(el.visProp.shadow); 1267 1268 if (!nodeShadow || el.visPropOld.shadow === ev_s) { 1269 return; 1270 } 1271 1272 if (ev_s) { 1273 this._setAttr(nodeShadow, "On", "True"); 1274 this._setAttr(nodeShadow, "Offset", "3pt,3pt"); 1275 this._setAttr(nodeShadow, "Opacity", "60%"); 1276 this._setAttr(nodeShadow, "Color", "#aaaaaa"); 1277 } else { 1278 this._setAttr(nodeShadow, "On", "False"); 1279 } 1280 1281 el.visPropOld.shadow = ev_s; 1282 }, 1283 1284 /* ************************** 1285 * renderer control 1286 * **************************/ 1287 1288 // Already documented in JXG.AbstractRenderer 1289 suspendRedraw: function () { 1290 this.container.style.display = "none"; 1291 }, 1292 1293 // Already documented in JXG.AbstractRenderer 1294 unsuspendRedraw: function () { 1295 this.container.style.display = ""; 1296 } 1297 } 1298 ); 1299 1300 export default JXG.VMLRenderer; 1301