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