1 /* 2 Copyright 2008-2017 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("www.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 = Type.evaluate(el.visProp.anchorx), 411 ev_ay = Type.evaluate(el.visProp.anchory); 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) if (el[nodes[i]]) { 1028 node = el[nodes[i]]; 1029 node.style.transition = transitionStr; 1030 } 1031 1032 el.visPropOld.transitionduration = duration; 1033 }, 1034 1035 /** 1036 * Call user-defined function to set visual attributes. 1037 * If "testAttribute" is the empty string, the function 1038 * is called immediately, otherwise it is called in a timeOut. 1039 * 1040 * This is necessary to realize smooth transitions buit avoid transistions 1041 * when first creating the objects. 1042 * 1043 * Usually, the string in testAttribute is the visPropOld attribute 1044 * of the values which are set. 1045 * 1046 * @param {Function} setFunc Some function which usually sets some attributes 1047 * @param {String} testAttribute If this string is the empty string the function is called immediately, 1048 * otherwise it is called in a setImeout. 1049 * @see JXG.SVGRenderer#setObjectFillColor 1050 * @see JXG.SVGRenderer#setObjectStrokeColor 1051 * @see JXG.SVGRenderer#_setArrowColor 1052 * @private 1053 */ 1054 _setAttribute: function(setFunc, testAttribute) { 1055 if (testAttribute === '') { 1056 setFunc(); 1057 } else { 1058 setTimeout(setFunc, 1); 1059 } 1060 }, 1061 1062 // documented in JXG.AbstractRenderer 1063 setObjectFillColor: function (el, color, opacity, rendNode) { 1064 var node, c, rgbo, oo, t, 1065 rgba = Type.evaluate(color), 1066 o = Type.evaluate(opacity); 1067 1068 o = (o > 0) ? o : 0; 1069 1070 if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) { 1071 return; 1072 } 1073 1074 if (Type.exists(rgba) && rgba !== false) { 1075 if (rgba.length !== 9) { // RGB, not RGBA 1076 c = rgba; 1077 oo = o; 1078 } else { // True RGBA, not RGB 1079 rgbo = Color.rgba2rgbo(rgba); 1080 c = rgbo[0]; 1081 oo = o * rgbo[1]; 1082 } 1083 1084 if (rendNode === undefined) { 1085 node = el.rendNode; 1086 } else { 1087 node = rendNode; 1088 } 1089 1090 if (c !== 'none') { 1091 this._setAttribute(function() { 1092 node.setAttributeNS(null, 'fill', c); 1093 }, el.visPropOld.fillcolor); 1094 } 1095 1096 if (el.type === JXG.OBJECT_TYPE_IMAGE) { 1097 this._setAttribute(function() { 1098 node.setAttributeNS(null, 'opacity', oo); 1099 }, el.visPropOld.fillopacity); 1100 //node.style['opacity'] = oo; // This would overwrite values set by CSS class. 1101 } else { 1102 if (c === 'none') { // This is done only for non-images 1103 // because images have no fill color. 1104 oo = 0; 1105 } 1106 this._setAttribute(function() { 1107 node.setAttributeNS(null, 'fill-opacity', oo); 1108 }, el.visPropOld.fillopacity); 1109 } 1110 1111 if (Type.exists(el.visProp.gradient)) { 1112 this.updateGradient(el); 1113 } 1114 } 1115 el.visPropOld.fillcolor = rgba; 1116 el.visPropOld.fillopacity = o; 1117 }, 1118 1119 // documented in JXG.AbstractRenderer 1120 setObjectStrokeColor: function (el, color, opacity) { 1121 var rgba = Type.evaluate(color), c, rgbo, 1122 o = Type.evaluate(opacity), oo, 1123 node; 1124 1125 o = (o > 0) ? o : 0; 1126 1127 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 1128 return; 1129 } 1130 1131 if (Type.exists(rgba) && rgba !== false) { 1132 if (rgba.length !== 9) { // RGB, not RGBA 1133 c = rgba; 1134 oo = o; 1135 } else { // True RGBA, not RGB 1136 rgbo = Color.rgba2rgbo(rgba); 1137 c = rgbo[0]; 1138 oo = o * rgbo[1]; 1139 } 1140 1141 node = el.rendNode; 1142 1143 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 1144 if (Type.evaluate(el.visProp.display) === 'html') { 1145 this._setAttribute(function() { 1146 node.style.color = c; 1147 node.style.opacity = oo; 1148 }, el.visPropOld.strokecolor); 1149 1150 } else { 1151 this._setAttribute(function() { 1152 node.setAttributeNS(null, "style", "fill:" + c); 1153 node.setAttributeNS(null, "style", "fill-opacity:" + oo); 1154 }, el.visPropOld.strokecolor); 1155 } 1156 } else { 1157 this._setAttribute(function() { 1158 node.setAttributeNS(null, 'stroke', c); 1159 node.setAttributeNS(null, 'stroke-opacity', oo); 1160 }, el.visPropOld.strokecolor); 1161 } 1162 1163 if (el.elementClass === Const.OBJECT_CLASS_CURVE || 1164 el.elementClass === Const.OBJECT_CLASS_LINE) { 1165 if (Type.evaluate(el.visProp.firstarrow)) { 1166 this._setArrowColor(el.rendNodeTriangleStart, c, oo, el); 1167 } 1168 1169 if (Type.evaluate(el.visProp.lastarrow)) { 1170 this._setArrowColor(el.rendNodeTriangleEnd, c, oo, el); 1171 } 1172 } 1173 } 1174 1175 el.visPropOld.strokecolor = rgba; 1176 el.visPropOld.strokeopacity = o; 1177 }, 1178 1179 // documented in JXG.AbstractRenderer 1180 setObjectStrokeWidth: function (el, width) { 1181 var node, 1182 w = Type.evaluate(width), 1183 rgba, c, rgbo, o, oo; 1184 1185 if (isNaN(w) || el.visPropOld.strokewidth === w) { 1186 return; 1187 } 1188 1189 node = el.rendNode; 1190 this.setPropertyPrim(node, 'stroked', 'true'); 1191 if (Type.exists(w)) { 1192 this.setPropertyPrim(node, 'stroke-width', w + 'px'); 1193 1194 // if (el.elementClass === Const.OBJECT_CLASS_CURVE || 1195 // el.elementClass === Const.OBJECT_CLASS_LINE) { 1196 // if (Type.evaluate(el.visProp.firstarrow)) { 1197 // this._setArrowWidth(el.rendNodeTriangleStart, w, el.rendNode); 1198 // } 1199 // 1200 // if (Type.evaluate(el.visProp.lastarrow)) { 1201 // this._setArrowWidth(el.rendNodeTriangleEnd, w, el.rendNode); 1202 // } 1203 // } 1204 } 1205 el.visPropOld.strokewidth = w; 1206 }, 1207 1208 // documented in JXG.AbstractRenderer 1209 setLineCap: function (el) { 1210 var capStyle = Type.evaluate(el.visProp.linecap); 1211 1212 if (capStyle === undefined || capStyle === '' || el.visPropOld.linecap === capStyle || 1213 !Type.exists(el.rendNode)) { 1214 return; 1215 } 1216 1217 this.setPropertyPrim(el.rendNode, 'stroke-linecap', capStyle); 1218 el.visPropOld.linecap = capStyle; 1219 1220 }, 1221 1222 // documented in JXG.AbstractRenderer 1223 setShadow: function (el) { 1224 var ev_s = Type.evaluate(el.visProp.shadow); 1225 if (el.visPropOld.shadow === ev_s) { 1226 return; 1227 } 1228 1229 if (Type.exists(el.rendNode)) { 1230 if (ev_s) { 1231 el.rendNode.setAttributeNS(null, 'filter', 'url(#' + this.container.id + '_' + 'f1)'); 1232 } else { 1233 el.rendNode.removeAttributeNS(null, 'filter'); 1234 } 1235 } 1236 el.visPropOld.shadow = ev_s; 1237 }, 1238 1239 /* ************************** 1240 * renderer control 1241 * **************************/ 1242 1243 // documented in JXG.AbstractRenderer 1244 suspendRedraw: function () { 1245 // It seems to be important for the Linux version of firefox 1246 //this.suspendHandle = this.svgRoot.suspendRedraw(10000); 1247 }, 1248 1249 // documented in JXG.AbstractRenderer 1250 unsuspendRedraw: function () { 1251 //this.svgRoot.unsuspendRedraw(this.suspendHandle); 1252 //this.svgRoot.unsuspendRedrawAll(); 1253 //this.svgRoot.forceRedraw(); 1254 }, 1255 1256 // documented in AbstractRenderer 1257 resize: function (w, h) { 1258 this.svgRoot.style.width = parseFloat(w) + 'px'; 1259 this.svgRoot.style.height = parseFloat(h) + 'px'; 1260 this.svgRoot.setAttribute("width", parseFloat(w)); 1261 this.svgRoot.setAttribute("height", parseFloat(h)); 1262 }, 1263 1264 // documented in JXG.AbstractRenderer 1265 createTouchpoints: function (n) { 1266 var i, na1, na2, node; 1267 this.touchpoints = []; 1268 for (i = 0; i < n; i++) { 1269 na1 = 'touchpoint1_' + i; 1270 node = this.createPrim('path', na1); 1271 this.appendChildPrim(node, 19); 1272 node.setAttributeNS(null, 'd', 'M 0 0'); 1273 this.touchpoints.push(node); 1274 1275 this.setPropertyPrim(node, 'stroked', 'true'); 1276 this.setPropertyPrim(node, 'stroke-width', '1px'); 1277 node.setAttributeNS(null, 'stroke', '#000000'); 1278 node.setAttributeNS(null, 'stroke-opacity', 1.0); 1279 node.setAttributeNS(null, 'display', 'none'); 1280 1281 na2 = 'touchpoint2_' + i; 1282 node = this.createPrim('ellipse', na2); 1283 this.appendChildPrim(node, 19); 1284 this.updateEllipsePrim(node, 0, 0, 0, 0); 1285 this.touchpoints.push(node); 1286 1287 this.setPropertyPrim(node, 'stroked', 'true'); 1288 this.setPropertyPrim(node, 'stroke-width', '1px'); 1289 node.setAttributeNS(null, 'stroke', '#000000'); 1290 node.setAttributeNS(null, 'stroke-opacity', 1.0); 1291 node.setAttributeNS(null, 'fill', '#ffffff'); 1292 node.setAttributeNS(null, 'fill-opacity', 0.0); 1293 1294 node.setAttributeNS(null, 'display', 'none'); 1295 } 1296 }, 1297 1298 // documented in JXG.AbstractRenderer 1299 showTouchpoint: function (i) { 1300 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1301 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'inline'); 1302 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'inline'); 1303 } 1304 }, 1305 1306 // documented in JXG.AbstractRenderer 1307 hideTouchpoint: function (i) { 1308 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1309 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'none'); 1310 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'none'); 1311 } 1312 }, 1313 1314 // documented in JXG.AbstractRenderer 1315 updateTouchpoint: function (i, pos) { 1316 var x, y, 1317 d = 37; 1318 1319 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1320 x = pos[0]; 1321 y = pos[1]; 1322 1323 this.touchpoints[2 * i].setAttributeNS(null, 'd', 'M ' + (x - d) + ' ' + y + ' ' + 1324 'L ' + (x + d) + ' ' + y + ' ' + 1325 'M ' + x + ' ' + (y - d) + ' ' + 1326 'L ' + x + ' ' + (y + d)); 1327 this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25); 1328 } 1329 }, 1330 1331 /** 1332 * Convert the SVG construction into an HTML canvas image. 1333 * This works for all SVG supporting browsers. 1334 * For IE it works from version 9. 1335 * But HTML texts are ignored on IE. The drawing is done with a delay of 1336 * 200 ms. Otherwise there are problems with IE. 1337 * 1338 * @param {String} canvasId Id of an HTML canvas element 1339 * @param {Number} w Width in pixel of the dumped image, i.e. of the canvas tag. 1340 * @param {Number} h Height in pixel of the dumped image, i.e. of the canvas tag. 1341 * @returns {Object} the svg renderer object. 1342 * 1343 * @example 1344 * board.renderer.dumpToCanvas('canvas'); 1345 */ 1346 dumpToCanvas: function(canvasId, w, h) { 1347 var svgRoot = this.svgRoot, 1348 btoa = window.btoa || Base64.encode, 1349 svg, tmpImg, cv, ctx, 1350 wOrg, hOrg, 1351 uriPayload; 1352 1353 // Move all HTML tags (beside the SVG root) of the container 1354 // to the foreignObject element inside of the svgRoot node 1355 if (this.container.hasChildNodes() && Type.exists(this.foreignObjLayer)) { 1356 while (svgRoot.nextSibling) { 1357 this.foreignObjLayer.appendChild(svgRoot.nextSibling); 1358 } 1359 } 1360 1361 svgRoot.setAttribute("xmlns", "http://www.w3.org/2000/svg"); 1362 wOrg = svgRoot.getAttribute('width'); 1363 hOrg = svgRoot.getAttribute('height'); 1364 1365 svg = new XMLSerializer().serializeToString(svgRoot); 1366 1367 // In IE we have to remove the namespace again. 1368 if ((svg.match(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/g) || []).length > 1) { 1369 svg = svg.replace(/xmlns=\"http:\/\/www.w3.org\/2000\/svg\"/g, ''); 1370 } 1371 1372 // Safari fails if the svg string contains a " " 1373 svg = svg.replace(/ /g, ' '); 1374 1375 cv = document.getElementById(canvasId); 1376 // Clear the canvas 1377 cv.width = cv.width; 1378 1379 ctx = cv.getContext("2d"); 1380 if (w !== undefined && h !== undefined) { 1381 // Scale twice the CSS size to make the image crisp 1382 cv.style.width = parseFloat(w) + 'px'; 1383 cv.style.height = parseFloat(h) + 'px'; 1384 cv.setAttribute('width', 2 * parseFloat(wOrg)); 1385 cv.setAttribute('height', 2 * parseFloat(hOrg)); 1386 ctx.scale(2 * wOrg / w, 2 * hOrg / h); 1387 } 1388 1389 tmpImg = new Image(); 1390 if (true) { 1391 tmpImg.onload = function () { 1392 // IE needs a pause... 1393 setTimeout(function(){ 1394 ctx.drawImage(tmpImg, 0, 0, w, h); 1395 }, 200); 1396 }; 1397 1398 tmpImg.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))); 1399 // uriPayload = encodeURIComponent(svg.replace(/\n+/g, '')) // remove newlines // encode URL-unsafe characters 1400 // .replace(/%20/g, ' ') // put spaces back in 1401 // .replace(/%3D/g, '=') // ditto equals signs 1402 // .replace(/%3A/g, ':') // ditto colons 1403 // .replace(/%2F/g, '/') // ditto slashes 1404 // .replace(/%22/g, "'"); 1405 // tmpImg.src = 'data:image/svg+xml,' + uriPayload; 1406 } else { 1407 // Alternative version 1408 var DOMURL = window.URL || window.webkitURL || window; 1409 var svgBlob = new Blob([svg], {type: 'image/svg+xml'}); 1410 var url = DOMURL.createObjectURL(svgBlob); 1411 tmpImg.src = url; 1412 1413 tmpImg.onload = function () { 1414 // IE needs a pause... 1415 setTimeout(function(){ 1416 ctx.drawImage(tmpImg, 0, 0, w, h); 1417 }, 200); 1418 DOMURL.revokeObjectURL(url); 1419 }; 1420 } 1421 1422 // Move all HTML tags back from 1423 // the foreignObject element to the container 1424 if (Type.exists(this.foreignObjLayer) && this.foreignObjLayer.hasChildNodes()) { 1425 while (this.foreignObjLayer.firstChild) { 1426 this.container.appendChild(this.foreignObjLayer.firstChild); 1427 } 1428 } 1429 1430 return this; 1431 }, 1432 1433 screenshot: function(board) { 1434 var node, doc, cPos, 1435 canvas, id, 1436 img, 1437 button, buttonText, 1438 w, h, 1439 bas = board.attr.screenshot, 1440 zbar, zbarDisplay, 1441 cssTxt; 1442 1443 if (this.type === 'no') { 1444 return; 1445 } 1446 1447 w = bas.scale * parseFloat(board.containerObj.style.width); 1448 h = bas.scale * parseFloat(board.containerObj.style.height); 1449 1450 // Create div which contains canvas element and close button 1451 doc = board.containerObj.ownerDocument; 1452 node = doc.createElement('div'); 1453 node.style.cssText = bas.css; 1454 node.style.width = (w) + 'px'; 1455 node.style.height = (h) + 'px'; 1456 node.style.zIndex = board.containerObj.style.zIndex + 120; 1457 1458 // Position the div exactly over the JSXGraph board 1459 cPos = board.getCoordsTopLeftCorner(); 1460 node.style.position= 'absolute'; 1461 node.style.left = (500+ cPos[0]) + 'px'; 1462 node.style.top = (cPos[1]) + 'px'; 1463 1464 // Create canvas element 1465 canvas = doc.createElement('canvas'); 1466 id = Math.random().toString(36).substr(2, 5); 1467 canvas.setAttribute('id', id); 1468 canvas.setAttribute('width', w); 1469 canvas.setAttribute('height', h); 1470 canvas.style.width = w + 'px'; 1471 canvas.style.height = w + 'px'; 1472 canvas.style.display = 'none'; 1473 1474 img = new Image(); //doc.createElement('img'); 1475 img.style.width = w + 'px'; 1476 img.style.height = h + 'px'; 1477 //img.crossOrigin = 'anonymous'; 1478 1479 // Create close button 1480 button = doc.createElement('span'); 1481 buttonText = doc.createTextNode('\u2716'); 1482 button.style.cssText = bas.cssButton; 1483 button.appendChild(buttonText); 1484 button.onclick = function() { 1485 node.parentNode.removeChild(node); 1486 }; 1487 1488 // Add all nodes 1489 node.appendChild(canvas); 1490 node.appendChild(img); 1491 node.appendChild(button); 1492 board.containerObj.parentNode.appendChild(node); 1493 1494 // Hide navigation bar in board 1495 zbar = document.getElementById(board.containerObj.id + '_navigationbar'); 1496 if (Type.exists(zbar)) { 1497 zbarDisplay = zbar.style.display; 1498 zbar.style.display = 'none'; 1499 } 1500 1501 // Create screenshot in canvas 1502 this.dumpToCanvas(id, w, h); 1503 // Show image in img tag 1504 setTimeout(function() { 1505 img.src = canvas.toDataURL('image/png'); 1506 }, 400); 1507 1508 // Show navigation bar in board 1509 if (Type.exists(zbar)) { 1510 zbar.style.display = zbarDisplay; 1511 } 1512 } 1513 1514 }); 1515 1516 return JXG.SVGRenderer; 1517 }); 1518