1 /* 2 Copyright 2008-2015 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */ 33 /*jslint nomen: true, plusplus: true, newcap:true*/ 34 35 /* depends: 36 jxg 37 options 38 renderer/abstract 39 base/constants 40 utils/type 41 utils/env 42 utils/color 43 math/numerics 44 */ 45 46 define([ 47 'jxg', 'options', 'renderer/abstract', 'base/constants', 'utils/type', 'utils/env', 'utils/color', 'math/numerics' 48 ], function (JXG, Options, AbstractRenderer, Const, Type, Env, Color, Numerics) { 49 50 "use strict"; 51 52 /** 53 * Uses SVG to implement the rendering methods defined in {@link JXG.AbstractRenderer}. 54 * @class JXG.AbstractRenderer 55 * @augments JXG.AbstractRenderer 56 * @param {Node} container Reference to a DOM node containing the board. 57 * @param {Object} dim The dimensions of the board 58 * @param {Number} dim.width 59 * @param {Number} dim.height 60 * @see JXG.AbstractRenderer 61 */ 62 JXG.SVGRenderer = function (container, dim) { 63 var i; 64 65 // docstring in AbstractRenderer 66 this.type = 'svg'; 67 68 this.isIE = navigator.appVersion.indexOf("MSIE") !== -1 || navigator.userAgent.match(/Trident\//); 69 70 /** 71 * SVG root node 72 * @type Node 73 */ 74 this.svgRoot = null; 75 76 /** 77 * The SVG Namespace used in JSXGraph. 78 * @see http://www.w3.org/TR/SVG/ 79 * @type String 80 * @default http://www.w3.org/2000/svg 81 */ 82 this.svgNamespace = 'http://www.w3.org/2000/svg'; 83 84 /** 85 * The xlink namespace. This is used for images. 86 * @see http://www.w3.org/TR/xlink/ 87 * @type String 88 * @default http://www.w3.org/1999/xlink 89 */ 90 this.xlinkNamespace = 'http://www.w3.org/1999/xlink'; 91 92 // container is documented in AbstractRenderer 93 this.container = container; 94 95 // prepare the div container and the svg root node for use with JSXGraph 96 this.container.style.MozUserSelect = 'none'; 97 98 this.container.style.overflow = 'hidden'; 99 if (this.container.style.position === '') { 100 this.container.style.position = 'relative'; 101 } 102 103 this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg"); 104 this.svgRoot.style.overflow = 'hidden'; 105 106 this.svgRoot.style.width = dim.width + 'px'; 107 this.svgRoot.style.height = dim.height + 'px'; 108 109 this.container.appendChild(this.svgRoot); 110 111 /** 112 * The <tt>defs</tt> element is a container element to reference reusable SVG elements. 113 * @type Node 114 * @see http://www.w3.org/TR/SVG/struct.html#DefsElement 115 */ 116 this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, 'defs'); 117 this.svgRoot.appendChild(this.defs); 118 119 /** 120 * Filters are used to apply shadows. 121 * @type Node 122 * @see http://www.w3.org/TR/SVG/filters.html#FilterElement 123 */ 124 this.filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter'); 125 this.filter.setAttributeNS(null, 'id', this.container.id + '_' + 'f1'); 126 /* 127 this.filter.setAttributeNS(null, 'x', '-100%'); 128 this.filter.setAttributeNS(null, 'y', '-100%'); 129 this.filter.setAttributeNS(null, 'width', '400%'); 130 this.filter.setAttributeNS(null, 'height', '400%'); 131 //this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse'); 132 */ 133 this.filter.setAttributeNS(null, 'width', '300%'); 134 this.filter.setAttributeNS(null, 'height', '300%'); 135 this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse'); 136 137 this.feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset'); 138 this.feOffset.setAttributeNS(null, 'result', 'offOut'); 139 this.feOffset.setAttributeNS(null, 'in', 'SourceAlpha'); 140 this.feOffset.setAttributeNS(null, 'dx', '5'); 141 this.feOffset.setAttributeNS(null, 'dy', '5'); 142 this.filter.appendChild(this.feOffset); 143 144 this.feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur'); 145 this.feGaussianBlur.setAttributeNS(null, 'result', 'blurOut'); 146 this.feGaussianBlur.setAttributeNS(null, 'in', 'offOut'); 147 this.feGaussianBlur.setAttributeNS(null, 'stdDeviation', '3'); 148 this.filter.appendChild(this.feGaussianBlur); 149 150 this.feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend'); 151 this.feBlend.setAttributeNS(null, 'in', 'SourceGraphic'); 152 this.feBlend.setAttributeNS(null, 'in2', 'blurOut'); 153 this.feBlend.setAttributeNS(null, 'mode', 'normal'); 154 this.filter.appendChild(this.feBlend); 155 156 this.defs.appendChild(this.filter); 157 158 /** 159 * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front 160 * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented 161 * there, too. The higher the number, the "more on top" are the elements on this layer. 162 * @type Array 163 */ 164 this.layer = []; 165 for (i = 0; i < Options.layer.numlayers; i++) { 166 this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g'); 167 this.svgRoot.appendChild(this.layer[i]); 168 } 169 170 /** 171 * Defines dash patterns. Defined styles are: <ol> 172 * <li value="-1"> 2px dash, 2px space</li> 173 * <li> 5px dash, 5px space</li> 174 * <li> 10px dash, 10px space</li> 175 * <li> 20px dash, 20px space</li> 176 * <li> 20px dash, 10px space, 10px dash, 10px dash</li> 177 * <li> 20px dash, 5px space, 10px dash, 5px space</li></ol> 178 * @type Array 179 * @default ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5'] 180 * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties 181 */ 182 this.dashArray = ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5']; 183 }; 184 185 JXG.SVGRenderer.prototype = new AbstractRenderer(); 186 187 JXG.extend(JXG.SVGRenderer.prototype, /** @lends JXG.SVGRenderer.prototype */ { 188 189 /** 190 * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag. 191 * @private 192 * @param {JXG.GeometryElement} element A JSXGraph element, preferably one that can have an arrow attached. 193 * @param {String} [idAppendix=''] A string that is added to the node's id. 194 * @returns {Node} Reference to the node added to the DOM. 195 */ 196 _createArrowHead: function (element, idAppendix) { 197 var node2, node3, 198 id = element.id + 'Triangle', 199 s, d; 200 201 if (Type.exists(idAppendix)) { 202 id += idAppendix; 203 } 204 node2 = this.createPrim('marker', id); 205 206 node2.setAttributeNS(null, 'stroke', Type.evaluate(element.visProp.strokecolor)); 207 node2.setAttributeNS(null, 'stroke-opacity', Type.evaluate(element.visProp.strokeopacity)); 208 node2.setAttributeNS(null, 'fill', Type.evaluate(element.visProp.strokecolor)); 209 node2.setAttributeNS(null, 'fill-opacity', Type.evaluate(element.visProp.strokeopacity)); 210 node2.setAttributeNS(null, 'stroke-width', 0); // this is the stroke-width of the arrow head. 211 // Should be zero to make the positioning easy 212 213 node2.setAttributeNS(null, 'orient', 'auto'); 214 node2.setAttributeNS(null, 'markerUnits', 'strokeWidth'); // 'strokeWidth' 'userSpaceOnUse'); 215 216 /* 217 * Changes here are also necessary in _setArrowAtts() 218 */ 219 s = parseInt(element.visProp.strokewidth, 10); 220 //node2.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 12 + ' ' + s * 12); 221 node2.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 10 + ' ' + s * 10); 222 223 /* 224 The arrow head is an equilateral triangle with base length 10 and height 10. 225 This 10 units are scaled to strokeWidth*3 pixels or minimum 10 pixels. 226 See also abstractRenderer.updateLine() where the line path is shortened accordingly. 227 */ 228 d = Math.max(s * 3, 10); 229 node2.setAttributeNS(null, 'markerHeight', d); 230 node2.setAttributeNS(null, 'markerWidth', d); 231 232 node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, 'path'); 233 234 if (idAppendix === 'End') { // First arrow 235 node2.setAttributeNS(null, 'refY', 5); 236 node2.setAttributeNS(null, 'refX', 10); 237 node3.setAttributeNS(null, 'd', 'M 10 0 L 0 5 L 10 10 z'); 238 } else { // Last arrow 239 node2.setAttributeNS(null, 'refY', 5); 240 node2.setAttributeNS(null, 'refX', 0); 241 node3.setAttributeNS(null, 'd', 'M 0 0 L 10 5 L 0 10 z'); 242 } 243 244 node2.appendChild(node3); 245 return node2; 246 }, 247 248 /** 249 * Updates an arrow DOM node. 250 * @param {Node} node The arrow node. 251 * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green. 252 * @param {Number} opacity 253 * @param {Number} width 254 */ 255 _setArrowAtts: function (node, color, opacity, width, parentNode) { 256 var s, d; 257 258 if (node) { 259 node.setAttributeNS(null, 'stroke', color); 260 node.setAttributeNS(null, 'stroke-opacity', opacity); 261 node.setAttributeNS(null, 'fill', color); 262 node.setAttributeNS(null, 'fill-opacity', opacity); 263 264 // This is the stroke-width of the arrow head. 265 // Should be zero to make the positioning easy 266 node.setAttributeNS(null, 'stroke-width', 0); 267 268 // The next lines are important if the strokeWidth of the line is changed. 269 s = width; 270 node.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 10 + ' ' + s * 10); 271 d = Math.max(s * 3, 10); 272 273 node.setAttributeNS(null, 'markerHeight', d); 274 node.setAttributeNS(null, 'markerWidth', d); 275 276 if (this.isIE) { 277 parentNode.parentNode.insertBefore(parentNode, parentNode); 278 } 279 } 280 281 }, 282 283 /* ******************************** * 284 * This renderer does not need to 285 * override draw/update* methods 286 * since it provides draw/update*Prim 287 * methods except for some cases like 288 * internal texts or images. 289 * ******************************** */ 290 291 /* ************************** 292 * Lines 293 * **************************/ 294 295 // documented in AbstractRenderer 296 updateTicks: function (ticks) { 297 var i, c, node, x, y, 298 tickStr = '', 299 len = ticks.ticks.length; 300 301 for (i = 0; i < len; i++) { 302 c = ticks.ticks[i]; 303 x = c[0]; 304 y = c[1]; 305 306 if (typeof x[0] === 'number' && typeof x[1] === 'number') { 307 tickStr += "M " + (x[0]) + " " + (y[0]) + " L " + (x[1]) + " " + (y[1]) + " "; 308 } 309 } 310 311 node = ticks.rendNode; 312 313 if (!Type.exists(node)) { 314 node = this.createPrim('path', ticks.id); 315 this.appendChildPrim(node, ticks.visProp.layer); 316 ticks.rendNode = node; 317 } 318 319 node.setAttributeNS(null, 'stroke', ticks.visProp.strokecolor); 320 node.setAttributeNS(null, 'stroke-opacity', ticks.visProp.strokeopacity); 321 node.setAttributeNS(null, 'stroke-width', ticks.visProp.strokewidth); 322 this.updatePathPrim(node, tickStr, ticks.board); 323 }, 324 325 /* ************************** 326 * Text related stuff 327 * **************************/ 328 329 // already documented in JXG.AbstractRenderer 330 displayCopyright: function (str, fontsize) { 331 var node = this.createPrim('text', 'licenseText'), 332 t; 333 node.setAttributeNS(null, 'x', '20px'); 334 node.setAttributeNS(null, 'y', (2 + fontsize) + 'px'); 335 node.setAttributeNS(null, "style", "font-family:Arial,Helvetica,sans-serif; font-size:" + fontsize + "px; fill:#356AA0; opacity:0.3;"); 336 t = this.container.ownerDocument.createTextNode(str); 337 node.appendChild(t); 338 this.appendChildPrim(node, 0); 339 }, 340 341 // already documented in JXG.AbstractRenderer 342 drawInternalText: function (el) { 343 var node = this.createPrim('text', el.id); 344 345 node.setAttributeNS(null, "class", el.visProp.cssclass); 346 //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox 347 348 // Preserve spaces 349 node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "space", "preserve"); 350 351 el.rendNodeText = this.container.ownerDocument.createTextNode(''); 352 node.appendChild(el.rendNodeText); 353 this.appendChildPrim(node, el.visProp.layer); 354 355 return node; 356 }, 357 358 // already documented in JXG.AbstractRenderer 359 updateInternalText: function (el) { 360 var content = el.plaintext, v; 361 362 // el.rendNode.setAttributeNS(null, "class", el.visProp.cssclass); 363 if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) { 364 365 // Horizontal 366 v = el.coords.scrCoords[1]; 367 if (el.visPropOld.left !== (el.visProp.anchorx + v)) { 368 el.rendNode.setAttributeNS(null, 'x', v + 'px'); 369 370 if (el.visProp.anchorx === 'left') { 371 el.rendNode.setAttributeNS(null, 'text-anchor', 'start'); 372 } else if (el.visProp.anchorx === 'right') { 373 el.rendNode.setAttributeNS(null, 'text-anchor', 'end'); 374 } else if (el.visProp.anchorx === 'middle') { 375 el.rendNode.setAttributeNS(null, 'text-anchor', 'middle'); 376 } 377 el.visPropOld.left = el.visProp.anchorx + v; 378 } 379 380 // Vertical 381 v = el.coords.scrCoords[2]; 382 if (el.visPropOld.top !== (el.visProp.anchory + v)) { 383 el.rendNode.setAttributeNS(null, 'y', (v + this.vOffsetText * 0.5) + 'px'); 384 385 if (el.visProp.anchory === 'bottom') { 386 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-after-edge'); 387 } else if (el.visProp.anchory === 'top') { 388 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-before-edge'); 389 } else if (el.visProp.anchory === 'middle') { 390 el.rendNode.setAttributeNS(null, 'dominant-baseline', 'middle'); 391 } 392 el.visPropOld.top = el.visProp.anchory + v; 393 } 394 } 395 if (el.htmlStr !== content) { 396 el.rendNodeText.data = content; 397 el.htmlStr = content; 398 } 399 this.transformImage(el, el.transformations); 400 }, 401 402 /** 403 * Set color and opacity of internal texts. 404 * SVG needs its own version. 405 * @private 406 * @see JXG.AbstractRenderer#updateTextStyle 407 * @see JXG.AbstractRenderer#updateInternalTextStyle 408 */ 409 updateInternalTextStyle: function (element, strokeColor, strokeOpacity) { 410 this.setObjectFillColor(element, strokeColor, strokeOpacity); 411 }, 412 413 /* ************************** 414 * Image related stuff 415 * **************************/ 416 417 // already documented in JXG.AbstractRenderer 418 drawImage: function (el) { 419 var node = this.createPrim('image', el.id); 420 421 node.setAttributeNS(null, 'preserveAspectRatio', 'none'); 422 this.appendChildPrim(node, el.visProp.layer); 423 el.rendNode = node; 424 425 this.updateImage(el); 426 }, 427 428 // already documented in JXG.AbstractRenderer 429 transformImage: function (el, t) { 430 var s, m, 431 node = el.rendNode, 432 str = "", 433 len = t.length; 434 435 if (len > 0) { 436 m = this.joinTransforms(el, t); 437 s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(','); 438 str += ' matrix(' + s + ') '; 439 node.setAttributeNS(null, 'transform', str); 440 } 441 }, 442 443 // already documented in JXG.AbstractRenderer 444 updateImageURL: function (el) { 445 var url = Type.evaluate(el.url); 446 447 el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url); 448 }, 449 450 // already documented in JXG.AbstractRenderer 451 updateImageStyle: function (el, doHighlight) { 452 var css = doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass; 453 454 el.rendNode.setAttributeNS(null, 'class', css); 455 }, 456 457 /* ************************** 458 * Render primitive objects 459 * **************************/ 460 461 // already documented in JXG.AbstractRenderer 462 appendChildPrim: function (node, level) { 463 if (!Type.exists(level)) { // trace nodes have level not set 464 level = 0; 465 } else if (level >= Options.layer.numlayers) { 466 level = Options.layer.numlayers - 1; 467 } 468 469 this.layer[level].appendChild(node); 470 471 return node; 472 }, 473 474 // already documented in JXG.AbstractRenderer 475 createPrim: function (type, id) { 476 var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type); 477 node.setAttributeNS(null, 'id', this.container.id + '_' + id); 478 node.style.position = 'absolute'; 479 if (type === 'path') { 480 node.setAttributeNS(null, 'stroke-linecap', 'round'); 481 node.setAttributeNS(null, 'stroke-linejoin', 'round'); 482 } 483 return node; 484 }, 485 486 // already documented in JXG.AbstractRenderer 487 remove: function (shape) { 488 if (Type.exists(shape) && Type.exists(shape.parentNode)) { 489 shape.parentNode.removeChild(shape); 490 } 491 }, 492 493 // already documented in JXG.AbstractRenderer 494 makeArrows: function (el) { 495 var node2; 496 497 if (el.visPropOld.firstarrow === el.visProp.firstarrow && el.visPropOld.lastarrow === el.visProp.lastarrow) { 498 if (this.isIE && el.visProp.visible && (el.visProp.firstarrow || el.visProp.lastarrow)) { 499 el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode); 500 } 501 return; 502 } 503 504 if (el.visProp.firstarrow) { 505 node2 = el.rendNodeTriangleStart; 506 if (!Type.exists(node2)) { 507 node2 = this._createArrowHead(el, 'End'); 508 this.defs.appendChild(node2); 509 el.rendNodeTriangleStart = node2; 510 el.rendNode.setAttributeNS(null, 'marker-start', 'url(#' + this.container.id + '_' + el.id + 'TriangleEnd)'); 511 } else { 512 this.defs.appendChild(node2); 513 } 514 } else { 515 node2 = el.rendNodeTriangleStart; 516 if (Type.exists(node2)) { 517 this.remove(node2); 518 } 519 } 520 if (el.visProp.lastarrow) { 521 node2 = el.rendNodeTriangleEnd; 522 if (!Type.exists(node2)) { 523 node2 = this._createArrowHead(el, 'Start'); 524 this.defs.appendChild(node2); 525 el.rendNodeTriangleEnd = node2; 526 el.rendNode.setAttributeNS(null, 'marker-end', 'url(#' + this.container.id + '_' + el.id + 'TriangleStart)'); 527 } else { 528 this.defs.appendChild(node2); 529 } 530 } else { 531 node2 = el.rendNodeTriangleEnd; 532 if (Type.exists(node2)) { 533 this.remove(node2); 534 } 535 } 536 el.visPropOld.firstarrow = el.visProp.firstarrow; 537 el.visPropOld.lastarrow = el.visProp.lastarrow; 538 }, 539 540 // already documented in JXG.AbstractRenderer 541 updateEllipsePrim: function (node, x, y, rx, ry) { 542 var huge = 1000000; 543 544 // webkit does not like huge values if the object is dashed 545 x = Math.abs(x) < huge ? x : huge * x / Math.abs(x); 546 y = Math.abs(y) < huge ? y : huge * y / Math.abs(y); 547 rx = Math.abs(rx) < huge ? rx : huge * rx / Math.abs(rx); 548 ry = Math.abs(ry) < huge ? ry : huge * ry / Math.abs(ry); 549 550 node.setAttributeNS(null, 'cx', x); 551 node.setAttributeNS(null, 'cy', y); 552 node.setAttributeNS(null, 'rx', Math.abs(rx)); 553 node.setAttributeNS(null, 'ry', Math.abs(ry)); 554 }, 555 556 // already documented in JXG.AbstractRenderer 557 updateLinePrim: function (node, p1x, p1y, p2x, p2y) { 558 var huge = 1000000; 559 560 if (!isNaN(p1x + p1y + p2x + p2y)) { 561 // webkit does not like huge values if the object is dashed 562 p1x = Math.abs(p1x) < huge ? p1x : huge * p1x / Math.abs(p1x); 563 p1y = Math.abs(p1y) < huge ? p1y : huge * p1y / Math.abs(p1y); 564 p2x = Math.abs(p2x) < huge ? p2x : huge * p2x / Math.abs(p2x); 565 p2y = Math.abs(p2y) < huge ? p2y : huge * p2y / Math.abs(p2y); 566 567 node.setAttributeNS(null, 'x1', p1x); 568 node.setAttributeNS(null, 'y1', p1y); 569 node.setAttributeNS(null, 'x2', p2x); 570 node.setAttributeNS(null, 'y2', p2y); 571 } 572 }, 573 574 // already documented in JXG.AbstractRenderer 575 updatePathPrim: function (node, pointString) { 576 if (pointString === '') { 577 pointString = 'M 0 0'; 578 } 579 node.setAttributeNS(null, 'd', pointString); 580 }, 581 582 // already documented in JXG.AbstractRenderer 583 updatePathStringPoint: function (el, size, type) { 584 var s = '', 585 scr = el.coords.scrCoords, 586 sqrt32 = size * Math.sqrt(3) * 0.5, 587 s05 = size * 0.5; 588 589 if (type === 'x') { 590 s = ' M ' + (scr[1] - size) + ' ' + (scr[2] - size) + 591 ' L ' + (scr[1] + size) + ' ' + (scr[2] + size) + 592 ' M ' + (scr[1] + size) + ' ' + (scr[2] - size) + 593 ' L ' + (scr[1] - size) + ' ' + (scr[2] + size); 594 } else if (type === '+') { 595 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 596 ' L ' + (scr[1] + size) + ' ' + (scr[2]) + 597 ' M ' + (scr[1]) + ' ' + (scr[2] - size) + 598 ' L ' + (scr[1]) + ' ' + (scr[2] + size); 599 } else if (type === '<>') { 600 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 601 ' L ' + (scr[1]) + ' ' + (scr[2] + size) + 602 ' L ' + (scr[1] + size) + ' ' + (scr[2]) + 603 ' L ' + (scr[1]) + ' ' + (scr[2] - size) + ' Z '; 604 } else if (type === '^') { 605 s = ' M ' + (scr[1]) + ' ' + (scr[2] - size) + 606 ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] + s05) + 607 ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] + s05) + 608 ' Z '; // close path 609 } else if (type === 'v') { 610 s = ' M ' + (scr[1]) + ' ' + (scr[2] + size) + 611 ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] - s05) + 612 ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] - s05) + 613 ' Z '; 614 } else if (type === '>') { 615 s = ' M ' + (scr[1] + size) + ' ' + (scr[2]) + 616 ' L ' + (scr[1] - s05) + ' ' + (scr[2] - sqrt32) + 617 ' L ' + (scr[1] - s05) + ' ' + (scr[2] + sqrt32) + 618 ' Z '; 619 } else if (type === '<') { 620 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) + 621 ' L ' + (scr[1] + s05) + ' ' + (scr[2] - sqrt32) + 622 ' L ' + (scr[1] + s05) + ' ' + (scr[2] + sqrt32) + 623 ' Z '; 624 } 625 return s; 626 }, 627 628 // already documented in JXG.AbstractRenderer 629 updatePathStringPrim: function (el) { 630 var i, scr, len, 631 symbm = ' M ', 632 symbl = ' L ', 633 symbc = ' C ', 634 nextSymb = symbm, 635 maxSize = 5000.0, 636 pStr = ''; 637 // isNotPlot = (el.visProp.curvetype !== 'plot'); 638 639 if (el.numberPoints <= 0) { 640 return ''; 641 } 642 643 len = Math.min(el.points.length, el.numberPoints); 644 645 if (el.bezierDegree === 1) { 646 /* 647 if (isNotPlot && el.visProp.rdpsmoothing) { 648 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5); 649 el.numberPoints = el.points.length; 650 } 651 */ 652 653 for (i = 0; i < len; i++) { 654 scr = el.points[i].scrCoords; 655 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 656 nextSymb = symbm; 657 } else { 658 // Chrome has problems with values being too far away. 659 scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize); 660 scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize); 661 662 // Attention: first coordinate may be inaccurate if far way 663 //pStr += [nextSymb, scr[1], ' ', scr[2]].join(''); 664 pStr += nextSymb + scr[1] + ' ' + scr[2]; // Seems to be faster now (webkit and firefox) 665 nextSymb = symbl; 666 } 667 } 668 } else if (el.bezierDegree === 3) { 669 i = 0; 670 while (i < len) { 671 scr = el.points[i].scrCoords; 672 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 673 nextSymb = symbm; 674 } else { 675 pStr += nextSymb + scr[1] + ' ' + scr[2]; 676 if (nextSymb === symbc) { 677 i += 1; 678 scr = el.points[i].scrCoords; 679 pStr += ' ' + scr[1] + ' ' + scr[2]; 680 i += 1; 681 scr = el.points[i].scrCoords; 682 pStr += ' ' + scr[1] + ' ' + scr[2]; 683 } 684 nextSymb = symbc; 685 } 686 i += 1; 687 } 688 } 689 return pStr; 690 }, 691 692 // already documented in JXG.AbstractRenderer 693 updatePathStringBezierPrim: function (el) { 694 var i, j, k, scr, lx, ly, len, 695 symbm = ' M ', 696 symbl = ' C ', 697 nextSymb = symbm, 698 maxSize = 5000.0, 699 pStr = '', 700 f = el.visProp.strokewidth, 701 isNoPlot = (el.visProp.curvetype !== 'plot'); 702 703 if (el.numberPoints <= 0) { 704 return ''; 705 } 706 707 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 708 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5); 709 } 710 711 len = Math.min(el.points.length, el.numberPoints); 712 for (j = 1; j < 3; j++) { 713 nextSymb = symbm; 714 for (i = 0; i < len; i++) { 715 scr = el.points[i].scrCoords; 716 717 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 718 nextSymb = symbm; 719 } else { 720 // Chrome has problems with values being too far away. 721 scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize); 722 scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize); 723 724 // Attention: first coordinate may be inaccurate if far way 725 if (nextSymb === symbm) { 726 //pStr += [nextSymb, scr[1], ' ', scr[2]].join(''); 727 pStr += nextSymb + scr[1] + ' ' + scr[2]; // Seems to be faster now (webkit and firefox) 728 } else { 729 k = 2 * j; 730 pStr += [nextSymb, 731 (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)), ' ', 732 (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)), ' ', 733 (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)), ' ', 734 (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)), ' ', 735 scr[1], ' ', scr[2]].join(''); 736 } 737 738 nextSymb = symbl; 739 lx = scr[1]; 740 ly = scr[2]; 741 } 742 } 743 } 744 return pStr; 745 }, 746 747 // already documented in JXG.AbstractRenderer 748 updatePolygonPrim: function (node, el) { 749 var i, 750 pStr = '', 751 scrCoords, 752 len = el.vertices.length; 753 754 node.setAttributeNS(null, 'stroke', 'none'); 755 756 for (i = 0; i < len - 1; i++) { 757 if (el.vertices[i].isReal) { 758 scrCoords = el.vertices[i].coords.scrCoords; 759 pStr = pStr + scrCoords[1] + "," + scrCoords[2]; 760 } else { 761 node.setAttributeNS(null, 'points', ''); 762 return; 763 } 764 765 if (i < len - 2) { 766 pStr += " "; 767 } 768 } 769 if (pStr.indexOf('NaN') === -1) { 770 node.setAttributeNS(null, 'points', pStr); 771 } 772 }, 773 774 // already documented in JXG.AbstractRenderer 775 updateRectPrim: function (node, x, y, w, h) { 776 node.setAttributeNS(null, 'x', x); 777 node.setAttributeNS(null, 'y', y); 778 node.setAttributeNS(null, 'width', w); 779 node.setAttributeNS(null, 'height', h); 780 }, 781 782 /* ************************** 783 * Set Attributes 784 * **************************/ 785 786 // documented in JXG.AbstractRenderer 787 setPropertyPrim: function (node, key, val) { 788 if (key === 'stroked') { 789 return; 790 } 791 node.setAttributeNS(null, key, val); 792 }, 793 794 // documented in JXG.AbstractRenderer 795 show: function (el) { 796 var node; 797 798 if (el && el.rendNode) { 799 node = el.rendNode; 800 node.setAttributeNS(null, 'display', 'inline'); 801 node.style.visibility = "inherit"; 802 } 803 }, 804 805 // documented in JXG.AbstractRenderer 806 hide: function (el) { 807 var node; 808 809 if (el && el.rendNode) { 810 node = el.rendNode; 811 node.setAttributeNS(null, 'display', 'none'); 812 node.style.visibility = "hidden"; 813 } 814 }, 815 816 // documented in JXG.AbstractRenderer 817 setBuffering: function (el, type) { 818 el.rendNode.setAttribute('buffered-rendering', type); 819 }, 820 821 // documented in JXG.AbstractRenderer 822 setDashStyle: function (el) { 823 var dashStyle = el.visProp.dash, node = el.rendNode; 824 825 if (el.visProp.dash > 0) { 826 node.setAttributeNS(null, 'stroke-dasharray', this.dashArray[dashStyle - 1]); 827 } else { 828 if (node.hasAttributeNS(null, 'stroke-dasharray')) { 829 node.removeAttributeNS(null, 'stroke-dasharray'); 830 } 831 } 832 }, 833 834 // documented in JXG.AbstractRenderer 835 setGradient: function (el) { 836 var fillNode = el.rendNode, col, op, 837 node, node2, node3, x1, x2, y1, y2; 838 839 op = Type.evaluate(el.visProp.fillopacity); 840 op = (op > 0) ? op : 0; 841 842 col = Type.evaluate(el.visProp.fillcolor); 843 844 if (el.visProp.gradient === 'linear') { 845 node = this.createPrim('linearGradient', el.id + '_gradient'); 846 x1 = '0%'; 847 x2 = '100%'; 848 y1 = '0%'; 849 y2 = '0%'; 850 851 node.setAttributeNS(null, 'x1', x1); 852 node.setAttributeNS(null, 'x2', x2); 853 node.setAttributeNS(null, 'y1', y1); 854 node.setAttributeNS(null, 'y2', y2); 855 node2 = this.createPrim('stop', el.id + '_gradient1'); 856 node2.setAttributeNS(null, 'offset', '0%'); 857 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 858 node3 = this.createPrim('stop', el.id + '_gradient2'); 859 node3.setAttributeNS(null, 'offset', '100%'); 860 node3.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity); 861 node.appendChild(node2); 862 node.appendChild(node3); 863 this.defs.appendChild(node); 864 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)'); 865 el.gradNode1 = node2; 866 el.gradNode2 = node3; 867 } else if (el.visProp.gradient === 'radial') { 868 node = this.createPrim('radialGradient', el.id + '_gradient'); 869 870 node.setAttributeNS(null, 'cx', '50%'); 871 node.setAttributeNS(null, 'cy', '50%'); 872 node.setAttributeNS(null, 'r', '50%'); 873 node.setAttributeNS(null, 'fx', el.visProp.gradientpositionx * 100 + '%'); 874 node.setAttributeNS(null, 'fy', el.visProp.gradientpositiony * 100 + '%'); 875 876 node2 = this.createPrim('stop', el.id + '_gradient1'); 877 node2.setAttributeNS(null, 'offset', '0%'); 878 node2.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity); 879 node3 = this.createPrim('stop', el.id + '_gradient2'); 880 node3.setAttributeNS(null, 'offset', '100%'); 881 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 882 883 node.appendChild(node2); 884 node.appendChild(node3); 885 this.defs.appendChild(node); 886 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)'); 887 el.gradNode1 = node2; 888 el.gradNode2 = node3; 889 } else { 890 fillNode.removeAttributeNS(null, 'style'); 891 } 892 }, 893 894 // documented in JXG.AbstractRenderer 895 updateGradient: function (el) { 896 var col, op, 897 node2 = el.gradNode1, 898 node3 = el.gradNode2; 899 900 if (!Type.exists(node2) || !Type.exists(node3)) { 901 return; 902 } 903 904 op = Type.evaluate(el.visProp.fillopacity); 905 op = (op > 0) ? op : 0; 906 907 col = Type.evaluate(el.visProp.fillcolor); 908 909 if (el.visProp.gradient === 'linear') { 910 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 911 node3.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity); 912 } else if (el.visProp.gradient === 'radial') { 913 node2.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity); 914 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); 915 } 916 }, 917 918 // documented in JXG.AbstractRenderer 919 setObjectFillColor: function (el, color, opacity) { 920 var node, c, rgbo, oo, 921 rgba = Type.evaluate(color), 922 o = Type.evaluate(opacity); 923 924 o = (o > 0) ? o : 0; 925 926 if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) { 927 return; 928 } 929 if (Type.exists(rgba) && rgba !== false) { 930 if (rgba.length !== 9) { // RGB, not RGBA 931 c = rgba; 932 oo = o; 933 } else { // True RGBA, not RGB 934 rgbo = Color.rgba2rgbo(rgba); 935 c = rgbo[0]; 936 oo = o * rgbo[1]; 937 } 938 939 node = el.rendNode; 940 941 if (c !== 'none') { // problem in firefox 17 942 node.setAttributeNS(null, 'fill', c); 943 } else { 944 oo = 0; 945 } 946 947 if (el.type === JXG.OBJECT_TYPE_IMAGE) { 948 node.setAttributeNS(null, 'opacity', oo); 949 //node.style['opacity'] = oo; // This would overwrite values set y CSS class. 950 } else { 951 node.setAttributeNS(null, 'fill-opacity', oo); 952 } 953 954 if (Type.exists(el.visProp.gradient)) { 955 this.updateGradient(el); 956 } 957 } 958 el.visPropOld.fillcolor = rgba; 959 el.visPropOld.fillopacity = o; 960 }, 961 962 // documented in JXG.AbstractRenderer 963 setObjectStrokeColor: function (el, color, opacity) { 964 var rgba = Type.evaluate(color), c, rgbo, 965 o = Type.evaluate(opacity), oo, 966 node; 967 968 o = (o > 0) ? o : 0; 969 970 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 971 return; 972 } 973 974 if (Type.exists(rgba) && rgba !== false) { 975 if (rgba.length !== 9) { // RGB, not RGBA 976 c = rgba; 977 oo = o; 978 } else { // True RGBA, not RGB 979 rgbo = Color.rgba2rgbo(rgba); 980 c = rgbo[0]; 981 oo = o * rgbo[1]; 982 } 983 984 node = el.rendNode; 985 986 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 987 if (el.visProp.display === 'html') { 988 node.style.color = c; 989 node.style.opacity = oo; 990 } else { 991 node.setAttributeNS(null, "style", "fill:" + c); 992 node.setAttributeNS(null, "style", "fill-opacity:" + oo); 993 } 994 } else { 995 node.setAttributeNS(null, 'stroke', c); 996 node.setAttributeNS(null, 'stroke-opacity', oo); 997 } 998 999 if (el.type === Const.OBJECT_TYPE_ARROW) { 1000 this._setArrowAtts(el.rendNodeTriangle, c, oo, el.visProp.strokewidth, el.rendNode); 1001 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE || el.elementClass === Const.OBJECT_CLASS_LINE) { 1002 if (el.visProp.firstarrow) { 1003 this._setArrowAtts(el.rendNodeTriangleStart, c, oo, el.visProp.strokewidth, el.rendNode); 1004 } 1005 1006 if (el.visProp.lastarrow) { 1007 this._setArrowAtts(el.rendNodeTriangleEnd, c, oo, el.visProp.strokewidth, el.rendNode); 1008 } 1009 } 1010 } 1011 1012 el.visPropOld.strokecolor = rgba; 1013 el.visPropOld.strokeopacity = o; 1014 }, 1015 1016 // documented in JXG.AbstractRenderer 1017 setObjectStrokeWidth: function (el, width) { 1018 var node, 1019 w = Type.evaluate(width); 1020 1021 if (isNaN(w) || el.visPropOld.strokewidth === w) { 1022 return; 1023 } 1024 1025 node = el.rendNode; 1026 this.setPropertyPrim(node, 'stroked', 'true'); 1027 if (Type.exists(w)) { 1028 this.setPropertyPrim(node, 'stroke-width', w + 'px'); 1029 1030 if (el.type === Const.OBJECT_TYPE_ARROW) { 1031 this._setArrowAtts(el.rendNodeTriangle, el.visProp.strokecolor, el.visProp.strokeopacity, w, el.rendNode); 1032 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE || el.elementClass === Const.OBJECT_CLASS_LINE) { 1033 if (el.visProp.firstarrow) { 1034 this._setArrowAtts(el.rendNodeTriangleStart, el.visProp.strokecolor, el.visProp.strokeopacity, w, el.rendNode); 1035 } 1036 1037 if (el.visProp.lastarrow) { 1038 this._setArrowAtts(el.rendNodeTriangleEnd, el.visProp.strokecolor, el.visProp.strokeopacity, w, el.rendNode); 1039 } 1040 } 1041 } 1042 el.visPropOld.strokewidth = w; 1043 }, 1044 1045 // documented in JXG.AbstractRenderer 1046 setShadow: function (el) { 1047 if (el.visPropOld.shadow === el.visProp.shadow) { 1048 return; 1049 } 1050 1051 if (Type.exists(el.rendNode)) { 1052 if (el.visProp.shadow) { 1053 el.rendNode.setAttributeNS(null, 'filter', 'url(#' + this.container.id + '_' + 'f1)'); 1054 } else { 1055 el.rendNode.removeAttributeNS(null, 'filter'); 1056 } 1057 } 1058 el.visPropOld.shadow = el.visProp.shadow; 1059 }, 1060 1061 /* ************************** 1062 * renderer control 1063 * **************************/ 1064 1065 // documented in JXG.AbstractRenderer 1066 suspendRedraw: function () { 1067 // It seems to be important for the Linux version of firefox 1068 //this.suspendHandle = this.svgRoot.suspendRedraw(10000); 1069 }, 1070 1071 // documented in JXG.AbstractRenderer 1072 unsuspendRedraw: function () { 1073 //this.svgRoot.unsuspendRedraw(this.suspendHandle); 1074 //this.svgRoot.unsuspendRedrawAll(); 1075 //this.svgRoot.forceRedraw(); 1076 }, 1077 1078 // documented in AbstractRenderer 1079 resize: function (w, h) { 1080 this.svgRoot.style.width = parseFloat(w) + 'px'; 1081 this.svgRoot.style.height = parseFloat(h) + 'px'; 1082 }, 1083 1084 // documented in JXG.AbstractRenderer 1085 createTouchpoints: function (n) { 1086 var i, na1, na2, node; 1087 this.touchpoints = []; 1088 for (i = 0; i < n; i++) { 1089 na1 = 'touchpoint1_' + i; 1090 node = this.createPrim('path', na1); 1091 this.appendChildPrim(node, 19); 1092 node.setAttributeNS(null, 'd', 'M 0 0'); 1093 this.touchpoints.push(node); 1094 1095 this.setPropertyPrim(node, 'stroked', 'true'); 1096 this.setPropertyPrim(node, 'stroke-width', '1px'); 1097 node.setAttributeNS(null, 'stroke', '#000000'); 1098 node.setAttributeNS(null, 'stroke-opacity', 1.0); 1099 node.setAttributeNS(null, 'display', 'none'); 1100 1101 na2 = 'touchpoint2_' + i; 1102 node = this.createPrim('ellipse', na2); 1103 this.appendChildPrim(node, 19); 1104 this.updateEllipsePrim(node, 0, 0, 0, 0); 1105 this.touchpoints.push(node); 1106 1107 this.setPropertyPrim(node, 'stroked', 'true'); 1108 this.setPropertyPrim(node, 'stroke-width', '1px'); 1109 node.setAttributeNS(null, 'stroke', '#000000'); 1110 node.setAttributeNS(null, 'stroke-opacity', 1.0); 1111 node.setAttributeNS(null, 'fill', '#ffffff'); 1112 node.setAttributeNS(null, 'fill-opacity', 0.0); 1113 1114 node.setAttributeNS(null, 'display', 'none'); 1115 } 1116 }, 1117 1118 // documented in JXG.AbstractRenderer 1119 showTouchpoint: function (i) { 1120 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1121 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'inline'); 1122 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'inline'); 1123 } 1124 }, 1125 1126 // documented in JXG.AbstractRenderer 1127 hideTouchpoint: function (i) { 1128 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1129 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'none'); 1130 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'none'); 1131 } 1132 }, 1133 1134 // documented in JXG.AbstractRenderer 1135 updateTouchpoint: function (i, pos) { 1136 var x, y, 1137 d = 37; 1138 1139 if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) { 1140 x = pos[0]; 1141 y = pos[1]; 1142 1143 this.touchpoints[2 * i].setAttributeNS(null, 'd', 'M ' + (x - d) + ' ' + y + ' ' + 1144 'L ' + (x + d) + ' ' + y + ' ' + 1145 'M ' + x + ' ' + (y - d) + ' ' + 1146 'L ' + x + ' ' + (y + d)); 1147 this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25); 1148 } 1149 } 1150 }); 1151 1152 return JXG.SVGRenderer; 1153 }); 1154