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