1 /* 2 Copyright 2008-2024 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */ 33 /*jslint nomen: true, plusplus: true, newcap:true*/ 34 35 import JXG from "../jxg.js"; 36 import Options from "../options.js"; 37 import AbstractRenderer from "./abstract.js"; 38 import Const from "../base/constants.js"; 39 import Type from "../utils/type.js"; 40 import Color from "../utils/color.js"; 41 import Base64 from "../utils/base64.js"; 42 import Numerics from "../math/numerics.js"; 43 44 /** 45 * Uses SVG to implement the rendering methods defined in {@link JXG.AbstractRenderer}. 46 * @class JXG.SVGRenderer 47 * @augments JXG.AbstractRenderer 48 * @param {Node} container Reference to a DOM node containing the board. 49 * @param {Object} dim The dimensions of the board 50 * @param {Number} dim.width 51 * @param {Number} dim.height 52 * @see JXG.AbstractRenderer 53 */ 54 JXG.SVGRenderer = function (container, dim) { 55 var i; 56 57 // docstring in AbstractRenderer 58 this.type = "svg"; 59 60 this.isIE = 61 navigator.appVersion.indexOf("MSIE") !== -1 || navigator.userAgent.match(/Trident\//); 62 63 /** 64 * SVG root node 65 * @type Node 66 */ 67 this.svgRoot = null; 68 69 /** 70 * The SVG Namespace used in JSXGraph. 71 * @see http://www.w3.org/TR/SVG2/ 72 * @type String 73 * @default http://www.w3.org/2000/svg 74 */ 75 this.svgNamespace = "http://www.w3.org/2000/svg"; 76 77 /** 78 * The xlink namespace. This is used for images. 79 * @see http://www.w3.org/TR/xlink/ 80 * @type String 81 * @default http://www.w3.org/1999/xlink 82 */ 83 this.xlinkNamespace = "http://www.w3.org/1999/xlink"; 84 85 // container is documented in AbstractRenderer. 86 // Type node 87 this.container = container; 88 89 // prepare the div container and the svg root node for use with JSXGraph 90 this.container.style.MozUserSelect = "none"; 91 this.container.style.userSelect = "none"; 92 93 this.container.style.overflow = "hidden"; 94 if (this.container.style.position === "") { 95 this.container.style.position = "relative"; 96 } 97 98 this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg"); 99 this.svgRoot.style.overflow = "hidden"; 100 this.svgRoot.style.display = "block"; 101 this.resize(dim.width, dim.height); 102 103 //this.svgRoot.setAttributeNS(null, 'shape-rendering', 'crispEdge'); //'optimizeQuality'); //geometricPrecision'); 104 105 this.container.appendChild(this.svgRoot); 106 107 /** 108 * The <tt>defs</tt> element is a container element to reference reusable SVG elements. 109 * @type Node 110 * @see https://www.w3.org/TR/SVG2/struct.html#DefsElement 111 */ 112 this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, "defs"); 113 this.svgRoot.appendChild(this.defs); 114 115 /** 116 * Filters are used to apply shadows. 117 * @type Node 118 * @see https://www.w3.org/TR/SVG2/struct.html#DefsElement 119 */ 120 /** 121 * Create an SVG shadow filter. If the object's RGB color is [r,g,b], it's opacity is op, and 122 * the parameter color is given as [r', g', b'] with opacity op' 123 * the shadow will have RGB color [blend*r + r', blend*g + g', blend*b + b'] and the opacity will be equal to op * op'. 124 * Further, blur and offset can be adjusted. 125 * 126 * The shadow color is [r*ble 127 * @param {String} id Node is of the filter. 128 * @param {Array|String} rgb RGB value for the blend color or the string 'none' for default values. Default 'black'. 129 * @param {Number} opacity Value between 0 and 1, default is 1. 130 * @param {Number} blend Value between 0 and 1, default is 0.1. 131 * @param {Number} blur Default: 3 132 * @param {Array} offset [dx, dy]. Default is [5,5]. 133 * @returns DOM node to be added to this.defs. 134 * @private 135 */ 136 this.createShadowFilter = function (id, rgb, opacity, blend, blur, offset) { 137 var filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter'), 138 feOffset, feColor, feGaussianBlur, feBlend, 139 mat; 140 141 filter.setAttributeNS(null, 'id', id); 142 filter.setAttributeNS(null, 'width', '300%'); 143 filter.setAttributeNS(null, 'height', '300%'); 144 filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse'); 145 146 feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset'); 147 feOffset.setAttributeNS(null, 'in', 'SourceGraphic'); // b/w: SourceAlpha, Color: SourceGraphic 148 feOffset.setAttributeNS(null, 'result', 'offOut'); 149 feOffset.setAttributeNS(null, 'dx', offset[0]); 150 feOffset.setAttributeNS(null, 'dy', offset[1]); 151 filter.appendChild(feOffset); 152 153 feColor = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feColorMatrix'); 154 feColor.setAttributeNS(null, 'in', 'offOut'); 155 feColor.setAttributeNS(null, 'result', 'colorOut'); 156 feColor.setAttributeNS(null, 'type', 'matrix'); 157 // See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix 158 if (rgb === 'none' || !Type.isArray(rgb) || rgb.length < 3) { 159 feColor.setAttributeNS(null, 'values', '0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 ' + opacity + ' 0'); 160 } else { 161 rgb[0] /= 255; 162 rgb[1] /= 255; 163 rgb[2] /= 255; 164 mat = blend + ' 0 0 0 ' + rgb[0] + 165 ' 0 ' + blend + ' 0 0 ' + rgb[1] + 166 ' 0 0 ' + blend + ' 0 ' + rgb[2] + 167 ' 0 0 0 ' + opacity + ' 0'; 168 feColor.setAttributeNS(null, 'values', mat); 169 } 170 filter.appendChild(feColor); 171 172 feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur'); 173 feGaussianBlur.setAttributeNS(null, 'in', 'colorOut'); 174 feGaussianBlur.setAttributeNS(null, 'result', 'blurOut'); 175 feGaussianBlur.setAttributeNS(null, 'stdDeviation', blur); 176 filter.appendChild(feGaussianBlur); 177 178 feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend'); 179 feBlend.setAttributeNS(null, 'in', 'SourceGraphic'); 180 feBlend.setAttributeNS(null, 'in2', 'blurOut'); 181 feBlend.setAttributeNS(null, 'mode', 'normal'); 182 filter.appendChild(feBlend); 183 184 return filter; 185 }; 186 187 /** 188 * Create a "unique" string id from the arguments of the function. 189 * Concatenate all arguments by "_". 190 * "Unique" is achieved by simply prepending the container id. 191 * Do not escape the string. 192 * 193 * If the id is used in an "url()" call it must be eascaped. 194 * 195 * @params {String} one or strings which will be concatenated. 196 * @return {String} 197 * @private 198 */ 199 this.uniqName = function () { 200 return this.container.id + '_' + 201 Array.prototype.slice.call(arguments).join('_'); 202 }; 203 204 /** 205 * Combine arguments to a string, joined by empty string. 206 * Masks the container id with CSS.escape. 207 * 208 * @params {String} str variable number of strings 209 * @returns String 210 * @see JXG.SVGRenderer#toURL 211 * @private 212 * @example 213 * this.toStr('aaa', '_', 'bbb', 'TriangleEnd') 214 * // Output: 215 * // xxx_bbbTriangleEnd 216 */ 217 this.toStr = function() { 218 // ES6 would be [...arguments].join() 219 var str = Array.prototype.slice.call(arguments).join(''); 220 // Mask special symbols like '/' and '\' in id 221 if (Type.exists(CSS) && Type.exists(CSS.escape)) { 222 str = CSS.escape(str); 223 } 224 return str; 225 }; 226 227 /** 228 * Combine arguments to an URL string of the form 229 * url(#...) 230 * Masks the container id. Calls {@link JXG.SVGRenderer#toStr}. 231 * 232 * @params {String} str variable number of strings 233 * @returns URL string 234 * @see JXG.SVGRenderer#toStr 235 * @private 236 * @example 237 * this.toURL('aaa', '_', 'bbb', 'TriangleEnd') 238 * // Output: 239 * // url(#xxx_bbbTriangleEnd) 240 */ 241 this.toURL = function () { 242 return 'url(#' + 243 this.toStr.apply(this, arguments) + // Pass the arguments to toStr 244 ')'; 245 }; 246 247 /* Default shadow filter */ 248 this.defs.appendChild(this.createShadowFilter(this.uniqName('f1'), 'none', 1, 0.1, 3, [5, 5])); 249 250 /** 251 * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front 252 * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented 253 * there, too. The higher the number, the "more on top" are the elements on this layer. 254 * @type Array 255 */ 256 this.layer = []; 257 for (i = 0; i < Options.layer.numlayers; i++) { 258 this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g'); 259 this.svgRoot.appendChild(this.layer[i]); 260 } 261 262 try { 263 this.foreignObjLayer = this.container.ownerDocument.createElementNS( 264 this.svgNamespace, 265 "foreignObject" 266 ); 267 this.foreignObjLayer.setAttribute("display", "none"); 268 this.foreignObjLayer.setAttribute("x", 0); 269 this.foreignObjLayer.setAttribute("y", 0); 270 this.foreignObjLayer.setAttribute("width", "100%"); 271 this.foreignObjLayer.setAttribute("height", "100%"); 272 this.foreignObjLayer.setAttribute("id", this.uniqName('foreignObj')); 273 this.svgRoot.appendChild(this.foreignObjLayer); 274 this.supportsForeignObject = true; 275 } catch (e) { 276 this.supportsForeignObject = false; 277 } 278 }; 279 280 JXG.SVGRenderer.prototype = new AbstractRenderer(); 281 282 JXG.extend( 283 JXG.SVGRenderer.prototype, 284 /** @lends JXG.SVGRenderer.prototype */ { 285 /** 286 * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag. 287 * @private 288 * @param {JXG.GeometryElement} el A JSXGraph element, preferably one that can have an arrow attached. 289 * @param {String} [idAppendix=''] A string that is added to the node's id. 290 * @returns {Node} Reference to the node added to the DOM. 291 */ 292 _createArrowHead: function (el, idAppendix, type) { 293 var node2, 294 node3, 295 id = el.id + "Triangle", 296 //type = null, 297 v, 298 h; 299 300 if (Type.exists(idAppendix)) { 301 id += idAppendix; 302 } 303 if (Type.exists(type)) { 304 id += type; 305 } 306 node2 = this.createPrim("marker", id); 307 308 node2.setAttributeNS(null, "stroke", el.evalVisProp('strokecolor')); 309 node2.setAttributeNS( 310 null, 311 "stroke-opacity", 312 el.evalVisProp('strokeopacity') 313 ); 314 node2.setAttributeNS(null, "fill", el.evalVisProp('strokecolor')); 315 node2.setAttributeNS(null, "fill-opacity", el.evalVisProp('strokeopacity')); 316 node2.setAttributeNS(null, "stroke-width", 0); // this is the stroke-width of the arrow head. 317 // Should be zero to simplify the calculations 318 319 node2.setAttributeNS(null, "orient", "auto"); 320 node2.setAttributeNS(null, "markerUnits", "strokeWidth"); // 'strokeWidth' 'userSpaceOnUse'); 321 322 /* 323 Types 1, 2: 324 The arrow head is an isosceles triangle with base length 10 and height 10. 325 326 Type 3: 327 A rectangle 328 329 Types 4, 5, 6: 330 Defined by Bezier curves from mp_arrowheads.html 331 332 In any case but type 3 the arrow head is 10 units long, 333 type 3 is 10 units high. 334 These 10 units are scaled to strokeWidth * arrowSize pixels, see 335 this._setArrowWidth(). 336 337 See also abstractRenderer.updateLine() where the line path is shortened accordingly. 338 339 Changes here are also necessary in setArrowWidth(). 340 341 So far, lines with arrow heads are shortenend to avoid overlapping of 342 arrow head and line. This is not the case for curves, yet. 343 Therefore, the offset refX has to be adapted to the path type. 344 */ 345 node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, "path"); 346 h = 5; 347 if (idAppendix === "Start") { 348 // First arrow 349 v = 0; 350 if (type === 2) { 351 node3.setAttributeNS(null, "d", "M 10,0 L 0,5 L 10,10 L 5,5 z"); 352 } else if (type === 3) { 353 node3.setAttributeNS(null, "d", "M 0,0 L 3.33,0 L 3.33,10 L 0,10 z"); 354 } else if (type === 4) { 355 // insetRatio:0.8 tipAngle:45 wingCurve:15 tailCurve:0 356 h = 3.31; 357 node3.setAttributeNS( 358 null, 359 "d", 360 "M 0.00,3.31 C 3.53,3.84 7.13,4.50 10.00,6.63 C 9.33,5.52 8.67,4.42 8.00,3.31 C 8.67,2.21 9.33,1.10 10.00,0.00 C 7.13,2.13 3.53,2.79 0.00,3.31" 361 ); 362 } else if (type === 5) { 363 // insetRatio:0.9 tipAngle:40 wingCurve:5 tailCurve:15 364 h = 3.28; 365 node3.setAttributeNS( 366 null, 367 "d", 368 "M 0.00,3.28 C 3.39,4.19 6.81,5.07 10.00,6.55 C 9.38,5.56 9.00,4.44 9.00,3.28 C 9.00,2.11 9.38,0.99 10.00,0.00 C 6.81,1.49 3.39,2.37 0.00,3.28" 369 ); 370 } else if (type === 6) { 371 // insetRatio:0.9 tipAngle:35 wingCurve:5 tailCurve:0 372 h = 2.84; 373 node3.setAttributeNS( 374 null, 375 "d", 376 "M 0.00,2.84 C 3.39,3.59 6.79,4.35 10.00,5.68 C 9.67,4.73 9.33,3.78 9.00,2.84 C 9.33,1.89 9.67,0.95 10.00,0.00 C 6.79,1.33 3.39,2.09 0.00,2.84" 377 ); 378 } else if (type === 7) { 379 // insetRatio:0.9 tipAngle:60 wingCurve:30 tailCurve:0 380 h = 5.2; 381 node3.setAttributeNS( 382 null, 383 "d", 384 "M 0.00,5.20 C 4.04,5.20 7.99,6.92 10.00,10.39 M 10.00,0.00 C 7.99,3.47 4.04,5.20 0.00,5.20" 385 ); 386 } else { 387 // type == 1 or > 6 388 node3.setAttributeNS(null, "d", "M 10,0 L 0,5 L 10,10 z"); 389 } 390 if ( 391 // !Type.exists(el.rendNode.getTotalLength) && 392 el.elementClass === Const.OBJECT_CLASS_LINE 393 ) { 394 if (type === 2) { 395 v = 4.9; 396 } else if (type === 3) { 397 v = 3.3; 398 } else if (type === 4 || type === 5 || type === 6) { 399 v = 6.66; 400 } else if (type === 7) { 401 v = 0.0; 402 } else { 403 v = 10.0; 404 } 405 } 406 } else { 407 // Last arrow 408 v = 10.0; 409 if (type === 2) { 410 node3.setAttributeNS(null, "d", "M 0,0 L 10,5 L 0,10 L 5,5 z"); 411 } else if (type === 3) { 412 v = 3.3; 413 node3.setAttributeNS(null, "d", "M 0,0 L 3.33,0 L 3.33,10 L 0,10 z"); 414 } else if (type === 4) { 415 // insetRatio:0.8 tipAngle:45 wingCurve:15 tailCurve:0 416 h = 3.31; 417 node3.setAttributeNS( 418 null, 419 "d", 420 "M 10.00,3.31 C 6.47,3.84 2.87,4.50 0.00,6.63 C 0.67,5.52 1.33,4.42 2.00,3.31 C 1.33,2.21 0.67,1.10 0.00,0.00 C 2.87,2.13 6.47,2.79 10.00,3.31" 421 ); 422 } else if (type === 5) { 423 // insetRatio:0.9 tipAngle:40 wingCurve:5 tailCurve:15 424 h = 3.28; 425 node3.setAttributeNS( 426 null, 427 "d", 428 "M 10.00,3.28 C 6.61,4.19 3.19,5.07 0.00,6.55 C 0.62,5.56 1.00,4.44 1.00,3.28 C 1.00,2.11 0.62,0.99 0.00,0.00 C 3.19,1.49 6.61,2.37 10.00,3.28" 429 ); 430 } else if (type === 6) { 431 // insetRatio:0.9 tipAngle:35 wingCurve:5 tailCurve:0 432 h = 2.84; 433 node3.setAttributeNS( 434 null, 435 "d", 436 "M 10.00,2.84 C 6.61,3.59 3.21,4.35 0.00,5.68 C 0.33,4.73 0.67,3.78 1.00,2.84 C 0.67,1.89 0.33,0.95 0.00,0.00 C 3.21,1.33 6.61,2.09 10.00,2.84" 437 ); 438 } else if (type === 7) { 439 // insetRatio:0.9 tipAngle:60 wingCurve:30 tailCurve:0 440 h = 5.2; 441 node3.setAttributeNS( 442 null, 443 "d", 444 "M 10.00,5.20 C 5.96,5.20 2.01,6.92 0.00,10.39 M 0.00,0.00 C 2.01,3.47 5.96,5.20 10.00,5.20" 445 ); 446 } else { 447 // type == 1 or > 6 448 node3.setAttributeNS(null, "d", "M 0,0 L 10,5 L 0,10 z"); 449 } 450 if ( 451 // !Type.exists(el.rendNode.getTotalLength) && 452 el.elementClass === Const.OBJECT_CLASS_LINE 453 ) { 454 if (type === 2) { 455 v = 5.1; 456 } else if (type === 3) { 457 v = 0.02; 458 } else if (type === 4 || type === 5 || type === 6) { 459 v = 3.33; 460 } else if (type === 7) { 461 v = 10.0; 462 } else { 463 v = 0.05; 464 } 465 } 466 } 467 if (type === 7) { 468 node2.setAttributeNS(null, "fill", "none"); 469 node2.setAttributeNS(null, "stroke-width", 1); // this is the stroke-width of the arrow head. 470 } 471 node2.setAttributeNS(null, "refY", h); 472 node2.setAttributeNS(null, "refX", v); 473 474 node2.appendChild(node3); 475 return node2; 476 }, 477 478 /** 479 * Updates color of an arrow DOM node. 480 * @param {Node} node The arrow node. 481 * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green. 482 * @param {Number} opacity 483 * @param {JXG.GeometryElement} el The element the arrows are to be attached to 484 */ 485 _setArrowColor: function (node, color, opacity, el, type) { 486 if (node) { 487 if (Type.isString(color)) { 488 if (type !== 7) { 489 this._setAttribute(function () { 490 node.setAttributeNS(null, "stroke", color); 491 node.setAttributeNS(null, "fill", color); 492 node.setAttributeNS(null, "stroke-opacity", opacity); 493 node.setAttributeNS(null, "fill-opacity", opacity); 494 }, el.visPropOld.fillcolor); 495 } else { 496 this._setAttribute(function () { 497 node.setAttributeNS(null, "fill", "none"); 498 node.setAttributeNS(null, "stroke", color); 499 node.setAttributeNS(null, "stroke-opacity", opacity); 500 }, el.visPropOld.fillcolor); 501 } 502 } 503 504 if (this.isIE) { 505 el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode); 506 } 507 } 508 }, 509 510 // Already documented in JXG.AbstractRenderer 511 _setArrowWidth: function (node, width, parentNode, size) { 512 var s, d; 513 514 if (node) { 515 // if (width === 0) { 516 // // display:none does not work well in webkit 517 // node.setAttributeNS(null, 'display', 'none'); 518 // } else { 519 s = width; 520 d = s * size; 521 node.setAttributeNS(null, "viewBox", 0 + " " + 0 + " " + s * 10 + " " + s * 10); 522 node.setAttributeNS(null, "markerHeight", d); 523 node.setAttributeNS(null, "markerWidth", d); 524 node.setAttributeNS(null, "display", "inherit"); 525 // } 526 527 if (this.isIE) { 528 parentNode.parentNode.insertBefore(parentNode, parentNode); 529 } 530 } 531 }, 532 533 /* ******************************** * 534 * This renderer does not need to 535 * override draw/update* methods 536 * since it provides draw/update*Prim 537 * methods except for some cases like 538 * internal texts or images. 539 * ******************************** */ 540 541 /* ************************** 542 * Lines 543 * **************************/ 544 545 // documented in AbstractRenderer 546 updateTicks: function (ticks) { 547 var i, 548 j, 549 c, 550 node, 551 x, 552 y, 553 tickStr = "", 554 len = ticks.ticks.length, 555 len2, 556 str, 557 isReal = true; 558 559 for (i = 0; i < len; i++) { 560 c = ticks.ticks[i]; 561 x = c[0]; 562 y = c[1]; 563 564 len2 = x.length; 565 str = " M " + x[0] + " " + y[0]; 566 if (!Type.isNumber(x[0])) { 567 isReal = false; 568 } 569 for (j = 1; isReal && j < len2; ++j) { 570 if (Type.isNumber(x[j])) { 571 str += " L " + x[j] + " " + y[j]; 572 } else { 573 isReal = false; 574 } 575 } 576 if (isReal) { 577 tickStr += str; 578 } 579 } 580 581 node = ticks.rendNode; 582 583 if (!Type.exists(node)) { 584 node = this.createPrim("path", ticks.id); 585 this.appendChildPrim(node, ticks.evalVisProp('layer')); 586 ticks.rendNode = node; 587 } 588 589 node.setAttributeNS(null, "stroke", ticks.evalVisProp('strokecolor')); 590 node.setAttributeNS(null, "fill", "none"); 591 // node.setAttributeNS(null, 'fill', ticks.evalVisProp('fillcolor')); 592 // node.setAttributeNS(null, 'fill-opacity', ticks.evalVisProp('fillopacity')); 593 node.setAttributeNS( 594 null, 595 "stroke-opacity", 596 ticks.evalVisProp('strokeopacity') 597 ); 598 node.setAttributeNS(null, "stroke-width", ticks.evalVisProp('strokewidth')); 599 this.updatePathPrim(node, tickStr, ticks.board); 600 }, 601 602 /* ************************** 603 * Text related stuff 604 * **************************/ 605 606 // Already documented in JXG.AbstractRenderer 607 displayCopyright: function (str, fontsize) { 608 var node = this.createPrim("text", 'licenseText'), 609 t; 610 node.setAttributeNS(null, 'x', '20px'); 611 node.setAttributeNS(null, 'y', 2 + fontsize + 'px'); 612 node.setAttributeNS(null, 'style', 'font-family:Arial,Helvetica,sans-serif; font-size:' + 613 fontsize + 'px; fill:#356AA0; opacity:0.3;'); 614 t = this.container.ownerDocument.createTextNode(str); 615 node.appendChild(t); 616 this.appendChildPrim(node, 0); 617 }, 618 619 // Already documented in JXG.AbstractRenderer 620 drawInternalText: function (el) { 621 var node = this.createPrim("text", el.id); 622 623 //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox 624 // Preserve spaces 625 //node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "space", "preserve"); 626 node.style.whiteSpace = "nowrap"; 627 628 el.rendNodeText = this.container.ownerDocument.createTextNode(""); 629 node.appendChild(el.rendNodeText); 630 this.appendChildPrim(node, el.evalVisProp('layer')); 631 632 return node; 633 }, 634 635 // Already documented in JXG.AbstractRenderer 636 updateInternalText: function (el) { 637 var content = el.plaintext, 638 v, css, 639 ev_ax = el.getAnchorX(), 640 ev_ay = el.getAnchorY(); 641 642 css = el.evalVisProp('cssclass'); 643 if (el.rendNode.getAttributeNS(null, "class") !== css) { 644 el.rendNode.setAttributeNS(null, "class", css); 645 el.needsSizeUpdate = true; 646 } 647 648 if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) { 649 // Horizontal 650 v = el.coords.scrCoords[1]; 651 if (el.visPropOld.left !== ev_ax + v) { 652 el.rendNode.setAttributeNS(null, "x", v + "px"); 653 654 if (ev_ax === "left") { 655 el.rendNode.setAttributeNS(null, "text-anchor", "start"); 656 } else if (ev_ax === "right") { 657 el.rendNode.setAttributeNS(null, "text-anchor", "end"); 658 } else if (ev_ax === "middle") { 659 el.rendNode.setAttributeNS(null, "text-anchor", "middle"); 660 } 661 el.visPropOld.left = ev_ax + v; 662 } 663 664 // Vertical 665 v = el.coords.scrCoords[2]; 666 if (el.visPropOld.top !== ev_ay + v) { 667 el.rendNode.setAttributeNS(null, "y", v + this.vOffsetText * 0.5 + "px"); 668 669 // Not supported by IE, edge 670 // el.rendNode.setAttributeNS(null, "dy", "0"); 671 // if (ev_ay === "bottom") { 672 // el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-after-edge'); 673 // } else if (ev_ay === "top") { 674 // el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-before-edge'); 675 // } else if (ev_ay === "middle") { 676 // el.rendNode.setAttributeNS(null, 'dominant-baseline', 'middle'); 677 // } 678 679 if (ev_ay === "bottom") { 680 el.rendNode.setAttributeNS(null, "dy", "0"); 681 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'auto'); 682 } else if (ev_ay === "top") { 683 el.rendNode.setAttributeNS(null, "dy", "1.6ex"); 684 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'auto'); 685 } else if (ev_ay === "middle") { 686 el.rendNode.setAttributeNS(null, "dy", "0.6ex"); 687 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'auto'); 688 } 689 el.visPropOld.top = ev_ay + v; 690 } 691 } 692 if (el.htmlStr !== content) { 693 el.rendNodeText.data = content; 694 el.htmlStr = content; 695 } 696 this.transformImage(el, el.transformations); 697 }, 698 699 /** 700 * Set color and opacity of internal texts. 701 * @private 702 * @see JXG.AbstractRenderer#updateTextStyle 703 * @see JXG.AbstractRenderer#updateInternalTextStyle 704 */ 705 updateInternalTextStyle: function (el, strokeColor, strokeOpacity, duration) { 706 this.setObjectFillColor(el, strokeColor, strokeOpacity); 707 }, 708 709 /* ************************** 710 * Image related stuff 711 * **************************/ 712 713 // Already documented in JXG.AbstractRenderer 714 drawImage: function (el) { 715 var node = this.createPrim("image", el.id); 716 717 node.setAttributeNS(null, "preserveAspectRatio", "none"); 718 this.appendChildPrim(node, el.evalVisProp('layer')); 719 el.rendNode = node; 720 721 this.updateImage(el); 722 }, 723 724 // Already documented in JXG.AbstractRenderer 725 transformImage: function (el, t) { 726 var s, m, node, 727 str = "", 728 cx, cy, 729 len = t.length; 730 731 if (len > 0) { 732 node = el.rendNode; 733 m = this.joinTransforms(el, t); 734 s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(","); 735 if (s.indexOf('NaN') === -1) { 736 str += " matrix(" + s + ") "; 737 if (el.elementClass === Const.OBJECT_CLASS_TEXT && el.visProp.display === 'html') { 738 node.style.transform = str; 739 cx = -el.coords.scrCoords[1]; 740 cy = -el.coords.scrCoords[2]; 741 switch (el.evalVisProp('anchorx')) { 742 case 'right': cx += el.size[0]; break; 743 case 'middle': cx += el.size[0] * 0.5; break; 744 } 745 switch (el.evalVisProp('anchory')) { 746 case 'bottom': cy += el.size[1]; break; 747 case 'middle': cy += el.size[1] * 0.5; break; 748 } 749 node.style['transform-origin'] = (cx) + 'px ' + (cy) + 'px'; 750 } else { 751 // Images and texts with display:'internal' 752 node.setAttributeNS(null, "transform", str); 753 } 754 } 755 } 756 }, 757 758 // Already documented in JXG.AbstractRenderer 759 updateImageURL: function (el) { 760 var url = el.eval(el.url); 761 762 if (el._src !== url) { 763 el.imgIsLoaded = false; 764 el.rendNode.setAttributeNS(this.xlinkNamespace, "xlink:href", url); 765 el._src = url; 766 767 return true; 768 } 769 770 return false; 771 }, 772 773 // Already documented in JXG.AbstractRenderer 774 updateImageStyle: function (el, doHighlight) { 775 var css = el.evalVisProp( 776 doHighlight ? 'highlightcssclass' : 'cssclass' 777 ); 778 779 el.rendNode.setAttributeNS(null, "class", css); 780 }, 781 782 // Already documented in JXG.AbstractRenderer 783 drawForeignObject: function (el) { 784 el.rendNode = this.appendChildPrim( 785 this.createPrim("foreignObject", el.id), 786 el.evalVisProp('layer') 787 ); 788 789 this.appendNodesToElement(el, "foreignObject"); 790 this.updateForeignObject(el); 791 }, 792 793 // Already documented in JXG.AbstractRenderer 794 updateForeignObject: function (el) { 795 if (el._useUserSize) { 796 el.rendNode.style.overflow = "hidden"; 797 } else { 798 el.rendNode.style.overflow = "visible"; 799 } 800 801 this.updateRectPrim( 802 el.rendNode, 803 el.coords.scrCoords[1], 804 el.coords.scrCoords[2] - el.size[1], 805 el.size[0], 806 el.size[1] 807 ); 808 809 el.rendNode.innerHTML = el.content; 810 this._updateVisual(el, { stroke: true, dash: true }, true); 811 }, 812 813 /* ************************** 814 * Render primitive objects 815 * **************************/ 816 817 // Already documented in JXG.AbstractRenderer 818 appendChildPrim: function (node, level) { 819 if (!Type.exists(level)) { 820 // trace nodes have level not set 821 level = 0; 822 } else if (level >= Options.layer.numlayers) { 823 level = Options.layer.numlayers - 1; 824 } 825 this.layer[level].appendChild(node); 826 827 return node; 828 }, 829 830 // Already documented in JXG.AbstractRenderer 831 createPrim: function (type, id) { 832 var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type); 833 node.setAttributeNS(null, "id", this.uniqName(id)); 834 node.style.position = "absolute"; 835 if (type === "path") { 836 node.setAttributeNS(null, "stroke-linecap", "round"); 837 node.setAttributeNS(null, "stroke-linejoin", "round"); 838 node.setAttributeNS(null, "fill-rule", "evenodd"); 839 } 840 841 return node; 842 }, 843 844 // Already documented in JXG.AbstractRenderer 845 remove: function (shape) { 846 if (Type.exists(shape) && Type.exists(shape.parentNode)) { 847 shape.parentNode.removeChild(shape); 848 } 849 }, 850 851 // Already documented in JXG.AbstractRenderer 852 setLayer: function (el, level) { 853 if (!Type.exists(level)) { 854 level = 0; 855 } else if (level >= Options.layer.numlayers) { 856 level = Options.layer.numlayers - 1; 857 } 858 859 this.layer[level].appendChild(el.rendNode); 860 }, 861 862 // Already documented in JXG.AbstractRenderer 863 makeArrows: function (el, a) { 864 var node2, str, 865 ev_fa = a.evFirst, 866 ev_la = a.evLast; 867 868 if (this.isIE && el.visPropCalc.visible && (ev_fa || ev_la)) { 869 el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode); 870 return; 871 } 872 873 // We can not compare against visPropOld if there is need for a new arrow head, 874 // since here visPropOld and ev_fa / ev_la already have the same value. 875 // This has been set in _updateVisual. 876 // 877 node2 = el.rendNodeTriangleStart; 878 if (ev_fa) { 879 str = this.toStr(this.container.id, '_', el.id, 'TriangleStart', a.typeFirst); 880 881 // If we try to set the same arrow head as is already set, we can bail out now 882 if (!Type.exists(node2) || node2.id !== str) { 883 node2 = this.container.ownerDocument.getElementById(str); 884 // Check if the marker already exists. 885 // If not, create a new marker 886 if (node2 === null) { 887 node2 = this._createArrowHead(el, "Start", a.typeFirst); 888 this.defs.appendChild(node2); 889 } 890 el.rendNodeTriangleStart = node2; 891 el.rendNode.setAttributeNS(null, "marker-start", this.toURL(str)); 892 } 893 } else { 894 if (Type.exists(node2)) { 895 this.remove(node2); 896 el.rendNodeTriangleStart = null; 897 } 898 el.rendNode.setAttributeNS(null, "marker-start", null); 899 } 900 901 node2 = el.rendNodeTriangleEnd; 902 if (ev_la) { 903 str = this.toStr(this.container.id, '_', el.id, 'TriangleEnd', a.typeLast); 904 905 // If we try to set the same arrow head as is already set, we can bail out now 906 if (!Type.exists(node2) || node2.id !== str) { 907 node2 = this.container.ownerDocument.getElementById(str); 908 // Check if the marker already exists. 909 // If not, create a new marker 910 if (node2 === null) { 911 node2 = this._createArrowHead(el, "End", a.typeLast); 912 this.defs.appendChild(node2); 913 } 914 el.rendNodeTriangleEnd = node2; 915 el.rendNode.setAttributeNS(null, "marker-end", this.toURL(str)); 916 } 917 } else { 918 if (Type.exists(node2)) { 919 this.remove(node2); 920 el.rendNodeTriangleEnd = null; 921 } 922 el.rendNode.setAttributeNS(null, "marker-end", null); 923 } 924 }, 925 926 // Already documented in JXG.AbstractRenderer 927 updateEllipsePrim: function (node, x, y, rx, ry) { 928 var huge = 1000000; 929 930 huge = 200000; // IE 931 // webkit does not like huge values if the object is dashed 932 // iE doesn't like huge values above 216000 933 x = Math.abs(x) < huge ? x : (huge * x) / Math.abs(x); 934 y = Math.abs(y) < huge ? y : (huge * y) / Math.abs(y); 935 rx = Math.abs(rx) < huge ? rx : (huge * rx) / Math.abs(rx); 936 ry = Math.abs(ry) < huge ? ry : (huge * ry) / Math.abs(ry); 937 938 node.setAttributeNS(null, "cx", x); 939 node.setAttributeNS(null, "cy", y); 940 node.setAttributeNS(null, "rx", Math.abs(rx)); 941 node.setAttributeNS(null, "ry", Math.abs(ry)); 942 }, 943 944 // Already documented in JXG.AbstractRenderer 945 updateLinePrim: function (node, p1x, p1y, p2x, p2y) { 946 var huge = 1000000; 947 948 huge = 200000; //IE 949 if (!isNaN(p1x + p1y + p2x + p2y)) { 950 // webkit does not like huge values if the object is dashed 951 // IE doesn't like huge values above 216000 952 p1x = Math.abs(p1x) < huge ? p1x : (huge * p1x) / Math.abs(p1x); 953 p1y = Math.abs(p1y) < huge ? p1y : (huge * p1y) / Math.abs(p1y); 954 p2x = Math.abs(p2x) < huge ? p2x : (huge * p2x) / Math.abs(p2x); 955 p2y = Math.abs(p2y) < huge ? p2y : (huge * p2y) / Math.abs(p2y); 956 957 node.setAttributeNS(null, "x1", p1x); 958 node.setAttributeNS(null, "y1", p1y); 959 node.setAttributeNS(null, "x2", p2x); 960 node.setAttributeNS(null, "y2", p2y); 961 } 962 }, 963 964 // Already documented in JXG.AbstractRenderer 965 updatePathPrim: function (node, pointString) { 966 if (pointString === "") { 967 pointString = "M 0 0"; 968 } 969 node.setAttributeNS(null, "d", pointString); 970 }, 971 972 // Already documented in JXG.AbstractRenderer 973 updatePathStringPoint: function (el, size, type) { 974 var s = "", 975 scr = el.coords.scrCoords, 976 sqrt32 = size * Math.sqrt(3) * 0.5, 977 s05 = size * 0.5; 978 979 if (type === "x") { 980 s = 981 " M " + 982 (scr[1] - size) + 983 " " + 984 (scr[2] - size) + 985 " L " + 986 (scr[1] + size) + 987 " " + 988 (scr[2] + size) + 989 " M " + 990 (scr[1] + size) + 991 " " + 992 (scr[2] - size) + 993 " L " + 994 (scr[1] - size) + 995 " " + 996 (scr[2] + size); 997 } else if (type === "+") { 998 s = 999 " M " + 1000 (scr[1] - size) + 1001 " " + 1002 scr[2] + 1003 " L " + 1004 (scr[1] + size) + 1005 " " + 1006 scr[2] + 1007 " M " + 1008 scr[1] + 1009 " " + 1010 (scr[2] - size) + 1011 " L " + 1012 scr[1] + 1013 " " + 1014 (scr[2] + size); 1015 } else if (type === "|") { 1016 s = 1017 " M " + 1018 scr[1] + 1019 " " + 1020 (scr[2] - size) + 1021 " L " + 1022 scr[1] + 1023 " " + 1024 (scr[2] + size); 1025 } else if (type === "-") { 1026 s = 1027 " M " + 1028 (scr[1] - size) + 1029 " " + 1030 scr[2] + 1031 " L " + 1032 (scr[1] + size) + 1033 " " + 1034 scr[2]; 1035 } else if (type === "<>" || type === "<<>>") { 1036 if (type === "<<>>") { 1037 size *= 1.41; 1038 } 1039 s = 1040 " M " + 1041 (scr[1] - size) + 1042 " " + 1043 scr[2] + 1044 " L " + 1045 scr[1] + 1046 " " + 1047 (scr[2] + size) + 1048 " L " + 1049 (scr[1] + size) + 1050 " " + 1051 scr[2] + 1052 " L " + 1053 scr[1] + 1054 " " + 1055 (scr[2] - size) + 1056 " Z "; 1057 } else if (type === "^") { 1058 s = 1059 " M " + 1060 scr[1] + 1061 " " + 1062 (scr[2] - size) + 1063 " L " + 1064 (scr[1] - sqrt32) + 1065 " " + 1066 (scr[2] + s05) + 1067 " L " + 1068 (scr[1] + sqrt32) + 1069 " " + 1070 (scr[2] + s05) + 1071 " Z "; // close path 1072 } else if (type === "v") { 1073 s = 1074 " M " + 1075 scr[1] + 1076 " " + 1077 (scr[2] + size) + 1078 " L " + 1079 (scr[1] - sqrt32) + 1080 " " + 1081 (scr[2] - s05) + 1082 " L " + 1083 (scr[1] + sqrt32) + 1084 " " + 1085 (scr[2] - s05) + 1086 " Z "; 1087 } else if (type === ">") { 1088 s = 1089 " M " + 1090 (scr[1] + size) + 1091 " " + 1092 scr[2] + 1093 " L " + 1094 (scr[1] - s05) + 1095 " " + 1096 (scr[2] - sqrt32) + 1097 " L " + 1098 (scr[1] - s05) + 1099 " " + 1100 (scr[2] + sqrt32) + 1101 " Z "; 1102 } else if (type === "<") { 1103 s = 1104 " M " + 1105 (scr[1] - size) + 1106 " " + 1107 scr[2] + 1108 " L " + 1109 (scr[1] + s05) + 1110 " " + 1111 (scr[2] - sqrt32) + 1112 " L " + 1113 (scr[1] + s05) + 1114 " " + 1115 (scr[2] + sqrt32) + 1116 " Z "; 1117 } 1118 return s; 1119 }, 1120 1121 // Already documented in JXG.AbstractRenderer 1122 updatePathStringPrim: function (el) { 1123 var i, 1124 scr, 1125 len, 1126 symbm = " M ", 1127 symbl = " L ", 1128 symbc = " C ", 1129 nextSymb = symbm, 1130 maxSize = 5000.0, 1131 pStr = ""; 1132 1133 if (el.numberPoints <= 0) { 1134 return ""; 1135 } 1136 1137 len = Math.min(el.points.length, el.numberPoints); 1138 1139 if (el.bezierDegree === 1) { 1140 for (i = 0; i < len; i++) { 1141 scr = el.points[i].scrCoords; 1142 if (isNaN(scr[1]) || isNaN(scr[2])) { 1143 // PenUp 1144 nextSymb = symbm; 1145 } else { 1146 // Chrome has problems with values being too far away. 1147 scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize); 1148 scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize); 1149 1150 // Attention: first coordinate may be inaccurate if far way 1151 //pStr += [nextSymb, scr[1], ' ', scr[2]].join(''); 1152 pStr += nextSymb + scr[1] + " " + scr[2]; // Seems to be faster now (webkit and firefox) 1153 nextSymb = symbl; 1154 } 1155 } 1156 } else if (el.bezierDegree === 3) { 1157 i = 0; 1158 while (i < len) { 1159 scr = el.points[i].scrCoords; 1160 if (isNaN(scr[1]) || isNaN(scr[2])) { 1161 // PenUp 1162 nextSymb = symbm; 1163 } else { 1164 pStr += nextSymb + scr[1] + " " + scr[2]; 1165 if (nextSymb === symbc) { 1166 i += 1; 1167 scr = el.points[i].scrCoords; 1168 pStr += " " + scr[1] + " " + scr[2]; 1169 i += 1; 1170 scr = el.points[i].scrCoords; 1171 pStr += " " + scr[1] + " " + scr[2]; 1172 } 1173 nextSymb = symbc; 1174 } 1175 i += 1; 1176 } 1177 } 1178 return pStr; 1179 }, 1180 1181 // Already documented in JXG.AbstractRenderer 1182 updatePathStringBezierPrim: function (el) { 1183 var i, j, k, 1184 scr, 1185 lx, ly, 1186 len, 1187 symbm = " M ", 1188 symbl = " C ", 1189 nextSymb = symbm, 1190 maxSize = 5000.0, 1191 pStr = "", 1192 f = el.evalVisProp('strokewidth'), 1193 isNoPlot = el.evalVisProp('curvetype') !== "plot"; 1194 1195 if (el.numberPoints <= 0) { 1196 return ""; 1197 } 1198 1199 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 1200 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5); 1201 } 1202 1203 len = Math.min(el.points.length, el.numberPoints); 1204 for (j = 1; j < 3; j++) { 1205 nextSymb = symbm; 1206 for (i = 0; i < len; i++) { 1207 scr = el.points[i].scrCoords; 1208 1209 if (isNaN(scr[1]) || isNaN(scr[2])) { 1210 // PenUp 1211 nextSymb = symbm; 1212 } else { 1213 // Chrome has problems with values being too far away. 1214 scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize); 1215 scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize); 1216 1217 // Attention: first coordinate may be inaccurate if far way 1218 if (nextSymb === symbm) { 1219 //pStr += [nextSymb, scr[1], ' ', scr[2]].join(''); 1220 pStr += nextSymb + scr[1] + " " + scr[2]; // Seems to be faster now (webkit and firefox) 1221 } else { 1222 k = 2 * j; 1223 pStr += [ 1224 nextSymb, 1225 lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j), 1226 " ", 1227 ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j), 1228 " ", 1229 lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j), 1230 " ", 1231 ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j), 1232 " ", 1233 scr[1], 1234 " ", 1235 scr[2] 1236 ].join(""); 1237 } 1238 1239 nextSymb = symbl; 1240 lx = scr[1]; 1241 ly = scr[2]; 1242 } 1243 } 1244 } 1245 return pStr; 1246 }, 1247 1248 // Already documented in JXG.AbstractRenderer 1249 updatePolygonPrim: function (node, el) { 1250 var i, 1251 pStr = "", 1252 scrCoords, 1253 len = el.vertices.length; 1254 1255 node.setAttributeNS(null, "stroke", "none"); 1256 node.setAttributeNS(null, "fill-rule", "evenodd"); 1257 if (el.elType === "polygonalchain") { 1258 len++; 1259 } 1260 1261 for (i = 0; i < len - 1; i++) { 1262 if (el.vertices[i].isReal) { 1263 scrCoords = el.vertices[i].coords.scrCoords; 1264 pStr = pStr + scrCoords[1] + "," + scrCoords[2]; 1265 } else { 1266 node.setAttributeNS(null, "points", ""); 1267 return; 1268 } 1269 1270 if (i < len - 2) { 1271 pStr += " "; 1272 } 1273 } 1274 if (pStr.indexOf("NaN") === -1) { 1275 node.setAttributeNS(null, "points", pStr); 1276 } 1277 }, 1278 1279 // Already documented in JXG.AbstractRenderer 1280 updateRectPrim: function (node, x, y, w, h) { 1281 node.setAttributeNS(null, "x", x); 1282 node.setAttributeNS(null, "y", y); 1283 node.setAttributeNS(null, "width", w); 1284 node.setAttributeNS(null, "height", h); 1285 }, 1286 1287 /* ************************** 1288 * Set Attributes 1289 * **************************/ 1290 1291 // documented in JXG.AbstractRenderer 1292 setPropertyPrim: function (node, key, val) { 1293 if (key === "stroked") { 1294 return; 1295 } 1296 node.setAttributeNS(null, key, val); 1297 }, 1298 1299 display: function (el, val) { 1300 var node; 1301 1302 if (el && el.rendNode) { 1303 el.visPropOld.visible = val; 1304 node = el.rendNode; 1305 if (val) { 1306 node.setAttributeNS(null, "display", "inline"); 1307 node.style.visibility = "inherit"; 1308 } else { 1309 node.setAttributeNS(null, "display", "none"); 1310 node.style.visibility = "hidden"; 1311 } 1312 } 1313 }, 1314 1315 // documented in JXG.AbstractRenderer 1316 show: function (el) { 1317 JXG.deprecated("Board.renderer.show()", "Board.renderer.display()"); 1318 this.display(el, true); 1319 // var node; 1320 // 1321 // if (el && el.rendNode) { 1322 // node = el.rendNode; 1323 // node.setAttributeNS(null, 'display', 'inline'); 1324 // node.style.visibility = "inherit"; 1325 // } 1326 }, 1327 1328 // documented in JXG.AbstractRenderer 1329 hide: function (el) { 1330 JXG.deprecated("Board.renderer.hide()", "Board.renderer.display()"); 1331 this.display(el, false); 1332 // var node; 1333 // 1334 // if (el && el.rendNode) { 1335 // node = el.rendNode; 1336 // node.setAttributeNS(null, 'display', 'none'); 1337 // node.style.visibility = "hidden"; 1338 // } 1339 }, 1340 1341 // documented in JXG.AbstractRenderer 1342 setBuffering: function (el, type) { 1343 el.rendNode.setAttribute("buffered-rendering", type); 1344 }, 1345 1346 // documented in JXG.AbstractRenderer 1347 setDashStyle: function (el) { 1348 var dashStyle = el.evalVisProp('dash'), 1349 ds = el.evalVisProp('dashscale'), 1350 sw = ds ? 0.5 * el.evalVisProp('strokewidth') : 1, 1351 node = el.rendNode; 1352 1353 if (dashStyle > 0) { 1354 node.setAttributeNS(null, "stroke-dasharray", 1355 // sw could distinguish highlighting or not. 1356 // But it seems to preferable to ignore this. 1357 this.dashArray[dashStyle - 1].map(function (x) { return x * sw; }).join(',') 1358 ); 1359 } else { 1360 if (node.hasAttributeNS(null, "stroke-dasharray")) { 1361 node.removeAttributeNS(null, "stroke-dasharray"); 1362 } 1363 } 1364 }, 1365 1366 // documented in JXG.AbstractRenderer 1367 setGradient: function (el) { 1368 var fillNode = el.rendNode, 1369 node, node2, node3, 1370 ev_g = el.evalVisProp('gradient'); 1371 1372 if (ev_g === "linear" || ev_g === "radial") { 1373 node = this.createPrim(ev_g + "Gradient", el.id + "_gradient"); 1374 node2 = this.createPrim("stop", el.id + "_gradient1"); 1375 node3 = this.createPrim("stop", el.id + "_gradient2"); 1376 node.appendChild(node2); 1377 node.appendChild(node3); 1378 this.defs.appendChild(node); 1379 fillNode.setAttributeNS( 1380 null, 1381 'style', 1382 // "fill:url(#" + this.container.id + "_" + el.id + "_gradient)" 1383 'fill:' + this.toURL(this.container.id + '_' + el.id + '_gradient') 1384 ); 1385 el.gradNode1 = node2; 1386 el.gradNode2 = node3; 1387 el.gradNode = node; 1388 } else { 1389 fillNode.removeAttributeNS(null, "style"); 1390 } 1391 }, 1392 1393 /** 1394 * Set the gradient angle for linear color gradients. 1395 * 1396 * @private 1397 * @param {SVGnode} node SVG gradient node of an arbitrary JSXGraph element. 1398 * @param {Number} radians angle value in radians. 0 is horizontal from left to right, Pi/4 is vertical from top to bottom. 1399 */ 1400 updateGradientAngle: function (node, radians) { 1401 // Angles: 1402 // 0: -> 1403 // 90: down 1404 // 180: <- 1405 // 90: up 1406 var f = 1.0, 1407 co = Math.cos(radians), 1408 si = Math.sin(radians); 1409 1410 if (Math.abs(co) > Math.abs(si)) { 1411 f /= Math.abs(co); 1412 } else { 1413 f /= Math.abs(si); 1414 } 1415 1416 if (co >= 0) { 1417 node.setAttributeNS(null, "x1", 0); 1418 node.setAttributeNS(null, "x2", co * f); 1419 } else { 1420 node.setAttributeNS(null, "x1", -co * f); 1421 node.setAttributeNS(null, "x2", 0); 1422 } 1423 if (si >= 0) { 1424 node.setAttributeNS(null, "y1", 0); 1425 node.setAttributeNS(null, "y2", si * f); 1426 } else { 1427 node.setAttributeNS(null, "y1", -si * f); 1428 node.setAttributeNS(null, "y2", 0); 1429 } 1430 }, 1431 1432 /** 1433 * Set circles for radial color gradients. 1434 * 1435 * @private 1436 * @param {SVGnode} node SVG gradient node 1437 * @param {Number} cx SVG value cx (value between 0 and 1) 1438 * @param {Number} cy SVG value cy (value between 0 and 1) 1439 * @param {Number} r SVG value r (value between 0 and 1) 1440 * @param {Number} fx SVG value fx (value between 0 and 1) 1441 * @param {Number} fy SVG value fy (value between 0 and 1) 1442 * @param {Number} fr SVG value fr (value between 0 and 1) 1443 */ 1444 updateGradientCircle: function (node, cx, cy, r, fx, fy, fr) { 1445 node.setAttributeNS(null, "cx", cx * 100 + "%"); // Center first color 1446 node.setAttributeNS(null, "cy", cy * 100 + "%"); 1447 node.setAttributeNS(null, "r", r * 100 + "%"); 1448 node.setAttributeNS(null, "fx", fx * 100 + "%"); // Center second color / focal point 1449 node.setAttributeNS(null, "fy", fy * 100 + "%"); 1450 node.setAttributeNS(null, "fr", fr * 100 + "%"); 1451 }, 1452 1453 // documented in JXG.AbstractRenderer 1454 updateGradient: function (el) { 1455 var col, 1456 op, 1457 node2 = el.gradNode1, 1458 node3 = el.gradNode2, 1459 ev_g = el.evalVisProp('gradient'); 1460 1461 if (!Type.exists(node2) || !Type.exists(node3)) { 1462 return; 1463 } 1464 1465 op = el.evalVisProp('fillopacity'); 1466 op = op > 0 ? op : 0; 1467 col = el.evalVisProp('fillcolor'); 1468 1469 node2.setAttributeNS(null, "style", "stop-color:" + col + ";stop-opacity:" + op); 1470 node3.setAttributeNS( 1471 null, 1472 "style", 1473 "stop-color:" + 1474 el.evalVisProp('gradientsecondcolor') + 1475 ";stop-opacity:" + 1476 el.evalVisProp('gradientsecondopacity') 1477 ); 1478 node2.setAttributeNS( 1479 null, 1480 "offset", 1481 el.evalVisProp('gradientstartoffset') * 100 + "%" 1482 ); 1483 node3.setAttributeNS( 1484 null, 1485 "offset", 1486 el.evalVisProp('gradientendoffset') * 100 + "%" 1487 ); 1488 if (ev_g === "linear") { 1489 this.updateGradientAngle(el.gradNode, el.evalVisProp('gradientangle')); 1490 } else if (ev_g === "radial") { 1491 this.updateGradientCircle( 1492 el.gradNode, 1493 el.evalVisProp('gradientcx'), 1494 el.evalVisProp('gradientcy'), 1495 el.evalVisProp('gradientr'), 1496 el.evalVisProp('gradientfx'), 1497 el.evalVisProp('gradientfy'), 1498 el.evalVisProp('gradientfr') 1499 ); 1500 } 1501 }, 1502 1503 // documented in JXG.AbstractRenderer 1504 setObjectTransition: function (el, duration) { 1505 var node, props, 1506 transitionArr = [], 1507 transitionStr, 1508 i, 1509 len = 0, 1510 nodes = ["rendNode", "rendNodeTriangleStart", "rendNodeTriangleEnd"]; 1511 1512 if (duration === undefined) { 1513 duration = el.evalVisProp('transitionduration'); 1514 } 1515 1516 props = el.evalVisProp('transitionproperties'); 1517 if (duration === el.visPropOld.transitionduration && 1518 props === el.visPropOld.transitionproperties) { 1519 return; 1520 } 1521 1522 // if ( 1523 // el.elementClass === Const.OBJECT_CLASS_TEXT && 1524 // el.evalVisProp('display') === "html" 1525 // ) { 1526 // // transitionStr = " color " + duration + "ms," + 1527 // // " opacity " + duration + "ms"; 1528 // transitionStr = " all " + duration + "ms ease"; 1529 // } else { 1530 // transitionStr = 1531 // " fill " + duration + "ms," + 1532 // " fill-opacity " + duration + "ms," + 1533 // " stroke " + duration + "ms," + 1534 // " stroke-opacity " + duration + "ms," + 1535 // " stroke-width " + duration + "ms," + 1536 // " width " + duration + "ms," + 1537 // " height " + duration + "ms," + 1538 // " rx " + duration + "ms," + 1539 // " ry " + duration + "ms"; 1540 // } 1541 1542 if (Type.exists(props)) { 1543 len = props.length; 1544 } 1545 for (i = 0; i < len; i++) { 1546 transitionArr.push(props[i] + ' ' + duration + 'ms'); 1547 } 1548 transitionStr = transitionArr.join(', '); 1549 1550 len = nodes.length; 1551 for (i = 0; i < len; ++i) { 1552 if (el[nodes[i]]) { 1553 node = el[nodes[i]]; 1554 node.style.transition = transitionStr; 1555 } 1556 } 1557 1558 el.visPropOld.transitionduration = duration; 1559 el.visPropOld.transitionproperties = props; 1560 }, 1561 1562 /** 1563 * Call user-defined function to set visual attributes. 1564 * If "testAttribute" is the empty string, the function 1565 * is called immediately, otherwise it is called in a timeOut. 1566 * 1567 * This is necessary to realize smooth transitions but avoid transitions 1568 * when first creating the objects. 1569 * 1570 * Usually, the string in testAttribute is the visPropOld attribute 1571 * of the values which are set. 1572 * 1573 * @param {Function} setFunc Some function which usually sets some attributes 1574 * @param {String} testAttribute If this string is the empty string the function is called immediately, 1575 * otherwise it is called in a setImeout. 1576 * @see JXG.SVGRenderer#setObjectFillColor 1577 * @see JXG.SVGRenderer#setObjectStrokeColor 1578 * @see JXG.SVGRenderer#_setArrowColor 1579 * @private 1580 */ 1581 _setAttribute: function (setFunc, testAttribute) { 1582 if (testAttribute === "") { 1583 setFunc(); 1584 } else { 1585 window.setTimeout(setFunc, 1); 1586 } 1587 }, 1588 1589 // documented in JXG.AbstractRenderer 1590 setARIA: function(el) { 1591 // This method is only called in abstractRenderer._updateVisual() if aria.enabled == true. 1592 var key, k, v; 1593 1594 // this.setPropertyPrim(el.rendNode, 'aria-label', el.evalVisProp('aria.label')); 1595 // this.setPropertyPrim(el.rendNode, 'aria-live', el.evalVisProp('aria.live')); 1596 for (key in el.visProp.aria) { 1597 if (el.visProp.aria.hasOwnProperty(key) && key !== 'enabled') { 1598 k = 'aria.' + key; 1599 v = el.evalVisProp('aria.' + key); 1600 if (el.visPropOld[k] !== v) { 1601 this.setPropertyPrim(el.rendNode, 'aria-' + key, v); 1602 el.visPropOld[k] = v; 1603 } 1604 } 1605 } 1606 }, 1607 1608 // documented in JXG.AbstractRenderer 1609 setCssClass(el, cssClass) { 1610 if (el.visPropOld.cssclass !== cssClass) { 1611 this.setPropertyPrim(el.rendNode, 'class', cssClass); 1612 el.visPropOld.cssclass = cssClass; 1613 } 1614 }, 1615 1616 // documented in JXG.AbstractRenderer 1617 setObjectFillColor: function (el, color, opacity, rendNode) { 1618 var node, c, rgbo, oo, 1619 rgba = color, 1620 o = opacity, 1621 grad = el.evalVisProp('gradient'); 1622 1623 o = o > 0 ? o : 0; 1624 1625 // TODO save gradient and gradientangle 1626 if ( 1627 el.visPropOld.fillcolor === rgba && 1628 el.visPropOld.fillopacity === o && 1629 grad === null 1630 ) { 1631 return; 1632 } 1633 1634 if (Type.exists(rgba) && rgba !== false) { 1635 if (rgba.length !== 9) { 1636 // RGB, not RGBA 1637 c = rgba; 1638 oo = o; 1639 } else { 1640 // True RGBA, not RGB 1641 rgbo = Color.rgba2rgbo(rgba); 1642 c = rgbo[0]; 1643 oo = o * rgbo[1]; 1644 } 1645 1646 if (rendNode === undefined) { 1647 node = el.rendNode; 1648 } else { 1649 node = rendNode; 1650 } 1651 1652 if (c !== "none") { 1653 this._setAttribute(function () { 1654 node.setAttributeNS(null, "fill", c); 1655 }, el.visPropOld.fillcolor); 1656 } 1657 1658 if (el.type === JXG.OBJECT_TYPE_IMAGE) { 1659 this._setAttribute(function () { 1660 node.setAttributeNS(null, "opacity", oo); 1661 }, el.visPropOld.fillopacity); 1662 //node.style['opacity'] = oo; // This would overwrite values set by CSS class. 1663 } else { 1664 if (c === "none") { 1665 // This is done only for non-images 1666 // because images have no fill color. 1667 oo = 0; 1668 // This is necessary if there is a foreignObject below. 1669 node.setAttributeNS(null, "pointer-events", "visibleStroke"); 1670 } else { 1671 // This is the default 1672 node.setAttributeNS(null, "pointer-events", "visiblePainted"); 1673 } 1674 this._setAttribute(function () { 1675 node.setAttributeNS(null, "fill-opacity", oo); 1676 }, el.visPropOld.fillopacity); 1677 } 1678 1679 if (grad === "linear" || grad === "radial") { 1680 this.updateGradient(el); 1681 } 1682 } 1683 el.visPropOld.fillcolor = rgba; 1684 el.visPropOld.fillopacity = o; 1685 }, 1686 1687 // documented in JXG.AbstractRenderer 1688 setObjectStrokeColor: function (el, color, opacity) { 1689 var rgba = color, 1690 c, rgbo, 1691 o = opacity, 1692 oo, node; 1693 1694 o = o > 0 ? o : 0; 1695 1696 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 1697 return; 1698 } 1699 1700 if (Type.exists(rgba) && rgba !== false) { 1701 if (rgba.length !== 9) { 1702 // RGB, not RGBA 1703 c = rgba; 1704 oo = o; 1705 } else { 1706 // True RGBA, not RGB 1707 rgbo = Color.rgba2rgbo(rgba); 1708 c = rgbo[0]; 1709 oo = o * rgbo[1]; 1710 } 1711 1712 node = el.rendNode; 1713 1714 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 1715 if (el.evalVisProp('display') === "html") { 1716 this._setAttribute(function () { 1717 node.style.color = c; 1718 node.style.opacity = oo; 1719 }, el.visPropOld.strokecolor); 1720 } else { 1721 this._setAttribute(function () { 1722 node.setAttributeNS(null, "style", "fill:" + c); 1723 node.setAttributeNS(null, "style", "fill-opacity:" + oo); 1724 }, el.visPropOld.strokecolor); 1725 } 1726 } else { 1727 this._setAttribute(function () { 1728 node.setAttributeNS(null, "stroke", c); 1729 node.setAttributeNS(null, "stroke-opacity", oo); 1730 }, el.visPropOld.strokecolor); 1731 } 1732 1733 if ( 1734 el.elementClass === Const.OBJECT_CLASS_CURVE || 1735 el.elementClass === Const.OBJECT_CLASS_LINE 1736 ) { 1737 if (el.evalVisProp('firstarrow')) { 1738 this._setArrowColor( 1739 el.rendNodeTriangleStart, 1740 c, oo, el, 1741 el.visPropCalc.typeFirst 1742 ); 1743 } 1744 1745 if (el.evalVisProp('lastarrow')) { 1746 this._setArrowColor( 1747 el.rendNodeTriangleEnd, 1748 c, oo, el, 1749 el.visPropCalc.typeLast 1750 ); 1751 } 1752 } 1753 } 1754 1755 el.visPropOld.strokecolor = rgba; 1756 el.visPropOld.strokeopacity = o; 1757 }, 1758 1759 // documented in JXG.AbstractRenderer 1760 setObjectStrokeWidth: function (el, width) { 1761 var node, 1762 w = width; 1763 1764 if (isNaN(w) || el.visPropOld.strokewidth === w) { 1765 return; 1766 } 1767 1768 node = el.rendNode; 1769 this.setPropertyPrim(node, "stroked", "true"); 1770 if (Type.exists(w)) { 1771 this.setPropertyPrim(node, "stroke-width", w + "px"); 1772 1773 // if (el.elementClass === Const.OBJECT_CLASS_CURVE || 1774 // el.elementClass === Const.OBJECT_CLASS_LINE) { 1775 // if (el.evalVisProp('firstarrow')) { 1776 // this._setArrowWidth(el.rendNodeTriangleStart, w, el.rendNode); 1777 // } 1778 // 1779 // if (el.evalVisProp('lastarrow')) { 1780 // this._setArrowWidth(el.rendNodeTriangleEnd, w, el.rendNode); 1781 // } 1782 // } 1783 } 1784 el.visPropOld.strokewidth = w; 1785 }, 1786 1787 // documented in JXG.AbstractRenderer 1788 setLineCap: function (el) { 1789 var capStyle = el.evalVisProp('linecap'); 1790 1791 if ( 1792 capStyle === undefined || 1793 capStyle === "" || 1794 el.visPropOld.linecap === capStyle || 1795 !Type.exists(el.rendNode) 1796 ) { 1797 return; 1798 } 1799 1800 this.setPropertyPrim(el.rendNode, "stroke-linecap", capStyle); 1801 el.visPropOld.linecap = capStyle; 1802 }, 1803 1804 // documented in JXG.AbstractRenderer 1805 setShadow: function (el) { 1806 var ev_s = el.evalVisProp('shadow'), 1807 ev_s_json, c, b, bl, o, op, id, node, 1808 use_board_filter = true, 1809 show = false; 1810 1811 ev_s_json = JSON.stringify(ev_s); 1812 if (ev_s_json === el.visPropOld.shadow) { 1813 return; 1814 } 1815 1816 if (typeof ev_s === 'boolean') { 1817 use_board_filter = true; 1818 show = ev_s; 1819 c = 'none'; 1820 b = 3; 1821 bl = 0.1; 1822 o = [5, 5]; 1823 op = 1; 1824 } else { 1825 if (el.evalVisProp('shadow.enabled')) { 1826 use_board_filter = false; 1827 show = true; 1828 c = JXG.rgbParser(el.evalVisProp('shadow.color')); 1829 b = el.evalVisProp('shadow.blur'); 1830 bl = el.evalVisProp('shadow.blend'); 1831 o = el.evalVisProp('shadow.offset'); 1832 op = el.evalVisProp('shadow.opacity'); 1833 } else { 1834 show = false; 1835 } 1836 } 1837 1838 if (Type.exists(el.rendNode)) { 1839 if (show) { 1840 if (use_board_filter) { 1841 el.rendNode.setAttributeNS(null, 'filter', this.toURL(this.container.id + '_' + 'f1')); 1842 // 'url(#' + this.container.id + '_' + 'f1)'); 1843 } else { 1844 node = this.container.ownerDocument.getElementById(id); 1845 if (node) { 1846 this.defs.removeChild(node); 1847 } 1848 id = el.rendNode.id + '_' + 'f1'; 1849 this.defs.appendChild(this.createShadowFilter(id, c, op, bl, b, o)); 1850 el.rendNode.setAttributeNS(null, 'filter', this.toURL(id)); 1851 // 'url(#' + id + ')'); 1852 } 1853 } else { 1854 el.rendNode.removeAttributeNS(null, 'filter'); 1855 } 1856 } 1857 1858 el.visPropOld.shadow = ev_s_json; 1859 }, 1860 1861 /* ************************** 1862 * renderer control 1863 * **************************/ 1864 1865 // documented in JXG.AbstractRenderer 1866 suspendRedraw: function () { 1867 // It seems to be important for the Linux version of firefox 1868 this.suspendHandle = this.svgRoot.suspendRedraw(10000); 1869 }, 1870 1871 // documented in JXG.AbstractRenderer 1872 unsuspendRedraw: function () { 1873 this.svgRoot.unsuspendRedraw(this.suspendHandle); 1874 // this.svgRoot.unsuspendRedrawAll(); 1875 //this.svgRoot.forceRedraw(); 1876 }, 1877 1878 // documented in AbstractRenderer 1879 resize: function (w, h) { 1880 this.svgRoot.setAttribute("width", parseFloat(w)); 1881 this.svgRoot.setAttribute("height", parseFloat(h)); 1882 }, 1883 1884 // documented in JXG.AbstractRenderer 1885 createTouchpoints: function (n) { 1886 var i, na1, na2, node; 1887 this.touchpoints = []; 1888 for (i = 0; i < n; i++) { 1889 na1 = "touchpoint1_" + i; 1890 node = this.createPrim("path", na1); 1891 this.appendChildPrim(node, 19); 1892 node.setAttributeNS(null, "d", "M 0 0"); 1893 this.touchpoints.push(node); 1894 1895 this.setPropertyPrim(node, "stroked", "true"); 1896 this.setPropertyPrim(node, "stroke-width", "1px"); 1897 node.setAttributeNS(null, "stroke", "#000000"); 1898 node.setAttributeNS(null, "stroke-opacity", 1.0); 1899 node.setAttributeNS(null, "display", "none"); 1900 1901 na2 = "touchpoint2_" + i; 1902 node = this.createPrim("ellipse", na2); 1903 this.appendChildPrim(node, 19); 1904 this.updateEllipsePrim(node, 0, 0, 0, 0); 1905 this.touchpoints.push(node); 1906 1907 this.setPropertyPrim(node, "stroked", "true"); 1908 this.setPropertyPrim(node, "stroke-width", "1px"); 1909 node.setAttributeNS(null, "stroke", "#000000"); 1910 node.setAttributeNS(null, "stroke-opacity", 1.0); 1911 node.setAttributeNS(null, "fill", "#ffffff"); 1912 node.setAttributeNS(null, "fill-opacity", 0.0); 1913 1914 node.setAttributeNS(null, "display", "none"); 1915 } 1916 }, 1917 1918 // documented in JXG.AbstractRenderer 1919 showTouchpoint: function (i) { 1920 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1921 this.touchpoints[2 * i].setAttributeNS(null, "display", "inline"); 1922 this.touchpoints[2 * i + 1].setAttributeNS(null, "display", "inline"); 1923 } 1924 }, 1925 1926 // documented in JXG.AbstractRenderer 1927 hideTouchpoint: function (i) { 1928 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1929 this.touchpoints[2 * i].setAttributeNS(null, "display", "none"); 1930 this.touchpoints[2 * i + 1].setAttributeNS(null, "display", "none"); 1931 } 1932 }, 1933 1934 // documented in JXG.AbstractRenderer 1935 updateTouchpoint: function (i, pos) { 1936 var x, 1937 y, 1938 d = 37; 1939 1940 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1941 x = pos[0]; 1942 y = pos[1]; 1943 1944 this.touchpoints[2 * i].setAttributeNS( 1945 null, 1946 "d", 1947 "M " + 1948 (x - d) + 1949 " " + 1950 y + 1951 " " + 1952 "L " + 1953 (x + d) + 1954 " " + 1955 y + 1956 " " + 1957 "M " + 1958 x + 1959 " " + 1960 (y - d) + 1961 " " + 1962 "L " + 1963 x + 1964 " " + 1965 (y + d) 1966 ); 1967 this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25); 1968 } 1969 }, 1970 1971 /** 1972 * Walk recursively through the DOM subtree of a node and collect all 1973 * value attributes together with the id of that node. 1974 * <b>Attention:</b> Only values of nodes having a valid id are taken. 1975 * @param {Node} node root node of DOM subtree that will be searched recursively. 1976 * @return {Array} Array with entries of the form [id, value] 1977 * @private 1978 */ 1979 _getValuesOfDOMElements: function (node) { 1980 var values = []; 1981 if (node.nodeType === 1) { 1982 node = node.firstChild; 1983 while (node) { 1984 if (node.id !== undefined && node.value !== undefined) { 1985 values.push([node.id, node.value]); 1986 } 1987 Type.concat(values, this._getValuesOfDOMElements(node)); 1988 node = node.nextSibling; 1989 } 1990 } 1991 return values; 1992 }, 1993 1994 // _getDataUri: function (url, callback) { 1995 // var image = new Image(); 1996 // image.onload = function () { 1997 // var canvas = document.createElement("canvas"); 1998 // canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size 1999 // canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size 2000 // canvas.getContext("2d").drawImage(this, 0, 0); 2001 // callback(canvas.toDataURL("image/png")); 2002 // canvas.remove(); 2003 // }; 2004 // image.src = url; 2005 // }, 2006 2007 _getImgDataURL: function (svgRoot) { 2008 var images, len, canvas, ctx, ur, i; 2009 2010 images = svgRoot.getElementsByTagName("image"); 2011 len = images.length; 2012 if (len > 0) { 2013 canvas = document.createElement("canvas"); 2014 //img = new Image(); 2015 for (i = 0; i < len; i++) { 2016 images[i].setAttribute("crossorigin", "anonymous"); 2017 //img.src = images[i].href; 2018 //img.onload = function() { 2019 // img.crossOrigin = "anonymous"; 2020 ctx = canvas.getContext("2d"); 2021 canvas.width = images[i].getAttribute("width"); 2022 canvas.height = images[i].getAttribute("height"); 2023 try { 2024 ctx.drawImage(images[i], 0, 0, canvas.width, canvas.height); 2025 2026 // If the image is not png, the format must be specified here 2027 ur = canvas.toDataURL(); 2028 images[i].setAttribute("xlink:href", ur); 2029 } catch (err) { 2030 console.log("CORS problem! Image can not be used", err); 2031 } 2032 } 2033 //canvas.remove(); 2034 } 2035 return true; 2036 }, 2037 2038 /** 2039 * Return a data URI of the SVG code representing the construction. 2040 * The SVG code of the construction is base64 encoded. The return string starts 2041 * with "data:image/svg+xml;base64,...". 2042 * 2043 * @param {Boolean} ignoreTexts If true, the foreignObject tag is set to display=none. 2044 * This is necessary for older versions of Safari. Default: false 2045 * @returns {String} data URI string 2046 * 2047 * @example 2048 * var A = board.create('point', [2, 2]); 2049 * 2050 * var txt = board.renderer.dumpToDataURI(false); 2051 * // txt consists of a string of the form 2052 * // data:image/svg+xml;base64,PHN2Zy. base64 encoded SVG..+PC9zdmc+ 2053 * // Behind the comma, there is the base64 encoded SVG code 2054 * // which is decoded with atob(). 2055 * // The call of decodeURIComponent(escape(...)) is necessary 2056 * // to handle unicode strings correctly. 2057 * var ar = txt.split(','); 2058 * document.getElementById('output').value = decodeURIComponent(escape(atob(ar[1]))); 2059 * 2060 * </pre><div id="JXG1bad4bec-6d08-4ce0-9b7f-d817e8dd762d" class="jxgbox" style="width: 300px; height: 300px;"></div> 2061 * <textarea id="output2023" rows="5" cols="50"></textarea> 2062 * <script type="text/javascript"> 2063 * (function() { 2064 * var board = JXG.JSXGraph.initBoard('JXG1bad4bec-6d08-4ce0-9b7f-d817e8dd762d', 2065 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2066 * var A = board.create('point', [2, 2]); 2067 * 2068 * var txt = board.renderer.dumpToDataURI(false); 2069 * // txt consists of a string of the form 2070 * // data:image/svg+xml;base64,PHN2Zy. base64 encoded SVG..+PC9zdmc+ 2071 * // Behind the comma, there is the base64 encoded SVG code 2072 * // which is decoded with atob(). 2073 * // The call of decodeURIComponent(escape(...)) is necessary 2074 * // to handle unicode strings correctly. 2075 * var ar = txt.split(','); 2076 * document.getElementById('output2023').value = decodeURIComponent(escape(atob(ar[1]))); 2077 * 2078 * })(); 2079 * 2080 * </script><pre> 2081 * 2082 */ 2083 dumpToDataURI: function (ignoreTexts) { 2084 var svgRoot = this.svgRoot, 2085 btoa = window.btoa || Base64.encode, 2086 svg, i, len, 2087 values = []; 2088 2089 // Move all HTML tags (beside the SVG root) of the container 2090 // to the foreignObject element inside of the svgRoot node 2091 // Problem: 2092 // input values are not copied. This can be verified by looking at an innerHTML output 2093 // of an input element. Therefore, we do it "by hand". 2094 if (this.container.hasChildNodes() && Type.exists(this.foreignObjLayer)) { 2095 if (!ignoreTexts) { 2096 this.foreignObjLayer.setAttribute("display", "inline"); 2097 } 2098 while (svgRoot.nextSibling) { 2099 // Copy all value attributes 2100 Type.concat(values, this._getValuesOfDOMElements(svgRoot.nextSibling)); 2101 this.foreignObjLayer.appendChild(svgRoot.nextSibling); 2102 } 2103 } 2104 2105 this._getImgDataURL(svgRoot); 2106 2107 // Convert the SVG graphic into a string containing SVG code 2108 svgRoot.setAttribute("xmlns", "http://www.w3.org/2000/svg"); 2109 svg = new XMLSerializer().serializeToString(svgRoot); 2110 2111 if (ignoreTexts !== true) { 2112 // Handle SVG texts 2113 // Insert all value attributes back into the svg string 2114 len = values.length; 2115 for (i = 0; i < len; i++) { 2116 svg = svg.replace( 2117 'id="' + values[i][0] + '"', 2118 'id="' + values[i][0] + '" value="' + values[i][1] + '"' 2119 ); 2120 } 2121 } 2122 2123 // if (false) { 2124 // // Debug: use example svg image 2125 // 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>'; 2126 // } 2127 2128 // In IE we have to remove the namespace again. 2129 // Since 2024 we have to check if the namespace attribute appears twice in one tag, because 2130 // there might by a svg inside of the svg, e.g. the screenshot icon. 2131 if (this.isIE && 2132 (svg.match(/xmlns="http:\/\/www.w3.org\/2000\/svg"\s+xmlns="http:\/\/www.w3.org\/2000\/svg"/g) || []).length > 1 2133 ) { 2134 svg = svg.replace(/xmlns="http:\/\/www.w3.org\/2000\/svg"\s+xmlns="http:\/\/www.w3.org\/2000\/svg"/g, ""); 2135 } 2136 2137 // Safari fails if the svg string contains a " " 2138 // Obsolete with Safari 12+ 2139 svg = svg.replace(/ /g, " "); 2140 svg = svg.replace(/url\("(.*)"\)/g, "url($1)"); 2141 2142 // Move all HTML tags back from 2143 // the foreignObject element to the container 2144 if (Type.exists(this.foreignObjLayer) && this.foreignObjLayer.hasChildNodes()) { 2145 // Restore all HTML elements 2146 while (this.foreignObjLayer.firstChild) { 2147 this.container.appendChild(this.foreignObjLayer.firstChild); 2148 } 2149 this.foreignObjLayer.setAttribute("display", "none"); 2150 } 2151 2152 return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg))); 2153 }, 2154 2155 /** 2156 * Convert the SVG construction into an HTML canvas image. 2157 * This works for all SVG supporting browsers. Implemented as Promise. 2158 * <p> 2159 * Might fail if any text element or foreign object element contains SVG. This 2160 * is the case e.g. for the default fullscreen symbol. 2161 * <p> 2162 * For IE, it is realized as function. 2163 * It works from version 9, with the exception that HTML texts 2164 * are ignored on IE. The drawing is done with a delay of 2165 * 200 ms. Otherwise there would be problems with IE. 2166 * 2167 * @param {String} canvasId Id of an HTML canvas element 2168 * @param {Number} w Width in pixel of the dumped image, i.e. of the canvas tag. 2169 * @param {Number} h Height in pixel of the dumped image, i.e. of the canvas tag. 2170 * @param {Boolean} ignoreTexts If true, the foreignObject tag is taken out from the SVG root. 2171 * This is necessary for older versions of Safari. Default: false 2172 * @returns {Promise} Promise object 2173 * 2174 * @example 2175 * board.renderer.dumpToCanvas('canvas').then(function() { console.log('done'); }); 2176 * 2177 * @example 2178 * // IE 11 example: 2179 * board.renderer.dumpToCanvas('canvas'); 2180 * setTimeout(function() { console.log('done'); }, 400); 2181 */ 2182 dumpToCanvas: function (canvasId, w, h, ignoreTexts) { 2183 var svg, tmpImg, 2184 cv, ctx, 2185 doc = this.container.ownerDocument; 2186 2187 // Prepare the canvas element 2188 cv = doc.getElementById(canvasId); 2189 2190 // Clear the canvas 2191 /* eslint-disable no-self-assign */ 2192 cv.width = cv.width; 2193 /* eslint-enable no-self-assign */ 2194 2195 ctx = cv.getContext("2d"); 2196 if (w !== undefined && h !== undefined) { 2197 cv.style.width = parseFloat(w) + "px"; 2198 cv.style.height = parseFloat(h) + "px"; 2199 // Scale twice the CSS size to make the image crisp 2200 // cv.setAttribute('width', 2 * parseFloat(wOrg)); 2201 // cv.setAttribute('height', 2 * parseFloat(hOrg)); 2202 // ctx.scale(2 * wOrg / w, 2 * hOrg / h); 2203 cv.setAttribute("width", parseFloat(w)); 2204 cv.setAttribute("height", parseFloat(h)); 2205 } 2206 2207 // Display the SVG string as data-uri in an HTML img. 2208 /** 2209 * @type {Image} 2210 * @ignore 2211 * {ignore} 2212 */ 2213 tmpImg = new Image(); 2214 svg = this.dumpToDataURI(ignoreTexts); 2215 tmpImg.src = svg; 2216 2217 // Finally, draw the HTML img in the canvas. 2218 if (!("Promise" in window)) { 2219 /** 2220 * @function 2221 * @ignore 2222 */ 2223 tmpImg.onload = function () { 2224 // IE needs a pause... 2225 // Seems to be broken 2226 window.setTimeout(function () { 2227 try { 2228 ctx.drawImage(tmpImg, 0, 0, w, h); 2229 } catch (err) { 2230 console.log("screenshots not longer supported on IE"); 2231 } 2232 }, 200); 2233 }; 2234 return this; 2235 } 2236 2237 return new Promise(function (resolve, reject) { 2238 try { 2239 tmpImg.onload = function () { 2240 ctx.drawImage(tmpImg, 0, 0, w, h); 2241 resolve(); 2242 }; 2243 } catch (e) { 2244 reject(e); 2245 } 2246 }); 2247 }, 2248 2249 /** 2250 * Display SVG image in html img-tag which enables 2251 * easy download for the user. 2252 * 2253 * Support: 2254 * <ul> 2255 * <li> IE: No 2256 * <li> Edge: full 2257 * <li> Firefox: full 2258 * <li> Chrome: full 2259 * <li> Safari: full (No text support in versions prior to 12). 2260 * </ul> 2261 * 2262 * @param {JXG.Board} board Link to the board. 2263 * @param {String} imgId Optional id of an img object. If given and different from the empty string, 2264 * the screenshot is copied to this img object. The width and height will be set to the values of the 2265 * JSXGraph container. 2266 * @param {Boolean} ignoreTexts If set to true, the foreignObject is taken out of the 2267 * SVGRoot and texts are not displayed. This is mandatory for Safari. Default: false 2268 * @return {Object} the svg renderer object 2269 */ 2270 screenshot: function (board, imgId, ignoreTexts) { 2271 var node, 2272 doc = this.container.ownerDocument, 2273 parent = this.container.parentNode, 2274 // cPos, 2275 // cssTxt, 2276 canvas, id, img, 2277 button, buttonText, 2278 w, h, 2279 bas = board.attr.screenshot, 2280 navbar, navbarDisplay, insert, 2281 newImg = false, 2282 _copyCanvasToImg, 2283 isDebug = false; 2284 2285 if (this.type === "no") { 2286 return this; 2287 } 2288 2289 w = bas.scale * this.container.getBoundingClientRect().width; 2290 h = bas.scale * this.container.getBoundingClientRect().height; 2291 2292 if (imgId === undefined || imgId === "") { 2293 newImg = true; 2294 img = new Image(); //doc.createElement('img'); 2295 img.style.width = w + "px"; 2296 img.style.height = h + "px"; 2297 } else { 2298 newImg = false; 2299 img = doc.getElementById(imgId); 2300 } 2301 // img.crossOrigin = 'anonymous'; 2302 2303 // Create div which contains canvas element and close button 2304 if (newImg) { 2305 node = doc.createElement("div"); 2306 node.style.cssText = bas.css; 2307 node.style.width = w + "px"; 2308 node.style.height = h + "px"; 2309 node.style.zIndex = this.container.style.zIndex + 120; 2310 2311 // Try to position the div exactly over the JSXGraph board 2312 node.style.position = "absolute"; 2313 node.style.top = this.container.offsetTop + "px"; 2314 node.style.left = this.container.offsetLeft + "px"; 2315 } 2316 2317 if (!isDebug) { 2318 // Create canvas element and add it to the DOM 2319 // It will be removed after the image has been stored. 2320 canvas = doc.createElement("canvas"); 2321 id = Math.random().toString(36).slice(2, 7); 2322 canvas.setAttribute("id", id); 2323 canvas.setAttribute("width", w); 2324 canvas.setAttribute("height", h); 2325 canvas.style.width = w + "px"; 2326 canvas.style.height = w + "px"; 2327 canvas.style.display = "none"; 2328 parent.appendChild(canvas); 2329 } else { 2330 // Debug: use canvas element 'jxgbox_canvas' from jsxdev/dump.html 2331 id = "jxgbox_canvas"; 2332 canvas = doc.getElementById(id); 2333 } 2334 2335 if (newImg) { 2336 // Create close button 2337 button = doc.createElement("span"); 2338 buttonText = doc.createTextNode("\u2716"); 2339 button.style.cssText = bas.cssButton; 2340 button.appendChild(buttonText); 2341 button.onclick = function () { 2342 node.parentNode.removeChild(node); 2343 }; 2344 2345 // Add all nodes 2346 node.appendChild(img); 2347 node.appendChild(button); 2348 parent.insertBefore(node, this.container.nextSibling); 2349 } 2350 2351 // Hide navigation bar in board 2352 navbar = doc.getElementById(this.uniqName('navigationbar')); 2353 if (Type.exists(navbar)) { 2354 navbarDisplay = navbar.style.display; 2355 navbar.style.display = "none"; 2356 insert = this.removeToInsertLater(navbar); 2357 } 2358 2359 _copyCanvasToImg = function () { 2360 // Show image in img tag 2361 img.src = canvas.toDataURL("image/png"); 2362 2363 // Remove canvas node 2364 if (!isDebug) { 2365 parent.removeChild(canvas); 2366 } 2367 }; 2368 2369 // Create screenshot in image element 2370 if ("Promise" in window) { 2371 this.dumpToCanvas(id, w, h, ignoreTexts).then(_copyCanvasToImg); 2372 } else { 2373 // IE 2374 this.dumpToCanvas(id, w, h, ignoreTexts); 2375 window.setTimeout(_copyCanvasToImg, 200); 2376 } 2377 2378 // Reinsert navigation bar in board 2379 if (Type.exists(navbar)) { 2380 navbar.style.display = navbarDisplay; 2381 insert(); 2382 } 2383 2384 return this; 2385 } 2386 } 2387 ); 2388 2389 export default JXG.SVGRenderer; 2390