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