1 /* 2 Copyright 2008-2022 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 <http://www.gnu.org/licenses/> 29 and <http://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 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 s, 387 maxX, 388 maxY, 389 minX, 390 minY, 391 i, 392 nt, 393 node = el.rendNode, 394 p = [], 395 len = t.length; 396 397 if (len > 0) { 398 /* 399 nt = el.rendNode.style.filter.toString(); 400 if (!nt.match(/DXImageTransform/)) { 401 node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') " + nt; 402 } 403 */ 404 405 m = this.joinTransforms(el, t); 406 p[0] = Mat.matVecMult(m, el.coords.scrCoords); 407 p[0][1] /= p[0][0]; 408 p[0][2] /= p[0][0]; 409 p[1] = Mat.matVecMult(m, [ 410 1, 411 el.coords.scrCoords[1] + el.size[0], 412 el.coords.scrCoords[2] 413 ]); 414 p[1][1] /= p[1][0]; 415 p[1][2] /= p[1][0]; 416 p[2] = Mat.matVecMult(m, [ 417 1, 418 el.coords.scrCoords[1] + el.size[0], 419 el.coords.scrCoords[2] - el.size[1] 420 ]); 421 p[2][1] /= p[2][0]; 422 p[2][2] /= p[2][0]; 423 p[3] = Mat.matVecMult(m, [ 424 1, 425 el.coords.scrCoords[1], 426 el.coords.scrCoords[2] - el.size[1] 427 ]); 428 p[3][1] /= p[3][0]; 429 p[3][2] /= p[3][0]; 430 maxX = p[0][1]; 431 minX = p[0][1]; 432 maxY = p[0][2]; 433 minY = p[0][2]; 434 435 for (i = 1; i < 4; i++) { 436 maxX = Math.max(maxX, p[i][1]); 437 minX = Math.min(minX, p[i][1]); 438 maxY = Math.max(maxY, p[i][2]); 439 minY = Math.min(minY, p[i][2]); 440 } 441 node.style.left = Math.floor(minX) + "px"; 442 node.style.top = Math.floor(minY) + "px"; 443 444 node.filters.item(0).M11 = m[1][1]; 445 node.filters.item(0).M12 = m[1][2]; 446 node.filters.item(0).M21 = m[2][1]; 447 node.filters.item(0).M22 = m[2][2]; 448 node.filters.item(0).enabled = true; 449 } 450 }, 451 452 // Already documented in JXG.AbstractRenderer 453 updateImageURL: function (el) { 454 var url = Type.evaluate(el.url); 455 456 this._setAttr(el.rendNode, "src", url); 457 }, 458 459 /* ************************** 460 * Render primitive objects 461 * **************************/ 462 463 // Already documented in JXG.AbstractRenderer 464 appendChildPrim: function (node, level) { 465 // For trace nodes 466 if (!Type.exists(level)) { 467 level = 0; 468 } 469 470 node.style.zIndex = level; 471 this.container.appendChild(node); 472 473 return node; 474 }, 475 476 // Already documented in JXG.AbstractRenderer 477 appendNodesToElement: function (el, type) { 478 if (type === "shape" || type === "path" || type === "polygon") { 479 el.rendNodePath = this.getElementById(el.id + "_path"); 480 } 481 el.rendNodeFill = this.getElementById(el.id + "_fill"); 482 el.rendNodeStroke = this.getElementById(el.id + "_stroke"); 483 el.rendNodeShadow = this.getElementById(el.id + "_shadow"); 484 el.rendNode = this.getElementById(el.id); 485 }, 486 487 // Already documented in JXG.AbstractRenderer 488 createPrim: function (type, id) { 489 var node, 490 pathNode, 491 fillNode = this.createNode("fill"), 492 strokeNode = this.createNode("stroke"), 493 shadowNode = this.createNode("shadow"); 494 495 this._setAttr(fillNode, "id", this.container.id + "_" + id + "_fill"); 496 this._setAttr(strokeNode, "id", this.container.id + "_" + id + "_stroke"); 497 this._setAttr(shadowNode, "id", this.container.id + "_" + id + "_shadow"); 498 499 if (type === "circle" || type === "ellipse") { 500 node = this.createNode("oval"); 501 node.appendChild(fillNode); 502 node.appendChild(strokeNode); 503 node.appendChild(shadowNode); 504 } else if ( 505 type === "polygon" || 506 type === "path" || 507 type === "shape" || 508 type === "line" 509 ) { 510 node = this.createNode("shape"); 511 node.appendChild(fillNode); 512 node.appendChild(strokeNode); 513 node.appendChild(shadowNode); 514 pathNode = this.createNode("path"); 515 this._setAttr(pathNode, "id", this.container.id + "_" + id + "_path"); 516 node.appendChild(pathNode); 517 } else { 518 node = this.createNode(type); 519 node.appendChild(fillNode); 520 node.appendChild(strokeNode); 521 node.appendChild(shadowNode); 522 } 523 524 node.style.position = "absolute"; 525 node.style.left = "0px"; 526 node.style.top = "0px"; 527 this._setAttr(node, "id", this.container.id + "_" + id); 528 529 return node; 530 }, 531 532 // Already documented in JXG.AbstractRenderer 533 remove: function (node) { 534 if (Type.exists(node)) { 535 node.removeNode(true); 536 } 537 }, 538 539 // Already documented in JXG.AbstractRenderer 540 makeArrows: function (el) { 541 var nodeStroke, 542 ev_fa = Type.evaluate(el.visProp.firstarrow), 543 ev_la = Type.evaluate(el.visProp.lastarrow); 544 545 if (el.visPropOld.firstarrow === ev_fa && el.visPropOld.lastarrow === ev_la) { 546 return; 547 } 548 549 if (ev_fa) { 550 nodeStroke = el.rendNodeStroke; 551 this._setAttr(nodeStroke, "startarrow", "block"); 552 this._setAttr(nodeStroke, "startarrowlength", "long"); 553 } else { 554 nodeStroke = el.rendNodeStroke; 555 if (Type.exists(nodeStroke)) { 556 this._setAttr(nodeStroke, "startarrow", "none"); 557 } 558 } 559 560 if (ev_la) { 561 nodeStroke = el.rendNodeStroke; 562 this._setAttr(nodeStroke, "id", this.container.id + "_" + el.id + "stroke"); 563 this._setAttr(nodeStroke, "endarrow", "block"); 564 this._setAttr(nodeStroke, "endarrowlength", "long"); 565 } else { 566 nodeStroke = el.rendNodeStroke; 567 if (Type.exists(nodeStroke)) { 568 this._setAttr(nodeStroke, "endarrow", "none"); 569 } 570 } 571 el.visPropOld.firstarrow = ev_fa; 572 el.visPropOld.lastarrow = ev_la; 573 }, 574 575 // Already documented in JXG.AbstractRenderer 576 updateEllipsePrim: function (node, x, y, rx, ry) { 577 node.style.left = Math.floor(x - rx) + "px"; 578 node.style.top = Math.floor(y - ry) + "px"; 579 node.style.width = Math.floor(Math.abs(rx) * 2) + "px"; 580 node.style.height = Math.floor(Math.abs(ry) * 2) + "px"; 581 }, 582 583 // Already documented in JXG.AbstractRenderer 584 updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { 585 var s, 586 r = this.resolution; 587 588 if (!isNaN(p1x + p1y + p2x + p2y)) { 589 s = [ 590 "m ", 591 Math.floor(r * p1x), 592 ", ", 593 Math.floor(r * p1y), 594 " l ", 595 Math.floor(r * p2x), 596 ", ", 597 Math.floor(r * p2y) 598 ]; 599 this.updatePathPrim(node, s, board); 600 } 601 }, 602 603 // Already documented in JXG.AbstractRenderer 604 updatePathPrim: function (node, pointString, board) { 605 var x = board.canvasWidth, 606 y = board.canvasHeight; 607 if (pointString.length <= 0) { 608 pointString = ["m 0,0"]; 609 } 610 node.style.width = x; 611 node.style.height = y; 612 this._setAttr( 613 node, 614 "coordsize", 615 [Math.floor(this.resolution * x), Math.floor(this.resolution * y)].join(",") 616 ); 617 this._setAttr(node, "path", pointString.join("")); 618 }, 619 620 // Already documented in JXG.AbstractRenderer 621 updatePathStringPoint: function (el, size, type) { 622 var s = [], 623 mround = Math.round, 624 scr = el.coords.scrCoords, 625 sqrt32 = size * Math.sqrt(3) * 0.5, 626 s05 = size * 0.5, 627 r = this.resolution; 628 629 if (type === "x") { 630 s.push( 631 [ 632 " m ", 633 mround(r * (scr[1] - size)), 634 ", ", 635 mround(r * (scr[2] - size)), 636 " l ", 637 mround(r * (scr[1] + size)), 638 ", ", 639 mround(r * (scr[2] + size)), 640 " m ", 641 mround(r * (scr[1] + size)), 642 ", ", 643 mround(r * (scr[2] - size)), 644 " l ", 645 mround(r * (scr[1] - size)), 646 ", ", 647 mround(r * (scr[2] + size)) 648 ].join("") 649 ); 650 } else if (type === "+") { 651 s.push( 652 [ 653 " m ", 654 mround(r * (scr[1] - size)), 655 ", ", 656 mround(r * scr[2]), 657 " l ", 658 mround(r * (scr[1] + size)), 659 ", ", 660 mround(r * scr[2]), 661 " m ", 662 mround(r * scr[1]), 663 ", ", 664 mround(r * (scr[2] - size)), 665 " l ", 666 mround(r * scr[1]), 667 ", ", 668 mround(r * (scr[2] + size)) 669 ].join("") 670 ); 671 } else if (type === "<>") { 672 s.push( 673 [ 674 " m ", 675 mround(r * (scr[1] - size)), 676 ", ", 677 mround(r * scr[2]), 678 " l ", 679 mround(r * scr[1]), 680 ", ", 681 mround(r * (scr[2] + size)), 682 " l ", 683 mround(r * (scr[1] + size)), 684 ", ", 685 mround(r * scr[2]), 686 " l ", 687 mround(r * scr[1]), 688 ", ", 689 mround(r * (scr[2] - size)), 690 " x e " 691 ].join("") 692 ); 693 } else if (type === "^") { 694 s.push( 695 [ 696 " m ", 697 mround(r * scr[1]), 698 ", ", 699 mround(r * (scr[2] - size)), 700 " l ", 701 mround(r * (scr[1] - sqrt32)), 702 ", ", 703 mround(r * (scr[2] + s05)), 704 " l ", 705 mround(r * (scr[1] + sqrt32)), 706 ", ", 707 mround(r * (scr[2] + s05)), 708 " x e " 709 ].join("") 710 ); 711 } else if (type === "v") { 712 s.push( 713 [ 714 " m ", 715 mround(r * scr[1]), 716 ", ", 717 mround(r * (scr[2] + size)), 718 " l ", 719 mround(r * (scr[1] - sqrt32)), 720 ", ", 721 mround(r * (scr[2] - s05)), 722 " l ", 723 mround(r * (scr[1] + sqrt32)), 724 ", ", 725 mround(r * (scr[2] - s05)), 726 " x e " 727 ].join("") 728 ); 729 } else if (type === ">") { 730 s.push( 731 [ 732 " m ", 733 mround(r * (scr[1] + size)), 734 ", ", 735 mround(r * scr[2]), 736 " l ", 737 mround(r * (scr[1] - s05)), 738 ", ", 739 mround(r * (scr[2] - sqrt32)), 740 " l ", 741 mround(r * (scr[1] - s05)), 742 ", ", 743 mround(r * (scr[2] + sqrt32)), 744 " l ", 745 mround(r * (scr[1] + size)), 746 ", ", 747 mround(r * scr[2]) 748 ].join("") 749 ); 750 } else if (type === "<") { 751 s.push( 752 [ 753 " m ", 754 mround(r * (scr[1] - size)), 755 ", ", 756 mround(r * scr[2]), 757 " l ", 758 mround(r * (scr[1] + s05)), 759 ", ", 760 mround(r * (scr[2] - sqrt32)), 761 " l ", 762 mround(r * (scr[1] + s05)), 763 ", ", 764 mround(r * (scr[2] + sqrt32)), 765 " x e " 766 ].join("") 767 ); 768 } 769 770 return s; 771 }, 772 773 // Already documented in JXG.AbstractRenderer 774 updatePathStringPrim: function (el) { 775 var i, 776 scr, 777 pStr = [], 778 r = this.resolution, 779 mround = Math.round, 780 symbm = " m ", 781 symbl = " l ", 782 symbc = " c ", 783 nextSymb = symbm, 784 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html 785 786 if (el.numberPoints <= 0) { 787 return ""; 788 } 789 len = Math.min(len, el.points.length); 790 791 if (el.bezierDegree === 1) { 792 /* 793 if (isNotPlot && el.board.options.curve.RDPsmoothing) { 794 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0); 795 } 796 */ 797 798 for (i = 0; i < len; i++) { 799 scr = el.points[i].scrCoords; 800 if (isNaN(scr[1]) || isNaN(scr[2])) { 801 // PenUp 802 nextSymb = symbm; 803 } else { 804 // IE has problems with values being too far away. 805 if (scr[1] > 20000.0) { 806 scr[1] = 20000.0; 807 } else if (scr[1] < -20000.0) { 808 scr[1] = -20000.0; 809 } 810 811 if (scr[2] > 20000.0) { 812 scr[2] = 20000.0; 813 } else if (scr[2] < -20000.0) { 814 scr[2] = -20000.0; 815 } 816 817 pStr.push( 818 [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 819 ); 820 nextSymb = symbl; 821 } 822 } 823 } else if (el.bezierDegree === 3) { 824 i = 0; 825 while (i < len) { 826 scr = el.points[i].scrCoords; 827 if (isNaN(scr[1]) || isNaN(scr[2])) { 828 // PenUp 829 nextSymb = symbm; 830 } else { 831 pStr.push( 832 [nextSymb, mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 833 ); 834 if (nextSymb === symbc) { 835 i += 1; 836 scr = el.points[i].scrCoords; 837 pStr.push( 838 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 839 ); 840 i += 1; 841 scr = el.points[i].scrCoords; 842 pStr.push( 843 [" ", mround(r * scr[1]), ", ", mround(r * scr[2])].join("") 844 ); 845 } 846 nextSymb = symbc; 847 } 848 i += 1; 849 } 850 } 851 pStr.push(" e"); 852 return pStr; 853 }, 854 855 // Already documented in JXG.AbstractRenderer 856 updatePathStringBezierPrim: function (el) { 857 var i, 858 j, 859 k, 860 scr, 861 lx, 862 ly, 863 pStr = [], 864 f = Type.evaluate(el.visProp.strokewidth), 865 r = this.resolution, 866 mround = Math.round, 867 symbm = " m ", 868 symbl = " c ", 869 nextSymb = symbm, 870 isNoPlot = Type.evaluate(el.visProp.curvetype) !== "plot", 871 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html 872 873 if (el.numberPoints <= 0) { 874 return ""; 875 } 876 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 877 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0); 878 } 879 len = Math.min(len, el.points.length); 880 881 for (j = 1; j < 3; j++) { 882 nextSymb = symbm; 883 for (i = 0; i < len; i++) { 884 scr = el.points[i].scrCoords; 885 if (isNaN(scr[1]) || isNaN(scr[2])) { 886 // PenUp 887 nextSymb = symbm; 888 } else { 889 // IE has problems with values being too far away. 890 if (scr[1] > 20000.0) { 891 scr[1] = 20000.0; 892 } else if (scr[1] < -20000.0) { 893 scr[1] = -20000.0; 894 } 895 896 if (scr[2] > 20000.0) { 897 scr[2] = 20000.0; 898 } else if (scr[2] < -20000.0) { 899 scr[2] = -20000.0; 900 } 901 902 if (nextSymb === symbm) { 903 pStr.push( 904 [nextSymb, mround(r * scr[1]), " ", mround(r * scr[2])].join("") 905 ); 906 } else { 907 k = 2 * j; 908 pStr.push( 909 [ 910 nextSymb, 911 mround( 912 r * 913 (lx + 914 (scr[1] - lx) * 0.333 + 915 f * (k * Math.random() - j)) 916 ), 917 " ", 918 mround( 919 r * 920 (ly + 921 (scr[2] - ly) * 0.333 + 922 f * (k * Math.random() - j)) 923 ), 924 " ", 925 mround( 926 r * 927 (lx + 928 (scr[1] - lx) * 0.666 + 929 f * (k * Math.random() - j)) 930 ), 931 " ", 932 mround( 933 r * 934 (ly + 935 (scr[2] - ly) * 0.666 + 936 f * (k * Math.random() - j)) 937 ), 938 " ", 939 mround(r * scr[1]), 940 " ", 941 mround(r * scr[2]) 942 ].join("") 943 ); 944 } 945 nextSymb = symbl; 946 lx = scr[1]; 947 ly = scr[2]; 948 } 949 } 950 } 951 pStr.push(" e"); 952 return pStr; 953 }, 954 955 // Already documented in JXG.AbstractRenderer 956 updatePolygonPrim: function (node, el) { 957 var i, 958 len = el.vertices.length, 959 r = this.resolution, 960 scr, 961 pStr = []; 962 963 this._setAttr(node, "stroked", "false"); 964 scr = el.vertices[0].coords.scrCoords; 965 966 if (isNaN(scr[1] + scr[2])) { 967 return; 968 } 969 970 pStr.push( 971 ["m ", Math.floor(r * scr[1]), ",", Math.floor(r * scr[2]), " l "].join("") 972 ); 973 974 for (i = 1; i < len - 1; i++) { 975 if (el.vertices[i].isReal) { 976 scr = el.vertices[i].coords.scrCoords; 977 978 if (isNaN(scr[1] + scr[2])) { 979 return; 980 } 981 982 pStr.push(Math.floor(r * scr[1]) + "," + Math.floor(r * scr[2])); 983 } else { 984 this.updatePathPrim(node, "", el.board); 985 return; 986 } 987 if (i < len - 2) { 988 pStr.push(", "); 989 } 990 } 991 pStr.push(" x e"); 992 this.updatePathPrim(node, pStr, el.board); 993 }, 994 995 // Already documented in JXG.AbstractRenderer 996 updateRectPrim: function (node, x, y, w, h) { 997 node.style.left = Math.floor(x) + "px"; 998 node.style.top = Math.floor(y) + "px"; 999 1000 if (w >= 0) { 1001 node.style.width = w + "px"; 1002 } 1003 1004 if (h >= 0) { 1005 node.style.height = h + "px"; 1006 } 1007 }, 1008 1009 /* ************************** 1010 * Set Attributes 1011 * **************************/ 1012 1013 // Already documented in JXG.AbstractRenderer 1014 setPropertyPrim: function (node, key, val) { 1015 var keyVml = "", 1016 v; 1017 1018 switch (key) { 1019 case "stroke": 1020 keyVml = "strokecolor"; 1021 break; 1022 case "stroke-width": 1023 keyVml = "strokeweight"; 1024 break; 1025 case "stroke-dasharray": 1026 keyVml = "dashstyle"; 1027 break; 1028 } 1029 1030 if (keyVml !== "") { 1031 v = Type.evaluate(val); 1032 this._setAttr(node, keyVml, v); 1033 } 1034 }, 1035 1036 // Already documented in JXG.AbstractRenderer 1037 display: function (el, val) { 1038 if (el && el.rendNode) { 1039 el.visPropOld.visible = val; 1040 if (val) { 1041 el.rendNode.style.visibility = "inherit"; 1042 } else { 1043 el.rendNode.style.visibility = "hidden"; 1044 } 1045 } 1046 }, 1047 1048 // Already documented in JXG.AbstractRenderer 1049 show: function (el) { 1050 JXG.deprecated("Board.renderer.show()", "Board.renderer.display()"); 1051 1052 if (el && el.rendNode) { 1053 el.rendNode.style.visibility = "inherit"; 1054 } 1055 }, 1056 1057 // Already documented in JXG.AbstractRenderer 1058 hide: function (el) { 1059 JXG.deprecated("Board.renderer.hide()", "Board.renderer.display()"); 1060 1061 if (el && el.rendNode) { 1062 el.rendNode.style.visibility = "hidden"; 1063 } 1064 }, 1065 1066 // Already documented in JXG.AbstractRenderer 1067 setDashStyle: function (el, visProp) { 1068 var node; 1069 if (visProp.dash >= 0) { 1070 node = el.rendNodeStroke; 1071 this._setAttr(node, "dashstyle", this.dashArray[visProp.dash]); 1072 } 1073 }, 1074 1075 // Already documented in JXG.AbstractRenderer 1076 setGradient: function (el) { 1077 var nodeFill = el.rendNodeFill, 1078 ev_g = Type.evaluate(el.visProp.gradient); 1079 1080 if (ev_g === "linear") { 1081 this._setAttr(nodeFill, "type", "gradient"); 1082 this._setAttr( 1083 nodeFill, 1084 "color2", 1085 Type.evaluate(el.visProp.gradientsecondcolor) 1086 ); 1087 this._setAttr( 1088 nodeFill, 1089 "opacity2", 1090 Type.evaluate(el.visProp.gradientsecondopacity) 1091 ); 1092 this._setAttr(nodeFill, "angle", Type.evaluate(el.visProp.gradientangle)); 1093 } else if (ev_g === "radial") { 1094 this._setAttr(nodeFill, "type", "gradientradial"); 1095 this._setAttr( 1096 nodeFill, 1097 "color2", 1098 Type.evaluate(el.visProp.gradientsecondcolor) 1099 ); 1100 this._setAttr( 1101 nodeFill, 1102 "opacity2", 1103 Type.evaluate(el.visProp.gradientsecondopacity) 1104 ); 1105 this._setAttr( 1106 nodeFill, 1107 "focusposition", 1108 Type.evaluate(el.visProp.gradientpositionx) * 100 + 1109 "%," + 1110 Type.evaluate(el.visProp.gradientpositiony) * 100 + 1111 "%" 1112 ); 1113 this._setAttr(nodeFill, "focussize", "0,0"); 1114 } else { 1115 this._setAttr(nodeFill, "type", "solid"); 1116 } 1117 }, 1118 1119 // Already documented in JXG.AbstractRenderer 1120 setObjectFillColor: function (el, color, opacity) { 1121 var rgba = Type.evaluate(color), 1122 c, 1123 rgbo, 1124 o = Type.evaluate(opacity), 1125 oo, 1126 node = el.rendNode, 1127 t; 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 t, 1183 o = Type.evaluate(opacity), 1184 oo, 1185 node = el.rendNode, 1186 nodeStroke; 1187 1188 o = o > 0 ? o : 0; 1189 1190 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 1191 return; 1192 } 1193 1194 // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor 1195 1196 if (Type.exists(rgba) && rgba !== false) { 1197 // RGB, not RGBA 1198 if (rgba.length !== 9) { 1199 c = rgba; 1200 oo = o; 1201 // True RGBA, not RGB 1202 } else { 1203 rgbo = color.rgba2rgbo(rgba); 1204 c = rgbo[0]; 1205 oo = o * rgbo[1]; 1206 } 1207 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 1208 //node.style.filter = ' alpha(opacity = ' + oo + ')'; 1209 /* 1210 t = node.style.filter.toString(); 1211 if (t.match(/alpha/)) { 1212 node.style.filter = 1213 t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + oo + ')'); 1214 } else { 1215 node.style.filter += ' alpha(opacity = ' + oo + ')'; 1216 } 1217 */ 1218 if (node.filters.length > 1) { 1219 // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]? 1220 // Setting axes:true shows text labels! 1221 node.filters.item(1).opacity = Math.round(oo * 100); 1222 node.filters.item(1).enabled = true; 1223 } 1224 1225 node.style.color = c; 1226 } else { 1227 if (c !== false) { 1228 this._setAttr(node, "stroked", "true"); 1229 this._setAttr(node, "strokecolor", c); 1230 } 1231 1232 nodeStroke = el.rendNodeStroke; 1233 if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) { 1234 this._setAttr(nodeStroke, "opacity", oo * 100 + "%"); 1235 } 1236 } 1237 } 1238 el.visPropOld.strokecolor = rgba; 1239 el.visPropOld.strokeopacity = o; 1240 }, 1241 1242 // Already documented in JXG.AbstractRenderer 1243 setObjectStrokeWidth: function (el, width) { 1244 var w = Type.evaluate(width), 1245 node; 1246 1247 if (isNaN(w) || el.visPropOld.strokewidth === w) { 1248 return; 1249 } 1250 1251 node = el.rendNode; 1252 this.setPropertyPrim(node, "stroked", "true"); 1253 1254 if (Type.exists(w)) { 1255 this.setPropertyPrim(node, "stroke-width", w); 1256 if (w === 0 && Type.exists(el.rendNodeStroke)) { 1257 this._setAttr(node, "stroked", "false"); 1258 } 1259 } 1260 1261 el.visPropOld.strokewidth = w; 1262 }, 1263 1264 // Already documented in JXG.AbstractRenderer 1265 setShadow: function (el) { 1266 var nodeShadow = el.rendNodeShadow, 1267 ev_s = Type.evaluate(el.visProp.shadow); 1268 1269 if (!nodeShadow || el.visPropOld.shadow === ev_s) { 1270 return; 1271 } 1272 1273 if (ev_s) { 1274 this._setAttr(nodeShadow, "On", "True"); 1275 this._setAttr(nodeShadow, "Offset", "3pt,3pt"); 1276 this._setAttr(nodeShadow, "Opacity", "60%"); 1277 this._setAttr(nodeShadow, "Color", "#aaaaaa"); 1278 } else { 1279 this._setAttr(nodeShadow, "On", "False"); 1280 } 1281 1282 el.visPropOld.shadow = ev_s; 1283 }, 1284 1285 /* ************************** 1286 * renderer control 1287 * **************************/ 1288 1289 // Already documented in JXG.AbstractRenderer 1290 suspendRedraw: function () { 1291 this.container.style.display = "none"; 1292 }, 1293 1294 // Already documented in JXG.AbstractRenderer 1295 unsuspendRedraw: function () { 1296 this.container.style.display = ""; 1297 } 1298 } 1299 ); 1300 1301 export default JXG.VMLRenderer; 1302