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