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 33 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */ 34 /*jslint nomen: true, plusplus: true, newcap:true*/ 35 36 /* depends: 37 jxg 38 renderer/abstract 39 base/constants 40 utils/type 41 utils/color 42 math/math 43 math/numerics 44 */ 45 46 define([ 47 'jxg', 'renderer/abstract', 'base/constants', 'utils/type', 'utils/color', 'math/math', 'math/numerics' 48 ], function (JXG, AbstractRenderer, Const, Type, Color, Mat, Numerics) { 49 50 "use strict"; 51 52 /** 53 * Uses VML 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 * @see JXG.AbstractRenderer 58 */ 59 JXG.VMLRenderer = function (container) { 60 this.type = 'vml'; 61 62 this.container = container; 63 this.container.style.overflow = 'hidden'; 64 if (this.container.style.position === '') { 65 this.container.style.position = 'relative'; 66 } 67 this.container.onselectstart = function () { 68 return false; 69 }; 70 71 this.resolution = 10; // Paths are drawn with a a resolution of this.resolution/pixel. 72 73 // Add VML includes and namespace 74 // Original: IE <=7 75 //container.ownerDocument.createStyleSheet().addRule("v\\:*", "behavior: url(#default#VML);"); 76 if (!Type.exists(JXG.vmlStylesheet)) { 77 container.ownerDocument.namespaces.add("jxgvml", "urn:schemas-microsoft-com:vml"); 78 JXG.vmlStylesheet = this.container.ownerDocument.createStyleSheet(); 79 JXG.vmlStylesheet.addRule(".jxgvml", "behavior:url(#default#VML)"); 80 } 81 82 try { 83 if (!container.ownerDocument.namespaces.jxgvml) { 84 container.ownerDocument.namespaces.add("jxgvml", "urn:schemas-microsoft-com:vml"); 85 } 86 87 this.createNode = function (tagName) { 88 return container.ownerDocument.createElement('<jxgvml:' + tagName + ' class="jxgvml">'); 89 }; 90 } catch (e) { 91 this.createNode = function (tagName) { 92 return container.ownerDocument.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="jxgvml">'); 93 }; 94 } 95 96 // dash styles 97 this.dashArray = ['Solid', '1 1', 'ShortDash', 'Dash', 'LongDash', 'ShortDashDot', 'LongDashDot']; 98 }; 99 100 JXG.VMLRenderer.prototype = new AbstractRenderer(); 101 102 JXG.extend(JXG.VMLRenderer.prototype, /** @lends JXG.VMLRenderer */ { 103 104 /** 105 * Sets attribute <tt>key</tt> of node <tt>node</tt> to <tt>value</tt>. 106 * @param {Node} node A DOM node. 107 * @param {String} key Name of the attribute. 108 * @param {String} val New value of the attribute. 109 * @param {Boolean} [iFlag=false] If false, the attribute's name is case insensitive. 110 */ 111 _setAttr: function (node, key, val, iFlag) { 112 try { 113 if (this.container.ownerDocument.documentMode === 8) { 114 node[key] = val; 115 } else { 116 node.setAttribute(key, val, iFlag); 117 } 118 } catch (e) { 119 JXG.debug('_setAttr:'/*node.id*/ + ' ' + key + ' ' + val + '<br>\n'); 120 } 121 }, 122 123 /* ******************************** * 124 * This renderer does not need to 125 * override draw/update* methods 126 * since it provides draw/update*Prim 127 * methods. 128 * ******************************** */ 129 130 /* ************************** 131 * Lines 132 * **************************/ 133 134 // documented in AbstractRenderer 135 updateTicks: function (ticks) { 136 var i, len, c, x, y, 137 r = this.resolution, 138 tickArr = []; 139 140 len = ticks.ticks.length; 141 for (i = 0; i < len; i++) { 142 c = ticks.ticks[i]; 143 x = c[0]; 144 y = c[1]; 145 146 if (typeof x[0] === 'number' && typeof x[1] === 'number') { 147 tickArr.push(' m ' + Math.round(r * x[0]) + ', ' + Math.round(r * y[0]) + 148 ' l ' + Math.round(r * x[1]) + ', ' + Math.round(r * y[1]) + ' '); 149 } 150 } 151 152 if (!Type.exists(ticks.rendNode)) { 153 ticks.rendNode = this.createPrim('path', ticks.id); 154 this.appendChildPrim(ticks.rendNode, ticks.visProp.layer); 155 } 156 157 this._setAttr(ticks.rendNode, 'stroked', 'true'); 158 this._setAttr(ticks.rendNode, 'strokecolor', ticks.visProp.strokecolor, 1); 159 this._setAttr(ticks.rendNode, 'strokeweight', ticks.visProp.strokewidth); 160 this._setAttr(ticks.rendNodeStroke, 'opacity', (ticks.visProp.strokeopacity * 100) + '%'); 161 this.updatePathPrim(ticks.rendNode, tickArr, ticks.board); 162 }, 163 164 /* ************************** 165 * Text related stuff 166 * **************************/ 167 168 // already documented in JXG.AbstractRenderer 169 displayCopyright: function (str, fontsize) { 170 var node, t; 171 172 node = this.createNode('textbox'); 173 node.style.position = 'absolute'; 174 this._setAttr(node, 'id', this.container.id + '_' + 'licenseText'); 175 176 node.style.left = 20; 177 node.style.top = 2; 178 node.style.fontSize = fontsize; 179 node.style.color = '#356AA0'; 180 node.style.fontFamily = 'Arial,Helvetica,sans-serif'; 181 this._setAttr(node, 'opacity', '30%'); 182 node.style.filter = 'alpha(opacity = 30)'; 183 184 t = this.container.ownerDocument.createTextNode(str); 185 node.appendChild(t); 186 this.appendChildPrim(node, 0); 187 }, 188 189 // documented in AbstractRenderer 190 drawInternalText: function (el) { 191 var node; 192 node = this.createNode('textbox'); 193 node.style.position = 'absolute'; 194 /* 195 if (this.container.ownerDocument.documentMode === 8) { // IE 8 196 node.setAttribute('class', el.visProp.cssclass); 197 } else { 198 node.setAttribute(this.container.ownerDocument.all ? 'className' : 'class', el.visProp.cssclass); 199 } 200 */ 201 el.rendNodeText = this.container.ownerDocument.createTextNode(''); 202 node.appendChild(el.rendNodeText); 203 this.appendChildPrim(node, 9); 204 return node; 205 }, 206 207 // documented in AbstractRenderer 208 updateInternalText: function (el) { 209 var v, 210 content = el.plaintext; 211 212 if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) { 213 // Horizontal 214 if (el.visProp.anchorx === 'right') { 215 v = Math.floor(el.board.canvasWidth - el.coords.scrCoords[1]); 216 } else if (el.visProp.anchorx === 'middle') { 217 v = Math.floor(el.coords.scrCoords[1] - 0.5 * el.size[0]); 218 } else { 219 v = Math.floor(el.coords.scrCoords[1]); 220 } 221 222 if (el.visPropOld.left !== (el.visProp.anchorx + v)) { 223 if (el.visProp.anchorx === 'right') { 224 el.rendNode.style.right = v + 'px'; 225 el.rendNode.style.left = 'auto'; 226 } else { 227 el.rendNode.style.left = v + 'px'; 228 el.rendNode.style.right = 'auto'; 229 } 230 el.visPropOld.left = el.visProp.anchorx + v; 231 } 232 233 // Vertical 234 if (el.visProp.anchory === 'top') { 235 v = Math.floor(el.coords.scrCoords[2] + this.vOffsetText); 236 } else if (el.visProp.anchory === 'middle') { 237 v = Math.floor(el.coords.scrCoords[2] - 0.5 * el.size[1] + this.vOffsetText); 238 } else { 239 v = Math.floor(el.board.canvasHeight - el.coords.scrCoords[2] - this.vOffsetText); 240 } 241 242 if (el.visPropOld.top !== (el.visProp.anchory + v)) { 243 if (el.visProp.anchory === 'bottom') { 244 el.rendNode.style.bottom = v + 'px'; 245 el.rendNode.style.top = 'auto'; 246 } else { 247 el.rendNode.style.top = v + 'px'; 248 el.rendNode.style.bottom = 'auto'; 249 } 250 el.visPropOld.top = el.visProp.anchory + v; 251 } 252 253 } 254 255 if (el.htmlStr !== content) { 256 el.rendNodeText.data = content; 257 el.htmlStr = content; 258 } 259 260 this.transformImage(el, el.transformations); 261 }, 262 263 /* ************************** 264 * Image related stuff 265 * **************************/ 266 267 // already documented in JXG.AbstractRenderer 268 drawImage: function (el) { 269 // IE 8: Bilder ueber data URIs werden bis 32kB unterstuetzt. 270 var node; 271 272 node = this.container.ownerDocument.createElement('img'); 273 node.style.position = 'absolute'; 274 this._setAttr(node, 'id', this.container.id + '_' + el.id); 275 276 this.container.appendChild(node); 277 this.appendChildPrim(node, el.visProp.layer); 278 279 // Adding the rotation filter. This is always filter item 0: 280 // node.filters.item(0), see transformImage 281 //node.style.filter = node.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')"; 282 node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')"; 283 el.rendNode = node; 284 this.updateImage(el); 285 }, 286 287 // already documented in JXG.AbstractRenderer 288 transformImage: function (el, t) { 289 var m, s, maxX, maxY, minX, minY, i, nt, 290 node = el.rendNode, 291 p = [], 292 len = t.length; 293 294 if (len > 0) { 295 nt = el.rendNode.style.filter.toString(); 296 if (!nt.match(/DXImageTransform/)) { 297 node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') " + nt; 298 } 299 300 m = this.joinTransforms(el, t); 301 p[0] = Mat.matVecMult(m, el.coords.scrCoords); 302 p[0][1] /= p[0][0]; 303 p[0][2] /= p[0][0]; 304 p[1] = Mat.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2]]); 305 p[1][1] /= p[1][0]; 306 p[1][2] /= p[1][0]; 307 p[2] = Mat.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2] - el.size[1]]); 308 p[2][1] /= p[2][0]; 309 p[2][2] /= p[2][0]; 310 p[3] = Mat.matVecMult(m, [1, el.coords.scrCoords[1], el.coords.scrCoords[2] - el.size[1]]); 311 p[3][1] /= p[3][0]; 312 p[3][2] /= p[3][0]; 313 maxX = p[0][1]; 314 minX = p[0][1]; 315 maxY = p[0][2]; 316 minY = p[0][2]; 317 318 for (i = 1; i < 4; i++) { 319 maxX = Math.max(maxX, p[i][1]); 320 minX = Math.min(minX, p[i][1]); 321 maxY = Math.max(maxY, p[i][2]); 322 minY = Math.min(minY, p[i][2]); 323 } 324 node.style.left = Math.floor(minX) + 'px'; 325 node.style.top = Math.floor(minY) + 'px'; 326 327 node.filters.item(0).M11 = m[1][1]; 328 node.filters.item(0).M12 = m[1][2]; 329 node.filters.item(0).M21 = m[2][1]; 330 node.filters.item(0).M22 = m[2][2]; 331 } 332 }, 333 334 // already documented in JXG.AbstractRenderer 335 updateImageURL: function (el) { 336 var url = Type.evaluate(el.url); 337 338 this._setAttr(el.rendNode, 'src', url); 339 }, 340 341 /* ************************** 342 * Render primitive objects 343 * **************************/ 344 345 // already documented in JXG.AbstractRenderer 346 appendChildPrim: function (node, level) { 347 // For trace nodes 348 if (!Type.exists(level)) { 349 level = 0; 350 } 351 352 node.style.zIndex = level; 353 this.container.appendChild(node); 354 355 return node; 356 }, 357 358 // already documented in JXG.AbstractRenderer 359 appendNodesToElement: function (element, type) { 360 if (type === 'shape' || type === 'path' || type === 'polygon') { 361 element.rendNodePath = this.getElementById(element.id + '_path'); 362 } 363 element.rendNodeFill = this.getElementById(element.id + '_fill'); 364 element.rendNodeStroke = this.getElementById(element.id + '_stroke'); 365 element.rendNodeShadow = this.getElementById(element.id + '_shadow'); 366 element.rendNode = this.getElementById(element.id); 367 }, 368 369 // already documented in JXG.AbstractRenderer 370 createPrim: function (type, id) { 371 var node, pathNode, 372 fillNode = this.createNode('fill'), 373 strokeNode = this.createNode('stroke'), 374 shadowNode = this.createNode('shadow'); 375 376 this._setAttr(fillNode, 'id', this.container.id + '_' + id + '_fill'); 377 this._setAttr(strokeNode, 'id', this.container.id + '_' + id + '_stroke'); 378 this._setAttr(shadowNode, 'id', this.container.id + '_' + id + '_shadow'); 379 380 if (type === 'circle' || type === 'ellipse') { 381 node = this.createNode('oval'); 382 node.appendChild(fillNode); 383 node.appendChild(strokeNode); 384 node.appendChild(shadowNode); 385 } else if (type === 'polygon' || type === 'path' || type === 'shape' || type === 'line') { 386 node = this.createNode('shape'); 387 node.appendChild(fillNode); 388 node.appendChild(strokeNode); 389 node.appendChild(shadowNode); 390 pathNode = this.createNode('path'); 391 this._setAttr(pathNode, 'id', this.container.id + '_' + id + '_path'); 392 node.appendChild(pathNode); 393 } else { 394 node = this.createNode(type); 395 node.appendChild(fillNode); 396 node.appendChild(strokeNode); 397 node.appendChild(shadowNode); 398 } 399 400 node.style.position = 'absolute'; 401 node.style.left = '0px'; 402 node.style.top = '0px'; 403 this._setAttr(node, 'id', this.container.id + '_' + id); 404 405 return node; 406 }, 407 408 // already documented in JXG.AbstractRenderer 409 remove: function (node) { 410 if (Type.exists(node)) { 411 node.removeNode(true); 412 } 413 }, 414 415 // already documented in JXG.AbstractRenderer 416 makeArrows: function (el) { 417 var nodeStroke; 418 419 if (el.visPropOld.firstarrow === el.visProp.firstarrow && el.visPropOld.lastarrow === el.visProp.lastarrow) { 420 return; 421 } 422 423 if (el.visProp.firstarrow) { 424 nodeStroke = el.rendNodeStroke; 425 this._setAttr(nodeStroke, 'startarrow', 'block'); 426 this._setAttr(nodeStroke, 'startarrowlength', 'long'); 427 } else { 428 nodeStroke = el.rendNodeStroke; 429 if (Type.exists(nodeStroke)) { 430 this._setAttr(nodeStroke, 'startarrow', 'none'); 431 } 432 } 433 434 if (el.visProp.lastarrow) { 435 nodeStroke = el.rendNodeStroke; 436 this._setAttr(nodeStroke, 'id', this.container.id + '_' + el.id + "stroke"); 437 this._setAttr(nodeStroke, 'endarrow', 'block'); 438 this._setAttr(nodeStroke, 'endarrowlength', 'long'); 439 } else { 440 nodeStroke = el.rendNodeStroke; 441 if (Type.exists(nodeStroke)) { 442 this._setAttr(nodeStroke, 'endarrow', 'none'); 443 } 444 } 445 el.visPropOld.firstarrow = el.visProp.firstarrow; 446 el.visPropOld.lastarrow = el.visProp.lastarrow; 447 }, 448 449 // already documented in JXG.AbstractRenderer 450 updateEllipsePrim: function (node, x, y, rx, ry) { 451 node.style.left = Math.floor(x - rx) + 'px'; 452 node.style.top = Math.floor(y - ry) + 'px'; 453 node.style.width = Math.floor(Math.abs(rx) * 2) + 'px'; 454 node.style.height = Math.floor(Math.abs(ry) * 2) + 'px'; 455 }, 456 457 // already documented in JXG.AbstractRenderer 458 updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { 459 var s, r = this.resolution; 460 461 if (!isNaN(p1x + p1y + p2x + p2y)) { 462 s = ['m ', Math.floor(r * p1x), ', ', Math.floor(r * p1y), ' l ', Math.floor(r * p2x), ', ', Math.floor(r * p2y)]; 463 this.updatePathPrim(node, s, board); 464 } 465 }, 466 467 // already documented in JXG.AbstractRenderer 468 updatePathPrim: function (node, pointString, board) { 469 var x = board.canvasWidth, 470 y = board.canvasHeight; 471 if (pointString.length <= 0) { 472 pointString = ['m 0,0']; 473 } 474 node.style.width = x; 475 node.style.height = y; 476 this._setAttr(node, 'coordsize', [Math.floor(this.resolution * x), Math.floor(this.resolution * y)].join(',')); 477 this._setAttr(node, 'path', pointString.join("")); 478 }, 479 480 // already documented in JXG.AbstractRenderer 481 updatePathStringPoint: function (el, size, type) { 482 var s = [], 483 mround = Math.round, 484 scr = el.coords.scrCoords, 485 sqrt32 = size * Math.sqrt(3) * 0.5, 486 s05 = size * 0.5, 487 r = this.resolution; 488 489 if (type === 'x') { 490 s.push([ 491 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] - size)), 492 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] + size)), 493 ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] - size)), 494 ' l ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] + size)) 495 ].join('')); 496 } else if (type === '+') { 497 s.push([ 498 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])), 499 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])), 500 ' m ', mround(r * (scr[1])), ', ', mround(r * (scr[2] - size)), 501 ' l ', mround(r * (scr[1])), ', ', mround(r * (scr[2] + size)) 502 ].join('')); 503 } else if (type === '<>') { 504 505 s.push([ 506 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])), 507 ' l ', mround(r * (scr[1])), ', ', mround(r * (scr[2] + size)), 508 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])), 509 ' l ', mround(r * (scr[1])), ', ', mround(r * (scr[2] - size)), 510 ' x e ' 511 ].join('')); 512 } else if (type === '^') { 513 s.push([ 514 ' m ', mround(r * (scr[1])), ', ', mround(r * (scr[2] - size)), 515 ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] + s05)), 516 ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] + s05)), 517 ' x e ' 518 ].join('')); 519 } else if (type === 'v') { 520 s.push([ 521 ' m ', mround(r * (scr[1])), ', ', mround(r * (scr[2] + size)), 522 ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] - s05)), 523 ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] - s05)), 524 ' x e ' 525 ].join('')); 526 } else if (type === '>') { 527 s.push([ 528 ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])), 529 ' l ', mround(r * (scr[1] - s05)), ', ', mround(r * (scr[2] - sqrt32)), 530 ' l ', mround(r * (scr[1] - s05)), ', ', mround(r * (scr[2] + sqrt32)), 531 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])) 532 ].join('')); 533 } else if (type === '<') { 534 s.push([ 535 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])), 536 ' l ', mround(r * (scr[1] + s05)), ', ', mround(r * (scr[2] - sqrt32)), 537 ' l ', mround(r * (scr[1] + s05)), ', ', mround(r * (scr[2] + sqrt32)), 538 ' x e ' 539 ].join('')); 540 } 541 542 return s; 543 }, 544 545 // already documented in JXG.AbstractRenderer 546 updatePathStringPrim: function (el) { 547 var i, scr, 548 pStr = [], 549 r = this.resolution, 550 mround = Math.round, 551 symbm = ' m ', 552 symbl = ' l ', 553 symbc = ' c ', 554 nextSymb = symbm, 555 isNotPlot = (el.visProp.curvetype !== 'plot'), 556 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html 557 558 if (el.numberPoints <= 0) { 559 return ''; 560 } 561 len = Math.min(len, el.points.length); 562 563 if (el.bezierDegree === 1) { 564 if (isNotPlot && el.board.options.curve.RDPsmoothing) { 565 el.points = Numerics.RamerDouglasPeuker(el.points, 1.0); 566 } 567 568 for (i = 0; i < len; i++) { 569 scr = el.points[i].scrCoords; 570 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 571 nextSymb = symbm; 572 } else { 573 // IE has problems with values being too far away. 574 if (scr[1] > 20000.0) { 575 scr[1] = 20000.0; 576 } else if (scr[1] < -20000.0) { 577 scr[1] = -20000.0; 578 } 579 580 if (scr[2] > 20000.0) { 581 scr[2] = 20000.0; 582 } else if (scr[2] < -20000.0) { 583 scr[2] = -20000.0; 584 } 585 586 pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join('')); 587 nextSymb = symbl; 588 } 589 } 590 } else if (el.bezierDegree === 3) { 591 i = 0; 592 while (i < len) { 593 scr = el.points[i].scrCoords; 594 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 595 nextSymb = symbm; 596 } else { 597 pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join('')); 598 if (nextSymb === symbc) { 599 i += 1; 600 scr = el.points[i].scrCoords; 601 pStr.push([' ', mround(r * scr[1]), ', ', mround(r * scr[2])].join('')); 602 i += 1; 603 scr = el.points[i].scrCoords; 604 pStr.push([' ', mround(r * scr[1]), ', ', mround(r * scr[2])].join('')); 605 } 606 nextSymb = symbc; 607 } 608 i += 1; 609 } 610 } 611 pStr.push(' e'); 612 return pStr; 613 }, 614 615 // already documented in JXG.AbstractRenderer 616 updatePathStringBezierPrim: function (el) { 617 var i, j, k, scr, lx, ly, 618 pStr = [], 619 f = el.visProp.strokewidth, 620 r = this.resolution, 621 mround = Math.round, 622 symbm = ' m ', 623 symbl = ' c ', 624 nextSymb = symbm, 625 isNoPlot = (el.visProp.curvetype !== 'plot'), 626 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html 627 628 if (el.numberPoints <= 0) { 629 return ''; 630 } 631 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 632 el.points = Numerics.RamerDouglasPeuker(el.points, 1.0); 633 } 634 len = Math.min(len, el.points.length); 635 636 for (j = 1; j < 3; j++) { 637 nextSymb = symbm; 638 for (i = 0; i < len; i++) { 639 scr = el.points[i].scrCoords; 640 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 641 nextSymb = symbm; 642 } else { 643 // IE has problems with values being too far away. 644 if (scr[1] > 20000.0) { 645 scr[1] = 20000.0; 646 } else if (scr[1] < -20000.0) { 647 scr[1] = -20000.0; 648 } 649 650 if (scr[2] > 20000.0) { 651 scr[2] = 20000.0; 652 } else if (scr[2] < -20000.0) { 653 scr[2] = -20000.0; 654 } 655 656 if (nextSymb === symbm) { 657 pStr.push([nextSymb, 658 mround(r * (scr[1])), ' ', mround(r * (scr[2]))].join('')); 659 } else { 660 k = 2 * j; 661 pStr.push([nextSymb, 662 mround(r * (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j))), ' ', 663 mround(r * (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j))), ' ', 664 mround(r * (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j))), ' ', 665 mround(r * (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j))), ' ', 666 mround(r * scr[1]), ' ', 667 mround(r * scr[2])].join('')); 668 } 669 nextSymb = symbl; 670 lx = scr[1]; 671 ly = scr[2]; 672 } 673 } 674 } 675 pStr.push(' e'); 676 return pStr; 677 }, 678 679 // already documented in JXG.AbstractRenderer 680 updatePolygonPrim: function (node, el) { 681 var i, 682 len = el.vertices.length, 683 r = this.resolution, 684 scr, 685 pStr = []; 686 687 this._setAttr(node, 'stroked', 'false'); 688 scr = el.vertices[0].coords.scrCoords; 689 690 if (isNaN(scr[1] + scr[2])) { 691 return; 692 } 693 694 pStr.push(["m ", Math.floor(r * scr[1]), ",", Math.floor(r * scr[2]), " l "].join('')); 695 696 for (i = 1; i < len - 1; i++) { 697 if (el.vertices[i].isReal) { 698 scr = el.vertices[i].coords.scrCoords; 699 700 if (isNaN(scr[1] + scr[2])) { 701 return; 702 } 703 704 pStr.push(Math.floor(r * scr[1]) + "," + Math.floor(r * scr[2])); 705 } else { 706 this.updatePathPrim(node, '', el.board); 707 return; 708 } 709 if (i < len - 2) { 710 pStr.push(", "); 711 } 712 } 713 pStr.push(" x e"); 714 this.updatePathPrim(node, pStr, el.board); 715 }, 716 717 // already documented in JXG.AbstractRenderer 718 updateRectPrim: function (node, x, y, w, h) { 719 node.style.left = Math.floor(x) + 'px'; 720 node.style.top = Math.floor(y) + 'px'; 721 722 if (w >= 0) { 723 node.style.width = w + 'px'; 724 } 725 726 if (h >= 0) { 727 node.style.height = h + 'px'; 728 } 729 }, 730 731 /* ************************** 732 * Set Attributes 733 * **************************/ 734 735 // already documented in JXG.AbstractRenderer 736 setPropertyPrim: function (node, key, val) { 737 var keyVml = '', 738 v; 739 740 switch (key) { 741 case 'stroke': 742 keyVml = 'strokecolor'; 743 break; 744 case 'stroke-width': 745 keyVml = 'strokeweight'; 746 break; 747 case 'stroke-dasharray': 748 keyVml = 'dashstyle'; 749 break; 750 } 751 752 if (keyVml !== '') { 753 v = Type.evaluate(val); 754 this._setAttr(node, keyVml, v); 755 } 756 }, 757 758 // already documented in JXG.AbstractRenderer 759 show: function (el) { 760 if (el && el.rendNode) { 761 el.rendNode.style.visibility = "inherit"; 762 } 763 }, 764 765 // already documented in JXG.AbstractRenderer 766 hide: function (el) { 767 if (el && el.rendNode) { 768 el.rendNode.style.visibility = "hidden"; 769 } 770 }, 771 772 // already documented in JXG.AbstractRenderer 773 setDashStyle: function (el, visProp) { 774 var node; 775 if (visProp.dash >= 0) { 776 node = el.rendNodeStroke; 777 this._setAttr(node, 'dashstyle', this.dashArray[visProp.dash]); 778 } 779 }, 780 781 // already documented in JXG.AbstractRenderer 782 setGradient: function (el) { 783 var nodeFill = el.rendNodeFill; 784 785 if (el.visProp.gradient === 'linear') { 786 this._setAttr(nodeFill, 'type', 'gradient'); 787 this._setAttr(nodeFill, 'color2', el.visProp.gradientsecondcolor); 788 this._setAttr(nodeFill, 'opacity2', el.visProp.gradientsecondopacity); 789 this._setAttr(nodeFill, 'angle', el.visProp.gradientangle); 790 } else if (el.visProp.gradient === 'radial') { 791 this._setAttr(nodeFill, 'type', 'gradientradial'); 792 this._setAttr(nodeFill, 'color2', el.visProp.gradientsecondcolor); 793 this._setAttr(nodeFill, 'opacity2', el.visProp.gradientsecondopacity); 794 this._setAttr(nodeFill, 'focusposition', el.visProp.gradientpositionx * 100 + '%,' + el.visProp.gradientpositiony * 100 + '%'); 795 this._setAttr(nodeFill, 'focussize', '0,0'); 796 } else { 797 this._setAttr(nodeFill, 'type', 'solid'); 798 } 799 }, 800 801 // already documented in JXG.AbstractRenderer 802 setObjectFillColor: function (el, color, opacity) { 803 var rgba = Type.evaluate(color), c, rgbo, 804 o = Type.evaluate(opacity), oo, 805 node = el.rendNode, 806 t; 807 808 o = (o > 0) ? o : 0; 809 810 if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) { 811 return; 812 } 813 814 if (Type.exists(rgba) && rgba !== false) { 815 // RGB, not RGBA 816 if (rgba.length !== 9) { 817 c = rgba; 818 oo = o; 819 // True RGBA, not RGB 820 } else { 821 rgbo = Color.rgba2rgbo(rgba); 822 c = rgbo[0]; 823 oo = o * rgbo[1]; 824 } 825 if (c === 'none' || c === false) { 826 this._setAttr(el.rendNode, 'filled', 'false'); 827 } else { 828 this._setAttr(el.rendNode, 'filled', 'true'); 829 this._setAttr(el.rendNode, 'fillcolor', c); 830 831 if (Type.exists(oo) && el.rendNodeFill) { 832 this._setAttr(el.rendNodeFill, 'opacity', (oo * 100) + '%'); 833 } 834 } 835 if (el.type === Const.OBJECT_TYPE_IMAGE) { 836 t = el.rendNode.style.filter.toString(); 837 if (t.match(/alpha/)) { 838 el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')'); 839 } else { 840 el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) + ')'; 841 } 842 } 843 } 844 el.visPropOld.fillcolor = rgba; 845 el.visPropOld.fillopacity = o; 846 }, 847 848 // already documented in JXG.AbstractRenderer 849 setObjectStrokeColor: function (el, color, opacity) { 850 var rgba = Type.evaluate(color), c, rgbo, 851 o = Type.evaluate(opacity), oo, 852 node = el.rendNode, nodeStroke; 853 854 o = (o > 0) ? o : 0; 855 856 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 857 return; 858 } 859 860 // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor 861 862 if (Type.exists(rgba) && rgba !== false) { 863 // RGB, not RGBA 864 if (rgba.length !== 9) { 865 c = rgba; 866 oo = o; 867 // True RGBA, not RGB 868 } else { 869 rgbo = color.rgba2rgbo(rgba); 870 c = rgbo[0]; 871 oo = o * rgbo[1]; 872 } 873 if (el.type === Const.OBJECT_TYPE_TEXT) { 874 oo = Math.round(oo * 100); 875 node.style.filter = ' alpha(opacity = ' + oo + ')'; 876 node.style.color = c; 877 } else { 878 if (c !== false) { 879 this._setAttr(node, 'stroked', 'true'); 880 this._setAttr(node, 'strokecolor', c); 881 } 882 883 nodeStroke = el.rendNodeStroke; 884 if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) { 885 this._setAttr(nodeStroke, 'opacity', (oo * 100) + '%'); 886 } 887 } 888 } 889 el.visPropOld.strokecolor = rgba; 890 el.visPropOld.strokeopacity = o; 891 }, 892 893 // already documented in JXG.AbstractRenderer 894 setObjectStrokeWidth: function (el, width) { 895 var w = Type.evaluate(width), 896 node; 897 898 if (isNaN(w) || el.visPropOld.strokewidth === w) { 899 return; 900 } 901 902 node = el.rendNode; 903 this.setPropertyPrim(node, 'stroked', 'true'); 904 905 if (Type.exists(w)) { 906 907 this.setPropertyPrim(node, 'stroke-width', w); 908 if (w === 0 && Type.exists(el.rendNodeStroke)) { 909 this._setAttr(node, 'stroked', 'false'); 910 } 911 } 912 913 el.visPropOld.strokewidth = w; 914 915 }, 916 917 // already documented in JXG.AbstractRenderer 918 setShadow: function (el) { 919 var nodeShadow = el.rendNodeShadow; 920 921 if (!nodeShadow || el.visPropOld.shadow === el.visProp.shadow) { 922 return; 923 } 924 925 if (el.visProp.shadow) { 926 this._setAttr(nodeShadow, 'On', 'True'); 927 this._setAttr(nodeShadow, 'Offset', '3pt,3pt'); 928 this._setAttr(nodeShadow, 'Opacity', '60%'); 929 this._setAttr(nodeShadow, 'Color', '#aaaaaa'); 930 } else { 931 this._setAttr(nodeShadow, 'On', 'False'); 932 } 933 934 el.visPropOld.shadow = el.visProp.shadow; 935 }, 936 937 /* ************************** 938 * renderer control 939 * **************************/ 940 941 // already documented in JXG.AbstractRenderer 942 suspendRedraw: function () { 943 this.container.style.display = 'none'; 944 }, 945 946 // already documented in JXG.AbstractRenderer 947 unsuspendRedraw: function () { 948 this.container.style.display = ''; 949 } 950 }); 951 952 return JXG.VMLRenderer; 953 }); 954