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