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