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