1 /* 2 Copyright 2008-2023 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */ 33 /*jslint nomen: true, plusplus: true, newcap:true*/ 34 35 import JXG from "../jxg"; 36 import AbstractRenderer from "./abstract"; 37 import Const from "../base/constants"; 38 import Type from "../utils/type"; 39 import Color from "../utils/color"; 40 import Mat from "../math/math"; 41 import Numerics from "../math/numerics"; 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 === "<>") { 670 s.push( 671 [ 672 " m ", 673 mround(r * (scr[1] - size)), 674 ", ", 675 mround(r * scr[2]), 676 " l ", 677 mround(r * scr[1]), 678 ", ", 679 mround(r * (scr[2] + size)), 680 " l ", 681 mround(r * (scr[1] + size)), 682 ", ", 683 mround(r * scr[2]), 684 " l ", 685 mround(r * scr[1]), 686 ", ", 687 mround(r * (scr[2] - size)), 688 " x e " 689 ].join("") 690 ); 691 } else if (type === "^") { 692 s.push( 693 [ 694 " m ", 695 mround(r * scr[1]), 696 ", ", 697 mround(r * (scr[2] - size)), 698 " l ", 699 mround(r * (scr[1] - sqrt32)), 700 ", ", 701 mround(r * (scr[2] + s05)), 702 " l ", 703 mround(r * (scr[1] + sqrt32)), 704 ", ", 705 mround(r * (scr[2] + s05)), 706 " x e " 707 ].join("") 708 ); 709 } else if (type === "v") { 710 s.push( 711 [ 712 " m ", 713 mround(r * scr[1]), 714 ", ", 715 mround(r * (scr[2] + size)), 716 " l ", 717 mround(r * (scr[1] - sqrt32)), 718 ", ", 719 mround(r * (scr[2] - s05)), 720 " l ", 721 mround(r * (scr[1] + sqrt32)), 722 ", ", 723 mround(r * (scr[2] - s05)), 724 " x e " 725 ].join("") 726 ); 727 } else if (type === ">") { 728 s.push( 729 [ 730 " m ", 731 mround(r * (scr[1] + size)), 732 ", ", 733 mround(r * scr[2]), 734 " l ", 735 mround(r * (scr[1] - s05)), 736 ", ", 737 mround(r * (scr[2] - sqrt32)), 738 " l ", 739 mround(r * (scr[1] - s05)), 740 ", ", 741 mround(r * (scr[2] + sqrt32)), 742 " l ", 743 mround(r * (scr[1] + size)), 744 ", ", 745 mround(r * scr[2]) 746 ].join("") 747 ); 748 } else if (type === "<") { 749 s.push( 750 [ 751 " m ", 752 mround(r * (scr[1] - size)), 753 ", ", 754 mround(r * scr[2]), 755 " l ", 756 mround(r * (scr[1] + s05)), 757 ", ", 758 mround(r * (scr[2] - sqrt32)), 759 " l ", 760 mround(r * (scr[1] + s05)), 761 ", ", 762 mround(r * (scr[2] + sqrt32)), 763 " x e " 764 ].join("") 765 ); 766 } 767 768 return s; 769 }, 770 771 // Already documented in JXG.AbstractRenderer 772 updatePathStringPrim: function (el) { 773 var i, 774 scr, 775 pStr = [], 776 r = this.resolution, 777 mround = Math.round, 778 symbm = " m ", 779 symbl = " l ", 780 symbc = " c ", 781 nextSymb = symbm, 782 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html 783 784 if (el.numberPoints <= 0) { 785 return ""; 786 } 787 len = Math.min(len, el.points.length); 788 789 if (el.bezierDegree === 1) { 790 /* 791 if (isNotPlot && el.board.options.curve.RDPsmoothing) { 792 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0); 793 } 794 */ 795 796 for (i = 0; i < len; i++) { 797 scr = el.points[i].scrCoords; 798 if (isNaN(scr[1]) || isNaN(scr[2])) { 799 // PenUp 800 nextSymb = symbm; 801 } else { 802 // IE has problems with values being too far away. 803 if (scr[1] > 20000.0) { 804 scr[1] = 20000.0; 805 } else if (scr[1] < -20000.0) { 806 scr[1] = -20000.0; 807 } 808 809 if (scr[2] > 20000.0) { 810 scr[2] = 20000.0; 811 } else if (scr[2] < -20000.0) { 812 scr[2] = -20000.0; 813 } 814 815 pStr.push( 816 [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 817 ); 818 nextSymb = symbl; 819 } 820 } 821 } else if (el.bezierDegree === 3) { 822 i = 0; 823 while (i < len) { 824 scr = el.points[i].scrCoords; 825 if (isNaN(scr[1]) || isNaN(scr[2])) { 826 // PenUp 827 nextSymb = symbm; 828 } else { 829 pStr.push( 830 [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 831 ); 832 if (nextSymb === symbc) { 833 i += 1; 834 scr = el.points[i].scrCoords; 835 pStr.push( 836 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 837 ); 838 i += 1; 839 scr = el.points[i].scrCoords; 840 pStr.push( 841 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 842 ); 843 } 844 nextSymb = symbc; 845 } 846 i += 1; 847 } 848 } 849 pStr.push(" e"); 850 return pStr; 851 }, 852 853 // Already documented in JXG.AbstractRenderer 854 updatePathStringBezierPrim: function (el) { 855 var i, 856 j, 857 k, 858 scr, 859 lx, 860 ly, 861 pStr = [], 862 f = Type.evaluate(el.visProp.strokewidth), 863 r = this.resolution, 864 mround = Math.round, 865 symbm = " m ", 866 symbl = " c ", 867 nextSymb = symbm, 868 isNoPlot = Type.evaluate(el.visProp.curvetype) !== "plot", 869 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html 870 871 if (el.numberPoints <= 0) { 872 return ""; 873 } 874 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 875 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0); 876 } 877 len = Math.min(len, el.points.length); 878 879 for (j = 1; j < 3; j++) { 880 nextSymb = symbm; 881 for (i = 0; i < len; i++) { 882 scr = el.points[i].scrCoords; 883 if (isNaN(scr[1]) || isNaN(scr[2])) { 884 // PenUp 885 nextSymb = symbm; 886 } else { 887 // IE has problems with values being too far away. 888 if (scr[1] > 20000.0) { 889 scr[1] = 20000.0; 890 } else if (scr[1] < -20000.0) { 891 scr[1] = -20000.0; 892 } 893 894 if (scr[2] > 20000.0) { 895 scr[2] = 20000.0; 896 } else if (scr[2] < -20000.0) { 897 scr[2] = -20000.0; 898 } 899 900 if (nextSymb === symbm) { 901 pStr.push( 902 [nextSymb, mround(r * scr[1]), " ", mround(r * scr[2])].join("") 903 ); 904 } else { 905 k = 2 * j; 906 pStr.push( 907 [ 908 nextSymb, 909 mround( 910 r * 911 (lx + 912 (scr[1] - lx) * 0.333 + 913 f * (k * Math.random() - j)) 914 ), 915 " ", 916 mround( 917 r * 918 (ly + 919 (scr[2] - ly) * 0.333 + 920 f * (k * Math.random() - j)) 921 ), 922 " ", 923 mround( 924 r * 925 (lx + 926 (scr[1] - lx) * 0.666 + 927 f * (k * Math.random() - j)) 928 ), 929 " ", 930 mround( 931 r * 932 (ly + 933 (scr[2] - ly) * 0.666 + 934 f * (k * Math.random() - j)) 935 ), 936 " ", 937 mround(r * scr[1]), 938 " ", 939 mround(r * scr[2]) 940 ].join("") 941 ); 942 } 943 nextSymb = symbl; 944 lx = scr[1]; 945 ly = scr[2]; 946 } 947 } 948 } 949 pStr.push(" e"); 950 return pStr; 951 }, 952 953 // Already documented in JXG.AbstractRenderer 954 updatePolygonPrim: function (node, el) { 955 var i, 956 len = el.vertices.length, 957 r = this.resolution, 958 scr, 959 pStr = []; 960 961 this._setAttr(node, "stroked", "false"); 962 scr = el.vertices[0].coords.scrCoords; 963 964 if (isNaN(scr[1] + scr[2])) { 965 return; 966 } 967 968 pStr.push( 969 ["m ", Math.floor(r * scr[1]), ",", Math.floor(r * scr[2]), " l "].join("") 970 ); 971 972 for (i = 1; i < len - 1; i++) { 973 if (el.vertices[i].isReal) { 974 scr = el.vertices[i].coords.scrCoords; 975 976 if (isNaN(scr[1] + scr[2])) { 977 return; 978 } 979 980 pStr.push(Math.floor(r * scr[1]) + "," + Math.floor(r * scr[2])); 981 } else { 982 this.updatePathPrim(node, "", el.board); 983 return; 984 } 985 if (i < len - 2) { 986 pStr.push(", "); 987 } 988 } 989 pStr.push(" x e"); 990 this.updatePathPrim(node, pStr, el.board); 991 }, 992 993 // Already documented in JXG.AbstractRenderer 994 updateRectPrim: function (node, x, y, w, h) { 995 node.style.left = Math.floor(x) + "px"; 996 node.style.top = Math.floor(y) + "px"; 997 998 if (w >= 0) { 999 node.style.width = w + "px"; 1000 } 1001 1002 if (h >= 0) { 1003 node.style.height = h + "px"; 1004 } 1005 }, 1006 1007 /* ************************** 1008 * Set Attributes 1009 * **************************/ 1010 1011 // Already documented in JXG.AbstractRenderer 1012 setPropertyPrim: function (node, key, val) { 1013 var keyVml = "", 1014 v; 1015 1016 switch (key) { 1017 case "stroke": 1018 keyVml = "strokecolor"; 1019 break; 1020 case "stroke-width": 1021 keyVml = "strokeweight"; 1022 break; 1023 case "stroke-dasharray": 1024 keyVml = "dashstyle"; 1025 break; 1026 } 1027 1028 if (keyVml !== "") { 1029 v = Type.evaluate(val); 1030 this._setAttr(node, keyVml, v); 1031 } 1032 }, 1033 1034 // Already documented in JXG.AbstractRenderer 1035 display: function (el, val) { 1036 if (el && el.rendNode) { 1037 el.visPropOld.visible = val; 1038 if (val) { 1039 el.rendNode.style.visibility = "inherit"; 1040 } else { 1041 el.rendNode.style.visibility = "hidden"; 1042 } 1043 } 1044 }, 1045 1046 // Already documented in JXG.AbstractRenderer 1047 show: function (el) { 1048 JXG.deprecated("Board.renderer.show()", "Board.renderer.display()"); 1049 1050 if (el && el.rendNode) { 1051 el.rendNode.style.visibility = "inherit"; 1052 } 1053 }, 1054 1055 // Already documented in JXG.AbstractRenderer 1056 hide: function (el) { 1057 JXG.deprecated("Board.renderer.hide()", "Board.renderer.display()"); 1058 1059 if (el && el.rendNode) { 1060 el.rendNode.style.visibility = "hidden"; 1061 } 1062 }, 1063 1064 // Already documented in JXG.AbstractRenderer 1065 setDashStyle: function (el, visProp) { 1066 var node; 1067 if (visProp.dash >= 0) { 1068 node = el.rendNodeStroke; 1069 this._setAttr(node, "dashstyle", this.dashArray[visProp.dash]); 1070 } 1071 }, 1072 1073 // Already documented in JXG.AbstractRenderer 1074 setGradient: function (el) { 1075 var nodeFill = el.rendNodeFill, 1076 ev_g = Type.evaluate(el.visProp.gradient); 1077 1078 if (ev_g === "linear") { 1079 this._setAttr(nodeFill, "type", "gradient"); 1080 this._setAttr( 1081 nodeFill, 1082 "color2", 1083 Type.evaluate(el.visProp.gradientsecondcolor) 1084 ); 1085 this._setAttr( 1086 nodeFill, 1087 "opacity2", 1088 Type.evaluate(el.visProp.gradientsecondopacity) 1089 ); 1090 this._setAttr(nodeFill, "angle", Type.evaluate(el.visProp.gradientangle)); 1091 } else if (ev_g === "radial") { 1092 this._setAttr(nodeFill, "type", "gradientradial"); 1093 this._setAttr( 1094 nodeFill, 1095 "color2", 1096 Type.evaluate(el.visProp.gradientsecondcolor) 1097 ); 1098 this._setAttr( 1099 nodeFill, 1100 "opacity2", 1101 Type.evaluate(el.visProp.gradientsecondopacity) 1102 ); 1103 this._setAttr( 1104 nodeFill, 1105 "focusposition", 1106 Type.evaluate(el.visProp.gradientpositionx) * 100 + 1107 "%," + 1108 Type.evaluate(el.visProp.gradientpositiony) * 100 + 1109 "%" 1110 ); 1111 this._setAttr(nodeFill, "focussize", "0,0"); 1112 } else { 1113 this._setAttr(nodeFill, "type", "solid"); 1114 } 1115 }, 1116 1117 // Already documented in JXG.AbstractRenderer 1118 setObjectFillColor: function (el, color, opacity) { 1119 var rgba = Type.evaluate(color), 1120 c, 1121 rgbo, 1122 o = Type.evaluate(opacity), 1123 oo, 1124 node = el.rendNode; 1125 1126 o = o > 0 ? o : 0; 1127 1128 if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) { 1129 return; 1130 } 1131 1132 if (Type.exists(rgba) && rgba !== false) { 1133 // RGB, not RGBA 1134 if (rgba.length !== 9) { 1135 c = rgba; 1136 oo = o; 1137 // True RGBA, not RGB 1138 } else { 1139 rgbo = Color.rgba2rgbo(rgba); 1140 c = rgbo[0]; 1141 oo = o * rgbo[1]; 1142 } 1143 if (c === "none" || c === false) { 1144 this._setAttr(el.rendNode, "filled", "false"); 1145 } else { 1146 this._setAttr(el.rendNode, "filled", "true"); 1147 this._setAttr(el.rendNode, "fillcolor", c); 1148 1149 if (Type.exists(oo) && el.rendNodeFill) { 1150 this._setAttr(el.rendNodeFill, "opacity", oo * 100 + "%"); 1151 } 1152 } 1153 if (el.type === Const.OBJECT_TYPE_IMAGE) { 1154 /* 1155 t = el.rendNode.style.filter.toString(); 1156 if (t.match(/alpha/)) { 1157 el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')'); 1158 } else { 1159 el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) + ')'; 1160 } 1161 */ 1162 if (node.filters.length > 1) { 1163 // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]? 1164 // Setting axes:true shows text labels! 1165 node.filters.item(1).opacity = Math.round(oo * 100); // Why does setObjectFillColor not use Math.round? 1166 node.filters.item(1).enabled = true; 1167 } 1168 } 1169 } 1170 el.visPropOld.fillcolor = rgba; 1171 el.visPropOld.fillopacity = o; 1172 }, 1173 1174 // Already documented in JXG.AbstractRenderer 1175 setObjectStrokeColor: function (el, color, opacity) { 1176 var rgba = Type.evaluate(color), 1177 c, 1178 rgbo, 1179 o = Type.evaluate(opacity), 1180 oo, 1181 node = el.rendNode, 1182 nodeStroke; 1183 1184 o = o > 0 ? o : 0; 1185 1186 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 1187 return; 1188 } 1189 1190 // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor 1191 1192 if (Type.exists(rgba) && rgba !== false) { 1193 // RGB, not RGBA 1194 if (rgba.length !== 9) { 1195 c = rgba; 1196 oo = o; 1197 // True RGBA, not RGB 1198 } else { 1199 rgbo = color.rgba2rgbo(rgba); 1200 c = rgbo[0]; 1201 oo = o * rgbo[1]; 1202 } 1203 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 1204 //node.style.filter = ' alpha(opacity = ' + oo + ')'; 1205 /* 1206 t = node.style.filter.toString(); 1207 if (t.match(/alpha/)) { 1208 node.style.filter = 1209 t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + oo + ')'); 1210 } else { 1211 node.style.filter += ' alpha(opacity = ' + oo + ')'; 1212 } 1213 */ 1214 if (node.filters.length > 1) { 1215 // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]? 1216 // Setting axes:true shows text labels! 1217 node.filters.item(1).opacity = Math.round(oo * 100); 1218 node.filters.item(1).enabled = true; 1219 } 1220 1221 node.style.color = c; 1222 } else { 1223 if (c !== false) { 1224 this._setAttr(node, "stroked", "true"); 1225 this._setAttr(node, "strokecolor", c); 1226 } 1227 1228 nodeStroke = el.rendNodeStroke; 1229 if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) { 1230 this._setAttr(nodeStroke, "opacity", oo * 100 + "%"); 1231 } 1232 } 1233 } 1234 el.visPropOld.strokecolor = rgba; 1235 el.visPropOld.strokeopacity = o; 1236 }, 1237 1238 // Already documented in JXG.AbstractRenderer 1239 setObjectStrokeWidth: function (el, width) { 1240 var w = Type.evaluate(width), 1241 node; 1242 1243 if (isNaN(w) || el.visPropOld.strokewidth === w) { 1244 return; 1245 } 1246 1247 node = el.rendNode; 1248 this.setPropertyPrim(node, "stroked", "true"); 1249 1250 if (Type.exists(w)) { 1251 this.setPropertyPrim(node, "stroke-width", w); 1252 if (w === 0 && Type.exists(el.rendNodeStroke)) { 1253 this._setAttr(node, "stroked", "false"); 1254 } 1255 } 1256 1257 el.visPropOld.strokewidth = w; 1258 }, 1259 1260 // Already documented in JXG.AbstractRenderer 1261 setShadow: function (el) { 1262 var nodeShadow = el.rendNodeShadow, 1263 ev_s = Type.evaluate(el.visProp.shadow); 1264 1265 if (!nodeShadow || el.visPropOld.shadow === ev_s) { 1266 return; 1267 } 1268 1269 if (ev_s) { 1270 this._setAttr(nodeShadow, "On", "True"); 1271 this._setAttr(nodeShadow, "Offset", "3pt,3pt"); 1272 this._setAttr(nodeShadow, "Opacity", "60%"); 1273 this._setAttr(nodeShadow, "Color", "#aaaaaa"); 1274 } else { 1275 this._setAttr(nodeShadow, "On", "False"); 1276 } 1277 1278 el.visPropOld.shadow = ev_s; 1279 }, 1280 1281 /* ************************** 1282 * renderer control 1283 * **************************/ 1284 1285 // Already documented in JXG.AbstractRenderer 1286 suspendRedraw: function () { 1287 this.container.style.display = "none"; 1288 }, 1289 1290 // Already documented in JXG.AbstractRenderer 1291 unsuspendRedraw: function () { 1292 this.container.style.display = ""; 1293 } 1294 } 1295 ); 1296 1297 export default JXG.VMLRenderer; 1298