1 /* 2 Copyright 2008-2018 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 /* depends: 36 jxg 37 options 38 renderer/abstract 39 base/constants 40 utils/type 41 utils/env 42 utils/color 43 math/numerics 44 */ 45 46 define([ 47 'jxg', 'options', 'renderer/abstract', 'base/constants', 'utils/type', 'utils/env', 'utils/color', 'utils/base64', 'math/numerics' 48 ], function (JXG, Options, AbstractRenderer, Const, Type, Env, Color, Base64, Numerics) { 49 50 "use strict"; 51 52 /** 53 * Uses SVG to implement the rendering methods defined in {@link JXG.AbstractRenderer}. 54 * @class JXG.AbstractRenderer 55 * @augments JXG.AbstractRenderer 56 * @param {Node} container Reference to a DOM node containing the board. 57 * @param {Object} dim The dimensions of the board 58 * @param {Number} dim.width 59 * @param {Number} dim.height 60 * @see JXG.AbstractRenderer 61 */ 62 JXG.SVGRenderer = function (container, dim) { 63 var i; 64 65 // docstring in AbstractRenderer 66 this.type = 'svg'; 67 68 this.isIE = navigator.appVersion.indexOf("MSIE") !== -1 || navigator.userAgent.match(/Trident\//); 69 70 /** 71 * SVG root node 72 * @type Node 73 */ 74 this.svgRoot = null; 75 76 /** 77 * The SVG Namespace used in JSXGraph. 78 * @see http://www.w3.org/TR/SVG/ 79 * @type String 80 * @default http://www.w3.org/2000/svg 81 */ 82 this.svgNamespace = 'http://www.w3.org/2000/svg'; 83 84 /** 85 * The xlink namespace. This is used for images. 86 * @see http://www.w3.org/TR/xlink/ 87 * @type String 88 * @default http://www.w3.org/1999/xlink 89 */ 90 this.xlinkNamespace = 'http://www.w3.org/1999/xlink'; 91 92 // container is documented in AbstractRenderer 93 this.container = container; 94 95 // prepare the div container and the svg root node for use with JSXGraph 96 this.container.style.MozUserSelect = 'none'; 97 this.container.style.userSelect = 'none'; 98 99 this.container.style.overflow = 'hidden'; 100 if (this.container.style.position === '') { 101 this.container.style.position = 'relative'; 102 } 103 104 this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg"); 105 this.svgRoot.style.overflow = 'hidden'; 106 107 this.resize(dim.width, dim.height); 108 109 //this.svgRoot.setAttributeNS(null, 'shape-rendering', 'crispEdge'); //'optimizeQuality'); //geometricPrecision'); 110 111 this.container.appendChild(this.svgRoot); 112 113 /** 114 * The <tt>defs</tt> element is a container element to reference reusable SVG elements. 115 * @type Node 116 * @see http://www.w3.org/TR/SVG/struct.html#DefsElement 117 */ 118 this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, 'defs'); 119 this.svgRoot.appendChild(this.defs); 120 121 /** 122 * Filters are used to apply shadows. 123 * @type Node 124 * @see http://www.w3.org/TR/SVG/filters.html#FilterElement 125 */ 126 this.filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter'); 127 this.filter.setAttributeNS(null, 'id', this.container.id + '_' + 'f1'); 128 /* 129 this.filter.setAttributeNS(null, 'x', '-100%'); 130 this.filter.setAttributeNS(null, 'y', '-100%'); 131 this.filter.setAttributeNS(null, 'width', '400%'); 132 this.filter.setAttributeNS(null, 'height', '400%'); 133 //this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse'); 134 */ 135 this.filter.setAttributeNS(null, 'width', '300%'); 136 this.filter.setAttributeNS(null, 'height', '300%'); 137 this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse'); 138 139 this.feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset'); 140 this.feOffset.setAttributeNS(null, 'result', 'offOut'); 141 this.feOffset.setAttributeNS(null, 'in', 'SourceAlpha'); 142 this.feOffset.setAttributeNS(null, 'dx', '5'); 143 this.feOffset.setAttributeNS(null, 'dy', '5'); 144 this.filter.appendChild(this.feOffset); 145 146 this.feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur'); 147 this.feGaussianBlur.setAttributeNS(null, 'result', 'blurOut'); 148 this.feGaussianBlur.setAttributeNS(null, 'in', 'offOut'); 149 this.feGaussianBlur.setAttributeNS(null, 'stdDeviation', '3'); 150 this.filter.appendChild(this.feGaussianBlur); 151 152 this.feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend'); 153 this.feBlend.setAttributeNS(null, 'in', 'SourceGraphic'); 154 this.feBlend.setAttributeNS(null, 'in2', 'blurOut'); 155 this.feBlend.setAttributeNS(null, 'mode', 'normal'); 156 this.filter.appendChild(this.feBlend); 157 158 this.defs.appendChild(this.filter); 159 160 /** 161 * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front 162 * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented 163 * there, too. The higher the number, the "more on top" are the elements on this layer. 164 * @type Array 165 */ 166 this.layer = []; 167 for (i = 0; i < Options.layer.numlayers; i++) { 168 this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g'); 169 this.svgRoot.appendChild(this.layer[i]); 170 } 171 172 // already documented in JXG.AbstractRenderer 173 this.supportsForeignObject = document.implementation.hasFeature("http://w3.org/TR/SVG11/feature#Extensibility", "1.1"); 174 175 if (this.supportsForeignObject) { 176 this.foreignObjLayer = this.container.ownerDocument.createElementNS(this.svgNamespace, 'foreignObject'); 177 this.foreignObjLayer.setAttribute("x",0); 178 this.foreignObjLayer.setAttribute("y",0); 179 this.foreignObjLayer.setAttribute("width","100%"); 180 this.foreignObjLayer.setAttribute("height","100%"); 181 this.foreignObjLayer.setAttribute('id', this.container.id + '_foreignObj'); 182 this.svgRoot.appendChild(this.foreignObjLayer); 183 } 184 185 /** 186 * Defines dash patterns. Defined styles are: <ol> 187 * <li value="-1"> 2px dash, 2px space</li> 188 * <li> 5px dash, 5px space</li> 189 * <li> 10px dash, 10px space</li> 190 * <li> 20px dash, 20px space</li> 191 * <li> 20px dash, 10px space, 10px dash, 10px dash</li> 192 * <li> 20px dash, 5px space, 10px dash, 5px space</li></ol> 193 * @type Array 194 * @default ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5'] 195 * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties 196 */ 197 this.dashArray = ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5']; 198 }; 199 200 JXG.SVGRenderer.prototype = new AbstractRenderer(); 201 202 JXG.extend(JXG.SVGRenderer.prototype, /** @lends JXG.SVGRenderer.prototype */ { 203 204 /** 205 * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag. 206 * @private 207 * @param {JXG.GeometryElement} el A JSXGraph element, preferably one that can have an arrow attached. 208 * @param {String} [idAppendix=''] A string that is added to the node's id. 209 * @returns {Node} Reference to the node added to the DOM. 210 */ 211 _createArrowHead: function (el, idAppendix) { 212 var node2, node3, 213 id = el.id + 'Triangle', 214 type = null, 215 w, s, 216 ev_fa = Type.evaluate(el.visProp.firstarrow), 217 ev_la = Type.evaluate(el.visProp.lastarrow); 218 219 if (Type.exists(idAppendix)) { 220 id += idAppendix; 221 } 222 node2 = this.createPrim('marker', id); 223 224 node2.setAttributeNS(null, 'stroke', Type.evaluate(el.visProp.strokecolor)); 225 node2.setAttributeNS(null, 'stroke-opacity', Type.evaluate(el.visProp.strokeopacity)); 226 node2.setAttributeNS(null, 'fill', Type.evaluate(el.visProp.strokecolor)); 227 node2.setAttributeNS(null, 'fill-opacity', Type.evaluate(el.visProp.strokeopacity)); 228 node2.setAttributeNS(null, 'stroke-width', 0); // this is the stroke-width of the arrow head. 229 // Should be zero to simplify the calculations 230 231 node2.setAttributeNS(null, 'orient', 'auto'); 232 node2.setAttributeNS(null, 'markerUnits', 'strokeWidth'); // 'strokeWidth' 'userSpaceOnUse'); 233 234 /* 235 The arrow head is an isosceles triangle with base length 10 and height 10. 236 This 10 units are scaled to strokeWidth * arrowSize pixels, see 237 this._setArrowWidth(). 238 239 See also abstractRenderer.updateLine() where the line path is shortened accordingly. 240 241 Changes here are also necessary in setArrowWidth(). 242 */ 243 node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, 'path'); 244 if (idAppendix === 'End') { 245 // First arrow 246 if (JXG.exists(ev_fa.type)) { 247 type = Type.evaluate(ev_fa.type); 248 } 249 250 node2.setAttributeNS(null, 'refY', 5); 251 if (type === 2) { 252 node2.setAttributeNS(null, 'refX', 4.9); 253 node3.setAttributeNS(null, 'd', 'M 10,0 L 0,5 L 10,10 L 5,5 z'); 254 } else if (type === 3) { 255 node2.setAttributeNS(null, 'refX', 3.33); 256 node3.setAttributeNS(null, 'd', 'M 0,0 L 3.33,0 L 3.33,10 L 0,10 z'); 257 } else { 258 node2.setAttributeNS(null, 'refX', 9.9); 259 node3.setAttributeNS(null, 'd', 'M 10,0 L 0,5 L 10,10 z'); 260 } 261 } else { 262 // Last arrow 263 if (JXG.exists(ev_la.type)) { 264 type = Type.evaluate(ev_la.type); 265 } 266 267 node2.setAttributeNS(null, 'refY', 5); 268 if (type === 2) { 269 node2.setAttributeNS(null, 'refX', 5.1); 270 node3.setAttributeNS(null, 'd', 'M 0,0 L 10,5 L 0,10 L 5,5 z'); 271 } else if (type === 3) { 272 node2.setAttributeNS(null, 'refX', 0.1); 273 node3.setAttributeNS(null, 'd', 'M 0,0 L 3.33,0 L 3.33,10 L 0,10 z'); 274 } else { 275 node2.setAttributeNS(null, 'refX', 0.1); 276 node3.setAttributeNS(null, 'd', 'M 0,0 L 10,5 L 0,10 z'); 277 } 278 } 279 280 node2.appendChild(node3); 281 return node2; 282 }, 283 284 /** 285 * Updates color of an arrow DOM node. 286 * @param {Node} node The arrow node. 287 * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green. 288 * @param {Number} opacity 289 * @param {JXG.GeometryElement} el The element the arrows are to be attached to 290 */ 291 _setArrowColor: function (node, color, opacity, el) { 292 var s, d; 293 294 if (node) { 295 if (Type.isString(color)) { 296 this._setAttribute(function() { 297 node.setAttributeNS(null, 'stroke', color); 298 node.setAttributeNS(null, 'fill', color); 299 node.setAttributeNS(null, 'stroke-opacity', opacity); 300 node.setAttributeNS(null, 'fill-opacity', opacity); 301 }, el.visPropOld.fillcolor); 302 } 303 304 if (this.isIE) { 305 el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode); 306 } 307 } 308 309 }, 310 311 // already documented in JXG.AbstractRenderer 312 _setArrowWidth: function (node, width, parentNode, size) { 313 var s, d; 314 315 if (node) { 316 if (width === 0) { 317 node.setAttributeNS(null, 'display', 'none'); 318 } else { 319 s = width; 320 d = s * size; 321 node.setAttributeNS(null, 'viewBox', (0) + ' ' + (0) + ' ' + (s * 10) + ' ' + (s * 10)); 322 node.setAttributeNS(null, 'markerHeight', d); 323 node.setAttributeNS(null, 'markerWidth', d); 324 node.setAttributeNS(null, 'display', 'inherit'); 325 } 326 327 if (this.isIE) { 328 parentNode.parentNode.insertBefore(parentNode, parentNode); 329 } 330 } 331 }, 332 333 /* ******************************** * 334 * This renderer does not need to 335 * override draw/update* methods 336 * since it provides draw/update*Prim 337 * methods except for some cases like 338 * internal texts or images. 339 * ******************************** */ 340 341 /* ************************** 342 * Lines 343 * **************************/ 344 345 // documented in AbstractRenderer 346 updateTicks: function (ticks) { 347 var i, c, node, x, y, 348 tickStr = '', 349 len = ticks.ticks.length; 350 351 for (i = 0; i < len; i++) { 352 c = ticks.ticks[i]; 353 x = c[0]; 354 y = c[1]; 355 356 if (Type.isNumber(x[0]) && Type.isNumber(x[1])) { 357 tickStr += "M " + (x[0]) + " " + (y[0]) + " L " + (x[1]) + " " + (y[1]) + " "; 358 } 359 } 360 361 node = ticks.rendNode; 362 363 if (!Type.exists(node)) { 364 node = this.createPrim('path', ticks.id); 365 this.appendChildPrim(node, Type.evaluate(ticks.visProp.layer)); 366 ticks.rendNode = node; 367 } 368 369 node.setAttributeNS(null, 'stroke', Type.evaluate(ticks.visProp.strokecolor)); 370 node.setAttributeNS(null, 'stroke-opacity', Type.evaluate(ticks.visProp.strokeopacity)); 371 node.setAttributeNS(null, 'stroke-width', Type.evaluate(ticks.visProp.strokewidth)); 372 this.updatePathPrim(node, tickStr, ticks.board); 373 }, 374 375 /* ************************** 376 * Text related stuff 377 * **************************/ 378 379 // already documented in JXG.AbstractRenderer 380 displayCopyright: function (str, fontsize) { 381 var node = this.createPrim('text', 'licenseText'), 382 t; 383 node.setAttributeNS(null, 'x', '20px'); 384 node.setAttributeNS(null, 'y', (2 + fontsize) + 'px'); 385 node.setAttributeNS(null, "style", "font-family:Arial,Helvetica,sans-serif; font-size:" + fontsize + "px; fill:#356AA0; opacity:0.3;"); 386 t = this.container.ownerDocument.createTextNode(str); 387 node.appendChild(t); 388 this.appendChildPrim(node, 0); 389 }, 390 391 // already documented in JXG.AbstractRenderer 392 drawInternalText: function (el) { 393 var node = this.createPrim('text', el.id); 394 395 //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox 396 // Preserve spaces 397 //node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "space", "preserve"); 398 node.style.whiteSpace = 'nowrap'; 399 400 el.rendNodeText = this.container.ownerDocument.createTextNode(''); 401 node.appendChild(el.rendNodeText); 402 this.appendChildPrim(node, Type.evaluate(el.visProp.layer)); 403 404 return node; 405 }, 406 407 // already documented in JXG.AbstractRenderer 408 updateInternalText: function (el) { 409 var content = el.plaintext, v, 410 ev_ax = el.getAnchorX(), 411 ev_ay = el.getAnchorY(); 412 413 if (el.rendNode.getAttributeNS(null, "class") !== el.visProp.cssclass) { 414 el.rendNode.setAttributeNS(null, "class", Type.evaluate(el.visProp.cssclass)); 415 el.needsSizeUpdate = true; 416 } 417 418 if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) { 419 // Horizontal 420 v = el.coords.scrCoords[1]; 421 if (el.visPropOld.left !== (ev_ax + v)) { 422 el.rendNode.setAttributeNS(null, 'x', v + 'px'); 423 424 if (ev_ax === 'left') { 425 el.rendNode.setAttributeNS(null, 'text-anchor', 'start'); 426 } else if (ev_ax === 'right') { 427 el.rendNode.setAttributeNS(null, 'text-anchor', 'end'); 428 } else if (ev_ax === 'middle') { 429 el.rendNode.setAttributeNS(null, 'text-anchor', 'middle'); 430 } 431 el.visPropOld.left = ev_ax + v; 432 } 433 434 // Vertical 435 v = el.coords.scrCoords[2]; 436 if (el.visPropOld.top !== (ev_ay + v)) { 437 el.rendNode.setAttributeNS(null, 'y', (v + this.vOffsetText * 0.5) + 'px'); 438 439 if (ev_ay === 'bottom') { 440 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-after-edge'); 441 } else if (ev_ay === 'top') { 442 el.rendNode.setAttributeNS(null, 'dy', '1.6ex'); 443 //el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-before-edge'); // Not supported by IE, edge 444 } else if (ev_ay === 'middle') { 445 //el.rendNode.setAttributeNS(null, 'dominant-baseline', 'middle'); 446 el.rendNode.setAttributeNS(null, 'dy', '0.6ex'); 447 } 448 el.visPropOld.top = ev_ay + v; 449 } 450 } 451 if (el.htmlStr !== content) { 452 el.rendNodeText.data = content; 453 el.htmlStr = content; 454 } 455 this.transformImage(el, el.transformations); 456 }, 457 458 /** 459 * Set color and opacity of internal texts. 460 * SVG needs its own version. 461 * @private 462 * @see JXG.AbstractRenderer#updateTextStyle 463 * @see JXG.AbstractRenderer#updateInternalTextStyle 464 */ 465 updateInternalTextStyle: function (el, strokeColor, strokeOpacity, duration) { 466 this.setObjectFillColor(el, strokeColor, strokeOpacity); 467 }, 468 469 /* ************************** 470 * Image related stuff 471 * **************************/ 472 473 // already documented in JXG.AbstractRenderer 474 drawImage: function (el) { 475 var node = this.createPrim('image', el.id); 476 477 node.setAttributeNS(null, 'preserveAspectRatio', 'none'); 478 this.appendChildPrim(node, Type.evaluate(el.visProp.layer)); 479 el.rendNode = node; 480 481 this.updateImage(el); 482 }, 483 484 // already documented in JXG.AbstractRenderer 485 transformImage: function (el, t) { 486 var s, m, 487 node = el.rendNode, 488 str = "", 489 len = t.length; 490 491 if (len > 0) { 492 m = this.joinTransforms(el, t); 493 s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(','); 494 str += ' matrix(' + s + ') '; 495 node.setAttributeNS(null, 'transform', str); 496 } 497 }, 498 499 // already documented in JXG.AbstractRenderer 500 updateImageURL: function (el) { 501 var url = Type.evaluate(el.url); 502 503 el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url); 504 }, 505 506 // already documented in JXG.AbstractRenderer 507 updateImageStyle: function (el, doHighlight) { 508 var css = Type.evaluate(doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass); 509 510 el.rendNode.setAttributeNS(null, 'class', css); 511 }, 512 513 /* ************************** 514 * Render primitive objects 515 * **************************/ 516 517 // already documented in JXG.AbstractRenderer 518 appendChildPrim: function (node, level) { 519 if (!Type.exists(level)) { // trace nodes have level not set 520 level = 0; 521 } else if (level >= Options.layer.numlayers) { 522 level = Options.layer.numlayers - 1; 523 } 524 525 this.layer[level].appendChild(node); 526 527 return node; 528 }, 529 530 // already documented in JXG.AbstractRenderer 531 createPrim: function (type, id) { 532 var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type); 533 node.setAttributeNS(null, 'id', this.container.id + '_' + id); 534 node.style.position = 'absolute'; 535 if (type === 'path') { 536 node.setAttributeNS(null, 'stroke-linecap', 'round'); 537 node.setAttributeNS(null, 'stroke-linejoin', 'round'); 538 } 539 return node; 540 }, 541 542 // already documented in JXG.AbstractRenderer 543 remove: function (shape) { 544 if (Type.exists(shape) && Type.exists(shape.parentNode)) { 545 shape.parentNode.removeChild(shape); 546 } 547 }, 548 549 // already documented in JXG.AbstractRenderer 550 makeArrows: function (el) { 551 var node2, 552 ev_fa = Type.evaluate(el.visProp.firstarrow), 553 ev_la = Type.evaluate(el.visProp.lastarrow); 554 555 if (el.visPropOld.firstarrow === ev_fa && 556 el.visPropOld.lastarrow === ev_la) { 557 if (this.isIE && el.visPropCalc.visible && 558 (ev_fa || ev_la)) { 559 el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode); 560 } 561 return; 562 } 563 564 if (ev_fa) { 565 node2 = el.rendNodeTriangleStart; 566 if (!Type.exists(node2)) { 567 node2 = this._createArrowHead(el, 'End'); 568 this.defs.appendChild(node2); 569 el.rendNodeTriangleStart = node2; 570 el.rendNode.setAttributeNS(null, 'marker-start', 'url(#' + this.container.id + '_' + el.id + 'TriangleEnd)'); 571 } else { 572 this.defs.appendChild(node2); 573 } 574 } else { 575 node2 = el.rendNodeTriangleStart; 576 if (Type.exists(node2)) { 577 this.remove(node2); 578 } 579 } 580 if (ev_la) { 581 node2 = el.rendNodeTriangleEnd; 582 if (!Type.exists(node2)) { 583 node2 = this._createArrowHead(el, 'Start'); 584 this.defs.appendChild(node2); 585 el.rendNodeTriangleEnd = node2; 586 el.rendNode.setAttributeNS(null, 'marker-end', 'url(#' + this.container.id + '_' + el.id + 'TriangleStart)'); 587 } else { 588 this.defs.appendChild(node2); 589 } 590 } else { 591 node2 = el.rendNodeTriangleEnd; 592 if (Type.exists(node2)) { 593 this.remove(node2); 594 } 595 } 596 el.visPropOld.firstarrow = ev_fa; 597 el.visPropOld.lastarrow = ev_la; 598 }, 599 600 // already documented in JXG.AbstractRenderer 601 updateEllipsePrim: function (node, x, y, rx, ry) { 602 var huge = 1000000; 603 604 huge = 200000; // IE 605 // webkit does not like huge values if the object is dashed 606 // iE doesn't like huge values above 216000 607 x = Math.abs(x) < huge ? x : huge * x / Math.abs(x); 608 y = Math.abs(y) < huge ? y : huge * y / Math.abs(y); 609 rx = Math.abs(rx) < huge ? rx : huge * rx / Math.abs(rx); 610 ry = Math.abs(ry) < huge ? ry : huge * ry / Math.abs(ry); 611 612 node.setAttributeNS(null, 'cx', x); 613 node.setAttributeNS(null, 'cy', y); 614 node.setAttributeNS(null, 'rx', Math.abs(rx)); 615 node.setAttributeNS(null, 'ry', Math.abs(ry)); 616 }, 617 618 // already documented in JXG.AbstractRenderer 619 updateLinePrim: function (node, p1x, p1y, p2x, p2y) { 620 var huge = 1000000; 621 622 huge = 200000; //IE 623 if (!isNaN(p1x + p1y + p2x + p2y)) { 624 // webkit does not like huge values if the object is dashed 625 // IE doesn't like huge values above 216000 626 p1x = Math.abs(p1x) < huge ? p1x : huge * p1x / Math.abs(p1x); 627 p1y = Math.abs(p1y) < huge ? p1y : huge * p1y / Math.abs(p1y); 628 p2x = Math.abs(p2x) < huge ? p2x : huge * p2x / Math.abs(p2x); 629 p2y = Math.abs(p2y) < huge ? p2y : huge * p2y / Math.abs(p2y); 630 631 node.setAttributeNS(null, 'x1', p1x); 632 node.setAttributeNS(null, 'y1', p1y); 633 node.setAttributeNS(null, 'x2', p2x); 634 node.setAttributeNS(null, 'y2', p2y); 635 } 636 }, 637 638 // already documented in JXG.AbstractRenderer 639 updatePathPrim: function (node, pointString) { 640 if (pointString === '') { 641 pointString = 'M 0 0'; 642 } 643 node.setAttributeNS(null, 'd', pointString); 644 }, 645 646 // already documented in JXG.AbstractRenderer 647 updatePathStringPoint: function (el, size, type) { 648 var s = '', 649 scr = el.coords.scrCoords, 650 sqrt32 = size * Math.sqrt(3) * 0.5, 651 s05 = size * 0.5; 652 653 if (type === 'x') { 654 s = ' M ' + (scr[1] - size) + ' ' + (scr[2] - size) + 655 ' L ' + (scr[1] + size) + ' ' + (scr[2] + size) + 656 ' M ' + (scr[1] + size) + ' ' + (scr[2] - size) + 657 ' L ' + (scr[1] - size) + ' ' + (scr[2] + size); 658 } else if (type === '+') { 659 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 660 ' L ' + (scr[1] + size) + ' ' + (scr[2]) + 661 ' M ' + (scr[1]) + ' ' + (scr[2] - size) + 662 ' L ' + (scr[1]) + ' ' + (scr[2] + size); 663 } else if (type === '<>') { 664 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 665 ' L ' + (scr[1]) + ' ' + (scr[2] + size) + 666 ' L ' + (scr[1] + size) + ' ' + (scr[2]) + 667 ' L ' + (scr[1]) + ' ' + (scr[2] - size) + ' Z '; 668 } else if (type === '^') { 669 s = ' M ' + (scr[1]) + ' ' + (scr[2] - size) + 670 ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] + s05) + 671 ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] + s05) + 672 ' Z '; // close path 673 } else if (type === 'v') { 674 s = ' M ' + (scr[1]) + ' ' + (scr[2] + size) + 675 ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] - s05) + 676 ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] - s05) + 677 ' Z '; 678 } else if (type === '>') { 679 s = ' M ' + (scr[1] + size) + ' ' + (scr[2]) + 680 ' L ' + (scr[1] - s05) + ' ' + (scr[2] - sqrt32) + 681 ' L ' + (scr[1] - s05) + ' ' + (scr[2] + sqrt32) + 682 ' Z '; 683 } else if (type === '<') { 684 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 685 ' L ' + (scr[1] + s05) + ' ' + (scr[2] - sqrt32) + 686 ' L ' + (scr[1] + s05) + ' ' + (scr[2] + sqrt32) + 687 ' Z '; 688 } 689 return s; 690 }, 691 692 // already documented in JXG.AbstractRenderer 693 updatePathStringPrim: function (el) { 694 var i, scr, len, 695 symbm = ' M ', 696 symbl = ' L ', 697 symbc = ' C ', 698 nextSymb = symbm, 699 maxSize = 5000.0, 700 pStr = ''; 701 702 if (el.numberPoints <= 0) { 703 return ''; 704 } 705 706 len = Math.min(el.points.length, el.numberPoints); 707 708 if (el.bezierDegree === 1) { 709 for (i = 0; i < len; i++) { 710 scr = el.points[i].scrCoords; 711 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 712 nextSymb = symbm; 713 } else { 714 // Chrome has problems with values being too far away. 715 scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize); 716 scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize); 717 718 // Attention: first coordinate may be inaccurate if far way 719 //pStr += [nextSymb, scr[1], ' ', scr[2]].join(''); 720 pStr += nextSymb + scr[1] + ' ' + scr[2]; // Seems to be faster now (webkit and firefox) 721 nextSymb = symbl; 722 } 723 } 724 } else if (el.bezierDegree === 3) { 725 i = 0; 726 while (i < len) { 727 scr = el.points[i].scrCoords; 728 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 729 nextSymb = symbm; 730 } else { 731 pStr += nextSymb + scr[1] + ' ' + scr[2]; 732 if (nextSymb === symbc) { 733 i += 1; 734 scr = el.points[i].scrCoords; 735 pStr += ' ' + scr[1] + ' ' + scr[2]; 736 i += 1; 737 scr = el.points[i].scrCoords; 738 pStr += ' ' + scr[1] + ' ' + scr[2]; 739 } 740 nextSymb = symbc; 741 } 742 i += 1; 743 } 744 } 745 return pStr; 746 }, 747 748 // already documented in JXG.AbstractRenderer 749 updatePathStringBezierPrim: function (el) { 750 var i, j, k, scr, lx, ly, len, 751 symbm = ' M ', 752 symbl = ' C ', 753 nextSymb = symbm, 754 maxSize = 5000.0, 755 pStr = '', 756 f = Type.evaluate(el.visProp.strokewidth), 757 isNoPlot = (Type.evaluate(el.visProp.curvetype) !== 'plot'); 758 759 if (el.numberPoints <= 0) { 760 return ''; 761 } 762 763 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 764 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5); 765 } 766 767 len = Math.min(el.points.length, el.numberPoints); 768 for (j = 1; j < 3; j++) { 769 nextSymb = symbm; 770 for (i = 0; i < len; i++) { 771 scr = el.points[i].scrCoords; 772 773 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 774 nextSymb = symbm; 775 } else { 776 // Chrome has problems with values being too far away. 777 scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize); 778 scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize); 779 780 // Attention: first coordinate may be inaccurate if far way 781 if (nextSymb === symbm) { 782 //pStr += [nextSymb, scr[1], ' ', scr[2]].join(''); 783 pStr += nextSymb + scr[1] + ' ' + scr[2]; // Seems to be faster now (webkit and firefox) 784 } else { 785 k = 2 * j; 786 pStr += [nextSymb, 787 (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)), ' ', 788 (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)), ' ', 789 (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)), ' ', 790 (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)), ' ', 791 scr[1], ' ', scr[2]].join(''); 792 } 793 794 nextSymb = symbl; 795 lx = scr[1]; 796 ly = scr[2]; 797 } 798 } 799 } 800 return pStr; 801 }, 802 803 // already documented in JXG.AbstractRenderer 804 updatePolygonPrim: function (node, el) { 805 var i, 806 pStr = '', 807 scrCoords, 808 len = el.vertices.length; 809 810 node.setAttributeNS(null, 'stroke', 'none'); 811 812 for (i = 0; i < len - 1; i++) { 813 if (el.vertices[i].isReal) { 814 scrCoords = el.vertices[i].coords.scrCoords; 815 pStr = pStr + scrCoords[1] + "," + scrCoords[2]; 816 } else { 817 node.setAttributeNS(null, 'points', ''); 818 return; 819 } 820 821 if (i < len - 2) { 822 pStr += " "; 823 } 824 } 825 if (pStr.indexOf('NaN') === -1) { 826 node.setAttributeNS(null, 'points', pStr); 827 } 828 }, 829 830 // already documented in JXG.AbstractRenderer 831 updateRectPrim: function (node, x, y, w, h) { 832 node.setAttributeNS(null, 'x', x); 833 node.setAttributeNS(null, 'y', y); 834 node.setAttributeNS(null, 'width', w); 835 node.setAttributeNS(null, 'height', h); 836 }, 837 838 /* ************************** 839 * Set Attributes 840 * **************************/ 841 842 // documented in JXG.AbstractRenderer 843 setPropertyPrim: function (node, key, val) { 844 if (key === 'stroked') { 845 return; 846 } 847 node.setAttributeNS(null, key, val); 848 }, 849 850 display: function(el, val) { 851 var node; 852 853 if (el && el.rendNode) { 854 el.visPropOld.visible = val; 855 node = el.rendNode; 856 if (val) { 857 node.setAttributeNS(null, 'display', 'inline'); 858 node.style.visibility = "inherit"; 859 } else { 860 node.setAttributeNS(null, 'display', 'none'); 861 node.style.visibility = "hidden"; 862 } 863 } 864 }, 865 866 // documented in JXG.AbstractRenderer 867 show: function (el) { 868 JXG.deprecated('Board.renderer.show()', 'Board.renderer.display()'); 869 this.display(el, true); 870 // var node; 871 // 872 // if (el && el.rendNode) { 873 // node = el.rendNode; 874 // node.setAttributeNS(null, 'display', 'inline'); 875 // node.style.visibility = "inherit"; 876 // } 877 }, 878 879 // documented in JXG.AbstractRenderer 880 hide: function (el) { 881 JXG.deprecated('Board.renderer.hide()', 'Board.renderer.display()'); 882 this.display(el, false); 883 // var node; 884 // 885 // if (el && el.rendNode) { 886 // node = el.rendNode; 887 // node.setAttributeNS(null, 'display', 'none'); 888 // node.style.visibility = "hidden"; 889 // } 890 }, 891 892 // documented in JXG.AbstractRenderer 893 setBuffering: function (el, type) { 894 el.rendNode.setAttribute('buffered-rendering', type); 895 }, 896 897 // documented in JXG.AbstractRenderer 898 setDashStyle: function (el) { 899 var dashStyle = Type.evaluate(el.visProp.dash), 900 node = el.rendNode; 901 902 if (dashStyle > 0) { 903 node.setAttributeNS(null, 'stroke-dasharray', this.dashArray[dashStyle - 1]); 904 } else { 905 if (node.hasAttributeNS(null, 'stroke-dasharray')) { 906 node.removeAttributeNS(null, 'stroke-dasharray'); 907 } 908 } 909 }, 910 911 // documented in JXG.AbstractRenderer 912 setGradient: function (el) { 913 var fillNode = el.rendNode, col, op, 914 node, node2, node3, x1, x2, y1, y2, 915 ev_g = Type.evaluate(el.visProp.gradient); 916 917 op = Type.evaluate(el.visProp.fillopacity); 918 op = (op > 0) ? op : 0; 919 col = Type.evaluate(el.visProp.fillcolor); 920 921 if (ev_g === 'linear') { 922 node = this.createPrim('linearGradient', el.id + '_gradient'); 923 x1 = '0%'; 924 x2 = '100%'; 925 y1 = '0%'; 926 y2 = '0%'; 927 928 node.setAttributeNS(null, 'x1', x1); 929 node.setAttributeNS(null, 'x2', x2); 930 node.setAttributeNS(null, 'y1', y1); 931 node.setAttributeNS(null, 'y2', y2); 932 node2 = this.createPrim('stop', el.id + '_gradient1'); 933 node2.setAttributeNS(null, 'offset', '0%'); 934 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 935 node3 = this.createPrim('stop', el.id + '_gradient2'); 936 node3.setAttributeNS(null, 'offset', '100%'); 937 node3.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) + 938 ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity)); 939 node.appendChild(node2); 940 node.appendChild(node3); 941 this.defs.appendChild(node); 942 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)'); 943 el.gradNode1 = node2; 944 el.gradNode2 = node3; 945 } else if (ev_g === 'radial') { 946 node = this.createPrim('radialGradient', el.id + '_gradient'); 947 948 node.setAttributeNS(null, 'cx', '50%'); 949 node.setAttributeNS(null, 'cy', '50%'); 950 node.setAttributeNS(null, 'r', '50%'); 951 node.setAttributeNS(null, 'fx', Type.evaluate(el.visProp.gradientpositionx) * 100 + '%'); 952 node.setAttributeNS(null, 'fy', Type.evaluate(el.visProp.gradientpositiony) * 100 + '%'); 953 954 node2 = this.createPrim('stop', el.id + '_gradient1'); 955 node2.setAttributeNS(null, 'offset', '0%'); 956 node2.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) + 957 ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity)); 958 node3 = this.createPrim('stop', el.id + '_gradient2'); 959 node3.setAttributeNS(null, 'offset', '100%'); 960 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 961 962 node.appendChild(node2); 963 node.appendChild(node3); 964 this.defs.appendChild(node); 965 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)'); 966 el.gradNode1 = node2; 967 el.gradNode2 = node3; 968 } else { 969 fillNode.removeAttributeNS(null, 'style'); 970 } 971 }, 972 973 // documented in JXG.AbstractRenderer 974 updateGradient: function (el) { 975 var col, op, 976 node2 = el.gradNode1, 977 node3 = el.gradNode2, 978 ev_g = Type.evaluate(el.visProp.gradient); 979 980 if (!Type.exists(node2) || !Type.exists(node3)) { 981 return; 982 } 983 984 op = Type.evaluate(el.visProp.fillopacity); 985 op = (op > 0) ? op : 0; 986 col = Type.evaluate(el.visProp.fillcolor); 987 988 if (ev_g === 'linear') { 989 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 990 node3.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) + 991 ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity)); 992 } else if (ev_g === 'radial') { 993 node2.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) + 994 ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity)); 995 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 996 } 997 }, 998 999 // documented in JXG.AbstractRenderer 1000 setObjectTransition: function (el, duration) { 1001 var node, transitionStr, 1002 i, len, 1003 nodes = ['rendNode', 1004 'rendNodeTriangleStart', 1005 'rendNodeTriangleEnd']; 1006 1007 if (duration === undefined) { 1008 duration = Type.evaluate(el.visProp.transitionduration); 1009 } 1010 1011 if (duration === el.visPropOld.transitionduration) { 1012 return; 1013 } 1014 1015 if (el.elementClass === Const.OBJECT_CLASS_TEXT && 1016 Type.evaluate(el.visProp.display) === 'html') { 1017 transitionStr = ' color ' + duration + 'ms,' + 1018 ' opacity ' + duration + 'ms'; 1019 } else { 1020 transitionStr = ' fill ' + duration + 'ms,' + 1021 ' fill-opacity ' + duration + 'ms,' + 1022 ' stroke ' + duration + 'ms,' + 1023 ' stroke-opacity ' + duration + 'ms'; 1024 } 1025 1026 len = nodes.length; 1027 for (i = 0; i < len; ++i) { 1028 if (el[nodes[i]]) { 1029 node = el[nodes[i]]; 1030 node.style.transition = transitionStr; 1031 } 1032 } 1033 1034 el.visPropOld.transitionduration = duration; 1035 }, 1036 1037 /** 1038 * Call user-defined function to set visual attributes. 1039 * If "testAttribute" is the empty string, the function 1040 * is called immediately, otherwise it is called in a timeOut. 1041 * 1042 * This is necessary to realize smooth transitions buit avoid transistions 1043 * when first creating the objects. 1044 * 1045 * Usually, the string in testAttribute is the visPropOld attribute 1046 * of the values which are set. 1047 * 1048 * @param {Function} setFunc Some function which usually sets some attributes 1049 * @param {String} testAttribute If this string is the empty string the function is called immediately, 1050 * otherwise it is called in a setImeout. 1051 * @see JXG.SVGRenderer#setObjectFillColor 1052 * @see JXG.SVGRenderer#setObjectStrokeColor 1053 * @see JXG.SVGRenderer#_setArrowColor 1054 * @private 1055 */ 1056 _setAttribute: function(setFunc, testAttribute) { 1057 if (testAttribute === '') { 1058 setFunc(); 1059 } else { 1060 setTimeout(setFunc, 1); 1061 } 1062 }, 1063 1064 // documented in JXG.AbstractRenderer 1065 setObjectFillColor: function (el, color, opacity, rendNode) { 1066 var node, c, rgbo, oo, t, 1067 rgba = Type.evaluate(color), 1068 o = Type.evaluate(opacity); 1069 1070 o = (o > 0) ? o : 0; 1071 1072 if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) { 1073 return; 1074 } 1075 1076 if (Type.exists(rgba) && rgba !== false) { 1077 if (rgba.length !== 9) { // RGB, not RGBA 1078 c = rgba; 1079 oo = o; 1080 } else { // True RGBA, not RGB 1081 rgbo = Color.rgba2rgbo(rgba); 1082 c = rgbo[0]; 1083 oo = o * rgbo[1]; 1084 } 1085 1086 if (rendNode === undefined) { 1087 node = el.rendNode; 1088 } else { 1089 node = rendNode; 1090 } 1091 1092 if (c !== 'none') { 1093 this._setAttribute(function() { 1094 node.setAttributeNS(null, 'fill', c); 1095 }, el.visPropOld.fillcolor); 1096 } 1097 1098 if (el.type === JXG.OBJECT_TYPE_IMAGE) { 1099 this._setAttribute(function() { 1100 node.setAttributeNS(null, 'opacity', oo); 1101 }, el.visPropOld.fillopacity); 1102 //node.style['opacity'] = oo; // This would overwrite values set by CSS class. 1103 } else { 1104 if (c === 'none') { // This is done only for non-images 1105 // because images have no fill color. 1106 oo = 0; 1107 } 1108 this._setAttribute(function() { 1109 node.setAttributeNS(null, 'fill-opacity', oo); 1110 }, el.visPropOld.fillopacity); 1111 } 1112 1113 if (Type.exists(el.visProp.gradient)) { 1114 this.updateGradient(el); 1115 } 1116 } 1117 el.visPropOld.fillcolor = rgba; 1118 el.visPropOld.fillopacity = o; 1119 }, 1120 1121 // documented in JXG.AbstractRenderer 1122 setObjectStrokeColor: function (el, color, opacity) { 1123 var rgba = Type.evaluate(color), c, rgbo, 1124 o = Type.evaluate(opacity), oo, 1125 node; 1126 1127 o = (o > 0) ? o : 0; 1128 1129 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 1130 return; 1131 } 1132 1133 if (Type.exists(rgba) && rgba !== false) { 1134 if (rgba.length !== 9) { // RGB, not RGBA 1135 c = rgba; 1136 oo = o; 1137 } else { // True RGBA, not RGB 1138 rgbo = Color.rgba2rgbo(rgba); 1139 c = rgbo[0]; 1140 oo = o * rgbo[1]; 1141 } 1142 1143 node = el.rendNode; 1144 1145 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 1146 if (Type.evaluate(el.visProp.display) === 'html') { 1147 this._setAttribute(function() { 1148 node.style.color = c; 1149 node.style.opacity = oo; 1150 }, el.visPropOld.strokecolor); 1151 1152 } else { 1153 this._setAttribute(function() { 1154 node.setAttributeNS(null, "style", "fill:" + c); 1155 node.setAttributeNS(null, "style", "fill-opacity:" + oo); 1156 }, el.visPropOld.strokecolor); 1157 } 1158 } else { 1159 this._setAttribute(function() { 1160 node.setAttributeNS(null, 'stroke', c); 1161 node.setAttributeNS(null, 'stroke-opacity', oo); 1162 }, el.visPropOld.strokecolor); 1163 } 1164 1165 if (el.elementClass === Const.OBJECT_CLASS_CURVE || 1166 el.elementClass === Const.OBJECT_CLASS_LINE) { 1167 if (Type.evaluate(el.visProp.firstarrow)) { 1168 this._setArrowColor(el.rendNodeTriangleStart, c, oo, el); 1169 } 1170 1171 if (Type.evaluate(el.visProp.lastarrow)) { 1172 this._setArrowColor(el.rendNodeTriangleEnd, c, oo, el); 1173 } 1174 } 1175 } 1176 1177 el.visPropOld.strokecolor = rgba; 1178 el.visPropOld.strokeopacity = o; 1179 }, 1180 1181 // documented in JXG.AbstractRenderer 1182 setObjectStrokeWidth: function (el, width) { 1183 var node, 1184 w = Type.evaluate(width), 1185 rgba, c, rgbo, o, oo; 1186 1187 if (isNaN(w) || el.visPropOld.strokewidth === w) { 1188 return; 1189 } 1190 1191 node = el.rendNode; 1192 this.setPropertyPrim(node, 'stroked', 'true'); 1193 if (Type.exists(w)) { 1194 this.setPropertyPrim(node, 'stroke-width', w + 'px'); 1195 1196 // if (el.elementClass === Const.OBJECT_CLASS_CURVE || 1197 // el.elementClass === Const.OBJECT_CLASS_LINE) { 1198 // if (Type.evaluate(el.visProp.firstarrow)) { 1199 // this._setArrowWidth(el.rendNodeTriangleStart, w, el.rendNode); 1200 // } 1201 // 1202 // if (Type.evaluate(el.visProp.lastarrow)) { 1203 // this._setArrowWidth(el.rendNodeTriangleEnd, w, el.rendNode); 1204 // } 1205 // } 1206 } 1207 el.visPropOld.strokewidth = w; 1208 }, 1209 1210 // documented in JXG.AbstractRenderer 1211 setLineCap: function (el) { 1212 var capStyle = Type.evaluate(el.visProp.linecap); 1213 1214 if (capStyle === undefined || capStyle === '' || el.visPropOld.linecap === capStyle || 1215 !Type.exists(el.rendNode)) { 1216 return; 1217 } 1218 1219 this.setPropertyPrim(el.rendNode, 'stroke-linecap', capStyle); 1220 el.visPropOld.linecap = capStyle; 1221 1222 }, 1223 1224 // documented in JXG.AbstractRenderer 1225 setShadow: function (el) { 1226 var ev_s = Type.evaluate(el.visProp.shadow); 1227 if (el.visPropOld.shadow === ev_s) { 1228 return; 1229 } 1230 1231 if (Type.exists(el.rendNode)) { 1232 if (ev_s) { 1233 el.rendNode.setAttributeNS(null, 'filter', 'url(#' + this.container.id + '_' + 'f1)'); 1234 } else { 1235 el.rendNode.removeAttributeNS(null, 'filter'); 1236 } 1237 } 1238 el.visPropOld.shadow = ev_s; 1239 }, 1240 1241 /* ************************** 1242 * renderer control 1243 * **************************/ 1244 1245 // documented in JXG.AbstractRenderer 1246 suspendRedraw: function () { 1247 // It seems to be important for the Linux version of firefox 1248 //this.suspendHandle = this.svgRoot.suspendRedraw(10000); 1249 }, 1250 1251 // documented in JXG.AbstractRenderer 1252 unsuspendRedraw: function () { 1253 //this.svgRoot.unsuspendRedraw(this.suspendHandle); 1254 //this.svgRoot.unsuspendRedrawAll(); 1255 //this.svgRoot.forceRedraw(); 1256 }, 1257 1258 // documented in AbstractRenderer 1259 resize: function (w, h) { 1260 this.svgRoot.style.width = parseFloat(w) + 'px'; 1261 this.svgRoot.style.height = parseFloat(h) + 'px'; 1262 this.svgRoot.setAttribute("width", parseFloat(w)); 1263 this.svgRoot.setAttribute("height", parseFloat(h)); 1264 }, 1265 1266 // documented in JXG.AbstractRenderer 1267 createTouchpoints: function (n) { 1268 var i, na1, na2, node; 1269 this.touchpoints = []; 1270 for (i = 0; i < n; i++) { 1271 na1 = 'touchpoint1_' + i; 1272 node = this.createPrim('path', na1); 1273 this.appendChildPrim(node, 19); 1274 node.setAttributeNS(null, 'd', 'M 0 0'); 1275 this.touchpoints.push(node); 1276 1277 this.setPropertyPrim(node, 'stroked', 'true'); 1278 this.setPropertyPrim(node, 'stroke-width', '1px'); 1279 node.setAttributeNS(null, 'stroke', '#000000'); 1280 node.setAttributeNS(null, 'stroke-opacity', 1.0); 1281 node.setAttributeNS(null, 'display', 'none'); 1282 1283 na2 = 'touchpoint2_' + i; 1284 node = this.createPrim('ellipse', na2); 1285 this.appendChildPrim(node, 19); 1286 this.updateEllipsePrim(node, 0, 0, 0, 0); 1287 this.touchpoints.push(node); 1288 1289 this.setPropertyPrim(node, 'stroked', 'true'); 1290 this.setPropertyPrim(node, 'stroke-width', '1px'); 1291 node.setAttributeNS(null, 'stroke', '#000000'); 1292 node.setAttributeNS(null, 'stroke-opacity', 1.0); 1293 node.setAttributeNS(null, 'fill', '#ffffff'); 1294 node.setAttributeNS(null, 'fill-opacity', 0.0); 1295 1296 node.setAttributeNS(null, 'display', 'none'); 1297 } 1298 }, 1299 1300 // documented in JXG.AbstractRenderer 1301 showTouchpoint: function (i) { 1302 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1303 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'inline'); 1304 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'inline'); 1305 } 1306 }, 1307 1308 // documented in JXG.AbstractRenderer 1309 hideTouchpoint: function (i) { 1310 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1311 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'none'); 1312 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'none'); 1313 } 1314 }, 1315 1316 // documented in JXG.AbstractRenderer 1317 updateTouchpoint: function (i, pos) { 1318 var x, y, 1319 d = 37; 1320 1321 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1322 x = pos[0]; 1323 y = pos[1]; 1324 1325 this.touchpoints[2 * i].setAttributeNS(null, 'd', 'M ' + (x - d) + ' ' + y + ' ' + 1326 'L ' + (x + d) + ' ' + y + ' ' + 1327 'M ' + x + ' ' + (y - d) + ' ' + 1328 'L ' + x + ' ' + (y + d)); 1329 this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25); 1330 } 1331 }, 1332 1333 /** 1334 * Convert the SVG construction into an HTML canvas image. 1335 * This works for all SVG supporting browsers. 1336 * For IE it works from version 9, with the execption that HTML texts 1337 * are ignored on IE. The drawing is done with a delay of 1338 * 200 ms. Otherwise there would be problems with IE. 1339 * 1340 * 1341 * @param {String} canvasId Id of an HTML canvas element 1342 * @param {Number} w Width in pixel of the dumped image, i.e. of the canvas tag. 1343 * @param {Number} h Height in pixel of the dumped image, i.e. of the canvas tag. 1344 * @param {Boolean} ignoreTexts If true, the foreignObject tag is taken out from the SVG root. 1345 * This is necessary for Safari. Default: false 1346 * @returns {Object} the svg renderer object. 1347 * 1348 * @example 1349 * board.renderer.dumpToCanvas('canvas'); 1350 */ 1351 dumpToCanvas: function(canvasId, w, h, ignoreTexts) { 1352 var svgRoot = this.svgRoot, 1353 btoa = window.btoa || Base64.encode, 1354 svg, tmpImg, cv, ctx, 1355 wOrg, hOrg, 1356 // uriPayload, 1357 // DOMURL, svgBlob, url, 1358 virtualNode, doc; 1359 1360 // Move all HTML tags (beside the SVG root) of the container 1361 // to the foreignObject element inside of the svgRoot node 1362 if (this.container.hasChildNodes() && Type.exists(this.foreignObjLayer)) { 1363 while (svgRoot.nextSibling) { 1364 this.foreignObjLayer.appendChild(svgRoot.nextSibling); 1365 } 1366 if (ignoreTexts === true) { 1367 doc = this.container.ownerDocument; 1368 virtualNode = doc.createElement('div'); 1369 virtualNode.appendChild(this.foreignObjLayer); 1370 } 1371 } 1372 1373 svgRoot.setAttribute("xmlns", "http://www.w3.org/2000/svg"); 1374 wOrg = svgRoot.getAttribute('width'); 1375 hOrg = svgRoot.getAttribute('height'); 1376 1377 svg = new XMLSerializer().serializeToString(svgRoot); 1378 1379 if (false) { 1380 // Debug: example svg image 1381 svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="220" height="220"><rect width="66" height="30" x="21" y="32" stroke="#204a87" stroke-width="2" fill="none" /></svg>'; 1382 } 1383 1384 // In IE we have to remove the namespace again. 1385 if ((svg.match(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/g) || []).length > 1) { 1386 svg = svg.replace(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/g, ''); 1387 } 1388 1389 // Safari fails if the svg string contains a " " 1390 svg = svg.replace(/ /g, ' '); 1391 1392 cv = document.getElementById(canvasId); 1393 // Clear the canvas 1394 cv.width = cv.width; 1395 1396 ctx = cv.getContext("2d"); 1397 if (w !== undefined && h !== undefined) { 1398 // Scale twice the CSS size to make the image crisp 1399 cv.style.width = parseFloat(w) + 'px'; 1400 cv.style.height = parseFloat(h) + 'px'; 1401 cv.setAttribute('width', 2 * parseFloat(wOrg)); 1402 cv.setAttribute('height', 2 * parseFloat(hOrg)); 1403 ctx.scale(2 * wOrg / w, 2 * hOrg / h); 1404 } 1405 1406 tmpImg = new Image(); 1407 if (true) { 1408 tmpImg.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))); 1409 // uriPayload = encodeURIComponent(svg.replace(/\n+/g, '')) // remove newlines // encode URL-unsafe characters 1410 // .replace(/%20/g, ' ') // put spaces back in 1411 // .replace(/%3D/g, '=') // ditto equals signs 1412 // .replace(/%3A/g, ':') // ditto colons 1413 // .replace(/%2F/g, '/') // ditto slashes 1414 // .replace(/%22/g, "'"); 1415 // tmpImg.src = 'data:image/svg+xml,' + uriPayload; 1416 1417 tmpImg.onload = function () { 1418 // IE needs a pause... 1419 setTimeout(function(){ 1420 ctx.drawImage(tmpImg, 0, 0, w, h); 1421 }, 200); 1422 }; 1423 } else { 1424 // // Alternative version 1425 // DOMURL = window.URL || window.webkitURL || window; 1426 // svgBlob = new Blob([svg], {type: 'image/svg+xml'}); 1427 // url = DOMURL.createObjectURL(svgBlob); 1428 // tmpImg.src = url; 1429 // 1430 // tmpImg.onload = function () { 1431 // // IE needs a pause... 1432 // setTimeout(function(){ 1433 // ctx.drawImage(tmpImg, 0, 0, w, h); 1434 // }, 200); 1435 // DOMURL.revokeObjectURL(url); 1436 // }; 1437 } 1438 1439 // Move all HTML tags back from 1440 // the foreignObject element to the container 1441 if (Type.exists(this.foreignObjLayer) && this.foreignObjLayer.hasChildNodes()) { 1442 if (ignoreTexts === true) { 1443 svgRoot.appendChild(this.foreignObjLayer); 1444 } 1445 while (this.foreignObjLayer.firstChild) { 1446 this.container.appendChild(this.foreignObjLayer.firstChild); 1447 } 1448 } 1449 1450 return this; 1451 }, 1452 1453 /** 1454 * Display SVG image in html img-tag which enables 1455 * easy download for the user. 1456 * 1457 * Support: 1458 * <ul> 1459 * <li> IE: No 1460 * <li> Edge: full 1461 * <li>Firefox: full 1462 * <li> Chrome: full 1463 * <li> Safari: supported, but no texts (to be precise, no foreignObject-element is allowed in SVG) 1464 * </ul> 1465 * 1466 * @param {JXG.Board} board Link to the board. 1467 * @param {String} imgId Optional id of an img object. If given and different from the empty string, 1468 * the screenshot is copied to this img object. The width and height will be set to the values of the 1469 * JSXGraph container. 1470 * @param {Boolean} ignoreTexts If set to true, the foreignObject is taken out of the 1471 * SVGRoot and texts are not displayed. This is mandatory for Safari. Default: false 1472 * @return {Object} the svg renderer object 1473 */ 1474 screenshot: function(board, imgId, ignoreTexts) { 1475 var node, 1476 doc = this.container.ownerDocument, 1477 parent = this.container.parentNode, 1478 cPos, 1479 canvas, id, 1480 img, 1481 button, buttonText, 1482 w, h, 1483 bas = board.attr.screenshot, 1484 zbar, zbarDisplay, cssTxt, 1485 newImg = false, 1486 isDebug = false; 1487 1488 if (this.type === 'no') { 1489 return this; 1490 } 1491 1492 w = bas.scale * parseFloat(this.container.style.width); 1493 h = bas.scale * parseFloat(this.container.style.height); 1494 1495 if (imgId === undefined || imgId === '') { 1496 newImg = true; 1497 img = new Image(); //doc.createElement('img'); 1498 img.style.width = w + 'px'; 1499 img.style.height = h + 'px'; 1500 } else { 1501 newImg = false; 1502 img = doc.getElementById(imgId); 1503 } 1504 // img.crossOrigin = 'anonymous'; 1505 1506 // Create div which contains canvas element and close button 1507 if (newImg) { 1508 node = doc.createElement('div'); 1509 node.style.cssText = bas.css; 1510 node.style.width = (w) + 'px'; 1511 node.style.height = (h) + 'px'; 1512 node.style.zIndex = this.container.style.zIndex + 120; 1513 1514 // Position the div exactly over the JSXGraph board 1515 cPos = board.getCoordsTopLeftCorner(); 1516 node.style.position= 'absolute'; 1517 node.style.left = (cPos[0]) + 'px'; 1518 node.style.top = (cPos[1]) + 'px'; 1519 } 1520 1521 if (!isDebug) { 1522 // Create canvas element and add it to the DOM 1523 // It will be removed after the image has been stored. 1524 canvas = doc.createElement('canvas'); 1525 id = Math.random().toString(36).substr(2, 5); 1526 canvas.setAttribute('id', id); 1527 canvas.setAttribute('width', w); 1528 canvas.setAttribute('height', h); 1529 canvas.style.width = w + 'px'; 1530 canvas.style.height = w + 'px'; 1531 canvas.style.display = 'none'; 1532 parent.append(canvas); 1533 } else { 1534 // Debug: use canvas element 1535 // 'jxgbox_canvas' from jsxdev/dump.html 1536 id = 'jxgbox_canvas'; 1537 canvas = document.getElementById(id); 1538 } 1539 1540 if (newImg) { 1541 // Create close button 1542 button = doc.createElement('span'); 1543 buttonText = doc.createTextNode('\u2716'); 1544 button.style.cssText = bas.cssButton; 1545 button.appendChild(buttonText); 1546 button.onclick = function() { 1547 node.parentNode.removeChild(node); 1548 }; 1549 1550 // Add all nodes 1551 node.appendChild(img); 1552 node.appendChild(button); 1553 parent.appendChild(node); 1554 } 1555 1556 // Hide navigation bar in board 1557 zbar = document.getElementById(this.container.id + '_navigationbar'); 1558 if (Type.exists(zbar)) { 1559 zbarDisplay = zbar.style.display; 1560 zbar.style.display = 'none'; 1561 } 1562 1563 // Create screenshot in canvas 1564 this.dumpToCanvas(id, w, h, ignoreTexts); 1565 1566 // Show image in img tag 1567 setTimeout(function() { 1568 //console.log(canvas.toDataURL('image/png')); 1569 img.src = canvas.toDataURL('image/png'); 1570 1571 // Remove canvas node 1572 if (!isDebug) { 1573 parent.removeChild(canvas); 1574 } 1575 }, 400); 1576 1577 // Show navigation bar in board 1578 if (Type.exists(zbar)) { 1579 zbar.style.display = zbarDisplay; 1580 } 1581 1582 return this; 1583 } 1584 1585 }); 1586 1587 return JXG.SVGRenderer; 1588 }); 1589