1 /* 2 Copyright 2008-2022 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, document: true, Image: true, module: true, require: true */ 34 /*jslint nomen: true, plusplus: true, newcap:true*/ 35 36 /* depends: 37 jxg 38 renderer/abstract 39 base/constants 40 utils/env 41 utils/type 42 utils/uuid 43 utils/color 44 base/coords 45 math/math 46 math/geometry 47 math/numerics 48 */ 49 50 define([ 51 'jxg', 'renderer/abstract', 'base/constants', 'utils/env', 'utils/type', 'utils/uuid', 'utils/color', 52 'base/coords', 'math/math', 'math/geometry', 'math/numerics' 53 ], function (JXG, AbstractRenderer, Const, Env, Type, UUID, Color, Coords, Mat, Geometry, Numerics) { 54 55 "use strict"; 56 57 /** 58 * Uses HTML Canvas to implement the rendering methods defined in {@link JXG.AbstractRenderer}. 59 * 60 * @class JXG.CanvasRenderer 61 * @augments JXG.AbstractRenderer 62 * @param {Node} container Reference to a DOM node containing the board. 63 * @param {Object} dim The dimensions of the board 64 * @param {Number} dim.width 65 * @param {Number} dim.height 66 * @see JXG.AbstractRenderer 67 */ 68 JXG.CanvasRenderer = function (container, dim) { 69 this.type = 'canvas'; 70 71 this.canvasRoot = null; 72 this.suspendHandle = null; 73 this.canvasId = UUID.genUUID(); 74 75 this.canvasNamespace = null; 76 77 if (Env.isBrowser) { 78 this.container = container; 79 this.container.style.MozUserSelect = 'none'; 80 this.container.style.userSelect = 'none'; 81 82 this.container.style.overflow = 'hidden'; 83 if (this.container.style.position === '') { 84 this.container.style.position = 'relative'; 85 } 86 87 this.container.innerHTML = ['<canvas id="', this.canvasId, 88 '" width="', dim.width, 89 'px" height="', dim.height, 90 'px"><', '/canvas>'].join(''); 91 this.canvasRoot = this.container.ownerDocument.getElementById(this.canvasId); 92 this.canvasRoot.style.display = 'block'; 93 this.context = this.canvasRoot.getContext('2d'); 94 95 } else if (Env.isNode()) { 96 try { 97 this.canvasId = (typeof module === 'object' ? module.require('canvas') : require('canvas')); 98 this.canvasRoot = new this.canvasId(500, 500); 99 this.context = this.canvasRoot.getContext('2d'); 100 } catch (err) { 101 console.log("Warning: 'canvas' not found. You might need to call 'npm install canvas'"); 102 } 103 } 104 105 this.dashArray = [[2, 2], [5, 5], [10, 10], [20, 20], [20, 10, 10, 10], [20, 5, 10, 5]]; 106 }; 107 108 JXG.CanvasRenderer.prototype = new AbstractRenderer(); 109 110 JXG.extend(JXG.CanvasRenderer.prototype, /** @lends JXG.CanvasRenderer.prototype */ { 111 112 /* ************************** 113 * private methods only used 114 * in this renderer. Should 115 * not be called from outside. 116 * **************************/ 117 118 /** 119 * Draws a filled polygon. 120 * @param {Array} shape A matrix presented by a two dimensional array of numbers. 121 * @see JXG.AbstractRenderer#drawArrows 122 * @private 123 */ 124 _drawPolygon: function (shape, degree, doFill) { 125 var i, len = shape.length, 126 context = this.context; 127 128 if (len > 0) { 129 if (doFill) { 130 context.lineWidth = 0; 131 } 132 context.beginPath(); 133 context.moveTo(shape[0][0], shape[0][1]); 134 if (degree === 1) { 135 for (i = 1; i < len; i++) { 136 context.lineTo(shape[i][0], shape[i][1]); 137 } 138 } else { 139 for (i = 1; i < len; i += 3) { 140 context.bezierCurveTo(shape[i][0], shape[i][1], shape[i + 1][0], shape[i + 1][1], shape[i + 2][0], shape[i + 2][1]); 141 } 142 } 143 if (doFill) { 144 context.lineTo(shape[0][0], shape[0][1]); 145 context.closePath(); 146 context.fill(); 147 } else { 148 context.stroke(); 149 } 150 } 151 }, 152 153 /** 154 * Sets the fill color and fills an area. 155 * @param {JXG.GeometryElement} el An arbitrary JSXGraph element, preferably one with an area. 156 * @private 157 */ 158 _fill: function (el) { 159 var context = this.context; 160 161 context.save(); 162 if (this._setColor(el, 'fill')) { 163 context.fill(); 164 } 165 context.restore(); 166 }, 167 168 /** 169 * Rotates a point around <tt>(0, 0)</tt> by a given angle. 170 * @param {Number} angle An angle, given in rad. 171 * @param {Number} x X coordinate of the point. 172 * @param {Number} y Y coordinate of the point. 173 * @returns {Array} An array containing the x and y coordinate of the rotated point. 174 * @private 175 */ 176 _rotatePoint: function (angle, x, y) { 177 return [ 178 (x * Math.cos(angle)) - (y * Math.sin(angle)), 179 (x * Math.sin(angle)) + (y * Math.cos(angle)) 180 ]; 181 }, 182 183 /** 184 * Rotates an array of points around <tt>(0, 0)</tt>. 185 * @param {Array} shape An array of array of point coordinates. 186 * @param {Number} angle The angle in rad the points are rotated by. 187 * @returns {Array} Array of array of two dimensional point coordinates. 188 * @private 189 */ 190 _rotateShape: function (shape, angle) { 191 var i, rv = [], len = shape.length; 192 193 if (len <= 0) { 194 return shape; 195 } 196 197 for (i = 0; i < len; i++) { 198 rv.push(this._rotatePoint(angle, shape[i][0], shape[i][1])); 199 } 200 201 return rv; 202 }, 203 204 /** 205 * Set the gradient angle for linear color gradients. 206 * 207 * @private 208 * @param {JXG.GeometryElement} node An arbitrary JSXGraph element, preferably one with an area. 209 * @param {Number} radians angle value in radians. 0 is horizontal from left to right, Pi/4 is vertical from top to bottom. 210 */ 211 updateGradientAngle: function(el, radians) { 212 // Angles: 213 // 0: -> 214 // 90: down 215 // 180: <- 216 // 90: up 217 var f = 1.0, 218 co = Math.cos(-radians), 219 si = Math.sin(-radians), 220 bb = el.getBoundingBox(), 221 c1, c2, x1, x2, y1, y2, x1s, x2s, y1s, y2s, dx, dy; 222 223 if (Math.abs(co) > Math.abs(si)) { 224 f /= Math.abs(co); 225 } else { 226 f /= Math.abs(si); 227 } 228 if (co >= 0) { 229 x1 = 0; 230 x2 = co * f; 231 } else { 232 x1 = -co * f; 233 x2 = 0; 234 } 235 if (si >= 0) { 236 y1 = 0; 237 y2 = si * f; 238 } else { 239 y1 = -si * f; 240 y2 = 0; 241 } 242 243 c1 = new Coords(Const.COORDS_BY_USER, [bb[0], bb[1]], el.board); 244 c2 = new Coords(Const.COORDS_BY_USER, [bb[2], bb[3]], el.board); 245 dx = c2.scrCoords[1] - c1.scrCoords[1]; 246 dy = c2.scrCoords[2] - c1.scrCoords[2]; 247 x1s = c1.scrCoords[1] + dx * x1; 248 y1s = c1.scrCoords[2] + dy * y1; 249 x2s = c1.scrCoords[1] + dx * x2; 250 y2s = c1.scrCoords[2] + dy * y2; 251 252 return this.context.createLinearGradient(x1s, y1s, x2s, y2s); 253 }, 254 255 /** 256 * Set circles for radial color gradients. 257 * 258 * @private 259 * @param {SVGnode} node SVG gradient node 260 * @param {Number} cx Canvas value x1 (but value between 0 and 1) 261 * @param {Number} cy Canvas value y1 (but value between 0 and 1) 262 * @param {Number} r Canvas value r1 (but value between 0 and 1) 263 * @param {Number} fx Canvas value x0 (but value between 0 and 1) 264 * @param {Number} fy Canvas value x1 (but value between 0 and 1) 265 * @param {Number} fr Canvas value r0 (but value between 0 and 1) 266 */ 267 updateGradientCircle: function(el, cx, cy, r, fx, fy, fr) { 268 var bb = el.getBoundingBox(), 269 c1, c2, cxs, cys, rs, fxs, fys, frs, dx, dy; 270 271 c1 = new Coords(Const.COORDS_BY_USER, [bb[0], bb[1]], el.board); 272 c2 = new Coords(Const.COORDS_BY_USER, [bb[2], bb[3]], el.board); 273 dx = c2.scrCoords[1] - c1.scrCoords[1]; 274 dy = c1.scrCoords[2] - c2.scrCoords[2]; 275 276 cxs = c1.scrCoords[1] + dx * cx; 277 cys = c2.scrCoords[2] + dy * cy; 278 fxs = c1.scrCoords[1] + dx * fx; 279 fys = c2.scrCoords[2] + dy * fy; 280 rs = r * (dx + dy) * 0.5; 281 frs = fr * (dx + dy) * 0.5; 282 283 return this.context.createRadialGradient(fxs, fys, frs, cxs, cys, rs); 284 }, 285 286 // documented in JXG.AbstractRenderer 287 updateGradient: function(el) { 288 var col, op, 289 ev_g = Type.evaluate(el.visProp.gradient), 290 gradient; 291 292 op = Type.evaluate(el.visProp.fillopacity); 293 op = (op > 0) ? op : 0; 294 col = Type.evaluate(el.visProp.fillcolor); 295 296 if (ev_g === 'linear') { 297 gradient = this.updateGradientAngle(el, Type.evaluate(el.visProp.gradientangle)); 298 } else if (ev_g === 'radial') { 299 gradient = this.updateGradientCircle(el, 300 Type.evaluate(el.visProp.gradientcx), 301 Type.evaluate(el.visProp.gradientcy), 302 Type.evaluate(el.visProp.gradientr), 303 Type.evaluate(el.visProp.gradientfx), 304 Type.evaluate(el.visProp.gradientfy), 305 Type.evaluate(el.visProp.gradientfr) 306 ); 307 } 308 gradient.addColorStop(Type.evaluate(el.visProp.gradientstartoffset), col); 309 gradient.addColorStop(Type.evaluate(el.visProp.gradientendoffset), 310 Type.evaluate(el.visProp.gradientsecondcolor)); 311 return gradient; 312 }, 313 314 /** 315 * Sets color and opacity for filling and stroking. 316 * type is the attribute from visProp and targetType the context[targetTypeStyle]. 317 * This is necessary, because the fill style of a text is set by the stroke attributes of the text element. 318 * @param {JXG.GeometryElement} el Any JSXGraph element. 319 * @param {String} [type='stroke'] Either <em>fill</em> or <em>stroke</em>. 320 * @param {String} [targetType=type] (optional) Either <em>fill</em> or <em>stroke</em>. 321 * @returns {Boolean} If the color could be set, <tt>true</tt> is returned. 322 * @private 323 */ 324 _setColor: function (el, type, targetType) { 325 var hasColor = true, 326 ev = el.visProp, hl, sw, 327 rgba, rgbo, c, o, oo, 328 grad; 329 330 type = type || 'stroke'; 331 targetType = targetType || type; 332 333 hl = this._getHighlighted(el); 334 335 grad = Type.evaluate(el.visProp.gradient); 336 if (grad === 'linear' || grad === 'radial') { 337 // TODO: opacity 338 this.context[targetType + 'Style'] = this.updateGradient(el); 339 return hasColor; 340 } 341 342 // type is equal to 'fill' or 'stroke' 343 rgba = Type.evaluate(ev[hl + type + 'color']); 344 if (rgba !== 'none' && rgba !== false) { 345 o = Type.evaluate(ev[hl + type + 'opacity']); 346 o = (o > 0) ? o : 0; 347 348 // RGB, not RGBA 349 if (rgba.length !== 9) { 350 c = rgba; 351 oo = o; 352 // True RGBA, not RGB 353 } else { 354 rgbo = Color.rgba2rgbo(rgba); 355 c = rgbo[0]; 356 oo = o * rgbo[1]; 357 } 358 this.context.globalAlpha = oo; 359 360 this.context[targetType + 'Style'] = c; 361 362 } else { 363 hasColor = false; 364 } 365 366 sw = parseFloat(Type.evaluate(ev[hl + 'strokewidth'])); 367 if (type === 'stroke' && !isNaN(sw)) { 368 if (sw === 0) { 369 this.context.globalAlpha = 0; 370 } else { 371 this.context.lineWidth = sw; 372 } 373 } 374 375 if (type === 'stroke' && ev.linecap !== undefined && ev.linecap !== '') { 376 this.context.lineCap = ev.linecap; 377 } 378 379 return hasColor; 380 }, 381 382 /** 383 * Sets color and opacity for drawing paths and lines and draws the paths and lines. 384 * @param {JXG.GeometryElement} el An JSXGraph element with a stroke. 385 * @private 386 */ 387 _stroke: function (el) { 388 var context = this.context, 389 ev_dash = Type.evaluate(el.visProp.dash); 390 391 context.save(); 392 393 if (ev_dash > 0) { 394 if (context.setLineDash) { 395 context.setLineDash(this.dashArray[ev_dash]); 396 } 397 } else { 398 this.context.lineDashArray = []; 399 } 400 401 if (this._setColor(el, 'stroke')) { 402 context.stroke(); 403 } 404 405 context.restore(); 406 }, 407 408 /** 409 * Translates a set of points. 410 * @param {Array} shape An array of point coordinates. 411 * @param {Number} x Translation in X direction. 412 * @param {Number} y Translation in Y direction. 413 * @returns {Array} An array of translated point coordinates. 414 * @private 415 */ 416 _translateShape: function (shape, x, y) { 417 var i, rv = [], len = shape.length; 418 419 if (len <= 0) { 420 return shape; 421 } 422 423 for (i = 0; i < len; i++) { 424 rv.push([ shape[i][0] + x, shape[i][1] + y ]); 425 } 426 427 return rv; 428 }, 429 430 /* ******************************** * 431 * Point drawing and updating * 432 * ******************************** */ 433 434 // documented in AbstractRenderer 435 drawPoint: function (el) { 436 var f = Type.evaluate(el.visProp.face), 437 size = Type.evaluate(el.visProp.size), 438 scr = el.coords.scrCoords, 439 sqrt32 = size * Math.sqrt(3) * 0.5, 440 s05 = size * 0.5, 441 stroke05 = parseFloat(Type.evaluate(el.visProp.strokewidth)) / 2.0, 442 context = this.context; 443 444 if (!el.visPropCalc.visible) { 445 return; 446 } 447 448 switch (f) { 449 case 'cross': // x 450 case 'x': 451 context.beginPath(); 452 context.moveTo(scr[1] - size, scr[2] - size); 453 context.lineTo(scr[1] + size, scr[2] + size); 454 context.moveTo(scr[1] + size, scr[2] - size); 455 context.lineTo(scr[1] - size, scr[2] + size); 456 context.lineCap = 'round'; 457 context.lineJoin = 'round'; 458 context.closePath(); 459 this._stroke(el); 460 break; 461 case 'circle': // dot 462 case 'o': 463 context.beginPath(); 464 context.arc(scr[1], scr[2], size + 1 + stroke05, 0, 2 * Math.PI, false); 465 context.closePath(); 466 this._fill(el); 467 this._stroke(el); 468 break; 469 case 'square': // rectangle 470 case '[]': 471 if (size <= 0) { 472 break; 473 } 474 475 context.save(); 476 if (this._setColor(el, 'stroke', 'fill')) { 477 context.fillRect(scr[1] - size - stroke05, scr[2] - size - stroke05, size * 2 + 3 * stroke05, size * 2 + 3 * stroke05); 478 } 479 context.restore(); 480 context.save(); 481 this._setColor(el, 'fill'); 482 context.fillRect(scr[1] - size + stroke05, scr[2] - size + stroke05, size * 2 - stroke05, size * 2 - stroke05); 483 context.restore(); 484 break; 485 case 'plus': // + 486 case '+': 487 context.beginPath(); 488 context.moveTo(scr[1] - size, scr[2]); 489 context.lineTo(scr[1] + size, scr[2]); 490 context.moveTo(scr[1], scr[2] - size); 491 context.lineTo(scr[1], scr[2] + size); 492 context.lineCap = 'round'; 493 context.lineJoin = 'round'; 494 context.closePath(); 495 this._stroke(el); 496 break; 497 case 'diamond': // <> 498 case '<>': 499 context.beginPath(); 500 context.moveTo(scr[1] - size, scr[2]); 501 context.lineTo(scr[1], scr[2] + size); 502 context.lineTo(scr[1] + size, scr[2]); 503 context.lineTo(scr[1], scr[2] - size); 504 context.closePath(); 505 this._fill(el); 506 this._stroke(el); 507 break; 508 case 'triangleup': 509 case 'a': 510 case '^': 511 context.beginPath(); 512 context.moveTo(scr[1], scr[2] - size); 513 context.lineTo(scr[1] - sqrt32, scr[2] + s05); 514 context.lineTo(scr[1] + sqrt32, scr[2] + s05); 515 context.closePath(); 516 this._fill(el); 517 this._stroke(el); 518 break; 519 case 'triangledown': 520 case 'v': 521 context.beginPath(); 522 context.moveTo(scr[1], scr[2] + size); 523 context.lineTo(scr[1] - sqrt32, scr[2] - s05); 524 context.lineTo(scr[1] + sqrt32, scr[2] - s05); 525 context.closePath(); 526 this._fill(el); 527 this._stroke(el); 528 break; 529 case 'triangleleft': 530 case '<': 531 context.beginPath(); 532 context.moveTo(scr[1] - size, scr[2]); 533 context.lineTo(scr[1] + s05, scr[2] - sqrt32); 534 context.lineTo(scr[1] + s05, scr[2] + sqrt32); 535 context.closePath(); 536 this._fill(el); 537 this._stroke(el); 538 break; 539 case 'triangleright': 540 case '>': 541 context.beginPath(); 542 context.moveTo(scr[1] + size, scr[2]); 543 context.lineTo(scr[1] - s05, scr[2] - sqrt32); 544 context.lineTo(scr[1] - s05, scr[2] + sqrt32); 545 context.closePath(); 546 this._fill(el); 547 this._stroke(el); 548 break; 549 } 550 }, 551 552 // documented in AbstractRenderer 553 updatePoint: function (el) { 554 this.drawPoint(el); 555 }, 556 557 /* ******************************** * 558 * Lines * 559 * ******************************** */ 560 561 /** 562 * Draws arrows of an element (usually a line) in canvas renderer. 563 * @param {JXG.GeometryElement} el Line to be drawn. 564 * @param {Array} scr1 Screen coordinates of the start position of the line or curve. 565 * @param {Array} scr2 Screen coordinates of the end position of the line or curve. 566 * @param {String} hl String which carries information if the element is highlighted. Used for getting the correct attribute. 567 * @private 568 */ 569 drawArrows: function (el, scr1, scr2, hl, a) { 570 var x1, y1, x2, y2, 571 w0, w, 572 arrowHead, 573 arrowTail, 574 context = this.context, 575 size = 6, 576 type = 1, 577 type_fa, type_la, 578 degree_fa = 1, 579 degree_la = 1, 580 doFill, 581 i, len, 582 d1x, d1y, d2x, d2y, last, 583 ang1, ang2, 584 ev_fa = a.evFirst, 585 ev_la = a.evLast; 586 587 if (Type.evaluate(el.visProp.strokecolor) !== 'none' && 588 (ev_fa || ev_la)) { 589 590 if (el.elementClass === Const.OBJECT_CLASS_LINE) { 591 x1 = scr1.scrCoords[1]; 592 y1 = scr1.scrCoords[2]; 593 x2 = scr2.scrCoords[1]; 594 y2 = scr2.scrCoords[2]; 595 ang1 = ang2 = Math.atan2(y2 - y1, x2 - x1); 596 } else { 597 x1 = el.points[0].scrCoords[1]; 598 y1 = el.points[0].scrCoords[2]; 599 600 last = el.points.length - 1; 601 if (last < 1) { 602 // No arrows for curves consisting of 1 point 603 return; 604 } 605 x2 = el.points[el.points.length - 1].scrCoords[1]; 606 y2 = el.points[el.points.length - 1].scrCoords[2]; 607 608 d1x = el.points[1].scrCoords[1] - el.points[0].scrCoords[1]; 609 d1y = el.points[1].scrCoords[2] - el.points[0].scrCoords[2]; 610 d2x = el.points[last].scrCoords[1] - el.points[last - 1].scrCoords[1]; 611 d2y = el.points[last].scrCoords[2] - el.points[last - 1].scrCoords[2]; 612 if (ev_fa) { 613 ang1 = Math.atan2(d1y, d1x); 614 } 615 if (ev_la) { 616 ang2 = Math.atan2(d2y, d2x); 617 } 618 } 619 620 w0 = Type.evaluate(el.visProp[hl + 'strokewidth']); 621 622 if (ev_fa) { 623 size = a.sizeFirst; 624 625 w = w0 * size; 626 627 type = a.typeFirst; 628 type_fa = type; 629 630 if (type === 2) { 631 arrowTail = [ 632 [ w, -w * 0.5], 633 [ 0.0, 0.0], 634 [ w, w * 0.5], 635 [ w * 0.5, 0.0], 636 ]; 637 } else if (type === 3) { 638 arrowTail = [ 639 [ w / 3.0, -w * 0.5], 640 [ 0.0, -w * 0.5], 641 [ 0.0, w * 0.5], 642 [ w / 3.0, w * 0.5] 643 ]; 644 } else if (type === 4) { 645 w /= 10; 646 degree_fa = 3; 647 arrowTail = [ 648 [10.00, 3.31], 649 [6.47, 3.84], 650 [2.87, 4.50], 651 [0.00, 6.63], 652 [0.67, 5.52], 653 [1.33, 4.42], 654 [2.00, 3.31], 655 [1.33, 2.21], 656 [0.67, 1.10], 657 [0.00, 0.00], 658 [2.87, 2.13], 659 [6.47, 2.79], 660 [10.00, 3.31] 661 ]; 662 len = arrowTail.length; 663 for (i = 0; i < len; i++) { 664 arrowTail[i][0] *= -w; 665 arrowTail[i][1] *= w; 666 arrowTail[i][0] += 10 * w; 667 arrowTail[i][1] -= 3.31 * w; 668 } 669 } else if (type === 5) { 670 w /= 10; 671 degree_fa = 3; 672 arrowTail = [ 673 [10.00,3.28], 674 [6.61,4.19], 675 [3.19,5.07], 676 [0.00,6.55], 677 [0.62,5.56], 678 [1.00,4.44], 679 [1.00,3.28], 680 [1.00,2.11], 681 [0.62,0.99], 682 [0.00,0.00], 683 [3.19,1.49], 684 [6.61,2.37], 685 [10.00,3.28] 686 ]; 687 len = arrowTail.length; 688 for (i = 0; i < len; i++) { 689 arrowTail[i][0] *= -w; 690 arrowTail[i][1] *= w; 691 arrowTail[i][0] += 10 * w; 692 arrowTail[i][1] -= 3.28 * w; 693 } 694 } else if (type === 6) { 695 w /= 10; 696 degree_fa = 3; 697 arrowTail = [ 698 [10.00,2.84], 699 [6.61,3.59], 700 [3.21,4.35], 701 [0.00,5.68], 702 [0.33,4.73], 703 [0.67,3.78], 704 [1.00,2.84], 705 [0.67,1.89], 706 [0.33,0.95], 707 [0.00,0.00], 708 [3.21,1.33], 709 [6.61,2.09], 710 [10.00,2.84] 711 ]; 712 len = arrowTail.length; 713 for (i = 0; i < len; i++) { 714 arrowTail[i][0] *= -w; 715 arrowTail[i][1] *= w; 716 arrowTail[i][0] += 10 * w; 717 arrowTail[i][1] -= 2.84 * w; 718 } 719 } else if (type === 7) { 720 w = w0; 721 degree_fa = 3; 722 arrowTail = [ 723 [0.00,10.39], 724 [2.01,6.92], 725 [5.96,5.20], 726 [10.00,5.20], 727 [5.96,5.20], 728 [2.01,3.47], 729 [0.00,0.00] 730 ]; 731 len = arrowTail.length; 732 for (i = 0; i < len; i++) { 733 arrowTail[i][0] *= -w; 734 arrowTail[i][1] *= w; 735 arrowTail[i][0] += 10 * w; 736 arrowTail[i][1] -= 5.20 * w; 737 } 738 } else { 739 arrowTail = [ 740 [ w, -w * 0.5], 741 [ 0.0, 0.0], 742 [ w, w * 0.5] 743 ]; 744 } 745 } 746 747 if (ev_la) { 748 size = a.sizeLast; 749 w = w0 * size; 750 751 type = a.typeLast; 752 type_la = type; 753 if (type === 2) { 754 arrowHead = [ 755 [ -w, -w * 0.5], 756 [ 0.0, 0.0], 757 [ -w, w * 0.5], 758 [ -w * 0.5, 0.0] 759 ]; 760 } else if (type === 3) { 761 arrowHead = [ 762 [-w / 3.0, -w * 0.5], 763 [ 0.0, -w * 0.5], 764 [ 0.0, w * 0.5], 765 [-w / 3.0, w * 0.5] 766 ]; 767 } else if (type === 4) { 768 w /= 10; 769 degree_la = 3; 770 arrowHead = [ 771 [10.00, 3.31], 772 [6.47, 3.84], 773 [2.87, 4.50], 774 [0.00, 6.63], 775 [0.67, 5.52], 776 [1.33, 4.42], 777 [2.00, 3.31], 778 [1.33, 2.21], 779 [0.67, 1.10], 780 [0.00, 0.00], 781 [2.87, 2.13], 782 [6.47, 2.79], 783 [10.00, 3.31] 784 ]; 785 len = arrowHead.length; 786 for (i = 0; i < len; i++) { 787 arrowHead[i][0] *= w; 788 arrowHead[i][1] *= w; 789 arrowHead[i][0] -= 10 * w; 790 arrowHead[i][1] -= 3.31 * w; 791 792 } 793 } else if (type === 5) { 794 w /= 10; 795 degree_la = 3; 796 arrowHead = [ 797 [10.00,3.28], 798 [6.61,4.19], 799 [3.19,5.07], 800 [0.00,6.55], 801 [0.62,5.56], 802 [1.00,4.44], 803 [1.00,3.28], 804 [1.00,2.11], 805 [0.62,0.99], 806 [0.00,0.00], 807 [3.19,1.49], 808 [6.61,2.37], 809 [10.00,3.28] 810 ]; 811 len = arrowHead.length; 812 for (i = 0; i < len; i++) { 813 arrowHead[i][0] *= w; 814 arrowHead[i][1] *= w; 815 arrowHead[i][0] -= 10 * w; 816 arrowHead[i][1] -= 3.28 * w; 817 818 } 819 } else if (type === 6) { 820 w /= 10; 821 degree_la = 3; 822 arrowHead = [ 823 [10.00,2.84], 824 [6.61,3.59], 825 [3.21,4.35], 826 [0.00,5.68], 827 [0.33,4.73], 828 [0.67,3.78], 829 [1.00,2.84], 830 [0.67,1.89], 831 [0.33,0.95], 832 [0.00,0.00], 833 [3.21,1.33], 834 [6.61,2.09], 835 [10.00,2.84] 836 ]; 837 len = arrowHead.length; 838 for (i = 0; i < len; i++) { 839 arrowHead[i][0] *= w; 840 arrowHead[i][1] *= w; 841 arrowHead[i][0] -= 10 * w; 842 arrowHead[i][1] -= 2.84 * w; 843 844 } 845 846 } else if (type === 7) { 847 w = w0; 848 degree_la = 3; 849 arrowHead = [ 850 [0.00,10.39], 851 [2.01,6.92], 852 [5.96,5.20], 853 [10.00,5.20], 854 [5.96,5.20], 855 [2.01,3.47], 856 [0.00,0.00] 857 ]; 858 len = arrowHead.length; 859 for (i = 0; i < len; i++) { 860 arrowHead[i][0] *= w; 861 arrowHead[i][1] *= w; 862 arrowHead[i][0] -= 10 * w; 863 arrowHead[i][1] -= 5.20 * w; 864 865 } 866 } else { 867 arrowHead = [ 868 [ -w, -w * 0.5], 869 [ 0.0, 0.0], 870 [ -w, w * 0.5] 871 ]; 872 } 873 } 874 875 context.save(); 876 if (this._setColor(el, 'stroke', 'fill')) { 877 this._setColor(el, 'stroke'); 878 if (ev_fa) { 879 if (type_fa === 7) { 880 doFill = false; 881 } else { 882 doFill = true; 883 } 884 this._drawPolygon(this._translateShape(this._rotateShape(arrowTail, ang1), x1, y1), degree_fa, doFill); 885 } 886 if (ev_la) { 887 if (type_la === 7) { 888 doFill = false; 889 } else { 890 doFill = true; 891 } 892 this._drawPolygon(this._translateShape(this._rotateShape(arrowHead, ang2), x2, y2), degree_la, doFill); 893 } 894 } 895 context.restore(); 896 } 897 }, 898 899 // documented in AbstractRenderer 900 drawLine: function (el) { 901 var c1_org, c2_org, 902 c1 = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords, el.board), 903 c2 = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords, el.board), 904 margin = null, 905 hl, w, arrowData; 906 907 if (!el.visPropCalc.visible) { 908 return; 909 } 910 911 hl = this._getHighlighted(el); 912 w = Type.evaluate(el.visProp[hl + 'strokewidth']); 913 arrowData = this.getArrowHeadData(el, w, hl); 914 915 if (arrowData.evFirst || arrowData.evLast) { 916 margin = -4; 917 } 918 Geometry.calcStraight(el, c1, c2, margin); 919 this.handleTouchpoints(el, c1, c2, arrowData); 920 921 c1_org = new Coords(Const.COORDS_BY_USER, c1.usrCoords, el.board); 922 c2_org = new Coords(Const.COORDS_BY_USER, c2.usrCoords, el.board); 923 924 this.getPositionArrowHead(el, c1, c2, arrowData); 925 926 this.context.beginPath(); 927 this.context.moveTo(c1.scrCoords[1], c1.scrCoords[2]); 928 this.context.lineTo(c2.scrCoords[1], c2.scrCoords[2]); 929 this._stroke(el); 930 931 if ((arrowData.evFirst/* && obj.sFirst > 0*/) || 932 (arrowData.evLast/* && obj.sLast > 0*/)) { 933 934 this.drawArrows(el, c1_org, c2_org, hl, arrowData); 935 } 936 }, 937 938 // documented in AbstractRenderer 939 updateLine: function (el) { 940 this.drawLine(el); 941 }, 942 943 // documented in AbstractRenderer 944 drawTicks: function () { 945 // this function is supposed to initialize the svg/vml nodes in the SVG/VMLRenderer. 946 // but in canvas there are no such nodes, hence we just do nothing and wait until 947 // updateTicks is called. 948 }, 949 950 // documented in AbstractRenderer 951 updateTicks: function (ticks) { 952 var i, c, x, y, 953 len = ticks.ticks.length, 954 len2, j, 955 context = this.context; 956 957 context.beginPath(); 958 for (i = 0; i < len; i++) { 959 c = ticks.ticks[i]; 960 x = c[0]; 961 y = c[1]; 962 963 // context.moveTo(x[0], y[0]); 964 // context.lineTo(x[1], y[1]); 965 len2 = x.length; 966 context.moveTo(x[0], y[0]); 967 for (j = 1; j < len2; ++j) { 968 context.lineTo(x[j], y[j]); 969 } 970 971 } 972 // Labels 973 // for (i = 0; i < len; i++) { 974 // c = ticks.ticks[i].scrCoords; 975 // if (ticks.ticks[i].major && 976 // (ticks.board.needsFullUpdate || ticks.needsRegularUpdate) && 977 // ticks.labels[i] && 978 // ticks.labels[i].visPropCalc.visible) { 979 // this.updateText(ticks.labels[i]); 980 // } 981 // } 982 context.lineCap = 'round'; 983 this._stroke(ticks); 984 }, 985 986 /* ************************** 987 * Curves 988 * **************************/ 989 990 // documented in AbstractRenderer 991 drawCurve: function (el) { 992 var hl, w, arrowData; 993 994 if (Type.evaluate(el.visProp.handdrawing)) { 995 this.updatePathStringBezierPrim(el); 996 } else { 997 this.updatePathStringPrim(el); 998 } 999 if (el.numberPoints > 1) { 1000 hl = this._getHighlighted(el); 1001 w = Type.evaluate(el.visProp[hl + 'strokewidth']); 1002 arrowData = this.getArrowHeadData(el, w, hl); 1003 if ((arrowData.evFirst/* && obj.sFirst > 0*/) || 1004 (arrowData.evLast/* && obj.sLast > 0*/)) { 1005 this.drawArrows(el, null, null, hl, arrowData); 1006 } 1007 } 1008 }, 1009 1010 // documented in AbstractRenderer 1011 updateCurve: function (el) { 1012 this.drawCurve(el); 1013 }, 1014 1015 /* ************************** 1016 * Circle related stuff 1017 * **************************/ 1018 1019 // documented in AbstractRenderer 1020 drawEllipse: function (el) { 1021 var m1 = el.center.coords.scrCoords[1], 1022 m2 = el.center.coords.scrCoords[2], 1023 sX = el.board.unitX, 1024 sY = el.board.unitY, 1025 rX = 2 * el.Radius(), 1026 rY = 2 * el.Radius(), 1027 aWidth = rX * sX, 1028 aHeight = rY * sY, 1029 aX = m1 - aWidth / 2, 1030 aY = m2 - aHeight / 2, 1031 hB = (aWidth / 2) * 0.5522848, 1032 vB = (aHeight / 2) * 0.5522848, 1033 eX = aX + aWidth, 1034 eY = aY + aHeight, 1035 mX = aX + aWidth / 2, 1036 mY = aY + aHeight / 2, 1037 context = this.context; 1038 1039 if (rX > 0.0 && rY > 0.0 && !isNaN(m1 + m2)) { 1040 context.beginPath(); 1041 context.moveTo(aX, mY); 1042 context.bezierCurveTo(aX, mY - vB, mX - hB, aY, mX, aY); 1043 context.bezierCurveTo(mX + hB, aY, eX, mY - vB, eX, mY); 1044 context.bezierCurveTo(eX, mY + vB, mX + hB, eY, mX, eY); 1045 context.bezierCurveTo(mX - hB, eY, aX, mY + vB, aX, mY); 1046 context.closePath(); 1047 this._fill(el); 1048 this._stroke(el); 1049 } 1050 }, 1051 1052 // documented in AbstractRenderer 1053 updateEllipse: function (el) { 1054 return this.drawEllipse(el); 1055 }, 1056 1057 /* ************************** 1058 * Polygon 1059 * **************************/ 1060 1061 // nothing here, using AbstractRenderer implementations 1062 1063 /* ************************** 1064 * Text related stuff 1065 * **************************/ 1066 1067 // Already documented in JXG.AbstractRenderer 1068 displayCopyright: function (str, fontSize) { 1069 var context = this.context; 1070 1071 // this should be called on EVERY update, otherwise it won't be shown after the first update 1072 context.save(); 1073 context.font = fontSize + 'px Arial'; 1074 context.fillStyle = '#aaa'; 1075 context.lineWidth = 0.5; 1076 context.fillText(str, 10, 2 + fontSize); 1077 context.restore(); 1078 }, 1079 1080 // Already documented in JXG.AbstractRenderer 1081 drawInternalText: function (el) { 1082 var ev_fs = Type.evaluate(el.visProp.fontsize), 1083 fontUnit = Type.evaluate(el.visProp.fontunit), 1084 ev_ax = el.getAnchorX(), 1085 ev_ay = el.getAnchorY(), 1086 context = this.context; 1087 1088 context.save(); 1089 if (this._setColor(el, 'stroke', 'fill') && 1090 !isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) { 1091 context.font = (ev_fs > 0 ? ev_fs : 0) + fontUnit + ' Arial'; 1092 1093 this.transformImage(el, el.transformations); 1094 if (ev_ax === 'left') { 1095 context.textAlign = 'left'; 1096 } else if (ev_ax === 'right') { 1097 context.textAlign = 'right'; 1098 } else if (ev_ax === 'middle') { 1099 context.textAlign = 'center'; 1100 } 1101 if (ev_ay === 'bottom') { 1102 context.textBaseline = 'bottom'; 1103 } else if (ev_ay === 'top') { 1104 context.textBaseline = 'top'; 1105 } else if (ev_ay === 'middle') { 1106 context.textBaseline = 'middle'; 1107 } 1108 context.fillText(el.plaintext, el.coords.scrCoords[1], el.coords.scrCoords[2]); 1109 } 1110 context.restore(); 1111 return null; 1112 }, 1113 1114 // Already documented in JXG.AbstractRenderer 1115 updateInternalText: function (el) { 1116 this.drawInternalText(el); 1117 }, 1118 1119 // documented in JXG.AbstractRenderer 1120 // Only necessary for texts 1121 setObjectStrokeColor: function (el, color, opacity) { 1122 var rgba = Type.evaluate(color), c, rgbo, 1123 o = Type.evaluate(opacity), oo, 1124 node; 1125 1126 o = (o > 0) ? o : 0; 1127 1128 if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) { 1129 return; 1130 } 1131 1132 // Check if this could be merged with _setColor 1133 1134 if (Type.exists(rgba) && rgba !== false) { 1135 // RGB, not RGBA 1136 if (rgba.length !== 9) { 1137 c = rgba; 1138 oo = o; 1139 // True RGBA, not RGB 1140 } else { 1141 rgbo = Color.rgba2rgbo(rgba); 1142 c = rgbo[0]; 1143 oo = o * rgbo[1]; 1144 } 1145 node = el.rendNode; 1146 if (el.elementClass === Const.OBJECT_CLASS_TEXT && Type.evaluate(el.visProp.display) === 'html') { 1147 node.style.color = c; 1148 node.style.opacity = oo; 1149 } 1150 } 1151 1152 el.visPropOld.strokecolor = rgba; 1153 el.visPropOld.strokeopacity = o; 1154 }, 1155 1156 /* ************************** 1157 * Image related stuff 1158 * **************************/ 1159 1160 // Already documented in JXG.AbstractRenderer 1161 drawImage: function (el) { 1162 el.rendNode = new Image(); 1163 // Store the file name of the image. 1164 // Before, this was done in el.rendNode.src 1165 // But there, the file name is expanded to 1166 // the full url. This may be different from 1167 // the url computed in updateImageURL(). 1168 el._src = ''; 1169 this.updateImage(el); 1170 }, 1171 1172 // Already documented in JXG.AbstractRenderer 1173 updateImage: function (el) { 1174 var context = this.context, 1175 o = Type.evaluate(el.visProp.fillopacity), 1176 paintImg = Type.bind(function () { 1177 el.imgIsLoaded = true; 1178 if (el.size[0] <= 0 || el.size[1] <= 0) { 1179 return; 1180 } 1181 context.save(); 1182 context.globalAlpha = o; 1183 // If det(el.transformations)=0, FireFox 3.6. breaks down. 1184 // This is tested in transformImage 1185 this.transformImage(el, el.transformations); 1186 context.drawImage(el.rendNode, 1187 el.coords.scrCoords[1], 1188 el.coords.scrCoords[2] - el.size[1], 1189 el.size[0], 1190 el.size[1]); 1191 context.restore(); 1192 }, this); 1193 1194 if (this.updateImageURL(el)) { 1195 el.rendNode.onload = paintImg; 1196 } else { 1197 if (el.imgIsLoaded) { 1198 paintImg(); 1199 } 1200 } 1201 }, 1202 1203 // Already documented in JXG.AbstractRenderer 1204 transformImage: function (el, t) { 1205 var m, len = t.length, 1206 ctx = this.context; 1207 1208 if (len > 0) { 1209 m = this.joinTransforms(el, t); 1210 if (Math.abs(Numerics.det(m)) >= Mat.eps) { 1211 ctx.transform(m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]); 1212 } 1213 } 1214 }, 1215 1216 // Already documented in JXG.AbstractRenderer 1217 updateImageURL: function (el) { 1218 var url; 1219 1220 url = Type.evaluate(el.url); 1221 if (el._src !== url) { 1222 el.imgIsLoaded = false; 1223 el.rendNode.src = url; 1224 el._src = url; 1225 return true; 1226 } 1227 1228 return false; 1229 }, 1230 1231 /* ************************** 1232 * Render primitive objects 1233 * **************************/ 1234 1235 // documented in AbstractRenderer 1236 remove: function (shape) { 1237 // sounds odd for a pixel based renderer but we need this for html texts 1238 if (Type.exists(shape) && Type.exists(shape.parentNode)) { 1239 shape.parentNode.removeChild(shape); 1240 } 1241 }, 1242 1243 // documented in AbstractRenderer 1244 updatePathStringPrim: function (el) { 1245 var i, scr, scr1, scr2, len, 1246 symbm = 'M', 1247 symbl = 'L', 1248 symbc = 'C', 1249 nextSymb = symbm, 1250 maxSize = 5000.0, 1251 context = this.context; 1252 1253 if (el.numberPoints <= 0) { 1254 return; 1255 } 1256 1257 len = Math.min(el.points.length, el.numberPoints); 1258 context.beginPath(); 1259 1260 if (el.bezierDegree === 1) { 1261 /* 1262 if (isNotPlot && el.board.options.curve.RDPsmoothing) { 1263 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5); 1264 } 1265 */ 1266 1267 for (i = 0; i < len; i++) { 1268 scr = el.points[i].scrCoords; 1269 1270 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 1271 nextSymb = symbm; 1272 } else { 1273 // Chrome has problems with values being too far away. 1274 if (scr[1] > maxSize) { 1275 scr[1] = maxSize; 1276 } else if (scr[1] < -maxSize) { 1277 scr[1] = -maxSize; 1278 } 1279 1280 if (scr[2] > maxSize) { 1281 scr[2] = maxSize; 1282 } else if (scr[2] < -maxSize) { 1283 scr[2] = -maxSize; 1284 } 1285 1286 if (nextSymb === symbm) { 1287 context.moveTo(scr[1], scr[2]); 1288 } else { 1289 context.lineTo(scr[1], scr[2]); 1290 } 1291 nextSymb = symbl; 1292 } 1293 } 1294 } else if (el.bezierDegree === 3) { 1295 i = 0; 1296 while (i < len) { 1297 scr = el.points[i].scrCoords; 1298 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 1299 nextSymb = symbm; 1300 } else { 1301 if (nextSymb === symbm) { 1302 context.moveTo(scr[1], scr[2]); 1303 } else { 1304 i += 1; 1305 scr1 = el.points[i].scrCoords; 1306 i += 1; 1307 scr2 = el.points[i].scrCoords; 1308 context.bezierCurveTo(scr[1], scr[2], scr1[1], scr1[2], scr2[1], scr2[2]); 1309 } 1310 nextSymb = symbc; 1311 } 1312 i += 1; 1313 } 1314 } 1315 context.lineCap = 'round'; 1316 this._fill(el); 1317 this._stroke(el); 1318 }, 1319 1320 // Already documented in JXG.AbstractRenderer 1321 updatePathStringBezierPrim: function (el) { 1322 var i, j, k, scr, lx, ly, len, 1323 symbm = 'M', 1324 symbl = 'C', 1325 nextSymb = symbm, 1326 maxSize = 5000.0, 1327 f = Type.evaluate(el.visProp.strokewidth), 1328 isNoPlot = (Type.evaluate(el.visProp.curvetype) !== 'plot'), 1329 context = this.context; 1330 1331 if (el.numberPoints <= 0) { 1332 return; 1333 } 1334 1335 if (isNoPlot && el.board.options.curve.RDPsmoothing) { 1336 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5); 1337 } 1338 1339 len = Math.min(el.points.length, el.numberPoints); 1340 context.beginPath(); 1341 1342 for (j = 1; j < 3; j++) { 1343 nextSymb = symbm; 1344 for (i = 0; i < len; i++) { 1345 scr = el.points[i].scrCoords; 1346 1347 if (isNaN(scr[1]) || isNaN(scr[2])) { // PenUp 1348 nextSymb = symbm; 1349 } else { 1350 // Chrome has problems with values being too far away. 1351 if (scr[1] > maxSize) { 1352 scr[1] = maxSize; 1353 } else if (scr[1] < -maxSize) { 1354 scr[1] = -maxSize; 1355 } 1356 1357 if (scr[2] > maxSize) { 1358 scr[2] = maxSize; 1359 } else if (scr[2] < -maxSize) { 1360 scr[2] = -maxSize; 1361 } 1362 1363 if (nextSymb === symbm) { 1364 context.moveTo(scr[1], scr[2]); 1365 } else { 1366 k = 2 * j; 1367 context.bezierCurveTo( 1368 (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)), 1369 (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)), 1370 (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)), 1371 (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)), 1372 scr[1], 1373 scr[2] 1374 ); 1375 } 1376 nextSymb = symbl; 1377 lx = scr[1]; 1378 ly = scr[2]; 1379 } 1380 } 1381 } 1382 context.lineCap = 'round'; 1383 this._fill(el); 1384 this._stroke(el); 1385 }, 1386 1387 // documented in AbstractRenderer 1388 updatePolygonPrim: function (node, el) { 1389 var scrCoords, i, j, 1390 len = el.vertices.length, 1391 context = this.context, 1392 isReal = true; 1393 1394 if (len <= 0 || !el.visPropCalc.visible) { 1395 return; 1396 } 1397 if (el.elType === 'polygonalchain') { 1398 len++; 1399 } 1400 1401 context.beginPath(); 1402 i = 0; 1403 while (!el.vertices[i].isReal && i < len - 1) { 1404 i++; 1405 isReal = false; 1406 } 1407 scrCoords = el.vertices[i].coords.scrCoords; 1408 context.moveTo(scrCoords[1], scrCoords[2]); 1409 1410 for (j = i; j < len - 1; j++) { 1411 if (!el.vertices[j].isReal) { 1412 isReal = false; 1413 } 1414 scrCoords = el.vertices[j].coords.scrCoords; 1415 context.lineTo(scrCoords[1], scrCoords[2]); 1416 } 1417 context.closePath(); 1418 1419 if (isReal) { 1420 this._fill(el); // The edges of a polygon are displayed separately (as segments). 1421 } 1422 }, 1423 1424 // ************************** Set Attributes ************************* 1425 1426 // Already documented in JXG.AbstractRenderer 1427 display: function(el, val) { 1428 if (el && el.rendNode) { 1429 el.visPropOld.visible = val; 1430 if (val) { 1431 el.rendNode.style.visibility = "inherit"; 1432 } else { 1433 el.rendNode.style.visibility = "hidden"; 1434 } 1435 } 1436 }, 1437 1438 // documented in AbstractRenderer 1439 show: function (el) { 1440 JXG.deprecated('Board.renderer.show()', 'Board.renderer.display()'); 1441 1442 if (Type.exists(el.rendNode)) { 1443 el.rendNode.style.visibility = "inherit"; 1444 } 1445 }, 1446 1447 // documented in AbstractRenderer 1448 hide: function (el) { 1449 JXG.deprecated('Board.renderer.hide()', 'Board.renderer.display()'); 1450 1451 if (Type.exists(el.rendNode)) { 1452 el.rendNode.style.visibility = "hidden"; 1453 } 1454 }, 1455 1456 // documented in AbstractRenderer 1457 setGradient: function (el) { 1458 var // col, 1459 op; 1460 1461 op = Type.evaluate(el.visProp.fillopacity); 1462 op = (op > 0) ? op : 0; 1463 1464 // col = Type.evaluate(el.visProp.fillcolor); 1465 }, 1466 1467 // documented in AbstractRenderer 1468 setShadow: function (el) { 1469 if (el.visPropOld.shadow === el.visProp.shadow) { 1470 return; 1471 } 1472 1473 // not implemented yet 1474 // we simply have to redraw the element 1475 // probably the best way to do so would be to call el.updateRenderer(), i think. 1476 1477 el.visPropOld.shadow = el.visProp.shadow; 1478 }, 1479 1480 // documented in AbstractRenderer 1481 highlight: function (obj) { 1482 if (obj.elementClass === Const.OBJECT_CLASS_TEXT && Type.evaluate(obj.visProp.display) === 'html') { 1483 this.updateTextStyle(obj, true); 1484 } else { 1485 obj.board.prepareUpdate(); 1486 obj.board.renderer.suspendRedraw(obj.board); 1487 obj.board.updateRenderer(); 1488 obj.board.renderer.unsuspendRedraw(); 1489 } 1490 return this; 1491 }, 1492 1493 // documented in AbstractRenderer 1494 noHighlight: function (obj) { 1495 if (obj.elementClass === Const.OBJECT_CLASS_TEXT && Type.evaluate(obj.visProp.display) === 'html') { 1496 this.updateTextStyle(obj, false); 1497 } else { 1498 obj.board.prepareUpdate(); 1499 obj.board.renderer.suspendRedraw(obj.board); 1500 obj.board.updateRenderer(); 1501 obj.board.renderer.unsuspendRedraw(); 1502 } 1503 return this; 1504 }, 1505 1506 /* ************************** 1507 * renderer control 1508 * **************************/ 1509 1510 // documented in AbstractRenderer 1511 suspendRedraw: function (board) { 1512 this.context.save(); 1513 this.context.clearRect(0, 0, this.canvasRoot.width, this.canvasRoot.height); 1514 1515 if (board && board.attr.showcopyright) { 1516 this.displayCopyright(JXG.licenseText, 12); 1517 } 1518 }, 1519 1520 // documented in AbstractRenderer 1521 unsuspendRedraw: function () { 1522 this.context.restore(); 1523 }, 1524 1525 // document in AbstractRenderer 1526 resize: function (w, h) { 1527 if (this.container) { 1528 this.canvasRoot.style.width = parseFloat(w) + 'px'; 1529 this.canvasRoot.style.height = parseFloat(h) + 'px'; 1530 1531 this.canvasRoot.setAttribute('width', (2 * parseFloat(w)) + 'px'); 1532 this.canvasRoot.setAttribute('height',(2 * parseFloat(h)) + 'px'); 1533 } else { 1534 this.canvasRoot.width = 2 * parseFloat(w); 1535 this.canvasRoot.height = 2 * parseFloat(h); 1536 } 1537 this.context = this.canvasRoot.getContext('2d'); 1538 // The width and height of the canvas is set to twice the CSS values, 1539 // followed by an appropiate scaling. 1540 // See http://stackoverflow.com/questions/22416462/canvas-element-with-blurred-lines 1541 this.context.scale(2, 2); 1542 }, 1543 1544 removeToInsertLater: function () { 1545 return function () {}; 1546 } 1547 }); 1548 1549 return JXG.CanvasRenderer; 1550 }); 1551