1 /* 2 Copyright 2008-2015 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 = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand', enabled = false) progid:DXImageTransform.Microsoft.Alpha(opacity = 30, enabled = true)"; 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 node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand', enabled = false) progid:DXImageTransform.Microsoft.Alpha(opacity = 100, enabled = false)"; 205 206 return node; 207 }, 208 209 // documented in AbstractRenderer 210 updateInternalText: function (el) { 211 var v, content = el.plaintext, 212 m = this.joinTransforms(el, el.transformations), 213 offset = [0, 0], 214 maxX, maxY, minX, minY, i, 215 node = el.rendNode, 216 p = []; 217 218 if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) { 219 // Horizontal 220 if (el.visProp.anchorx === 'right') { 221 offset[0] = 1; 222 } else if (el.visProp.anchorx === 'middle') { 223 offset[0] = 0.5; 224 } // default (el.visProp.anchorx === 'left') offset[0] = 0; 225 226 // Vertical 227 if (el.visProp.anchory === 'bottom') { 228 offset[1] = 1; 229 } else if (el.visProp.anchory === 'middle') { 230 offset[1] = 0.5; 231 } // default (el.visProp.anchory === 'top') offset[1] = 0; 232 233 // Compute maxX, maxY, minX, minY 234 p[0] = Mat.matVecMult(m, [1, 235 el.coords.scrCoords[1] - offset[0] * el.size[0], 236 el.coords.scrCoords[2] + (1 - offset[1]) * el.size[1] + this.vOffsetText]); 237 p[0][1] /= p[0][0]; 238 p[0][2] /= p[0][0]; 239 p[1] = Mat.matVecMult(m, [1, 240 el.coords.scrCoords[1] + (1 - offset[0]) * el.size[0], 241 el.coords.scrCoords[2] + (1 - offset[1]) * el.size[1] + this.vOffsetText]); 242 p[1][1] /= p[1][0]; 243 p[1][2] /= p[1][0]; 244 p[2] = Mat.matVecMult(m, [1, 245 el.coords.scrCoords[1] + (1 - offset[0]) * el.size[0], 246 el.coords.scrCoords[2] - offset[1] * el.size[1] + this.vOffsetText]); 247 p[2][1] /= p[2][0]; 248 p[2][2] /= p[2][0]; 249 p[3] = Mat.matVecMult(m, [1, 250 el.coords.scrCoords[1] - offset[0] * el.size[0], 251 el.coords.scrCoords[2] - offset[1] * el.size[1] + this.vOffsetText]); 252 p[3][1] /= p[3][0]; 253 p[3][2] /= p[3][0]; 254 maxX = p[0][1]; 255 minX = p[0][1]; 256 maxY = p[0][2]; 257 minY = p[0][2]; 258 259 for (i = 1; i < 4; i++) { 260 maxX = Math.max(maxX, p[i][1]); 261 minX = Math.min(minX, p[i][1]); 262 maxY = Math.max(maxY, p[i][2]); 263 minY = Math.min(minY, p[i][2]); 264 } 265 266 // Horizontal 267 v = offset[0] === 1 ? Math.floor(el.board.canvasWidth - maxX) : Math.floor(minX); 268 if (el.visPropOld.left !== (el.visProp.anchorx + v)) { 269 if (offset[0] === 1) { 270 el.rendNode.style.right = v + 'px'; 271 el.rendNode.style.left = 'auto'; 272 } else { 273 el.rendNode.style.left = v + 'px'; 274 el.rendNode.style.right = 'auto'; 275 } 276 el.visPropOld.left = el.visProp.anchorx + v; 277 } 278 279 // Vertical 280 v = offset[1] === 1 ? Math.floor(el.board.canvasHeight - maxY) : Math.floor(minY); 281 if (el.visPropOld.top !== (el.visProp.anchory + v)) { 282 if (offset[1] === 1) { 283 el.rendNode.style.bottom = v + 'px'; 284 el.rendNode.style.top = 'auto'; 285 } else { 286 el.rendNode.style.top = v + 'px'; 287 el.rendNode.style.bottom = 'auto'; 288 } 289 el.visPropOld.top = el.visProp.anchory + v; 290 } 291 292 } 293 294 if (el.htmlStr !== content) { 295 el.rendNodeText.data = content; 296 el.htmlStr = content; 297 } 298 299 //this.transformImage(el, el.transformations); 300 node.filters.item(0).M11 = m[1][1]; 301 node.filters.item(0).M12 = m[1][2]; 302 node.filters.item(0).M21 = m[2][1]; 303 node.filters.item(0).M22 = m[2][2]; 304 node.filters.item(0).enabled = true; 305 }, 306 307 /* ************************** 308 * Image related stuff 309 * **************************/ 310 311 // already documented in JXG.AbstractRenderer 312 drawImage: function (el) { 313 // IE 8: Bilder ueber data URIs werden bis 32kB unterstuetzt. 314 var node; 315 316 node = this.container.ownerDocument.createElement('img'); 317 node.style.position = 'absolute'; 318 this._setAttr(node, 'id', this.container.id + '_' + el.id); 319 320 this.container.appendChild(node); 321 this.appendChildPrim(node, el.visProp.layer); 322 323 // Adding the rotation filter. This is always filter item 0: 324 // node.filters.item(0), see transformImage 325 // Also add the alpha filter. This is always filter item 1 326 // node.filters.item(1), see setObjectFillColor and setObjectSTrokeColor 327 //node.style.filter = node.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')"; 328 node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') progid:DXImageTransform.Microsoft.Alpha(opacity = 100, enabled = false)"; 329 el.rendNode = node; 330 this.updateImage(el); 331 }, 332 333 // already documented in JXG.AbstractRenderer 334 transformImage: function (el, t) { 335 var m, s, maxX, maxY, minX, minY, i, nt, 336 node = el.rendNode, 337 p = [], 338 len = t.length; 339 340 if (len > 0) { 341 /* 342 nt = el.rendNode.style.filter.toString(); 343 if (!nt.match(/DXImageTransform/)) { 344 node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') " + nt; 345 } 346 */ 347 348 m = this.joinTransforms(el, t); 349 p[0] = Mat.matVecMult(m, el.coords.scrCoords); 350 p[0][1] /= p[0][0]; 351 p[0][2] /= p[0][0]; 352 p[1] = Mat.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2]]); 353 p[1][1] /= p[1][0]; 354 p[1][2] /= p[1][0]; 355 p[2] = Mat.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2] - el.size[1]]); 356 p[2][1] /= p[2][0]; 357 p[2][2] /= p[2][0]; 358 p[3] = Mat.matVecMult(m, [1, el.coords.scrCoords[1], el.coords.scrCoords[2] - el.size[1]]); 359 p[3][1] /= p[3][0]; 360 p[3][2] /= p[3][0]; 361 maxX = p[0][1]; 362 minX = p[0][1]; 363 maxY = p[0][2]; 364 minY = p[0][2]; 365 366 for (i = 1; i < 4; i++) { 367 maxX = Math.max(maxX, p[i][1]); 368 minX = Math.min(minX, p[i][1]); 369 maxY = Math.max(maxY, p[i][2]); 370 minY = Math.min(minY, p[i][2]); 371 } 372 node.style.left = Math.floor(minX) + 'px'; 373 node.style.top = Math.floor(minY) + 'px'; 374 375 node.filters.item(0).M11 = m[1][1]; 376 node.filters.item(0).M12 = m[1][2]; 377 node.filters.item(0).M21 = m[2][1]; 378 node.filters.item(0).M22 = m[2][2]; 379 node.filters.item(0).enabled = true; 380 } 381 }, 382 383 // already documented in JXG.AbstractRenderer 384 updateImageURL: function (el) { 385 var url = Type.evaluate(el.url); 386 387 this._setAttr(el.rendNode, 'src', url); 388 }, 389 390 /* ************************** 391 * Render primitive objects 392 * **************************/ 393 394 // already documented in JXG.AbstractRenderer 395 appendChildPrim: function (node, level) { 396 // For trace nodes 397 if (!Type.exists(level)) { 398 level = 0; 399 } 400 401 node.style.zIndex = level; 402 this.container.appendChild(node); 403 404 return node; 405 }, 406 407 // already documented in JXG.AbstractRenderer 408 appendNodesToElement: function (element, type) { 409 if (type === 'shape' || type === 'path' || type === 'polygon') { 410 element.rendNodePath = this.getElementById(element.id + '_path'); 411 } 412 element.rendNodeFill = this.getElementById(element.id + '_fill'); 413 element.rendNodeStroke = this.getElementById(element.id + '_stroke'); 414 element.rendNodeShadow = this.getElementById(element.id + '_shadow'); 415 element.rendNode = this.getElementById(element.id); 416 }, 417 418 // already documented in JXG.AbstractRenderer 419 createPrim: function (type, id) { 420 var node, pathNode, 421 fillNode = this.createNode('fill'), 422 strokeNode = this.createNode('stroke'), 423 shadowNode = this.createNode('shadow'); 424 425 this._setAttr(fillNode, 'id', this.container.id + '_' + id + '_fill'); 426 this._setAttr(strokeNode, 'id', this.container.id + '_' + id + '_stroke'); 427 this._setAttr(shadowNode, 'id', this.container.id + '_' + id + '_shadow'); 428 429 if (type === 'circle' || type === 'ellipse') { 430 node = this.createNode('oval'); 431 node.appendChild(fillNode); 432 node.appendChild(strokeNode); 433 node.appendChild(shadowNode); 434 } else if (type === 'polygon' || type === 'path' || type === 'shape' || type === 'line') { 435 node = this.createNode('shape'); 436 node.appendChild(fillNode); 437 node.appendChild(strokeNode); 438 node.appendChild(shadowNode); 439 pathNode = this.createNode('path'); 440 this._setAttr(pathNode, 'id', this.container.id + '_' + id + '_path'); 441 node.appendChild(pathNode); 442 } else { 443 node = this.createNode(type); 444 node.appendChild(fillNode); 445 node.appendChild(strokeNode); 446 node.appendChild(shadowNode); 447 } 448 449 node.style.position = 'absolute'; 450 node.style.left = '0px'; 451 node.style.top = '0px'; 452 this._setAttr(node, 'id', this.container.id + '_' + id); 453 454 return node; 455 }, 456 457 // already documented in JXG.AbstractRenderer 458 remove: function (node) { 459 if (Type.exists(node)) { 460 node.removeNode(true); 461 } 462 }, 463 464 // already documented in JXG.AbstractRenderer 465 makeArrows: function (el) { 466 var nodeStroke; 467 468 if (el.visPropOld.firstarrow === el.visProp.firstarrow && el.visPropOld.lastarrow === el.visProp.lastarrow) { 469 return; 470 } 471 472 if (el.visProp.firstarrow) { 473 nodeStroke = el.rendNodeStroke; 474 this._setAttr(nodeStroke, 'startarrow', 'block'); 475 this._setAttr(nodeStroke, 'startarrowlength', 'long'); 476 } else { 477 nodeStroke = el.rendNodeStroke; 478 if (Type.exists(nodeStroke)) { 479 this._setAttr(nodeStroke, 'startarrow', 'none'); 480 } 481 } 482 483 if (el.visProp.lastarrow) { 484 nodeStroke = el.rendNodeStroke; 485 this._setAttr(nodeStroke, 'id', this.container.id + '_' + el.id + "stroke"); 486 this._setAttr(nodeStroke, 'endarrow', 'block'); 487 this._setAttr(nodeStroke, 'endarrowlength', 'long'); 488 } else { 489 nodeStroke = el.rendNodeStroke; 490 if (Type.exists(nodeStroke)) { 491 this._setAttr(nodeStroke, 'endarrow', 'none'); 492 } 493 } 494 el.visPropOld.firstarrow = el.visProp.firstarrow; 495 el.visPropOld.lastarrow = el.visProp.lastarrow; 496 }, 497 498 // already documented in JXG.AbstractRenderer 499 updateEllipsePrim: function (node, x, y, rx, ry) { 500 node.style.left = Math.floor(x - rx) + 'px'; 501 node.style.top = Math.floor(y - ry) + 'px'; 502 node.style.width = Math.floor(Math.abs(rx) * 2) + 'px'; 503 node.style.height = Math.floor(Math.abs(ry) * 2) + 'px'; 504 }, 505 506 // already documented in JXG.AbstractRenderer 507 updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { 508 var s, r = this.resolution; 509 510 if (!isNaN(p1x + p1y + p2x + p2y)) { 511 s = ['m ', Math.floor(r * p1x), ', ', Math.floor(r * p1y), ' l ', Math.floor(r * p2x), ', ', Math.floor(r * p2y)]; 512 this.updatePathPrim(node, s, board); 513 } 514 }, 515 516 // already documented in JXG.AbstractRenderer 517 updatePathPrim: function (node, pointString, board) { 518 var x = board.canvasWidth, 519 y = board.canvasHeight; 520 if (pointString.length <= 0) { 521 pointString = ['m 0,0']; 522 } 523 node.style.width = x; 524 node.style.height = y; 525 this._setAttr(node, 'coordsize', [Math.floor(this.resolution * x), Math.floor(this.resolution * y)].join(',')); 526 this._setAttr(node, 'path', pointString.join("")); 527 }, 528 529 // already documented in JXG.AbstractRenderer 530 updatePathStringPoint: function (el, size, type) { 531 var s = [], 532 mround = Math.round, 533 scr = el.coords.scrCoords, 534 sqrt32 = size * Math.sqrt(3) * 0.5, 535 s05 = size * 0.5, 536 r = this.resolution; 537 538 if (type === 'x') { 539 s.push([ 540 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] - size)), 541 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] + size)), 542 ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] - size)), 543 ' l ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] + size)) 544 ].join('')); 545 } else if (type === '+') { 546 s.push([ 547 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])), 548 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])), 549 ' m ', mround(r * (scr[1])), ', ', mround(r * (scr[2] - size)), 550 ' l ', mround(r * (scr[1])), ', ', mround(r * (scr[2] + size)) 551 ].join('')); 552 } else if (type === '<>') { 553 554 s.push([ 555 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])), 556 ' l ', mround(r * (scr[1])), ', ', mround(r * (scr[2] + size)), 557 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])), 558 ' l ', mround(r * (scr[1])), ', ', mround(r * (scr[2] - size)), 559 ' x e ' 560 ].join('')); 561 } else if (type === '^') { 562 s.push([ 563 ' m ', mround(r * (scr[1])), ', ', mround(r * (scr[2] - size)), 564 ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] + s05)), 565 ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] + s05)), 566 ' x e ' 567 ].join('')); 568 } else if (type === 'v') { 569 s.push([ 570 ' m ', mround(r * (scr[1])), ', ', mround(r * (scr[2] + size)), 571 ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] - s05)), 572 ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] - s05)), 573 ' x e ' 574 ].join('')); 575 } else if (type === '>') { 576 s.push([ 577 ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])), 578 ' l ', mround(r * (scr[1] - s05)), ', ', mround(r * (scr[2] - sqrt32)), 579 ' l ', mround(r * (scr[1] - s05)), ', ', mround(r * (scr[2] + sqrt32)), 580 ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])) 581 ].join('')); 582 } else if (type === '<') { 583 s.push([ 584 ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])), 585 ' l ', mround(r * (scr[1] + s05)), ', ', mround(r * (scr[2] - sqrt32)), 586 ' l ', mround(r * (scr[1] + s05)), ', ', mround(r * (scr[2] + sqrt32)), 587 ' x e ' 588 ].join('')); 589 } 590 591 return s; 592 }, 593 594 // already documented in JXG.AbstractRenderer 595 updatePathStringPrim: function (el) { 596 var i, scr, 597 pStr = [], 598 r = this.resolution, 599 mround = Math.round, 600 symbm = ' m ', 601 symbl = ' l ', 602 symbc = ' c ', 603 nextSymb = symbm, 604 // isNotPlot = (el.visProp.curvetype !== 'plot'), 605 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html 606 607 if (el.numberPoints <= 0) { 608 return ''; 609 } 610 len = Math.min(len, el.points.length); 611 612 if (el.bezierDegree === 1) { 613 /* 614 if (isNotPlot && el.board.options.curve.RDPsmoothing) { 615 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0); 616 } 617 */ 618 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 // IE has problems with values being too far away. 625 if (scr[1] > 20000.0) { 626 scr[1] = 20000.0; 627 } else if (scr[1] < -20000.0) { 628 scr[1] = -20000.0; 629 } 630 631 if (scr[2] > 20000.0) { 632 scr[2] = 20000.0; 633 } else if (scr[2] < -20000.0) { 634 scr[2] = -20000.0; 635 } 636 637 pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join('')); 638 nextSymb = symbl; 639 } 640 } 641 } else if (el.bezierDegree === 3) { 642 i = 0; 643 while (i < len) { 644 scr = el.points[i].scrCoords; 645 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 646 nextSymb = symbm; 647 } else { 648 pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join('')); 649 if (nextSymb === symbc) { 650 i += 1; 651 scr = el.points[i].scrCoords; 652 pStr.push([' ', mround(r * scr[1]), ', ', mround(r * scr[2])].join('')); 653 i += 1; 654 scr = el.points[i].scrCoords; 655 pStr.push([' ', mround(r * scr[1]), ', ', mround(r * scr[2])].join('')); 656 } 657 nextSymb = symbc; 658 } 659 i += 1; 660 } 661 } 662 pStr.push(' e'); 663 return pStr; 664 }, 665 666 // already documented in JXG.AbstractRenderer 667 updatePathStringBezierPrim: function (el) { 668 var i, j, k, scr, lx, ly, 669 pStr = [], 670 f = el.visProp.strokewidth, 671 r = this.resolution, 672 mround = Math.round, 673 symbm = ' m ', 674 symbl = ' c ', 675 nextSymb = symbm, 676 isNoPlot = (el.visProp.curvetype !== 'plot'), 677 len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html 678 679 if (el.numberPoints <= 0) { 680 return ''; 681 } 682 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 683 el.points = Numerics.RamerDouglasPeucker(el.points, 1.0); 684 } 685 len = Math.min(len, el.points.length); 686 687 for (j = 1; j < 3; j++) { 688 nextSymb = symbm; 689 for (i = 0; i < len; i++) { 690 scr = el.points[i].scrCoords; 691 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 692 nextSymb = symbm; 693 } else { 694 // IE has problems with values being too far away. 695 if (scr[1] > 20000.0) { 696 scr[1] = 20000.0; 697 } else if (scr[1] < -20000.0) { 698 scr[1] = -20000.0; 699 } 700 701 if (scr[2] > 20000.0) { 702 scr[2] = 20000.0; 703 } else if (scr[2] < -20000.0) { 704 scr[2] = -20000.0; 705 } 706 707 if (nextSymb === symbm) { 708 pStr.push([nextSymb, 709 mround(r * (scr[1])), ' ', mround(r * (scr[2]))].join('')); 710 } else { 711 k = 2 * j; 712 pStr.push([nextSymb, 713 mround(r * (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j))), ' ', 714 mround(r * (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j))), ' ', 715 mround(r * (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j))), ' ', 716 mround(r * (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j))), ' ', 717 mround(r * scr[1]), ' ', 718 mround(r * scr[2])].join('')); 719 } 720 nextSymb = symbl; 721 lx = scr[1]; 722 ly = scr[2]; 723 } 724 } 725 } 726 pStr.push(' e'); 727 return pStr; 728 }, 729 730 // already documented in JXG.AbstractRenderer 731 updatePolygonPrim: function (node, el) { 732 var i, 733 len = el.vertices.length, 734 r = this.resolution, 735 scr, 736 pStr = []; 737 738 this._setAttr(node, 'stroked', 'false'); 739 scr = el.vertices[0].coords.scrCoords; 740 741 if (isNaN(scr[1] + scr[2])) { 742 return; 743 } 744 745 pStr.push(["m ", Math.floor(r * scr[1]), ",", Math.floor(r * scr[2]), " l "].join('')); 746 747 for (i = 1; i < len - 1; i++) { 748 if (el.vertices[i].isReal) { 749 scr = el.vertices[i].coords.scrCoords; 750 751 if (isNaN(scr[1] + scr[2])) { 752 return; 753 } 754 755 pStr.push(Math.floor(r * scr[1]) + "," + Math.floor(r * scr[2])); 756 } else { 757 this.updatePathPrim(node, '', el.board); 758 return; 759 } 760 if (i < len - 2) { 761 pStr.push(", "); 762 } 763 } 764 pStr.push(" x e"); 765 this.updatePathPrim(node, pStr, el.board); 766 }, 767 768 // already documented in JXG.AbstractRenderer 769 updateRectPrim: function (node, x, y, w, h) { 770 node.style.left = Math.floor(x) + 'px'; 771 node.style.top = Math.floor(y) + 'px'; 772 773 if (w >= 0) { 774 node.style.width = w + 'px'; 775 } 776 777 if (h >= 0) { 778 node.style.height = h + 'px'; 779 } 780 }, 781 782 /* ************************** 783 * Set Attributes 784 * **************************/ 785 786 // already documented in JXG.AbstractRenderer 787 setPropertyPrim: function (node, key, val) { 788 var keyVml = '', 789 v; 790 791 switch (key) { 792 case 'stroke': 793 keyVml = 'strokecolor'; 794 break; 795 case 'stroke-width': 796 keyVml = 'strokeweight'; 797 break; 798 case 'stroke-dasharray': 799 keyVml = 'dashstyle'; 800 break; 801 } 802 803 if (keyVml !== '') { 804 v = Type.evaluate(val); 805 this._setAttr(node, keyVml, v); 806 } 807 }, 808 809 // already documented in JXG.AbstractRenderer 810 show: function (el) { 811 if (el && el.rendNode) { 812 el.rendNode.style.visibility = "inherit"; 813 } 814 }, 815 816 // already documented in JXG.AbstractRenderer 817 hide: function (el) { 818 if (el && el.rendNode) { 819 el.rendNode.style.visibility = "hidden"; 820 } 821 }, 822 823 // already documented in JXG.AbstractRenderer 824 setDashStyle: function (el, visProp) { 825 var node; 826 if (visProp.dash >= 0) { 827 node = el.rendNodeStroke; 828 this._setAttr(node, 'dashstyle', this.dashArray[visProp.dash]); 829 } 830 }, 831 832 // already documented in JXG.AbstractRenderer 833 setGradient: function (el) { 834 var nodeFill = el.rendNodeFill; 835 836 if (el.visProp.gradient === 'linear') { 837 this._setAttr(nodeFill, 'type', 'gradient'); 838 this._setAttr(nodeFill, 'color2', el.visProp.gradientsecondcolor); 839 this._setAttr(nodeFill, 'opacity2', el.visProp.gradientsecondopacity); 840 this._setAttr(nodeFill, 'angle', el.visProp.gradientangle); 841 } else if (el.visProp.gradient === 'radial') { 842 this._setAttr(nodeFill, 'type', 'gradientradial'); 843 this._setAttr(nodeFill, 'color2', el.visProp.gradientsecondcolor); 844 this._setAttr(nodeFill, 'opacity2', el.visProp.gradientsecondopacity); 845 this._setAttr(nodeFill, 'focusposition', el.visProp.gradientpositionx * 100 + '%,' + el.visProp.gradientpositiony * 100 + '%'); 846 this._setAttr(nodeFill, 'focussize', '0,0'); 847 } else { 848 this._setAttr(nodeFill, 'type', 'solid'); 849 } 850 }, 851 852 // already documented in JXG.AbstractRenderer 853 setObjectFillColor: function (el, color, opacity) { 854 var rgba = Type.evaluate(color), c, rgbo, 855 o = Type.evaluate(opacity), oo, 856 node = el.rendNode, 857 t; 858 859 o = (o > 0) ? o : 0; 860 861 if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) { 862 return; 863 } 864 865 if (Type.exists(rgba) && rgba !== false) { 866 // RGB, not RGBA 867 if (rgba.length !== 9) { 868 c = rgba; 869 oo = o; 870 // True RGBA, not RGB 871 } else { 872 rgbo = Color.rgba2rgbo(rgba); 873 c = rgbo[0]; 874 oo = o * rgbo[1]; 875 } 876 if (c === 'none' || c === false) { 877 this._setAttr(el.rendNode, 'filled', 'false'); 878 } else { 879 this._setAttr(el.rendNode, 'filled', 'true'); 880 this._setAttr(el.rendNode, 'fillcolor', c); 881 882 if (Type.exists(oo) && el.rendNodeFill) { 883 this._setAttr(el.rendNodeFill, 'opacity', (oo * 100) + '%'); 884 } 885 } 886 if (el.type === Const.OBJECT_TYPE_IMAGE) { 887 /* 888 t = el.rendNode.style.filter.toString(); 889 if (t.match(/alpha/)) { 890 el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')'); 891 } else { 892 el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) + ')'; 893 } 894 */ 895 if (node.filters.length > 1) { 896 // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]? 897 // Setting axes:true shows text labels! 898 node.filters.item(1).opacity = Math.round(oo * 100); // Why does setObjectFillColor not use Math.round? 899 node.filters.item(1).enabled = true; 900 } 901 } 902 } 903 el.visPropOld.fillcolor = rgba; 904 el.visPropOld.fillopacity = o; 905 }, 906 907 // already documented in JXG.AbstractRenderer 908 setObjectStrokeColor: function (el, color, opacity) { 909 var rgba = Type.evaluate(color), c, rgbo, t, 910 o = Type.evaluate(opacity), oo, 911 node = el.rendNode, nodeStroke; 912 913 o = (o > 0) ? o : 0; 914 915 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 916 return; 917 } 918 919 // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor 920 921 if (Type.exists(rgba) && rgba !== false) { 922 // RGB, not RGBA 923 if (rgba.length !== 9) { 924 c = rgba; 925 oo = o; 926 // True RGBA, not RGB 927 } else { 928 rgbo = color.rgba2rgbo(rgba); 929 c = rgbo[0]; 930 oo = o * rgbo[1]; 931 } 932 if (el.elementClass === Const.OBJECT_CLASS_TEXT) { 933 //node.style.filter = ' alpha(opacity = ' + oo + ')'; 934 /* 935 t = node.style.filter.toString(); 936 if (t.match(/alpha/)) { 937 node.style.filter = 938 t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + oo + ')'); 939 } else { 940 node.style.filter += ' alpha(opacity = ' + oo + ')'; 941 } 942 */ 943 if (node.filters.length > 1) { 944 // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]? 945 // Setting axes:true shows text labels! 946 node.filters.item(1).opacity = Math.round(oo * 100); 947 node.filters.item(1).enabled = true; 948 } 949 950 node.style.color = c; 951 } else { 952 if (c !== false) { 953 this._setAttr(node, 'stroked', 'true'); 954 this._setAttr(node, 'strokecolor', c); 955 } 956 957 nodeStroke = el.rendNodeStroke; 958 if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) { 959 this._setAttr(nodeStroke, 'opacity', (oo * 100) + '%'); 960 } 961 } 962 } 963 el.visPropOld.strokecolor = rgba; 964 el.visPropOld.strokeopacity = o; 965 }, 966 967 // already documented in JXG.AbstractRenderer 968 setObjectStrokeWidth: function (el, width) { 969 var w = Type.evaluate(width), 970 node; 971 972 if (isNaN(w) || el.visPropOld.strokewidth === w) { 973 return; 974 } 975 976 node = el.rendNode; 977 this.setPropertyPrim(node, 'stroked', 'true'); 978 979 if (Type.exists(w)) { 980 981 this.setPropertyPrim(node, 'stroke-width', w); 982 if (w === 0 && Type.exists(el.rendNodeStroke)) { 983 this._setAttr(node, 'stroked', 'false'); 984 } 985 } 986 987 el.visPropOld.strokewidth = w; 988 989 }, 990 991 // already documented in JXG.AbstractRenderer 992 setShadow: function (el) { 993 var nodeShadow = el.rendNodeShadow; 994 995 if (!nodeShadow || el.visPropOld.shadow === el.visProp.shadow) { 996 return; 997 } 998 999 if (el.visProp.shadow) { 1000 this._setAttr(nodeShadow, 'On', 'True'); 1001 this._setAttr(nodeShadow, 'Offset', '3pt,3pt'); 1002 this._setAttr(nodeShadow, 'Opacity', '60%'); 1003 this._setAttr(nodeShadow, 'Color', '#aaaaaa'); 1004 } else { 1005 this._setAttr(nodeShadow, 'On', 'False'); 1006 } 1007 1008 el.visPropOld.shadow = el.visProp.shadow; 1009 }, 1010 1011 /* ************************** 1012 * renderer control 1013 * **************************/ 1014 1015 // already documented in JXG.AbstractRenderer 1016 suspendRedraw: function () { 1017 this.container.style.display = 'none'; 1018 }, 1019 1020 // already documented in JXG.AbstractRenderer 1021 unsuspendRedraw: function () { 1022 this.container.style.display = ''; 1023 } 1024 }); 1025 1026 return JXG.VMLRenderer; 1027 }); 1028