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