1 /* 2 Copyright 2010-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: false, sub: false*/ 29 /*global JXG: true, AMprocessNode: true, document: true, Image: true */ 30 31 /** 32 * Uses HTML Canvas 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.CanvasRenderer = function (container) { 39 var i; 40 41 this.type = 'canvas'; 42 43 this.canvasRoot = null; 44 this.suspendHandle = null; 45 this.canvasId = JXG.Util.genUUID(); 46 47 this.canvasNamespace = null; 48 49 this.container = container; 50 this.container.style.MozUserSelect = 'none'; 51 52 this.container.style.overflow = 'hidden'; 53 if (this.container.style.position === '') { 54 this.container.style.position = 'relative'; 55 } 56 57 this.container.innerHTML = ['<canvas id="', this.canvasId, '" width="', JXG.getStyle(this.container, 'width'), '" height="', JXG.getStyle(this.container, 'height'), '"><', '/canvas>'].join(''); 58 this.canvasRoot = document.getElementById(this.canvasId); 59 this.context = this.canvasRoot.getContext('2d'); 60 61 this.dashArray = [[2, 2], [5, 5], [10, 10], [20, 20], [20, 10, 10, 10], [20, 5, 10, 5]]; 62 }; 63 64 JXG.CanvasRenderer.prototype = new JXG.AbstractRenderer(); 65 66 JXG.extend(JXG.CanvasRenderer.prototype, /** @lends JXG.CanvasRenderer.prototype */ { 67 68 /* ************************** 69 * private methods only used 70 * in this renderer. Should 71 * not be called from outside. 72 * **************************/ 73 74 /** 75 * Draws a filled polygon. 76 * @param {Array} shape A matrix presented by a two dimensional array of numbers. 77 * @see JXG.AbstractRenderer#makeArrows 78 * @private 79 */ 80 _drawFilledPolygon: function (shape) { 81 var i, len = shape.length, 82 context = this.context; 83 84 if (len > 0) { 85 context.beginPath(); 86 context.moveTo(shape[0][0], shape[0][1]); 87 for (i = 0; i < len; i++) { 88 if (i > 0) { 89 context.lineTo(shape[i][0], shape[i][1]); 90 } 91 } 92 context.lineTo(shape[0][0], shape[0][1]); 93 context.fill(); 94 } 95 }, 96 97 /** 98 * Sets the fill color and fills an area. 99 * @param {JXG.GeometryElement} element An arbitrary JSXGraph element, preferably one with an area. 100 * @private 101 */ 102 _fill: function (element) { 103 var context = this.context; 104 105 context.save(); 106 if (this._setColor(element, 'fill')) { 107 context.fill(); 108 } 109 context.restore(); 110 }, 111 112 /** 113 * Rotates a point around <tt>(0, 0)</tt> by a given angle. 114 * @param {Number} angle An angle, given in rad. 115 * @param {Number} x X coordinate of the point. 116 * @param {Number} y Y coordinate of the point. 117 * @returns {Array} An array containing the x and y coordinate of the rotated point. 118 * @private 119 */ 120 _rotatePoint: function (angle, x, y) { 121 return [ 122 (x * Math.cos(angle)) - (y * Math.sin(angle)), 123 (x * Math.sin(angle)) + (y * Math.cos(angle)) 124 ]; 125 }, 126 127 /** 128 * Rotates an array of points around <tt>(0, 0)</tt>. 129 * @param {Array} shape An array of array of point coordinates. 130 * @param {Number} angle The angle in rad the points are rotated by. 131 * @returns {Array} Array of array of two dimensional point coordinates. 132 * @private 133 */ 134 _rotateShape: function (shape, angle) { 135 var i, rv = [], len = shape.length; 136 137 if (len <= 0) { 138 return shape; 139 } 140 141 for (i = 0; i < len; i++) { 142 rv.push(this._rotatePoint(angle, shape[i][0], shape[i][1])); 143 } 144 145 return rv; 146 }, 147 148 /** 149 * Sets color and opacity for filling and stroking. 150 * type is the attribute from visProp and targetType the context[targetTypeStyle]. 151 * This is necessary, because the fill style of a text is set by the stroke attributes of the text element. 152 * @param {JXG.GeometryElement} element Any JSXGraph element. 153 * @param {String} [type='stroke'] Either <em>fill</em> or <em>stroke</em>. 154 * @param {String} [targetType=type] (optional) Either <em>fill</em> or <em>stroke</em>. 155 * @returns {Boolean} If the color could be set, <tt>true</tt> is returned. 156 * @private 157 */ 158 _setColor: function (element, type, targetType) { 159 var hasColor = true, isTrace = false, 160 ev = element.visProp, hl, 161 rgba, rgbo, c, o, oo; 162 163 type = type || 'stroke'; 164 targetType = targetType || type; 165 if (!JXG.exists(element.board) || !JXG.exists(element.board.highlightedObjects)) { 166 // This case handles trace elements. 167 // To make them work, we simply neglect highlighting. 168 isTrace = true; 169 } 170 171 if (!isTrace && JXG.exists(element.board.highlightedObjects[element.id])) { 172 hl = 'highlight'; 173 } else { 174 hl = ''; 175 } 176 177 // type is equal to 'fill' or 'stroke' 178 rgba = JXG.evaluate(ev[hl+type+'color']); 179 if (rgba !== 'none' && rgba !== false ) { 180 o = JXG.evaluate(ev[hl+type+'opacity']); 181 o = (o > 0) ? o : 0; 182 if (rgba.length!=9) { // RGB, not RGBA 183 c = rgba; 184 oo = o; 185 } else { // True RGBA, not RGB 186 rgbo = JXG.rgba2rgbo(rgba); 187 c = rgbo[0]; 188 oo = o*rgbo[1]; 189 } 190 this.context.globalAlpha = oo; 191 this.context[targetType+'Style'] = c; 192 } else { 193 hasColor = false; 194 } 195 if (type === 'stroke') { 196 this.context.lineWidth = parseFloat(ev.strokewidth); 197 } 198 return hasColor; 199 }, 200 201 202 /** 203 * Sets color and opacity for drawing paths and lines and draws the paths and lines. 204 * @param {JXG.GeometryElement} element An JSXGraph element with a stroke. 205 * @private 206 */ 207 _stroke: function (element) { 208 var context = this.context; 209 210 context.save(); 211 212 if (element.visProp.dash > 0) { 213 if (context.setLineDash) { 214 context.setLineDash(this.dashArray[element.visProp.dash]); 215 } 216 } else { 217 this.context.lineDashArray = []; 218 } 219 220 if (this._setColor(element, 'stroke')) { 221 context.stroke(); 222 } 223 context.restore(); 224 }, 225 226 /** 227 * Translates a set of points. 228 * @param {Array} shape An array of point coordinates. 229 * @param {Number} x Translation in X direction. 230 * @param {Number} y Translation in Y direction. 231 * @returns {Array} An array of translated point coordinates. 232 * @private 233 */ 234 _translateShape: function (shape, x, y) { 235 var i, rv = [], len = shape.length; 236 237 if (len <= 0) { 238 return shape; 239 } 240 241 for (i = 0; i < len; i++) { 242 rv.push([ shape[i][0] + x, shape[i][1] + y ]); 243 } 244 245 return rv; 246 }, 247 248 /* ******************************** * 249 * Point drawing and updating * 250 * ******************************** */ 251 252 // documented in AbstractRenderer 253 drawPoint: function (el) { 254 var f = el.visProp.face, 255 size = el.visProp.size, 256 scr = el.coords.scrCoords, 257 sqrt32 = size * Math.sqrt(3) * 0.5, 258 s05 = size * 0.5, 259 stroke05 = parseFloat(el.visProp.strokewidth) / 2.0, 260 context = this.context; 261 262 if (size <= 0 || !el.visProp.visible) { 263 return; 264 } 265 266 switch (f) { 267 case 'cross': // x 268 case 'x': 269 context.beginPath(); 270 context.moveTo(scr[1] - size, scr[2] - size); 271 context.lineTo(scr[1] + size, scr[2] + size); 272 context.moveTo(scr[1] + size, scr[2] - size); 273 context.lineTo(scr[1] - size, scr[2] + size); 274 context.closePath(); 275 this._stroke(el); 276 break; 277 case 'circle': // dot 278 case 'o': 279 context.beginPath(); 280 context.arc(scr[1], scr[2], size + 1 + stroke05, 0, 2 * Math.PI, false); 281 context.closePath(); 282 this._fill(el); 283 this._stroke(el); 284 break; 285 case 'square': // rectangle 286 case '[]': 287 if (size <= 0) { 288 break; 289 } 290 291 context.save(); 292 if (this._setColor(el, 'stroke', 'fill')) { 293 context.fillRect(scr[1] - size - stroke05, scr[2] - size - stroke05, size * 2 + 3 * stroke05, size * 2 + 3 * stroke05); 294 } 295 context.restore(); 296 context.save(); 297 this._setColor(el, 'fill'); 298 context.fillRect(scr[1] - size + stroke05, scr[2] - size + stroke05, size * 2 - stroke05, size * 2 - stroke05); 299 context.restore(); 300 break; 301 case 'plus': // + 302 case '+': 303 context.beginPath(); 304 context.moveTo(scr[1] - size, scr[2]); 305 context.lineTo(scr[1] + size, scr[2]); 306 context.moveTo(scr[1], scr[2] - size); 307 context.lineTo(scr[1], scr[2] + size); 308 context.closePath(); 309 this._stroke(el); 310 break; 311 case 'diamond': // <> 312 case '<>': 313 context.beginPath(); 314 context.moveTo(scr[1] - size, scr[2]); 315 context.lineTo(scr[1], scr[2] + size); 316 context.lineTo(scr[1] + size, scr[2]); 317 context.lineTo(scr[1], scr[2] - size); 318 context.closePath(); 319 this._fill(el); 320 this._stroke(el); 321 break; 322 case 'triangleup': 323 case 'a': 324 case '^': 325 context.beginPath(); 326 context.moveTo(scr[1], scr[2] - size); 327 context.lineTo(scr[1] - sqrt32, scr[2] + s05); 328 context.lineTo(scr[1] + sqrt32, scr[2] + s05); 329 context.closePath(); 330 this._fill(el); 331 this._stroke(el); 332 break; 333 case 'triangledown': 334 case 'v': 335 context.beginPath(); 336 context.moveTo(scr[1], scr[2] + size); 337 context.lineTo(scr[1] - sqrt32, scr[2] - s05); 338 context.lineTo(scr[1] + sqrt32, scr[2] - s05); 339 context.closePath(); 340 this._fill(el); 341 this._stroke(el); 342 break; 343 case 'triangleleft': 344 case '<': 345 context.beginPath(); 346 context.moveTo(scr[1] - size, scr[2]); 347 context.lineTo(scr[1] + s05, scr[2] - sqrt32); 348 context.lineTo(scr[1] + s05, scr[2] + sqrt32); 349 context.closePath(); 350 this.fill(el); 351 this._stroke(el); 352 break; 353 case 'triangleright': 354 case '>': 355 context.beginPath(); 356 context.moveTo(scr[1] + size, scr[2]); 357 context.lineTo(scr[1] - s05, scr[2] - sqrt32); 358 context.lineTo(scr[1] - s05, scr[2] + sqrt32); 359 context.closePath(); 360 this._fill(el); 361 this._stroke(el); 362 break; 363 } 364 }, 365 366 // documented in AbstractRenderer 367 updatePoint: function (el) { 368 this.drawPoint(el); 369 }, 370 371 /* ******************************** * 372 * Lines * 373 * ******************************** */ 374 375 // documented in AbstractRenderer 376 drawLine: function (el) { 377 var scr1 = new JXG.Coords(JXG.COORDS_BY_USER, el.point1.coords.usrCoords, el.board), 378 scr2 = new JXG.Coords(JXG.COORDS_BY_USER, el.point2.coords.usrCoords, el.board); 379 380 JXG.Math.Geometry.calcStraight(el, scr1, scr2); 381 382 this.context.beginPath(); 383 this.context.moveTo(scr1.scrCoords[1], scr1.scrCoords[2]); 384 this.context.lineTo(scr2.scrCoords[1], scr2.scrCoords[2]); 385 this._stroke(el); 386 387 this.makeArrows(el, scr1, scr2); 388 }, 389 390 // documented in AbstractRenderer 391 updateLine: function (el) { 392 this.drawLine(el); 393 }, 394 395 // documented in AbstractRenderer 396 drawTicks: function () { 397 // this function is supposed to initialize the svg/vml nodes in the SVG/VMLRenderer. 398 // but in canvas there are no such nodes, hence we just do nothing and wait until 399 // updateTicks is called. 400 }, 401 402 // documented in AbstractRenderer 403 updateTicks: function (axis, dxMaj, dyMaj, dxMin, dyMin) { 404 var i, c, 405 len = axis.ticks.length, 406 context = this.context; 407 408 context.beginPath(); 409 for (i = 0; i < len; i++) { 410 c = axis.ticks[i]; 411 x = c[0]; 412 y = c[1]; 413 context.moveTo(x[0], y[0]); 414 context.lineTo(x[1], y[1]); 415 } 416 // Labels 417 for (i = 0; i < len; i++) { 418 c = axis.ticks[i].scrCoords; 419 if (axis.ticks[i].major 420 && (axis.board.needsFullUpdate || axis.needsRegularUpdate) 421 && axis.labels[i] 422 && axis.labels[i].visProp.visible) { 423 this.updateText(axis.labels[i]); 424 } 425 } 426 this._stroke(axis); 427 }, 428 429 /* ************************** 430 * Curves 431 * **************************/ 432 433 // documented in AbstractRenderer 434 drawCurve: function (el) { 435 if (el.visProp.handdrawing) { 436 this.updatePathStringBezierPrim(el); 437 } else { 438 this.updatePathStringPrim(el); 439 } 440 }, 441 442 // documented in AbstractRenderer 443 updateCurve: function (el) { 444 this.drawCurve(el); 445 }, 446 447 /* ************************** 448 * Circle related stuff 449 * **************************/ 450 451 // documented in AbstractRenderer 452 drawEllipse: function (el) { 453 var m1 = el.center.coords.scrCoords[1], 454 m2 = el.center.coords.scrCoords[2], 455 sX = el.board.unitX, 456 sY = el.board.unitY, 457 rX = 2 * el.Radius(), 458 rY = 2 * el.Radius(), 459 aWidth = rX * sX, 460 aHeight = rY * sY, 461 aX = m1 - aWidth / 2, 462 aY = m2 - aHeight / 2, 463 hB = (aWidth / 2) * 0.5522848, 464 vB = (aHeight / 2) * 0.5522848, 465 eX = aX + aWidth, 466 eY = aY + aHeight, 467 mX = aX + aWidth / 2, 468 mY = aY + aHeight / 2, 469 context = this.context; 470 471 if (rX > 0.0 && rY > 0.0 && !isNaN(m1 + m2)) { 472 context.beginPath(); 473 context.moveTo(aX, mY); 474 context.bezierCurveTo(aX, mY - vB, mX - hB, aY, mX, aY); 475 context.bezierCurveTo(mX + hB, aY, eX, mY - vB, eX, mY); 476 context.bezierCurveTo(eX, mY + vB, mX + hB, eY, mX, eY); 477 context.bezierCurveTo(mX - hB, eY, aX, mY + vB, aX, mY); 478 context.closePath(); 479 this._fill(el); 480 this._stroke(el); 481 } 482 }, 483 484 // documented in AbstractRenderer 485 updateEllipse: function (el) { 486 return this.drawEllipse(el); 487 }, 488 489 /* ************************** 490 * Polygon 491 * **************************/ 492 493 // nothing here, using AbstractRenderer implementations 494 495 /* ************************** 496 * Text related stuff 497 * **************************/ 498 499 // already documented in JXG.AbstractRenderer 500 displayCopyright: function (str, fontSize) { 501 var context = this.context; 502 503 // this should be called on EVERY update, otherwise it won't be shown after the first update 504 context.save(); 505 context.font = fontSize + 'px Arial'; 506 context.fillStyle = '#aaa'; 507 context.lineWidth = 0.5; 508 context.fillText(str, 10, 2 + fontSize); 509 context.restore(); 510 }, 511 512 // already documented in JXG.AbstractRenderer 513 drawInternalText: function (el) { 514 var fs, context = this.context; 515 516 context.save(); 517 // el.rendNode.setAttributeNS(null, "class", el.visProp.cssclass); 518 if (this._setColor(el, 'stroke', 'fill') && !isNaN(el.coords.scrCoords[1]+el.coords.scrCoords[2]) ) { 519 if (el.visProp.fontsize) { 520 if (typeof el.visProp.fontsize === 'function') { 521 fs = el.visProp.fontsize(); 522 context.font = (fs > 0 ? fs : 0) + 'px Arial'; 523 } else { 524 context.font = (el.visProp.fontsize) + 'px Arial'; 525 } 526 } 527 528 this.transformImage(el, el.transformations); 529 context.fillText(el.plaintext, el.coords.scrCoords[1], el.coords.scrCoords[2]); 530 } 531 context.restore(); 532 533 return null; 534 }, 535 536 // already documented in JXG.AbstractRenderer 537 updateInternalText: function (element) { 538 this.drawInternalText(element); 539 }, 540 541 // documented in JXG.AbstractRenderer 542 // Only necessary for texts 543 setObjectStrokeColor: function (el, color, opacity) { 544 var rgba = JXG.evaluate(color), c, rgbo, 545 o = JXG.evaluate(opacity), oo, 546 node; 547 548 o = (o > 0) ? o : 0; 549 550 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 551 return; 552 } 553 554 if (JXG.exists(rgba) && rgba !== false) { 555 if (rgba.length!=9) { // RGB, not RGBA 556 c = rgba; 557 oo = o; 558 } else { // True RGBA, not RGB 559 rgbo = JXG.rgba2rgbo(rgba); 560 c = rgbo[0]; 561 oo = o*rgbo[1]; 562 } 563 node = el.rendNode; 564 if (el.type === JXG.OBJECT_TYPE_TEXT && el.visProp.display === 'html') { 565 node.style.color = c; 566 node.style.opacity = oo; 567 } 568 } 569 570 el.visPropOld.strokecolor = rgba; 571 el.visPropOld.strokeopacity = o; 572 }, 573 574 // already documented in JXG.AbstractRenderer 575 /* 576 updateTextStyle: function (element) { 577 var fs = JXG.evaluate(element.visProp.fontsize); 578 579 if (element.visProp.display === 'html') { 580 element.rendNode.style.fontSize = fs + 'px'; 581 } 582 }, 583 */ 584 /* ************************** 585 * Image related stuff 586 * **************************/ 587 588 // already documented in JXG.AbstractRenderer 589 drawImage: function (el) { 590 el.rendNode = new Image(); 591 // Store the file name of the image. 592 // Before, this was done in el.rendNode.src 593 // But there, the file name is expanded to 594 // the full url. This may be different from 595 // the url computed in updateImageURL(). 596 el._src = ''; 597 this.updateImage(el); 598 }, 599 600 // already documented in JXG.AbstractRenderer 601 updateImage: function (el) { 602 var context = this.context, 603 o = JXG.evaluate(el.visProp.fillopacity), 604 paintImg = JXG.bind(function () { 605 el.imgIsLoaded = true; 606 if (el.size[0] <= 0 || el.size[1] <= 0) { 607 return; 608 } 609 context.save(); 610 context.globalAlpha = o; 611 // If det(el.transformations)=0, FireFox 3.6. breaks down. 612 // This is tested in transformImage 613 this.transformImage(el, el.transformations); 614 context.drawImage(el.rendNode, 615 el.coords.scrCoords[1], 616 el.coords.scrCoords[2] - el.size[1], 617 el.size[0], 618 el.size[1]); 619 context.restore(); 620 }, this); 621 622 if (this.updateImageURL(el)) { 623 el.rendNode.onload = paintImg; 624 } else { 625 if (el.imgIsLoaded) { 626 paintImg(); 627 } 628 } 629 }, 630 631 // already documented in JXG.AbstractRenderer 632 transformImage: function (el, t) { 633 var m, len = t.length, 634 ctx = this.context; 635 636 if (len > 0) { 637 m = this.joinTransforms(el, t); 638 if (Math.abs(JXG.Math.Numerics.det(m)) >= JXG.Math.eps) { 639 ctx.transform(m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]); 640 } 641 } 642 }, 643 644 // already documented in JXG.AbstractRenderer 645 updateImageURL: function (el) { 646 var url; 647 648 url = JXG.evaluate(el.url); 649 if (el._src !== url) { 650 el.imgIsLoaded = false; 651 el.rendNode.src = url; 652 el._src = url; 653 return true; 654 } 655 656 return false; 657 }, 658 659 /* ************************** 660 * Render primitive objects 661 * **************************/ 662 663 // documented in AbstractRenderer 664 remove: function (shape) { 665 // sounds odd for a pixel based renderer but we need this for html texts 666 if (JXG.exists(shape) && JXG.exists(shape.parentNode)) { 667 shape.parentNode.removeChild(shape); 668 } 669 }, 670 671 // documented in AbstractRenderer 672 makeArrows: function (el, scr1, scr2) { 673 // not done yet for curves and arcs. 674 var arrowHead = [ 675 [ 2, 0 ], 676 [ -10, -4 ], 677 [ -10, 4] 678 ], 679 arrowTail = [ 680 [ -2, 0 ], 681 [ 10, -4 ], 682 [ 10, 4] 683 ], 684 x1, y1, x2, y2, ang, 685 context = this.context; 686 687 if (el.visProp.strokecolor !== 'none' && (el.visProp.lastarrow || el.visProp.firstarrow)) { 688 if (el.elementClass === JXG.OBJECT_CLASS_LINE) { 689 x1 = scr1.scrCoords[1]; 690 y1 = scr1.scrCoords[2]; 691 x2 = scr2.scrCoords[1]; 692 y2 = scr2.scrCoords[2]; 693 } else { 694 return; 695 } 696 697 context.save(); 698 if (this._setColor(el, 'stroke', 'fill')) { 699 ang = Math.atan2(y2 - y1, x2 - x1); 700 if (el.visProp.lastarrow) { 701 this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowHead, ang), x2, y2)); 702 } 703 704 if (el.visProp.firstarrow) { 705 this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowTail, ang), x1, y1)); 706 } 707 } 708 context.restore(); 709 } 710 }, 711 712 // documented in AbstractRenderer 713 updatePathStringPrim: function (el) { 714 var symbm = 'M', 715 symbl = 'L', 716 nextSymb = symbm, 717 maxSize = 5000.0, 718 i, scr, 719 isNotPlot = (el.visProp.curvetype !== 'plot'), 720 len, 721 context = this.context; 722 723 if (el.numberPoints <= 0) { 724 return; 725 } 726 727 if (isNotPlot && el.board.options.curve.RDPsmoothing) { 728 el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 0.5); 729 } 730 len = Math.min(el.points.length, el.numberPoints); 731 732 context.beginPath(); 733 for (i = 0; i < len; i++) { 734 scr = el.points[i].scrCoords; 735 736 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 737 nextSymb = symbm; 738 } else { 739 // Chrome has problems with values being too far away. 740 if (scr[1] > maxSize) { 741 scr[1] = maxSize; 742 } else if (scr[1] < -maxSize) { 743 scr[1] = -maxSize; 744 } 745 746 if (scr[2] > maxSize) { 747 scr[2] = maxSize; 748 } else if (scr[2] < -maxSize) { 749 scr[2] = -maxSize; 750 } 751 752 if (nextSymb === symbm) { 753 context.moveTo(scr[1], scr[2]); 754 } else { 755 context.lineTo(scr[1], scr[2]); 756 } 757 nextSymb = symbl; 758 } 759 } 760 this._fill(el); 761 this._stroke(el); 762 }, 763 764 // already documented in JXG.AbstractRenderer 765 updatePathStringBezierPrim: function (el) { 766 var symbm = 'M', 767 symbl = 'C', 768 nextSymb = symbm, 769 maxSize = 5000.0, 770 i, j, scr, 771 lx, ly, f = el.visProp.strokewidth, 772 isNoPlot = (el.visProp.curvetype !== 'plot'), 773 len, 774 context = this.context; 775 776 if (el.numberPoints <= 0) { 777 return; 778 } 779 780 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 781 el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 0.5); 782 } 783 len = Math.min(el.points.length, el.numberPoints); 784 785 context.beginPath(); 786 for (j=1; j<3; j++) { 787 nextSymb = symbm; 788 for (i = 0; i < len; i++) { 789 scr = el.points[i].scrCoords; 790 791 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 792 nextSymb = symbm; 793 } else { 794 // Chrome has problems with values being too far away. 795 if (scr[1] > maxSize) { 796 scr[1] = maxSize; 797 } else if (scr[1] < -maxSize) { 798 scr[1] = -maxSize; 799 } 800 801 if (scr[2] > maxSize) { 802 scr[2] = maxSize; 803 } else if (scr[2] < -maxSize) { 804 scr[2] = -maxSize; 805 } 806 807 if (nextSymb == symbm) { 808 context.moveTo(scr[1]+0*f*(2*j*Math.random()-j), 809 scr[2]+0*f*(2*j*Math.random()-j)); 810 } else { 811 context.bezierCurveTo( 812 (lx + (scr[1]-lx)*0.333 + f*(2*j*Math.random()-j)), 813 (ly + (scr[2]-ly)*0.333 + f*(2*j*Math.random()-j)), 814 (lx + 2*(scr[1]-lx)*0.333 + f*(2*j*Math.random()-j)), 815 (ly + 2*(scr[2]-ly)*0.333 + f*(2*j*Math.random()-j)), 816 scr[1], scr[2]); 817 } 818 nextSymb = symbl; 819 lx = scr[1]; 820 ly = scr[2]; 821 } 822 } 823 } 824 this._fill(el); 825 this._stroke(el); 826 }, 827 828 // documented in AbstractRenderer 829 updatePolygonPrim: function (node, el) { 830 var scrCoords, i, 831 len = el.vertices.length, 832 context = this.context; 833 834 if (len <= 0) { 835 return; 836 } 837 838 context.beginPath(); 839 scrCoords = el.vertices[0].coords.scrCoords; 840 context.moveTo(scrCoords[1], scrCoords[2]); 841 for (i = 1; i < len; i++) { 842 scrCoords = el.vertices[i].coords.scrCoords; 843 context.lineTo(scrCoords[1], scrCoords[2]); 844 } 845 context.closePath(); 846 847 this._fill(el); // The edges of a polygon are displayed separately (as segments). 848 }, 849 850 /* ************************** 851 * Set Attributes 852 * **************************/ 853 854 // documented in AbstractRenderer 855 show: function (el) { 856 // sounds odd for a pixel based renderer but we need this for html texts 857 if (JXG.exists(el.rendNode)) { 858 el.rendNode.style.visibility = "inherit"; 859 } 860 }, 861 862 // documented in AbstractRenderer 863 hide: function (el) { 864 // sounds odd for a pixel based renderer but we need this for html texts 865 if (JXG.exists(el.rendNode)) { 866 el.rendNode.style.visibility = "hidden"; 867 } 868 }, 869 870 // documented in AbstractRenderer 871 setGradient: function (el) { 872 var col, op; 873 874 op = JXG.evaluate(el.visProp.fillopacity); 875 op = (op > 0) ? op : 0; 876 877 col = JXG.evaluate(el.visProp.fillcolor); 878 }, 879 880 // documented in AbstractRenderer 881 setShadow: function (el) { 882 if (el.visPropOld.shadow === el.visProp.shadow) { 883 return; 884 } 885 886 // not implemented yet 887 // we simply have to redraw the element 888 // probably the best way to do so would be to call el.updateRenderer(), i think. 889 890 el.visPropOld.shadow = el.visProp.shadow; 891 }, 892 893 // documented in AbstractRenderer 894 highlight: function (obj) { 895 obj.board.prepareUpdate(); 896 obj.board.renderer.suspendRedraw(obj.board); 897 obj.board.updateRenderer(); 898 obj.board.renderer.unsuspendRedraw(); 899 return this; 900 }, 901 902 // documented in AbstractRenderer 903 noHighlight: function (obj) { 904 obj.board.prepareUpdate(); 905 obj.board.renderer.suspendRedraw(obj.board); 906 obj.board.updateRenderer(); 907 obj.board.renderer.unsuspendRedraw(); 908 return this; 909 }, 910 911 /* ************************** 912 * renderer control 913 * **************************/ 914 915 // documented in AbstractRenderer 916 suspendRedraw: function (board) { 917 this.context.save(); 918 this.context.clearRect(0, 0, this.canvasRoot.width, this.canvasRoot.height); 919 920 if (board && board.showCopyright) { 921 this.displayCopyright(JXG.JSXGraph.licenseText, 12); 922 } 923 }, 924 925 // documented in AbstractRenderer 926 unsuspendRedraw: function () { 927 this.context.restore(); 928 }, 929 930 // document in AbstractRenderer 931 resize: function (w, h) { 932 this.canvasRoot.style.width = parseFloat(w) + 'px'; 933 this.canvasRoot.style.height = parseFloat(h) + 'px'; 934 935 this.canvasRoot.setAttribute('width', parseFloat(w) + 'px'); 936 this.canvasRoot.setAttribute('height', parseFloat(h) + 'px'); 937 } 938 939 }); 940