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