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