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