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