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