      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, MathJax: true, document: true */
     33 /*jslint nomen: true, plusplus: true, newcap:true*/
     34
     35 import JXG from "../jxg.js";
     36 import Options from "../options.js";
     37 import AbstractRenderer from "./abstract.js";
     38 import Const from "../base/constants.js";
     39 import Type from "../utils/type.js";
     40 import Color from "../utils/color.js";
     41 import Base64 from "../utils/base64.js";
     42 import Numerics from "../math/numerics.js";
     43
     44 /**
     45  * Uses SVG to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
     46  * @class JXG.SVGRenderer
     47  * @augments JXG.AbstractRenderer
     48  * @param {Node} container Reference to a DOM node containing the board.
     49  * @param {Object} dim The dimensions of the board
     50  * @param {Number} dim.width
     51  * @param {Number} dim.height
     52  * @see JXG.AbstractRenderer
     53  */
     54 JXG.SVGRenderer = function (container, dim) {
     55     var i;
     56
     57     // docstring in AbstractRenderer
     58     this.type = "svg";
     59
     60     this.isIE =
     61         navigator.appVersion.indexOf("MSIE") !== -1 || navigator.userAgent.match(/Trident\//);
     62
     63     /**
     64      * SVG root node
     65      * @type Node
     66      */
     67     this.svgRoot = null;
     68
     69     /**
     70      * The SVG Namespace used in JSXGraph.
     71      * @see http://www.w3.org/TR/SVG2/
     72      * @type String
     73      * @default http://www.w3.org/2000/svg
     74      */
     75     this.svgNamespace = "http://www.w3.org/2000/svg";
     76
     77     /**
     78      * The xlink namespace. This is used for images.
     79      * @see http://www.w3.org/TR/xlink/
     80      * @type String
     81      * @default http://www.w3.org/1999/xlink
     82      */
     83     this.xlinkNamespace = "http://www.w3.org/1999/xlink";
     84
     85     // container is documented in AbstractRenderer.
     86     // Type node
     87     this.container = container;
     88
     89     // prepare the div container and the svg root node for use with JSXGraph
     90     this.container.style.MozUserSelect = "none";
     91     this.container.style.userSelect = "none";
     92
     93     this.container.style.overflow = "hidden";
     94     if (this.container.style.position === "") {
     95         this.container.style.position = "relative";
     96     }
     97
     98     this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg");
     99     this.svgRoot.style.overflow = "hidden";
    100     this.svgRoot.style.display = "block";
    101     this.resize(dim.width, dim.height);
    102
    103     //this.svgRoot.setAttributeNS(null, 'shape-rendering', 'crispEdge'); //'optimizeQuality'); //geometricPrecision');
    104
    105     this.container.appendChild(this.svgRoot);
    106
    107     /**
    108      * The <tt>defs</tt> element is a container element to reference reusable SVG elements.
    109      * @type Node
    110      * @see https://www.w3.org/TR/SVG2/struct.html#DefsElement
    111      */
    112     this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, "defs");
    113     this.svgRoot.appendChild(this.defs);
    114
    115     /**
    116      * Filters are used to apply shadows.
    117      * @type Node
    118      * @see https://www.w3.org/TR/SVG2/struct.html#DefsElement
    119      */
    120     /**
    121      * Create an SVG shadow filter. If the object's RGB color is [r,g,b], it's opacity is op, and
    122      * the parameter color is given as [r', g', b'] with opacity op'
    123      * the shadow will have RGB color [blend*r + r', blend*g + g', blend*b + b'] and the opacity will be equal to op * op'.
    124      * Further, blur and offset can be adjusted.
    125      *
    126      * The shadow color is [r*ble
    127      * @param {String} id Node is of the filter.
    128      * @param {Array|String} rgb RGB value for the blend color or the string 'none' for default values. Default 'black'.
    129      * @param {Number} opacity Value between 0 and 1, default is 1.
    130      * @param {Number} blend  Value between 0 and 1, default is 0.1.
    131      * @param {Number} blur  Default: 3
    132      * @param {Array} offset [dx, dy]. Default is [5,5].
    133      * @returns DOM node to be added to this.defs.
    134      * @private
    135      */
    136     this.createShadowFilter = function (id, rgb, opacity, blend, blur, offset) {
    137         var filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter'),
    138             feOffset, feColor, feGaussianBlur, feBlend,
    139             mat;
    140
    141         filter.setAttributeNS(null, 'id', id);
    142         filter.setAttributeNS(null, 'width', '300%');
    143         filter.setAttributeNS(null, 'height', '300%');
    144         filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse');
    145
    146         feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset');
    147         feOffset.setAttributeNS(null, 'in', 'SourceGraphic'); // b/w: SourceAlpha, Color: SourceGraphic
    148         feOffset.setAttributeNS(null, 'result', 'offOut');
    149         feOffset.setAttributeNS(null, 'dx', offset[0]);
    150         feOffset.setAttributeNS(null, 'dy', offset[1]);
    151         filter.appendChild(feOffset);
    152
    153         feColor = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feColorMatrix');
    154         feColor.setAttributeNS(null, 'in', 'offOut');
    155         feColor.setAttributeNS(null, 'result', 'colorOut');
    156         feColor.setAttributeNS(null, 'type', 'matrix');
    157         // See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix
    158         if (rgb === 'none' || !Type.isArray(rgb) || rgb.length < 3) {
    159             feColor.setAttributeNS(null, 'values', '0.1 0 0 0 0  0 0.1 0 0 0  0 0 0.1 0 0  0 0 0 ' + opacity + ' 0');
    160         } else {
    161             rgb[0] /= 255;
    162             rgb[1] /= 255;
    163             rgb[2] /= 255;
    164             mat = blend + ' 0 0 0 ' + rgb[0] +
    165                 '  0 ' + blend + ' 0 0 ' + rgb[1] +
    166                 '  0 0 ' + blend + ' 0 ' + rgb[2] +
    167                 '  0 0 0 ' + opacity + ' 0';
    168             feColor.setAttributeNS(null, 'values', mat);
    169         }
    170         filter.appendChild(feColor);
    171
    172         feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur');
    173         feGaussianBlur.setAttributeNS(null, 'in', 'colorOut');
    174         feGaussianBlur.setAttributeNS(null, 'result', 'blurOut');
    175         feGaussianBlur.setAttributeNS(null, 'stdDeviation', blur);
    176         filter.appendChild(feGaussianBlur);
    177
    178         feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend');
    179         feBlend.setAttributeNS(null, 'in', 'SourceGraphic');
    180         feBlend.setAttributeNS(null, 'in2', 'blurOut');
    181         feBlend.setAttributeNS(null, 'mode', 'normal');
    182         filter.appendChild(feBlend);
    183
    184         return filter;
    185     };
    186
    187     /**
    188      * Create a "unique" string id from the arguments of the function.
    189      * Concatenate all arguments by "_".
    190      * "Unique" is achieved by simply prepending the container id.
    191      * Do not escape the string.
    192      *
    193      * If the id is used in an "url()" call it must be eascaped.
    194      *
    195      * @params {String} one or strings which will be concatenated.
    196      * @return {String}
    197      * @private
    198      */
    199     this.uniqName = function () {
    200         return this.container.id + '_' +
    201             Array.prototype.slice.call(arguments).join('_');
    202     };
    203
    204     /**
    205      * Combine arguments to a string, joined by empty string.
    206      * Masks the container id with CSS.escape.
    207      *
    208      * @params {String} str variable number of strings
    209      * @returns String
    210      * @see JXG.SVGRenderer#toURL
    211      * @private
    212      * @example
    213      * this.toStr('aaa', '_', 'bbb', 'TriangleEnd')
    214      * // Output:
    215      * // xxx_bbbTriangleEnd
    216      */
    217     this.toStr = function() {
    218         // ES6 would be [...arguments].join()
    219         var str = Array.prototype.slice.call(arguments).join('');
    220         // Mask special symbols like '/' and '\' in id
    221         if (Type.exists(CSS) && Type.exists(CSS.escape)) {
    222             str = CSS.escape(str);
    223         }
    224         return str;
    225     };
    226
    227     /**
    228      * Combine arguments to an URL string of the form
    229      * url(#...)
    230      * Masks the container id. Calls {@link JXG.SVGRenderer#toStr}.
    231      *
    232      * @params {String} str variable number of strings
    233      * @returns URL string
    234      * @see JXG.SVGRenderer#toStr
    235      * @private
    236      * @example
    237      * this.toURL('aaa', '_', 'bbb', 'TriangleEnd')
    238      * // Output:
    239      * // url(#xxx_bbbTriangleEnd)
    240      */
    241     this.toURL = function () {
    242         return 'url(#' +
    243             this.toStr.apply(this, arguments) + // Pass the arguments to toStr
    244             ')';
    245     };
    246
    247     /* Default shadow filter */
    248     this.defs.appendChild(this.createShadowFilter(this.uniqName('f1'), 'none', 1, 0.1, 3, [5, 5]));
    249
    250     /**
    251      * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front
    252      * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented
    253      * there, too. The higher the number, the "more on top" are the elements on this layer.
    254      * @type Array
    255      */
    256     this.layer = [];
    257     for (i = 0; i < Options.layer.numlayers; i++) {
    258         this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g');
    259         this.svgRoot.appendChild(this.layer[i]);
    260     }
    261
    262     try {
    263         this.foreignObjLayer = this.container.ownerDocument.createElementNS(
    264             this.svgNamespace,
    265             "foreignObject"
    266         );
    267         this.foreignObjLayer.setAttribute("display", "none");
    268         this.foreignObjLayer.setAttribute("x", 0);
    269         this.foreignObjLayer.setAttribute("y", 0);
    270         this.foreignObjLayer.setAttribute("width", "100%");
    271         this.foreignObjLayer.setAttribute("height", "100%");
    272         this.foreignObjLayer.setAttribute("id", this.uniqName('foreignObj'));
    273         this.svgRoot.appendChild(this.foreignObjLayer);
    274         this.supportsForeignObject = true;
    275     } catch (e) {
    276         this.supportsForeignObject = false;
    277     }
    278 };
    279
    280 JXG.SVGRenderer.prototype = new AbstractRenderer();
    281
    282 JXG.extend(
    283     JXG.SVGRenderer.prototype,
    284     /** @lends JXG.SVGRenderer.prototype */ {
    285         /**
    286          * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag.
    287          * @private
    288          * @param {JXG.GeometryElement} el A JSXGraph element, preferably one that can have an arrow attached.
    289          * @param {String} [idAppendix=''] A string that is added to the node's id.
    290          * @returns {Node} Reference to the node added to the DOM.
    291          */
    292         _createArrowHead: function (el, idAppendix, type) {
    293             var node2,
    294                 node3,
    295                 id = el.id + "Triangle",
    296                 //type = null,
    297                 v,
    298                 h;
    299
    300             if (Type.exists(idAppendix)) {
    301                 id += idAppendix;
    302             }
    303             if (Type.exists(type)) {
    304                 id += type;
    305             }
    306             node2 = this.createPrim("marker", id);
    307
    308             node2.setAttributeNS(null, "stroke", Type.evaluate(el.visProp.strokecolor));
    309             node2.setAttributeNS(
    310                 null,
    311                 "stroke-opacity",
    312                 Type.evaluate(el.visProp.strokeopacity)
    313             );
    314             node2.setAttributeNS(null, "fill", Type.evaluate(el.visProp.strokecolor));
    315             node2.setAttributeNS(null, "fill-opacity", Type.evaluate(el.visProp.strokeopacity));
    316             node2.setAttributeNS(null, "stroke-width", 0); // this is the stroke-width of the arrow head.
    317             // Should be zero to simplify the calculations
    318
    319             node2.setAttributeNS(null, "orient", "auto");
    320             node2.setAttributeNS(null, "markerUnits", "strokeWidth"); // 'strokeWidth' 'userSpaceOnUse');
    321
    322             /*
    323                Types 1, 2:
    324                The arrow head is an isosceles triangle with base length 10 and height 10.
    325
    326                Type 3:
    327                A rectangle
    328
    329                Types 4, 5, 6:
    330                Defined by Bezier curves from mp_arrowheads.html
    331
    332                In any case but type 3 the arrow head is 10 units long,
    333                type 3 is 10 units high.
    334                These 10 units are scaled to strokeWidth * arrowSize pixels, see
    335                this._setArrowWidth().
    336
    337                See also abstractRenderer.updateLine() where the line path is shortened accordingly.
    338
    339                Changes here are also necessary in setArrowWidth().
    340
    341                So far, lines with arrow heads are shortenend to avoid overlapping of
    342                arrow head and line. This is not the case for curves, yet.
    343                Therefore, the offset refX has to be adapted to the path type.
    344             */
    345             node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, "path");
    346             h = 5;
    347             if (idAppendix === "Start") {
    348                 // First arrow
    349                 //type = a.typeFirst;
    350                 // if (JXG.exists(ev_fa.type)) {
    351                 //     type = Type.evaluate(ev_fa.type);
    352                 // }
    353
    354                 v = 0;
    355                 if (type === 2) {
    356                     node3.setAttributeNS(null, "d", "M 10,0 L 0,5 L 10,10 L 5,5 z");
    357                 } else if (type === 3) {
    358                     node3.setAttributeNS(null, "d", "M 0,0 L 3.33,0 L 3.33,10 L 0,10 z");
    359                 } else if (type === 4) {
    360                     // insetRatio:0.8 tipAngle:45 wingCurve:15 tailCurve:0
    361                     h = 3.31;
    362                     node3.setAttributeNS(
    363                         null,
    364                         "d",
    365                         "M 0.00,3.31 C 3.53,3.84 7.13,4.50 10.00,6.63 C 9.33,5.52 8.67,4.42 8.00,3.31 C 8.67,2.21 9.33,1.10 10.00,0.00 C 7.13,2.13 3.53,2.79 0.00,3.31"
    366                     );
    367                 } else if (type === 5) {
    368                     // insetRatio:0.9 tipAngle:40 wingCurve:5 tailCurve:15
    369                     h = 3.28;
    370                     node3.setAttributeNS(
    371                         null,
    372                         "d",
    373                         "M 0.00,3.28 C 3.39,4.19 6.81,5.07 10.00,6.55 C 9.38,5.56 9.00,4.44 9.00,3.28 C 9.00,2.11 9.38,0.99 10.00,0.00 C 6.81,1.49 3.39,2.37 0.00,3.28"
    374                     );
    375                 } else if (type === 6) {
    376                     // insetRatio:0.9 tipAngle:35 wingCurve:5 tailCurve:0
    377                     h = 2.84;
    378                     node3.setAttributeNS(
    379                         null,
    380                         "d",
    381                         "M 0.00,2.84 C 3.39,3.59 6.79,4.35 10.00,5.68 C 9.67,4.73 9.33,3.78 9.00,2.84 C 9.33,1.89 9.67,0.95 10.00,0.00 C 6.79,1.33 3.39,2.09 0.00,2.84"
    382                     );
    383                 } else if (type === 7) {
    384                     // insetRatio:0.9 tipAngle:60 wingCurve:30 tailCurve:0
    385                     h = 5.2;
    386                     node3.setAttributeNS(
    387                         null,
    388                         "d",
    389                         "M 0.00,5.20 C 4.04,5.20 7.99,6.92 10.00,10.39 M 10.00,0.00 C 7.99,3.47 4.04,5.20 0.00,5.20"
    390                     );
    391                 } else {
    392                     // type == 1 or > 6
    393                     node3.setAttributeNS(null, "d", "M 10,0 L 0,5 L 10,10 z");
    394                 }
    395                 if (
    396                     // !Type.exists(el.rendNode.getTotalLength) &&
    397                     el.elementClass === Const.OBJECT_CLASS_LINE
    398                 ) {
    399                     if (type === 2) {
    400                         v = 4.9;
    401                     } else if (type === 3) {
    402                         v = 3.3;
    403                     } else if (type === 4 || type === 5 || type === 6) {
    404                         v = 6.66;
    405                     } else if (type === 7) {
    406                         v = 0.0;
    407                     } else {
    408                         v = 10.0;
    409                     }
    410                 }
    411             } else {
    412                 // Last arrow
    413                 // if (JXG.exists(ev_la.type)) {
    414                 //     type = Type.evaluate(ev_la.type);
    415                 // }
    416                 //type = a.typeLast;
    417
    418                 v = 10.0;
    419                 if (type === 2) {
    420                     node3.setAttributeNS(null, "d", "M 0,0 L 10,5 L 0,10 L 5,5 z");
    421                 } else if (type === 3) {
    422                     v = 3.3;
    423                     node3.setAttributeNS(null, "d", "M 0,0 L 3.33,0 L 3.33,10 L 0,10 z");
    424                 } else if (type === 4) {
    425                     // insetRatio:0.8 tipAngle:45 wingCurve:15 tailCurve:0
    426                     h = 3.31;
    427                     node3.setAttributeNS(
    428                         null,
    429                         "d",
    430                         "M 10.00,3.31 C 6.47,3.84 2.87,4.50 0.00,6.63 C 0.67,5.52 1.33,4.42 2.00,3.31 C 1.33,2.21 0.67,1.10 0.00,0.00 C 2.87,2.13 6.47,2.79 10.00,3.31"
    431                     );
    432                 } else if (type === 5) {
    433                     // insetRatio:0.9 tipAngle:40 wingCurve:5 tailCurve:15
    434                     h = 3.28;
    435                     node3.setAttributeNS(
    436                         null,
    437                         "d",
    438                         "M 10.00,3.28 C 6.61,4.19 3.19,5.07 0.00,6.55 C 0.62,5.56 1.00,4.44 1.00,3.28 C 1.00,2.11 0.62,0.99 0.00,0.00 C 3.19,1.49 6.61,2.37 10.00,3.28"
    439                     );
    440                 } else if (type === 6) {
    441                     // insetRatio:0.9 tipAngle:35 wingCurve:5 tailCurve:0
    442                     h = 2.84;
    443                     node3.setAttributeNS(
    444                         null,
    445                         "d",
    446                         "M 10.00,2.84 C 6.61,3.59 3.21,4.35 0.00,5.68 C 0.33,4.73 0.67,3.78 1.00,2.84 C 0.67,1.89 0.33,0.95 0.00,0.00 C 3.21,1.33 6.61,2.09 10.00,2.84"
    447                     );
    448                 } else if (type === 7) {
    449                     // insetRatio:0.9 tipAngle:60 wingCurve:30 tailCurve:0
    450                     h = 5.2;
    451                     node3.setAttributeNS(
    452                         null,
    453                         "d",
    454                         "M 10.00,5.20 C 5.96,5.20 2.01,6.92 0.00,10.39 M 0.00,0.00 C 2.01,3.47 5.96,5.20 10.00,5.20"
    455                     );
    456                 } else {
    457                     // type == 1 or > 6
    458                     node3.setAttributeNS(null, "d", "M 0,0 L 10,5 L 0,10 z");
    459                 }
    460                 if (
    461                     // !Type.exists(el.rendNode.getTotalLength) &&
    462                     el.elementClass === Const.OBJECT_CLASS_LINE
    463                 ) {
    464                     if (type === 2) {
    465                         v = 5.1;
    466                     } else if (type === 3) {
    467                         v = 0.02;
    468                     } else if (type === 4 || type === 5 || type === 6) {
    469                         v = 3.33;
    470                     } else if (type === 7) {
    471                         v = 10.0;
    472                     } else {
    473                         v = 0.05;
    474                     }
    475                 }
    476             }
    477             if (type === 7) {
    478                 node2.setAttributeNS(null, "fill", "none");
    479                 node2.setAttributeNS(null, "stroke-width", 1); // this is the stroke-width of the arrow head.
    480             }
    481             node2.setAttributeNS(null, "refY", h);
    482             node2.setAttributeNS(null, "refX", v);
    483
    484             node2.appendChild(node3);
    485             return node2;
    486         },
    487
    488         /**
    489          * Updates color of an arrow DOM node.
    490          * @param {Node} node The arrow node.
    491          * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green.
    492          * @param {Number} opacity
    493          * @param {JXG.GeometryElement} el The element the arrows are to be attached to
    494          */
    495         _setArrowColor: function (node, color, opacity, el, type) {
    496             if (node) {
    497                 if (Type.isString(color)) {
    498                     if (type !== 7) {
    499                         this._setAttribute(function () {
    500                             node.setAttributeNS(null, "stroke", color);
    501                             node.setAttributeNS(null, "fill", color);
    502                             node.setAttributeNS(null, "stroke-opacity", opacity);
    503                             node.setAttributeNS(null, "fill-opacity", opacity);
    504                         }, el.visPropOld.fillcolor);
    505                     } else {
    506                         this._setAttribute(function () {
    507                             node.setAttributeNS(null, "fill", "none");
    508                             node.setAttributeNS(null, "stroke", color);
    509                             node.setAttributeNS(null, "stroke-opacity", opacity);
    510                         }, el.visPropOld.fillcolor);
    511                     }
    512                 }
    513
    514                 if (this.isIE) {
    515                     el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode);
    516                 }
    517             }
    518         },
    519
    520         // Already documented in JXG.AbstractRenderer
    521         _setArrowWidth: function (node, width, parentNode, size) {
    522             var s, d;
    523
    524             if (node) {
    525                 // if (width === 0) {
    526                 //     // display:none does not work well in webkit
    527                 //     node.setAttributeNS(null, 'display', 'none');
    528                 // } else {
    529                 s = width;
    530                 d = s * size;
    531                 node.setAttributeNS(null, "viewBox", 0 + " " + 0 + " " + s * 10 + " " + s * 10);
    532                 node.setAttributeNS(null, "markerHeight", d);
    533                 node.setAttributeNS(null, "markerWidth", d);
    534                 node.setAttributeNS(null, "display", "inherit");
    535                 // }
    536
    537                 if (this.isIE) {
    538                     parentNode.parentNode.insertBefore(parentNode, parentNode);
    539                 }
    540             }
    541         },
    542
    543         /* ******************************** *
    544          *  This renderer does not need to
    545          *  override draw/update* methods
    546          *  since it provides draw/update*Prim
    547          *  methods except for some cases like
    548          *  internal texts or images.
    549          * ******************************** */
    550
    551         /* **************************
    552          *    Lines
    553          * **************************/
    554
    555         // documented in AbstractRenderer
    556         updateTicks: function (ticks) {
    557             var i,
    558                 j,
    559                 c,
    560                 node,
    561                 x,
    562                 y,
    563                 tickStr = "",
    564                 len = ticks.ticks.length,
    565                 len2,
    566                 str,
    567                 isReal = true;
    568
    569             for (i = 0; i < len; i++) {
    570                 c = ticks.ticks[i];
    571                 x = c[0];
    572                 y = c[1];
    573
    574                 len2 = x.length;
    575                 str = " M " + x[0] + " " + y[0];
    576                 if (!Type.isNumber(x[0])) {
    577                     isReal = false;
    578                 }
    579                 for (j = 1; isReal && j < len2; ++j) {
    580                     if (Type.isNumber(x[j])) {
    581                         str += " L " + x[j] + " " + y[j];
    582                     } else {
    583                         isReal = false;
    584                     }
    585                 }
    586                 if (isReal) {
    587                     tickStr += str;
    588                 }
    589             }
    590
    591             node = ticks.rendNode;
    592
    593             if (!Type.exists(node)) {
    594                 node = this.createPrim("path", ticks.id);
    595                 this.appendChildPrim(node, Type.evaluate(ticks.visProp.layer));
    596                 ticks.rendNode = node;
    597             }
    598
    599             node.setAttributeNS(null, "stroke", Type.evaluate(ticks.visProp.strokecolor));
    600             node.setAttributeNS(null, "fill", "none");
    601             // node.setAttributeNS(null, 'fill', Type.evaluate(ticks.visProp.fillcolor));
    602             // node.setAttributeNS(null, 'fill-opacity', Type.evaluate(ticks.visProp.fillopacity));
    603             node.setAttributeNS(
    604                 null,
    605                 "stroke-opacity",
    606                 Type.evaluate(ticks.visProp.strokeopacity)
    607             );
    608             node.setAttributeNS(null, "stroke-width", Type.evaluate(ticks.visProp.strokewidth));
    609             this.updatePathPrim(node, tickStr, ticks.board);
    610             this.setObjectViewport(ticks);
    611         },
    612
    613         /* **************************
    614          *    Text related stuff
    615          * **************************/
    616
    617         // Already documented in JXG.AbstractRenderer
    618         displayCopyright: function (str, fontsize) {
    619             var node = this.createPrim("text", 'licenseText'),
    620                 t;
    621             node.setAttributeNS(null, 'x', '20px');
    622             node.setAttributeNS(null, 'y', 2 + fontsize + 'px');
    623             node.setAttributeNS(null, 'style', 'font-family:Arial,Helvetica,sans-serif; font-size:' +
    624                 fontsize + 'px; fill:#356AA0;  opacity:0.3;');
    625             t = this.container.ownerDocument.createTextNode(str);
    626             node.appendChild(t);
    627             this.appendChildPrim(node, 0);
    628         },
    629
    630         // Already documented in JXG.AbstractRenderer
    631         drawInternalText: function (el) {
    632             var node = this.createPrim("text", el.id);
    633
    634             //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox
    635             // Preserve spaces
    636             //node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "space", "preserve");
    637             node.style.whiteSpace = "nowrap";
    638
    639             el.rendNodeText = this.container.ownerDocument.createTextNode("");
    640             node.appendChild(el.rendNodeText);
    641             this.appendChildPrim(node, Type.evaluate(el.visProp.layer));
    642
    643             return node;
    644         },
    645
    646         // Already documented in JXG.AbstractRenderer
    647         updateInternalText: function (el) {
    648             var content = el.plaintext,
    649                 v,
    650                 ev_ax = el.getAnchorX(),
    651                 ev_ay = el.getAnchorY();
    652
    653             if (el.rendNode.getAttributeNS(null, "class") !== el.visProp.cssclass) {
    654                 el.rendNode.setAttributeNS(null, "class", Type.evaluate(el.visProp.cssclass));
    655                 el.needsSizeUpdate = true;
    656             }
    657
    658             if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
    659                 // Horizontal
    660                 v = el.coords.scrCoords[1];
    661                 if (el.visPropOld.left !== ev_ax + v) {
    662                     el.rendNode.setAttributeNS(null, "x", v + "px");
    663
    664                     if (ev_ax === "left") {
    665                         el.rendNode.setAttributeNS(null, "text-anchor", "start");
    666                     } else if (ev_ax === "right") {
    667                         el.rendNode.setAttributeNS(null, "text-anchor", "end");
    668                     } else if (ev_ax === "middle") {
    669                         el.rendNode.setAttributeNS(null, "text-anchor", "middle");
    670                     }
    671                     el.visPropOld.left = ev_ax + v;
    672                 }
    673
    674                 // Vertical
    675                 v = el.coords.scrCoords[2];
    676                 if (el.visPropOld.top !== ev_ay + v) {
    677                     el.rendNode.setAttributeNS(null, "y", v + this.vOffsetText * 0.5 + "px");
    678
    679                     // Not supported by IE, edge
    680                     // el.rendNode.setAttributeNS(null, "dy", "0");
    681                     // if (ev_ay === "bottom") {
    682                     //     el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-after-edge');
    683                     // } else if (ev_ay === "top") {
    684                     //     el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-before-edge');
    685                     // } else if (ev_ay === "middle") {
    686                     //     el.rendNode.setAttributeNS(null, 'dominant-baseline', 'middle');
    687                     // }
    688
    689                     if (ev_ay === "bottom") {
    690                         el.rendNode.setAttributeNS(null, "dy", "0");
    691                         el.rendNode.setAttributeNS(null, 'dominant-baseline', 'auto');
    692                     } else if (ev_ay === "top") {
    693                         el.rendNode.setAttributeNS(null, "dy", "1.6ex");
    694                         el.rendNode.setAttributeNS(null, 'dominant-baseline', 'auto');
    695                     } else if (ev_ay === "middle") {
    696                         el.rendNode.setAttributeNS(null, "dy", "0.6ex");
    697                         el.rendNode.setAttributeNS(null, 'dominant-baseline', 'auto');
    698                     }
    699                     el.visPropOld.top = ev_ay + v;
    700                 }
    701             }
    702             if (el.htmlStr !== content) {
    703                 el.rendNodeText.data = content;
    704                 el.htmlStr = content;
    705             }
    706             this.transformImage(el, el.transformations);
    707         },
    708
    709         /**
    710          * Set color and opacity of internal texts.
    711          * @private
    712          * @see JXG.AbstractRenderer#updateTextStyle
    713          * @see JXG.AbstractRenderer#updateInternalTextStyle
    714          */
    715         updateInternalTextStyle: function (el, strokeColor, strokeOpacity, duration) {
    716             this.setObjectViewport(el);
    717             this.setObjectFillColor(el, strokeColor, strokeOpacity);
    718         },
    719
    720         /* **************************
    721          *    Image related stuff
    722          * **************************/
    723
    724         // Already documented in JXG.AbstractRenderer
    725         drawImage: function (el) {
    726             var node = this.createPrim("image", el.id);
    727
    728             node.setAttributeNS(null, "preserveAspectRatio", "none");
    729             this.appendChildPrim(node, Type.evaluate(el.visProp.layer));
    730             el.rendNode = node;
    731
    732             this.updateImage(el);
    733         },
    734
    735         // Already documented in JXG.AbstractRenderer
    736         transformImage: function (el, t) {
    737             var s, m,
    738                 node = el.rendNode,
    739                 str = "",
    740                 cx, cy,
    741                 len = t.length;
    742
    743             if (len > 0) {
    744                 m = this.joinTransforms(el, t);
    745                 s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(",");
    746                 if (s.indexOf('NaN') === -1) {
    747                     str += " matrix(" + s + ") ";
    748                     if (el.elementClass === Const.OBJECT_CLASS_TEXT && el.visProp.display === 'html') {
    749                         node.style.transform = str;
    750                         cx = -el.coords.scrCoords[1];
    751                         cy = -el.coords.scrCoords[2];
    752                         switch (Type.evaluate(el.visProp.anchorx)) {
    753                             case 'right': cx += el.size[0]; break;
    754                             case 'middle': cx += el.size[0] * 0.5; break;
    755                         }
    756                         switch (Type.evaluate(el.visProp.anchory)) {
    757                             case 'bottom': cy += el.size[1]; break;
    758                             case 'middle': cy += el.size[1] * 0.5; break;
    759                         }
    760                         node.style['transform-origin'] = (cx) + 'px ' + (cy) + 'px';
    761                     } else {
    762                         // Images and texts with display:'internal'
    763                         node.setAttributeNS(null, "transform", str);
    764                     }
    765                 }
    766             }
    767         },
    768
    769         // Already documented in JXG.AbstractRenderer
    770         updateImageURL: function (el) {
    771             var url = Type.evaluate(el.url);
    772
    773             if (el._src !== url) {
    774                 el.imgIsLoaded = false;
    775                 el.rendNode.setAttributeNS(this.xlinkNamespace, "xlink:href", url);
    776                 el._src = url;
    777
    778                 return true;
    779             }
    780
    781             return false;
    782         },
    783
    784         // Already documented in JXG.AbstractRenderer
    785         updateImageStyle: function (el, doHighlight) {
    786             var css = Type.evaluate(
    787                 doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass
    788             );
    789
    790             el.rendNode.setAttributeNS(null, "class", css);
    791         },
    792
    793         // Already documented in JXG.AbstractRenderer
    794         drawForeignObject: function (el) {
    795             el.rendNode = this.appendChildPrim(
    796                 this.createPrim("foreignObject", el.id),
    797                 Type.evaluate(el.visProp.layer)
    798             );
    799
    800             this.appendNodesToElement(el, "foreignObject");
    801             this.updateForeignObject(el);
    802         },
    803
    804         // Already documented in JXG.AbstractRenderer
    805         updateForeignObject: function (el) {
    806             if (el._useUserSize) {
    807                 el.rendNode.style.overflow = "hidden";
    808             } else {
    809                 el.rendNode.style.overflow = "visible";
    810             }
    811
    812             this.updateRectPrim(
    813                 el.rendNode,
    814                 el.coords.scrCoords[1],
    815                 el.coords.scrCoords[2] - el.size[1],
    816                 el.size[0],
    817                 el.size[1]
    818             );
    819
    820             el.rendNode.innerHTML = el.content;
    821             this._updateVisual(el, { stroke: true, dash: true }, true);
    822         },
    823
    824         /* **************************
    825          * Render primitive objects
    826          * **************************/
    827
    828         // Already documented in JXG.AbstractRenderer
    829         appendChildPrim: function (node, level) {
    830             if (!Type.exists(level)) {
    831                 // trace nodes have level not set
    832                 level = 0;
    833             } else if (level >= Options.layer.numlayers) {
    834                 level = Options.layer.numlayers - 1;
    835             }
    836
    837             this.layer[level].appendChild(node);
    838
    839             return node;
    840         },
    841
    842         // Already documented in JXG.AbstractRenderer
    843         createPrim: function (type, id) {
    844             var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type);
    845             node.setAttributeNS(null, "id", this.uniqName(id));
    846             node.style.position = "absolute";
    847             if (type === "path") {
    848                 node.setAttributeNS(null, "stroke-linecap", "round");
    849                 node.setAttributeNS(null, "stroke-linejoin", "round");
    850                 node.setAttributeNS(null, "fill-rule", "evenodd");
    851             }
    852
    853             return node;
    854         },
    855
    856         // Already documented in JXG.AbstractRenderer
    857         remove: function (shape) {
    858             if (Type.exists(shape) && Type.exists(shape.parentNode)) {
    859                 shape.parentNode.removeChild(shape);
    860             }
    861         },
    862
    863         // Already documented in JXG.AbstractRenderer
    864         setLayer: function (el, level) {
    865             if (!Type.exists(level)) {
    866                 level = 0;
    867             } else if (level >= Options.layer.numlayers) {
    868                 level = Options.layer.numlayers - 1;
    869             }
    870
    871             this.layer[level].appendChild(el.rendNode);
    872         },
    873
    874         // Already documented in JXG.AbstractRenderer
    875         makeArrows: function (el, a) {
    876             var node2, str,
    877                 ev_fa = a.evFirst,
    878                 ev_la = a.evLast;
    879
    880             if (this.isIE && el.visPropCalc.visible && (ev_fa || ev_la)) {
    881                 el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode);
    882                 return;
    883             }
    884
    885             // We can not compare against visPropOld if there is need for a new arrow head,
    886             // since here visPropOld and ev_fa / ev_la already have the same value.
    887             // This has been set in _updateVisual.
    888             //
    889             node2 = el.rendNodeTriangleStart;
    890             if (ev_fa) {
    891                 str = this.toStr(this.container.id, '_', el.id, 'TriangleStart', a.typeFirst);
    892
    893                 // If we try to set the same arrow head as is already set, we can bail out now
    894                 if (!Type.exists(node2) || node2.id !== str) {
    895                     node2 = this.container.ownerDocument.getElementById(str);
    896                     // Check if the marker already exists.
    897                     // If not, create a new marker
    898                     if (node2 === null) {
    899                         node2 = this._createArrowHead(el, "Start", a.typeFirst);
    900                         this.defs.appendChild(node2);
    901                     }
    902                     el.rendNodeTriangleStart = node2;
    903                     el.rendNode.setAttributeNS(null, "marker-start", this.toURL(str));
    904                 }
    905             } else {
    906                 if (Type.exists(node2)) {
    907                     this.remove(node2);
    908                     el.rendNodeTriangleStart = null;
    909                 }
    910                 el.rendNode.setAttributeNS(null, "marker-start", null);
    911             }
    912
    913             node2 = el.rendNodeTriangleEnd;
    914             if (ev_la) {
    915                 str = this.toStr(this.container.id, '_', el.id, 'TriangleEnd', a.typeLast);
    916
    917                 // If we try to set the same arrow head as is already set, we can bail out now
    918                 if (!Type.exists(node2) || node2.id !== str) {
    919                     node2 = this.container.ownerDocument.getElementById(str);
    920                     // Check if the marker already exists.
    921                     // If not, create a new marker
    922                     if (node2 === null) {
    923                         node2 = this._createArrowHead(el, "End", a.typeLast);
    924                         this.defs.appendChild(node2);
    925                     }
    926                     el.rendNodeTriangleEnd = node2;
    927                     el.rendNode.setAttributeNS(null, "marker-end", this.toURL(str));
    928                 }
    929             } else {
    930                 if (Type.exists(node2)) {
    931                     this.remove(node2);
    932                     el.rendNodeTriangleEnd = null;
    933                 }
    934                 el.rendNode.setAttributeNS(null, "marker-end", null);
    935             }
    936         },
    937
    938         // Already documented in JXG.AbstractRenderer
    939         updateEllipsePrim: function (node, x, y, rx, ry) {
    940             var huge = 1000000;
    941
    942             huge = 200000; // IE
    943             // webkit does not like huge values if the object is dashed
    944             // iE doesn't like huge values above 216000
    945             x = Math.abs(x) < huge ? x : (huge * x) / Math.abs(x);
    946             y = Math.abs(y) < huge ? y : (huge * y) / Math.abs(y);
    947             rx = Math.abs(rx) < huge ? rx : (huge * rx) / Math.abs(rx);
    948             ry = Math.abs(ry) < huge ? ry : (huge * ry) / Math.abs(ry);
    949
    950             node.setAttributeNS(null, "cx", x);
    951             node.setAttributeNS(null, "cy", y);
    952             node.setAttributeNS(null, "rx", Math.abs(rx));
    953             node.setAttributeNS(null, "ry", Math.abs(ry));
    954         },
    955
    956         // Already documented in JXG.AbstractRenderer
    957         updateLinePrim: function (node, p1x, p1y, p2x, p2y) {
    958             var huge = 1000000;
    959
    960             huge = 200000; //IE
    961             if (!isNaN(p1x + p1y + p2x + p2y)) {
    962                 // webkit does not like huge values if the object is dashed
    963                 // IE doesn't like huge values above 216000
    964                 p1x = Math.abs(p1x) < huge ? p1x : (huge * p1x) / Math.abs(p1x);
    965                 p1y = Math.abs(p1y) < huge ? p1y : (huge * p1y) / Math.abs(p1y);
    966                 p2x = Math.abs(p2x) < huge ? p2x : (huge * p2x) / Math.abs(p2x);
    967                 p2y = Math.abs(p2y) < huge ? p2y : (huge * p2y) / Math.abs(p2y);
    968
    969                 node.setAttributeNS(null, "x1", p1x);
    970                 node.setAttributeNS(null, "y1", p1y);
    971                 node.setAttributeNS(null, "x2", p2x);
    972                 node.setAttributeNS(null, "y2", p2y);
    973             }
    974         },
    975
    976         // Already documented in JXG.AbstractRenderer
    977         updatePathPrim: function (node, pointString) {
    978             if (pointString === "") {
    979                 pointString = "M 0 0";
    980             }
    981             node.setAttributeNS(null, "d", pointString);
    982         },
    983
    984         // Already documented in JXG.AbstractRenderer
    985         updatePathStringPoint: function (el, size, type) {
    986             var s = "",
    987                 scr = el.coords.scrCoords,
    988                 sqrt32 = size * Math.sqrt(3) * 0.5,
    989                 s05 = size * 0.5;
    990
    991             if (type === "x") {
    992                 s =
    993                     " M " +
    994                     (scr[1] - size) +
    995                     " " +
    996                     (scr[2] - size) +
    997                     " L " +
    998                     (scr[1] + size) +
    999                     " " +
    1000                     (scr[2] + size) +
    1001                     " M " +
    1002                     (scr[1] + size) +
    1003                     " " +
    1004                     (scr[2] - size) +
    1005                     " L " +
    1006                     (scr[1] - size) +
    1007                     " " +
    1008                     (scr[2] + size);
    1009             } else if (type === "+") {
    1010                 s =
    1011                     " M " +
    1012                     (scr[1] - size) +
    1013                     " " +
    1014                     scr[2] +
    1015                     " L " +
    1016                     (scr[1] + size) +
    1017                     " " +
    1018                     scr[2] +
    1019                     " M " +
    1020                     scr[1] +
    1021                     " " +
    1022                     (scr[2] - size) +
    1023                     " L " +
    1024                     scr[1] +
    1025                     " " +
    1026                     (scr[2] + size);
    1027             } else if (type === "|") {
    1028                 s =
    1029                     " M " +
    1030                     scr[1] +
    1031                     " " +
    1032                     (scr[2] - size) +
    1033                     " L " +
    1034                     scr[1] +
    1035                     " " +
    1036                     (scr[2] + size);
    1037             } else if (type === "-") {
    1038                 s =
    1039                     " M " +
    1040                     (scr[1] - size) +
    1041                     " " +
    1042                     scr[2] +
    1043                     " L " +
    1044                     (scr[1] + size) +
    1045                     " " +
    1046                     scr[2];
    1047             } else if (type === "<>" || type === "<<>>") {
    1048                 if (type === "<<>>") {
    1049                     size *= 1.41;
    1050                 }
    1051                 s =
    1052                     " M " +
    1053                     (scr[1] - size) +
    1054                     " " +
    1055                     scr[2] +
    1056                     " L " +
    1057                     scr[1] +
    1058                     " " +
    1059                     (scr[2] + size) +
    1060                     " L " +
    1061                     (scr[1] + size) +
    1062                     " " +
    1063                     scr[2] +
    1064                     " L " +
    1065                     scr[1] +
    1066                     " " +
    1067                     (scr[2] - size) +
    1068                     " Z ";
    1069                 } else if (type === "^") {
    1070                     s =
    1071                     " M " +
    1072                     scr[1] +
    1073                     " " +
    1074                     (scr[2] - size) +
    1075                     " L " +
    1076                     (scr[1] - sqrt32) +
    1077                     " " +
    1078                     (scr[2] + s05) +
    1079                     " L " +
    1080                     (scr[1] + sqrt32) +
    1081                     " " +
    1082                     (scr[2] + s05) +
    1083                     " Z "; // close path
    1084             } else if (type === "v") {
    1085                 s =
    1086                     " M " +
    1087                     scr[1] +
    1088                     " " +
    1089                     (scr[2] + size) +
    1090                     " L " +
    1091                     (scr[1] - sqrt32) +
    1092                     " " +
    1093                     (scr[2] - s05) +
    1094                     " L " +
    1095                     (scr[1] + sqrt32) +
    1096                     " " +
    1097                     (scr[2] - s05) +
    1098                     " Z ";
    1099             } else if (type === ">") {
    1100                 s =
    1101                     " M " +
    1102                     (scr[1] + size) +
    1103                     " " +
    1104                     scr[2] +
    1105                     " L " +
    1106                     (scr[1] - s05) +
    1107                     " " +
    1108                     (scr[2] - sqrt32) +
    1109                     " L " +
    1110                     (scr[1] - s05) +
    1111                     " " +
    1112                     (scr[2] + sqrt32) +
    1113                     " Z ";
    1114             } else if (type === "<") {
    1115                 s =
    1116                     " M " +
    1117                     (scr[1] - size) +
    1118                     " " +
    1119                     scr[2] +
    1120                     " L " +
    1121                     (scr[1] + s05) +
    1122                     " " +
    1123                     (scr[2] - sqrt32) +
    1124                     " L " +
    1125                     (scr[1] + s05) +
    1126                     " " +
    1127                     (scr[2] + sqrt32) +
    1128                     " Z ";
    1129             }
    1130             return s;
    1131         },
    1132
    1133         // Already documented in JXG.AbstractRenderer
    1134         updatePathStringPrim: function (el) {
    1135             var i,
    1136                 scr,
    1137                 len,
    1138                 symbm = " M ",
    1139                 symbl = " L ",
    1140                 symbc = " C ",
    1141                 nextSymb = symbm,
    1142                 maxSize = 5000.0,
    1143                 pStr = "";
    1144
    1145             if (el.numberPoints <= 0) {
    1146                 return "";
    1147             }
    1148
    1149             len = Math.min(el.points.length, el.numberPoints);
    1150
    1151             if (el.bezierDegree === 1) {
    1152                 for (i = 0; i < len; i++) {
    1153                     scr = el.points[i].scrCoords;
    1154                     if (isNaN(scr[1]) || isNaN(scr[2])) {
    1155                         // PenUp
    1156                         nextSymb = symbm;
    1157                     } else {
    1158                         // Chrome has problems with values being too far away.
    1159                         scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize);
    1160                         scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize);
    1161
    1162                         // Attention: first coordinate may be inaccurate if far way
    1163                         //pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
    1164                         pStr += nextSymb + scr[1] + " " + scr[2]; // Seems to be faster now (webkit and firefox)
    1165                         nextSymb = symbl;
    1166                     }
    1167                 }
    1168             } else if (el.bezierDegree === 3) {
    1169                 i = 0;
    1170                 while (i < len) {
    1171                     scr = el.points[i].scrCoords;
    1172                     if (isNaN(scr[1]) || isNaN(scr[2])) {
    1173                         // PenUp
    1174                         nextSymb = symbm;
    1175                     } else {
    1176                         pStr += nextSymb + scr[1] + " " + scr[2];
    1177                         if (nextSymb === symbc) {
    1178                             i += 1;
    1179                             scr = el.points[i].scrCoords;
    1180                             pStr += " " + scr[1] + " " + scr[2];
    1181                             i += 1;
    1182                             scr = el.points[i].scrCoords;
    1183                             pStr += " " + scr[1] + " " + scr[2];
    1184                         }
    1185                         nextSymb = symbc;
    1186                     }
    1187                     i += 1;
    1188                 }
    1189             }
    1190             return pStr;
    1191         },
    1192
    1193         // Already documented in JXG.AbstractRenderer
    1194         updatePathStringBezierPrim: function (el) {
    1195             var i,
    1196                 j,
    1197                 k,
    1198                 scr,
    1199                 lx,
    1200                 ly,
    1201                 len,
    1202                 symbm = " M ",
    1203                 symbl = " C ",
    1204                 nextSymb = symbm,
    1205                 maxSize = 5000.0,
    1206                 pStr = "",
    1207                 f = Type.evaluate(el.visProp.strokewidth),
    1208                 isNoPlot = Type.evaluate(el.visProp.curvetype) !== "plot";
    1209
    1210             if (el.numberPoints <= 0) {
    1211                 return "";
    1212             }
    1213
    1214             if (isNoPlot && el.board.options.curve.RDPsmoothing) {
    1215                 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
    1216             }
    1217
    1218             len = Math.min(el.points.length, el.numberPoints);
    1219             for (j = 1; j < 3; j++) {
    1220                 nextSymb = symbm;
    1221                 for (i = 0; i < len; i++) {
    1222                     scr = el.points[i].scrCoords;
    1223
    1224                     if (isNaN(scr[1]) || isNaN(scr[2])) {
    1225                         // PenUp
    1226                         nextSymb = symbm;
    1227                     } else {
    1228                         // Chrome has problems with values being too far away.
    1229                         scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize);
    1230                         scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize);
    1231
    1232                         // Attention: first coordinate may be inaccurate if far way
    1233                         if (nextSymb === symbm) {
    1234                             //pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
    1235                             pStr += nextSymb + scr[1] + " " + scr[2]; // Seems to be faster now (webkit and firefox)
    1236                         } else {
    1237                             k = 2 * j;
    1238                             pStr += [
    1239                                 nextSymb,
    1240                                 lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j),
    1241                                 " ",
    1242                                 ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j),
    1243                                 " ",
    1244                                 lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j),
    1245                                 " ",
    1246                                 ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j),
    1247                                 " ",
    1248                                 scr[1],
    1249                                 " ",
    1250                                 scr[2]
    1251                             ].join("");
    1252                         }
    1253
    1254                         nextSymb = symbl;
    1255                         lx = scr[1];
    1256                         ly = scr[2];
    1257                     }
    1258                 }
    1259             }
    1260             return pStr;
    1261         },
    1262
    1263         // Already documented in JXG.AbstractRenderer
    1264         updatePolygonPrim: function (node, el) {
    1265             var i,
    1266                 pStr = "",
    1267                 scrCoords,
    1268                 len = el.vertices.length;
    1269
    1270             node.setAttributeNS(null, "stroke", "none");
    1271             node.setAttributeNS(null, "fill-rule", "evenodd");
    1272             if (el.elType === "polygonalchain") {
    1273                 len++;
    1274             }
    1275
    1276             for (i = 0; i < len - 1; i++) {
    1277                 if (el.vertices[i].isReal) {
    1278                     scrCoords = el.vertices[i].coords.scrCoords;
    1279                     pStr = pStr + scrCoords[1] + "," + scrCoords[2];
    1280                 } else {
    1281                     node.setAttributeNS(null, "points", "");
    1282                     return;
    1283                 }
    1284
    1285                 if (i < len - 2) {
    1286                     pStr += " ";
    1287                 }
    1288             }
    1289             if (pStr.indexOf("NaN") === -1) {
    1290                 node.setAttributeNS(null, "points", pStr);
    1291             }
    1292         },
    1293
    1294         // Already documented in JXG.AbstractRenderer
    1295         updateRectPrim: function (node, x, y, w, h) {
    1296             node.setAttributeNS(null, "x", x);
    1297             node.setAttributeNS(null, "y", y);
    1298             node.setAttributeNS(null, "width", w);
    1299             node.setAttributeNS(null, "height", h);
    1300         },
    1301
    1302         /* **************************
    1303          *  Set Attributes
    1304          * **************************/
    1305
    1306         // documented in JXG.AbstractRenderer
    1307         setPropertyPrim: function (node, key, val) {
    1308             if (key === "stroked") {
    1309                 return;
    1310             }
    1311             node.setAttributeNS(null, key, val);
    1312         },
    1313
    1314         display: function (el, val) {
    1315             var node;
    1316
    1317             if (el && el.rendNode) {
    1318                 el.visPropOld.visible = val;
    1319                 node = el.rendNode;
    1320                 if (val) {
    1321                     node.setAttributeNS(null, "display", "inline");
    1322                     node.style.visibility = "inherit";
    1323                 } else {
    1324                     node.setAttributeNS(null, "display", "none");
    1325                     node.style.visibility = "hidden";
    1326                 }
    1327             }
    1328         },
    1329
    1330         // documented in JXG.AbstractRenderer
    1331         show: function (el) {
    1332             JXG.deprecated("Board.renderer.show()", "Board.renderer.display()");
    1333             this.display(el, true);
    1334             // var node;
    1335             //
    1336             // if (el && el.rendNode) {
    1337             //     node = el.rendNode;
    1338             //     node.setAttributeNS(null, 'display', 'inline');
    1339             //     node.style.visibility = "inherit";
    1340             // }
    1341         },
    1342
    1343         // documented in JXG.AbstractRenderer
    1344         hide: function (el) {
    1345             JXG.deprecated("Board.renderer.hide()", "Board.renderer.display()");
    1346             this.display(el, false);
    1347             // var node;
    1348             //
    1349             // if (el && el.rendNode) {
    1350             //     node = el.rendNode;
    1351             //     node.setAttributeNS(null, 'display', 'none');
    1352             //     node.style.visibility = "hidden";
    1353             // }
    1354         },
    1355
    1356         // documented in JXG.AbstractRenderer
    1357         setBuffering: function (el, type) {
    1358             el.rendNode.setAttribute("buffered-rendering", type);
    1359         },
    1360
    1361         // documented in JXG.AbstractRenderer
    1362         setDashStyle: function (el) {
    1363             var dashStyle = Type.evaluate(el.visProp.dash),
    1364                 ds = Type.evaluate(el.visProp.dashscale),
    1365                 sw = ds ? 0.5 * Type.evaluate(el.visProp.strokewidth) : 1,
    1366                 node = el.rendNode;
    1367
    1368             if (dashStyle > 0) {
    1369                 node.setAttributeNS(null, "stroke-dasharray",
    1370                     // sw could distinguish highlighting or not.
    1371                     // But it seems to preferable to ignore this.
    1372                     this.dashArray[dashStyle - 1].map(function (x) { return x * sw; }).join(',')
    1373                 );
    1374             } else {
    1375                 if (node.hasAttributeNS(null, "stroke-dasharray")) {
    1376                     node.removeAttributeNS(null, "stroke-dasharray");
    1377                 }
    1378             }
    1379         },
    1380
    1381         // documented in JXG.AbstractRenderer
    1382         setGradient: function (el) {
    1383             var fillNode = el.rendNode,
    1384                 node, node2, node3,
    1385                 ev_g = Type.evaluate(el.visProp.gradient);
    1386
    1387             if (ev_g === "linear" || ev_g === "radial") {
    1388                 node = this.createPrim(ev_g + "Gradient", el.id + "_gradient");
    1389                 node2 = this.createPrim("stop", el.id + "_gradient1");
    1390                 node3 = this.createPrim("stop", el.id + "_gradient2");
    1391                 node.appendChild(node2);
    1392                 node.appendChild(node3);
    1393                 this.defs.appendChild(node);
    1394                 fillNode.setAttributeNS(
    1395                     null,
    1396                     'style',
    1397                     // "fill:url(#" + this.container.id + "_" + el.id + "_gradient)"
    1398                     'fill:' + this.toURL(this.container.id + '_' + el.id + '_gradient')
    1399                 );
    1400                 el.gradNode1 = node2;
    1401                 el.gradNode2 = node3;
    1402                 el.gradNode = node;
    1403             } else {
    1404                 fillNode.removeAttributeNS(null, "style");
    1405             }
    1406         },
    1407
    1408         /**
    1409          * Set the gradient angle for linear color gradients.
    1410          *
    1411          * @private
    1412          * @param {SVGnode} node SVG gradient node of an arbitrary JSXGraph element.
    1413          * @param {Number} radians angle value in radians. 0 is horizontal from left to right, Pi/4 is vertical from top to bottom.
    1414          */
    1415         updateGradientAngle: function (node, radians) {
    1416             // Angles:
    1417             // 0: ->
    1418             // 90: down
    1419             // 180: <-
    1420             // 90: up
    1421             var f = 1.0,
    1422                 co = Math.cos(radians),
    1423                 si = Math.sin(radians);
    1424
    1425             if (Math.abs(co) > Math.abs(si)) {
    1426                 f /= Math.abs(co);
    1427             } else {
    1428                 f /= Math.abs(si);
    1429             }
    1430
    1431             if (co >= 0) {
    1432                 node.setAttributeNS(null, "x1", 0);
    1433                 node.setAttributeNS(null, "x2", co * f);
    1434             } else {
    1435                 node.setAttributeNS(null, "x1", -co * f);
    1436                 node.setAttributeNS(null, "x2", 0);
    1437             }
    1438             if (si >= 0) {
    1439                 node.setAttributeNS(null, "y1", 0);
    1440                 node.setAttributeNS(null, "y2", si * f);
    1441             } else {
    1442                 node.setAttributeNS(null, "y1", -si * f);
    1443                 node.setAttributeNS(null, "y2", 0);
    1444             }
    1445         },
    1446
    1447         /**
    1448          * Set circles for radial color gradients.
    1449          *
    1450          * @private
    1451          * @param {SVGnode} node SVG gradient node
    1452          * @param {Number} cx SVG value cx (value between 0 and 1)
    1453          * @param {Number} cy  SVG value cy (value between 0 and 1)
    1454          * @param {Number} r  SVG value r (value between 0 and 1)
    1455          * @param {Number} fx  SVG value fx (value between 0 and 1)
    1456          * @param {Number} fy  SVG value fy (value between 0 and 1)
    1457          * @param {Number} fr  SVG value fr (value between 0 and 1)
    1458          */
    1459         updateGradientCircle: function (node, cx, cy, r, fx, fy, fr) {
    1460             node.setAttributeNS(null, "cx", cx * 100 + "%"); // Center first color
    1461             node.setAttributeNS(null, "cy", cy * 100 + "%");
    1462             node.setAttributeNS(null, "r", r * 100 + "%");
    1463             node.setAttributeNS(null, "fx", fx * 100 + "%"); // Center second color / focal point
    1464             node.setAttributeNS(null, "fy", fy * 100 + "%");
    1465             node.setAttributeNS(null, "fr", fr * 100 + "%");
    1466         },
    1467
    1468         // documented in JXG.AbstractRenderer
    1469         updateGradient: function (el) {
    1470             var col,
    1471                 op,
    1472                 node2 = el.gradNode1,
    1473                 node3 = el.gradNode2,
    1474                 ev_g = Type.evaluate(el.visProp.gradient);
    1475
    1476             if (!Type.exists(node2) || !Type.exists(node3)) {
    1477                 return;
    1478             }
    1479
    1480             op = Type.evaluate(el.visProp.fillopacity);
    1481             op = op > 0 ? op : 0;
    1482             col = Type.evaluate(el.visProp.fillcolor);
    1483
    1484             node2.setAttributeNS(null, "style", "stop-color:" + col + ";stop-opacity:" + op);
    1485             node3.setAttributeNS(
    1486                 null,
    1487                 "style",
    1488                 "stop-color:" +
    1489                 Type.evaluate(el.visProp.gradientsecondcolor) +
    1490                 ";stop-opacity:" +
    1491                 Type.evaluate(el.visProp.gradientsecondopacity)
    1492             );
    1493             node2.setAttributeNS(
    1494                 null,
    1495                 "offset",
    1496                 Type.evaluate(el.visProp.gradientstartoffset) * 100 + "%"
    1497             );
    1498             node3.setAttributeNS(
    1499                 null,
    1500                 "offset",
    1501                 Type.evaluate(el.visProp.gradientendoffset) * 100 + "%"
    1502             );
    1503             if (ev_g === "linear") {
    1504                 this.updateGradientAngle(el.gradNode, Type.evaluate(el.visProp.gradientangle));
    1505             } else if (ev_g === "radial") {
    1506                 this.updateGradientCircle(
    1507                     el.gradNode,
    1508                     Type.evaluate(el.visProp.gradientcx),
    1509                     Type.evaluate(el.visProp.gradientcy),
    1510                     Type.evaluate(el.visProp.gradientr),
    1511                     Type.evaluate(el.visProp.gradientfx),
    1512                     Type.evaluate(el.visProp.gradientfy),
    1513                     Type.evaluate(el.visProp.gradientfr)
    1514                 );
    1515             }
    1516         },
    1517
    1518         // documented in JXG.AbstractRenderer
    1519         setObjectTransition: function (el, duration) {
    1520             var node, props,
    1521                 transitionArr = [],
    1522                 transitionStr,
    1523                 i,
    1524                 len = 0,
    1525                 nodes = ["rendNode", "rendNodeTriangleStart", "rendNodeTriangleEnd"];
    1526
    1527             if (duration === undefined) {
    1528                 duration = Type.evaluate(el.visProp.transitionduration);
    1529             }
    1530
    1531             props = Type.evaluate(el.visProp.transitionproperties);
    1532             if (duration === el.visPropOld.transitionduration &&
    1533                 props === el.visPropOld.transitionproperties) {
    1534                 return;
    1535             }
    1536
    1537             // if (
    1538             //     el.elementClass === Const.OBJECT_CLASS_TEXT &&
    1539             //     Type.evaluate(el.visProp.display) === "html"
    1540             // ) {
    1541             //     // transitionStr = " color " + duration + "ms," +
    1542             //     //     " opacity " + duration + "ms";
    1543             //     transitionStr = " all " + duration + "ms ease";
    1544             // } else {
    1545             //     transitionStr =
    1546             //         " fill " + duration + "ms," +
    1547             //         " fill-opacity " + duration + "ms," +
    1548             //         " stroke " + duration + "ms," +
    1549             //         " stroke-opacity " + duration + "ms," +
    1550             //         " stroke-width " + duration + "ms," +
    1551             //         " width " + duration + "ms," +
    1552             //         " height " + duration + "ms," +
    1553             //         " rx " + duration + "ms," +
    1554             //         " ry " + duration + "ms";
    1555             // }
    1556
    1557             if (Type.exists(props)) {
    1558                 len = props.length;
    1559             }
    1560             for (i = 0; i < len; i++) {
    1561                 transitionArr.push(props[i] + ' ' + duration + 'ms');
    1562             }
    1563             transitionStr = transitionArr.join(', ');
    1564
    1565             len = nodes.length;
    1566             for (i = 0; i < len; ++i) {
    1567                 if (el[nodes[i]]) {
    1568                     node = el[nodes[i]];
    1569                     node.style.transition = transitionStr;
    1570                 }
    1571             }
    1572
    1573             el.visPropOld.transitionduration = duration;
    1574             el.visPropOld.transitionproperties = props;
    1575         },
    1576
    1577         // documented in JXG.AbstractRenderer
    1578         setObjectViewport: function(el, isHtml) {
    1579             // var val = Type.evaluate(el.visProp.viewport),
    1580             //     vp, i,
    1581             //     len = 0,
    1582             //     bb, bbc, l, t, r, b,
    1583             //     nodes = ['rendNode']; //, "rendNodeTriangleStart", "rendNodeTriangleEnd"];
    1584
    1585             // TODO
    1586             // viewport is still screwed.
    1587             // Example: if an image is cropped by the viewport,
    1588             // so will be any transformed copies.
    1589             // See #695.
    1590
    1591             // // Check viewport attribute of the board
    1592             // if (val === 'inherit') {
    1593             //     val = Type.evaluate(el.board.attr.viewport);
    1594             // }
    1595
    1596             // // Required order: top, right, bottom, left
    1597             // if (isHtml) {
    1598             //     bb = el.rendNode.getBoundingClientRect();
    1599             //     bbc = this.container.getBoundingClientRect();
    1600             //     t = parseFloat(val[1]);
    1601             //     r = parseFloat(val[2]);
    1602             //     b = parseFloat(val[3]);
    1603             //     l = parseFloat(val[0]);
    1604
    1605             //     if (Type.isString(val[1]) && val[1].indexOf('%') > 0) {
    1606             //         t = (bbc.height) * t / 100;
    1607             //     }
    1608             //     if (Type.isString(val[2]) && val[2].indexOf('%') > 0) {
    1609             //         r = (bbc.width) * r / 100;
    1610             //     }
    1611             //     if (Type.isString(val[3]) && val[3].indexOf('%') > 0) {
    1612             //         b = (bbc.height) * b / 100;
    1613             //     }
    1614             //     if (Type.isString(val[0]) && val[0].indexOf('%') > 0) {
    1615             //         l = (bbc.width) * l / 100;
    1616             //     }
    1617
    1618             //     t = parseFloat(bbc.top) - parseFloat(bb.top) + t;
    1619             //     r = parseFloat(bb.right) - parseFloat(bbc.right) + r;
    1620             //     b = parseFloat(bb.bottom) - parseFloat(bbc.bottom) + b;
    1621             //     l = parseFloat(bbc.left) - parseFloat(bb.left) + l;
    1622             //     val = [l, t, r, b];
    1623             // }
    1624
    1625             // vp = [
    1626             //     (typeof val[1] === 'number') ? val[1] + 'px' : val[1],
    1627             //     (typeof val[2] === 'number') ? val[2] + 'px' : val[2],
    1628             //     (typeof val[3] === 'number') ? val[3] + 'px' : val[3],
    1629             //     (typeof val[0] === 'number') ? val[0] + 'px' : val[0]
    1630             // ].join(' ');
    1631
    1632             // len = nodes.length;
    1633             // for (i = 0; i < len; ++i) {
    1634             //     if (el[nodes[i]]) {
    1635             //         if (isHtml) {
    1636             //             el[nodes[i]].style.clipPath = 'inset(' + vp + ')';
    1637             //         } else {
    1638             //             el[nodes[i]].setAttributeNS(null, "clip-path", 'view-box inset(' + vp + ')');
    1639             //         }
    1640             //     }
    1641             // }
    1642         },
    1643
    1644         /**
    1645          * Call user-defined function to set visual attributes.
    1646          * If "testAttribute" is the empty string, the function
    1647          * is called immediately, otherwise it is called in a timeOut.
    1648          *
    1649          * This is necessary to realize smooth transitions but avoid transitions
    1650          * when first creating the objects.
    1651          *
    1652          * Usually, the string in testAttribute is the visPropOld attribute
    1653          * of the values which are set.
    1654          *
    1655          * @param {Function} setFunc       Some function which usually sets some attributes
    1656          * @param {String} testAttribute If this string is the empty string  the function is called immediately,
    1657          *                               otherwise it is called in a setImeout.
    1658          * @see JXG.SVGRenderer#setObjectFillColor
    1659          * @see JXG.SVGRenderer#setObjectStrokeColor
    1660          * @see JXG.SVGRenderer#_setArrowColor
    1661          * @private
    1662          */
    1663         _setAttribute: function (setFunc, testAttribute) {
    1664             if (testAttribute === "") {
    1665                 setFunc();
    1666             } else {
    1667                 window.setTimeout(setFunc, 1);
    1668             }
    1669         },
    1670
    1671         // documented in JXG.AbstractRenderer
    1672         setObjectFillColor: function (el, color, opacity, rendNode) {
    1673             var node, c, rgbo, oo,
    1674                 rgba = Type.evaluate(color),
    1675                 o = Type.evaluate(opacity),
    1676                 grad = Type.evaluate(el.visProp.gradient);
    1677
    1678             o = o > 0 ? o : 0;
    1679
    1680             // TODO  save gradient and gradientangle
    1681             if (
    1682                 el.visPropOld.fillcolor === rgba &&
    1683                 el.visPropOld.fillopacity === o &&
    1684                 grad === null
    1685             ) {
    1686                 return;
    1687             }
    1688
    1689             if (Type.exists(rgba) && rgba !== false) {
    1690                 if (rgba.length !== 9) {
    1691                     // RGB, not RGBA
    1692                     c = rgba;
    1693                     oo = o;
    1694                 } else {
    1695                     // True RGBA, not RGB
    1696                     rgbo = Color.rgba2rgbo(rgba);
    1697                     c = rgbo[0];
    1698                     oo = o * rgbo[1];
    1699                 }
    1700
    1701                 if (rendNode === undefined) {
    1702                     node = el.rendNode;
    1703                 } else {
    1704                     node = rendNode;
    1705                 }
    1706
    1707                 if (c !== "none") {
    1708                     this._setAttribute(function () {
    1709                         node.setAttributeNS(null, "fill", c);
    1710                     }, el.visPropOld.fillcolor);
    1711                 }
    1712
    1713                 if (el.type === JXG.OBJECT_TYPE_IMAGE) {
    1714                     this._setAttribute(function () {
    1715                         node.setAttributeNS(null, "opacity", oo);
    1716                     }, el.visPropOld.fillopacity);
    1717                     //node.style['opacity'] = oo;  // This would overwrite values set by CSS class.
    1718                 } else {
    1719                     if (c === "none") {
    1720                         // This is done only for non-images
    1721                         // because images have no fill color.
    1722                         oo = 0;
    1723                         // This is necessary if there is a foreignObject below.
    1724                         node.setAttributeNS(null, "pointer-events", "visibleStroke");
    1725                     } else {
    1726                         // This is the default
    1727                         node.setAttributeNS(null, "pointer-events", "visiblePainted");
    1728                     }
    1729                     this._setAttribute(function () {
    1730                         node.setAttributeNS(null, "fill-opacity", oo);
    1731                     }, el.visPropOld.fillopacity);
    1732                 }
    1733
    1734                 if (grad === "linear" || grad === "radial") {
    1735                     this.updateGradient(el);
    1736                 }
    1737             }
    1738             el.visPropOld.fillcolor = rgba;
    1739             el.visPropOld.fillopacity = o;
    1740         },
    1741
    1742         // documented in JXG.AbstractRenderer
    1743         setObjectStrokeColor: function (el, color, opacity) {
    1744             var rgba = Type.evaluate(color),
    1745                 c, rgbo,
    1746                 o = Type.evaluate(opacity),
    1747                 oo, node;
    1748
    1749             o = o > 0 ? o : 0;
    1750
    1751             if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
    1752                 return;
    1753             }
    1754
    1755             if (Type.exists(rgba) && rgba !== false) {
    1756                 if (rgba.length !== 9) {
    1757                     // RGB, not RGBA
    1758                     c = rgba;
    1759                     oo = o;
    1760                 } else {
    1761                     // True RGBA, not RGB
    1762                     rgbo = Color.rgba2rgbo(rgba);
    1763                     c = rgbo[0];
    1764                     oo = o * rgbo[1];
    1765                 }
    1766
    1767                 node = el.rendNode;
    1768
    1769                 if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
    1770                     if (Type.evaluate(el.visProp.display) === "html") {
    1771                         this._setAttribute(function () {
    1772                             node.style.color = c;
    1773                             node.style.opacity = oo;
    1774                         }, el.visPropOld.strokecolor);
    1775                     } else {
    1776                         this._setAttribute(function () {
    1777                             node.setAttributeNS(null, "style", "fill:" + c);
    1778                             node.setAttributeNS(null, "style", "fill-opacity:" + oo);
    1779                         }, el.visPropOld.strokecolor);
    1780                     }
    1781                 } else {
    1782                     this._setAttribute(function () {
    1783                         node.setAttributeNS(null, "stroke", c);
    1784                         node.setAttributeNS(null, "stroke-opacity", oo);
    1785                     }, el.visPropOld.strokecolor);
    1786                 }
    1787
    1788                 if (
    1789                     el.elementClass === Const.OBJECT_CLASS_CURVE ||
    1790                     el.elementClass === Const.OBJECT_CLASS_LINE
    1791                 ) {
    1792                     if (Type.evaluate(el.visProp.firstarrow)) {
    1793                         this._setArrowColor(
    1794                             el.rendNodeTriangleStart,
    1795                             c, oo, el,
    1796                             el.visPropCalc.typeFirst
    1797                         );
    1798                     }
    1799
    1800                     if (Type.evaluate(el.visProp.lastarrow)) {
    1801                         this._setArrowColor(
    1802                             el.rendNodeTriangleEnd,
    1803                             c, oo, el,
    1804                             el.visPropCalc.typeLast
    1805                         );
    1806                     }
    1807                 }
    1808             }
    1809
    1810             el.visPropOld.strokecolor = rgba;
    1811             el.visPropOld.strokeopacity = o;
    1812         },
    1813
    1814         // documented in JXG.AbstractRenderer
    1815         setObjectStrokeWidth: function (el, width) {
    1816             var node,
    1817                 w = Type.evaluate(width);
    1818
    1819             if (isNaN(w) || el.visPropOld.strokewidth === w) {
    1820                 return;
    1821             }
    1822
    1823             node = el.rendNode;
    1824             this.setPropertyPrim(node, "stroked", "true");
    1825             if (Type.exists(w)) {
    1826                 this.setPropertyPrim(node, "stroke-width", w + "px");
    1827
    1828                 // if (el.elementClass === Const.OBJECT_CLASS_CURVE ||
    1829                 // el.elementClass === Const.OBJECT_CLASS_LINE) {
    1830                 //     if (Type.evaluate(el.visProp.firstarrow)) {
    1831                 //         this._setArrowWidth(el.rendNodeTriangleStart, w, el.rendNode);
    1832                 //     }
    1833                 //
    1834                 //     if (Type.evaluate(el.visProp.lastarrow)) {
    1835                 //         this._setArrowWidth(el.rendNodeTriangleEnd, w, el.rendNode);
    1836                 //     }
    1837                 // }
    1838             }
    1839             el.visPropOld.strokewidth = w;
    1840         },
    1841
    1842         // documented in JXG.AbstractRenderer
    1843         setLineCap: function (el) {
    1844             var capStyle = Type.evaluate(el.visProp.linecap);
    1845
    1846             if (
    1847                 capStyle === undefined ||
    1848                 capStyle === "" ||
    1849                 el.visPropOld.linecap === capStyle ||
    1850                 !Type.exists(el.rendNode)
    1851             ) {
    1852                 return;
    1853             }
    1854
    1855             this.setPropertyPrim(el.rendNode, "stroke-linecap", capStyle);
    1856             el.visPropOld.linecap = capStyle;
    1857         },
    1858
    1859         // documented in JXG.AbstractRenderer
    1860         setShadow: function (el) {
    1861             var ev_s = Type.evaluate(el.visProp.shadow),
    1862                 ev_s_json, c, b, bl, o, op, id, node,
    1863                 use_board_filter = true,
    1864                 show = false;
    1865
    1866             ev_s_json = JSON.stringify(ev_s);
    1867             if (ev_s_json === el.visPropOld.shadow) {
    1868                 return;
    1869             }
    1870
    1871             if (typeof ev_s === 'boolean') {
    1872                 use_board_filter = true;
    1873                 show = ev_s;
    1874                 c = 'none';
    1875                 b = 3;
    1876                 bl = 0.1;
    1877                 o = [5, 5];
    1878                 op = 1;
    1879             } else {
    1880                 if (Type.evaluate(ev_s.enabled)) {
    1881                     use_board_filter = false;
    1882                     show = true;
    1883                     c = JXG.rgbParser(Type.evaluate(ev_s.color));
    1884                     b = Type.evaluate(ev_s.blur);
    1885                     bl = Type.evaluate(ev_s.blend);
    1886                     o = Type.evaluate(ev_s.offset);
    1887                     op = Type.evaluate(ev_s.opacity);
    1888                 } else {
    1889                     show = false;
    1890                 }
    1891             }
    1892
    1893             if (Type.exists(el.rendNode)) {
    1894                 if (show) {
    1895                     if (use_board_filter) {
    1896                         el.rendNode.setAttributeNS(null, 'filter', this.toURL(this.container.id + '_' + 'f1'));
    1897                         // 'url(#' + this.container.id + '_' + 'f1)');
    1898                     } else {
    1899                         node = this.container.ownerDocument.getElementById(id);
    1900                         if (node) {
    1901                             this.defs.removeChild(node);
    1902                         }
    1903                         id = el.rendNode.id + '_' + 'f1';
    1904                         this.defs.appendChild(this.createShadowFilter(id, c, op, bl, b, o));
    1905                         el.rendNode.setAttributeNS(null, 'filter', this.toURL(id));
    1906                         // 'url(#' + id + ')');
    1907                     }
    1908                 } else {
    1909                     el.rendNode.removeAttributeNS(null, 'filter');
    1910                 }
    1911             }
    1912
    1913             el.visPropOld.shadow = ev_s_json;
    1914         },
    1915
    1916         /* **************************
    1917          * renderer control
    1918          * **************************/
    1919
    1920         // documented in JXG.AbstractRenderer
    1921         suspendRedraw: function () {
    1922             // It seems to be important for the Linux version of firefox
    1923             this.suspendHandle = this.svgRoot.suspendRedraw(10000);
    1924         },
    1925
    1926         // documented in JXG.AbstractRenderer
    1927         unsuspendRedraw: function () {
    1928             this.svgRoot.unsuspendRedraw(this.suspendHandle);
    1929             // this.svgRoot.unsuspendRedrawAll();
    1930             //this.svgRoot.forceRedraw();
    1931         },
    1932
    1933         // documented in AbstractRenderer
    1934         resize: function (w, h) {
    1935             this.svgRoot.setAttribute("width", parseFloat(w));
    1936             this.svgRoot.setAttribute("height", parseFloat(h));
    1937         },
    1938
    1939         // documented in JXG.AbstractRenderer
    1940         createTouchpoints: function (n) {
    1941             var i, na1, na2, node;
    1942             this.touchpoints = [];
    1943             for (i = 0; i < n; i++) {
    1944                 na1 = "touchpoint1_" + i;
    1945                 node = this.createPrim("path", na1);
    1946                 this.appendChildPrim(node, 19);
    1947                 node.setAttributeNS(null, "d", "M 0 0");
    1948                 this.touchpoints.push(node);
    1949
    1950                 this.setPropertyPrim(node, "stroked", "true");
    1951                 this.setPropertyPrim(node, "stroke-width", "1px");
    1952                 node.setAttributeNS(null, "stroke", "#000000");
    1953                 node.setAttributeNS(null, "stroke-opacity", 1.0);
    1954                 node.setAttributeNS(null, "display", "none");
    1955
    1956                 na2 = "touchpoint2_" + i;
    1957                 node = this.createPrim("ellipse", na2);
    1958                 this.appendChildPrim(node, 19);
    1959                 this.updateEllipsePrim(node, 0, 0, 0, 0);
    1960                 this.touchpoints.push(node);
    1961
    1962                 this.setPropertyPrim(node, "stroked", "true");
    1963                 this.setPropertyPrim(node, "stroke-width", "1px");
    1964                 node.setAttributeNS(null, "stroke", "#000000");
    1965                 node.setAttributeNS(null, "stroke-opacity", 1.0);
    1966                 node.setAttributeNS(null, "fill", "#ffffff");
    1967                 node.setAttributeNS(null, "fill-opacity", 0.0);
    1968
    1969                 node.setAttributeNS(null, "display", "none");
    1970             }
    1971         },
    1972
    1973         // documented in JXG.AbstractRenderer
    1974         showTouchpoint: function (i) {
    1975             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
    1976                 this.touchpoints[2 * i].setAttributeNS(null, "display", "inline");
    1977                 this.touchpoints[2 * i + 1].setAttributeNS(null, "display", "inline");
    1978             }
    1979         },
    1980
    1981         // documented in JXG.AbstractRenderer
    1982         hideTouchpoint: function (i) {
    1983             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
    1984                 this.touchpoints[2 * i].setAttributeNS(null, "display", "none");
    1985                 this.touchpoints[2 * i + 1].setAttributeNS(null, "display", "none");
    1986             }
    1987         },
    1988
    1989         // documented in JXG.AbstractRenderer
    1990         updateTouchpoint: function (i, pos) {
    1991             var x,
    1992                 y,
    1993                 d = 37;
    1994
    1995             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
    1996                 x = pos[0];
    1997                 y = pos[1];
    1998
    1999                 this.touchpoints[2 * i].setAttributeNS(
    2000                     null,
    2001                     "d",
    2002                     "M " +
    2003                     (x - d) +
    2004                     " " +
    2005                     y +
    2006                     " " +
    2007                     "L " +
    2008                     (x + d) +
    2009                     " " +
    2010                     y +
    2011                     " " +
    2012                     "M " +
    2013                     x +
    2014                     " " +
    2015                     (y - d) +
    2016                     " " +
    2017                     "L " +
    2018                     x +
    2019                     " " +
    2020                     (y + d)
    2021                 );
    2022                 this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25);
    2023             }
    2024         },
    2025
    2026         /**
    2027          * Walk recursively through the DOM subtree of a node and collect all
    2028          * value attributes together with the id of that node.
    2029          * <b>Attention:</b> Only values of nodes having a valid id are taken.
    2030          * @param  {Node} node   root node of DOM subtree that will be searched recursively.
    2031          * @return {Array}      Array with entries of the form [id, value]
    2032          * @private
    2033          */
    2034         _getValuesOfDOMElements: function (node) {
    2035             var values = [];
    2036             if (node.nodeType === 1) {
    2037                 node = node.firstChild;
    2038                 while (node) {
    2039                     if (node.id !== undefined && node.value !== undefined) {
    2040                         values.push([node.id, node.value]);
    2041                     }
    2042                     Type.concat(values, this._getValuesOfDOMElements(node));
    2043                     node = node.nextSibling;
    2044                 }
    2045             }
    2046             return values;
    2047         },
    2048
    2049         // _getDataUri: function (url, callback) {
    2050         //     var image = new Image();
    2051         //     image.onload = function () {
    2052         //         var canvas = document.createElement("canvas");
    2053         //         canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
    2054         //         canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size
    2055         //         canvas.getContext("2d").drawImage(this, 0, 0);
    2056         //         callback(canvas.toDataURL("image/png"));
    2057         //         canvas.remove();
    2058         //     };
    2059         //     image.src = url;
    2060         // },
    2061
    2062         _getImgDataURL: function (svgRoot) {
    2063             var images, len, canvas, ctx, ur, i;
    2064
    2065             images = svgRoot.getElementsByTagName("image");
    2066             len = images.length;
    2067             if (len > 0) {
    2068                 canvas = document.createElement("canvas");
    2069                 //img = new Image();
    2070                 for (i = 0; i < len; i++) {
    2071                     images[i].setAttribute("crossorigin", "anonymous");
    2072                     //img.src = images[i].href;
    2073                     //img.onload = function() {
    2074                     // img.crossOrigin = "anonymous";
    2075                     ctx = canvas.getContext("2d");
    2076                     canvas.width = images[i].getAttribute("width");
    2077                     canvas.height = images[i].getAttribute("height");
    2078                     try {
    2079                         ctx.drawImage(images[i], 0, 0, canvas.width, canvas.height);
    2080
    2081                         // If the image is not png, the format must be specified here
    2082                         ur = canvas.toDataURL();
    2083                         images[i].setAttribute("xlink:href", ur);
    2084                     } catch (err) {
    2085                         console.log("CORS problem! Image can not be used", err);
    2086                     }
    2087                 }
    2088                 //canvas.remove();
    2089             }
    2090             return true;
    2091         },
    2092
    2093         /**
    2094          * Return a data URI of the SVG code representing the construction.
    2095          * The SVG code of the construction is base64 encoded. The return string starts
    2096          * with "data:image/svg+xml;base64,...".
    2097          *
    2098          * @param {Boolean} ignoreTexts If true, the foreignObject tag is set to display=none.
    2099          * This is necessary for older versions of Safari. Default: false
    2100          * @returns {String}  data URI string
    2101          *
    2102          * @example
    2103          * var A = board.create('point', [2, 2]);
    2104          *
    2105          * var txt = board.renderer.dumpToDataURI(false);
    2106          * // txt consists of a string of the form
    2107          * // data:image/svg+xml;base64,PHN2Zy. base64 encoded SVG..+PC9zdmc+
    2108          * // Behind the comma, there is the base64 encoded SVG code
    2109          * // which is decoded with atob().
    2110          * // The call of decodeURIComponent(escape(...)) is necessary
    2111          * // to handle unicode strings correctly.
    2112          * var ar = txt.split(',');
    2113          * document.getElementById('output').value = decodeURIComponent(escape(atob(ar[1])));
    2114          *
    2115          * </pre><div id="JXG1bad4bec-6d08-4ce0-9b7f-d817e8dd762d" class="jxgbox" style="width: 300px; height: 300px;"></div>
    2116          * <textarea id="output2023" rows="5" cols="50"></textarea>
    2117          * <script type="text/javascript">
    2118          *     (function() {
    2119          *         var board = JXG.JSXGraph.initBoard('JXG1bad4bec-6d08-4ce0-9b7f-d817e8dd762d',
    2120          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    2121          *     var A = board.create('point', [2, 2]);
    2122          *
    2123          *     var txt = board.renderer.dumpToDataURI(false);
    2124          *     // txt consists of a string of the form
    2125          *     // data:image/svg+xml;base64,PHN2Zy. base64 encoded SVG..+PC9zdmc+
    2126          *     // Behind the comma, there is the base64 encoded SVG code
    2127          *     // which is decoded with atob().
    2128          *     // The call of decodeURIComponent(escape(...)) is necessary
    2129          *     // to handle unicode strings correctly.
    2130          *     var ar = txt.split(',');
    2131          *     document.getElementById('output2023').value = decodeURIComponent(escape(atob(ar[1])));
    2132          *
    2133          *     })();
    2134          *
    2135          * </script><pre>
    2136          *
    2137          */
    2138         dumpToDataURI: function (ignoreTexts) {
    2139             var svgRoot = this.svgRoot,
    2140                 btoa = window.btoa || Base64.encode,
    2141                 svg, i, len,
    2142                 values = [];
    2143
    2144             // Move all HTML tags (beside the SVG root) of the container
    2145             // to the foreignObject element inside of the svgRoot node
    2146             // Problem:
    2147             // input values are not copied. This can be verified by looking at an innerHTML output
    2148             // of an input element. Therefore, we do it "by hand".
    2149             if (this.container.hasChildNodes() && Type.exists(this.foreignObjLayer)) {
    2150                 if (!ignoreTexts) {
    2151                     this.foreignObjLayer.setAttribute("display", "inline");
    2152                 }
    2153                 while (svgRoot.nextSibling) {
    2154                     // Copy all value attributes
    2155                     Type.concat(values, this._getValuesOfDOMElements(svgRoot.nextSibling));
    2156                     this.foreignObjLayer.appendChild(svgRoot.nextSibling);
    2157                 }
    2158             }
    2159
    2160             this._getImgDataURL(svgRoot);
    2161
    2162             // Convert the SVG graphic into a string containing SVG code
    2163             svgRoot.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    2164             svg = new XMLSerializer().serializeToString(svgRoot);
    2165
    2166             if (ignoreTexts !== true) {
    2167                 // Handle SVG texts
    2168                 // Insert all value attributes back into the svg string
    2169                 len = values.length;
    2170                 for (i = 0; i < len; i++) {
    2171                     svg = svg.replace(
    2172                         'id="' + values[i][0] + '"',
    2173                         'id="' + values[i][0] + '" value="' + values[i][1] + '"'
    2174                     );
    2175                 }
    2176             }
    2177
    2178             // if (false) {
    2179             //     // Debug: use example svg image
    2180             //     svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="220" height="220"><rect width="66" height="30" x="21" y="32" stroke="#204a87" stroke-width="2" fill="none" /></svg>';
    2181             // }
    2182
    2183             // In IE we have to remove the namespace again.
    2184             // Since 2024 we have to check if the namespace attribute appears twice in one tag, because
    2185             // there might by a svg inside of the svg, e.g. the screenshot icon.
    2186             if (this.isIE &&
    2187                 (svg.match(/xmlns="http:\/\/www.w3.org\/2000\/svg"\s+xmlns="http:\/\/www.w3.org\/2000\/svg"/g) || []).length > 1
    2188             ) {
    2189                 svg = svg.replace(/xmlns="http:\/\/www.w3.org\/2000\/svg"\s+xmlns="http:\/\/www.w3.org\/2000\/svg"/g, "");
    2190             }
    2191
    2192             // Safari fails if the svg string contains a " "
    2193             // Obsolete with Safari 12+
    2194             svg = svg.replace(/ /g, " ");
    2195             svg = svg.replace(/url\("(.*)"\)/g, "url($1)");
    2196
    2197             // Move all HTML tags back from
    2198             // the foreignObject element to the container
    2199             if (Type.exists(this.foreignObjLayer) && this.foreignObjLayer.hasChildNodes()) {
    2200                 // Restore all HTML elements
    2201                 while (this.foreignObjLayer.firstChild) {
    2202                     this.container.appendChild(this.foreignObjLayer.firstChild);
    2203                 }
    2204                 this.foreignObjLayer.setAttribute("display", "none");
    2205             }
    2206
    2207             return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg)));
    2208         },
    2209
    2210         /**
    2211          * Convert the SVG construction into an HTML canvas image.
    2212          * This works for all SVG supporting browsers. Implemented as Promise.
    2213          * <p>
    2214          * Might fail if any text element or foreign object element contains SVG. This
    2215          * is the case e.g. for the default fullscreen symbol.
    2216          * <p>
    2217          * For IE, it is realized as function.
    2218          * It works from version 9, with the exception that HTML texts
    2219          * are ignored on IE. The drawing is done with a delay of
    2220          * 200 ms. Otherwise there would be problems with IE.
    2221          *
    2222          * @param {String} canvasId Id of an HTML canvas element
    2223          * @param {Number} w Width in pixel of the dumped image, i.e. of the canvas tag.
    2224          * @param {Number} h Height in pixel of the dumped image, i.e. of the canvas tag.
    2225          * @param {Boolean} ignoreTexts If true, the foreignObject tag is taken out from the SVG root.
    2226          * This is necessary for older versions of Safari. Default: false
    2227          * @returns {Promise}  Promise object
    2228          *
    2229          * @example
    2230          * 	board.renderer.dumpToCanvas('canvas').then(function() { console.log('done'); });
    2231          *
    2232          * @example
    2233          *  // IE 11 example:
    2234          * 	board.renderer.dumpToCanvas('canvas');
    2235          * 	setTimeout(function() { console.log('done'); }, 400);
    2236          */
    2237         dumpToCanvas: function (canvasId, w, h, ignoreTexts) {
    2238             var svg, tmpImg,
    2239                 cv, ctx,
    2240                 doc = this.container.ownerDocument;
    2241
    2242             // Prepare the canvas element
    2243             cv = doc.getElementById(canvasId);
    2244
    2245             // Clear the canvas
    2246             /* eslint-disable no-self-assign */
    2247             cv.width = cv.width;
    2248             /* eslint-enable no-self-assign */
    2249
    2250             ctx = cv.getContext("2d");
    2251             if (w !== undefined && h !== undefined) {
    2252                 cv.style.width = parseFloat(w) + "px";
    2253                 cv.style.height = parseFloat(h) + "px";
    2254                 // Scale twice the CSS size to make the image crisp
    2255                 // cv.setAttribute('width', 2 * parseFloat(wOrg));
    2256                 // cv.setAttribute('height', 2 * parseFloat(hOrg));
    2257                 // ctx.scale(2 * wOrg / w, 2 * hOrg / h);
    2258                 cv.setAttribute("width", parseFloat(w));
    2259                 cv.setAttribute("height", parseFloat(h));
    2260             }
    2261
    2262             // Display the SVG string as data-uri in an HTML img.
    2263             /**
    2264              * @type {Image}
    2265              * @ignore
    2266              * {ignore}
    2267              */
    2268             tmpImg = new Image();
    2269             svg = this.dumpToDataURI(ignoreTexts);
    2270             tmpImg.src = svg;
    2271
    2272             // Finally, draw the HTML img in the canvas.
    2273             if (!("Promise" in window)) {
    2274                 /**
    2275                  * @function
    2276                  * @ignore
    2277                  */
    2278                 tmpImg.onload = function () {
    2279                     // IE needs a pause...
    2280                     // Seems to be broken
    2281                     window.setTimeout(function () {
    2282                         try {
    2283                             ctx.drawImage(tmpImg, 0, 0, w, h);
    2284                         } catch (err) {
    2285                             console.log("screenshots not longer supported on IE");
    2286                         }
    2287                     }, 200);
    2288                 };
    2289                 return this;
    2290             }
    2291
    2292             return new Promise(function (resolve, reject) {
    2293                 try {
    2294                     tmpImg.onload = function () {
    2295                         ctx.drawImage(tmpImg, 0, 0, w, h);
    2296                         resolve();
    2297                     };
    2298                 } catch (e) {
    2299                     reject(e);
    2300                 }
    2301             });
    2302         },
    2303
    2304         /**
    2305          * Display SVG image in html img-tag which enables
    2306          * easy download for the user.
    2307          *
    2308          * Support:
    2309          * <ul>
    2310          * <li> IE: No
    2311          * <li> Edge: full
    2312          * <li> Firefox: full
    2313          * <li> Chrome: full
    2314          * <li> Safari: full (No text support in versions prior to 12).
    2315          * </ul>
    2316          *
    2317          * @param {JXG.Board} board Link to the board.
    2318          * @param {String} imgId Optional id of an img object. If given and different from the empty string,
    2319          * the screenshot is copied to this img object. The width and height will be set to the values of the
    2320          * JSXGraph container.
    2321          * @param {Boolean} ignoreTexts If set to true, the foreignObject is taken out of the
    2322          *  SVGRoot and texts are not displayed. This is mandatory for Safari. Default: false
    2323          * @return {Object}       the svg renderer object
    2324          */
    2325         screenshot: function (board, imgId, ignoreTexts) {
    2326             var node,
    2327                 doc = this.container.ownerDocument,
    2328                 parent = this.container.parentNode,
    2329                 // cPos,
    2330                 // cssTxt,
    2331                 canvas, id, img,
    2332                 button, buttonText,
    2333                 w, h,
    2334                 bas = board.attr.screenshot,
    2335                 navbar, navbarDisplay, insert,
    2336                 newImg = false,
    2337                 _copyCanvasToImg,
    2338                 isDebug = false;
    2339
    2340             if (this.type === "no") {
    2341                 return this;
    2342             }
    2343
    2344             w = bas.scale * this.container.getBoundingClientRect().width;
    2345             h = bas.scale * this.container.getBoundingClientRect().height;
    2346
    2347             if (imgId === undefined || imgId === "") {
    2348                 newImg = true;
    2349                 img = new Image(); //doc.createElement('img');
    2350                 img.style.width = w + "px";
    2351                 img.style.height = h + "px";
    2352             } else {
    2353                 newImg = false;
    2354                 img = doc.getElementById(imgId);
    2355             }
    2356             // img.crossOrigin = 'anonymous';
    2357
    2358             // Create div which contains canvas element and close button
    2359             if (newImg) {
    2360                 node = doc.createElement("div");
    2361                 node.style.cssText = bas.css;
    2362                 node.style.width = w + "px";
    2363                 node.style.height = h + "px";
    2364                 node.style.zIndex = this.container.style.zIndex + 120;
    2365
    2366                 // Try to position the div exactly over the JSXGraph board
    2367                 node.style.position = "absolute";
    2368                 node.style.top = this.container.offsetTop + "px";
    2369                 node.style.left = this.container.offsetLeft + "px";
    2370             }
    2371
    2372             if (!isDebug) {
    2373                 // Create canvas element and add it to the DOM
    2374                 // It will be removed after the image has been stored.
    2375                 canvas = doc.createElement("canvas");
    2376                 id = Math.random().toString(36).slice(2, 7);
    2377                 canvas.setAttribute("id", id);
    2378                 canvas.setAttribute("width", w);
    2379                 canvas.setAttribute("height", h);
    2380                 canvas.style.width = w + "px";
    2381                 canvas.style.height = w + "px";
    2382                 canvas.style.display = "none";
    2383                 parent.appendChild(canvas);
    2384             } else {
    2385                 // Debug: use canvas element 'jxgbox_canvas' from jsxdev/dump.html
    2386                 id = "jxgbox_canvas";
    2387                 canvas = doc.getElementById(id);
    2388             }
    2389
    2390             if (newImg) {
    2391                 // Create close button
    2392                 button = doc.createElement("span");
    2393                 buttonText = doc.createTextNode("\u2716");
    2394                 button.style.cssText = bas.cssButton;
    2395                 button.appendChild(buttonText);
    2396                 button.onclick = function () {
    2397                     node.parentNode.removeChild(node);
    2398                 };
    2399
    2400                 // Add all nodes
    2401                 node.appendChild(img);
    2402                 node.appendChild(button);
    2403                 parent.insertBefore(node, this.container.nextSibling);
    2404             }
    2405
    2406             // Hide navigation bar in board
    2407             navbar = doc.getElementById(this.uniqName('navigationbar'));
    2408             if (Type.exists(navbar)) {
    2409                 navbarDisplay = navbar.style.display;
    2410                 navbar.style.display = "none";
    2411                 insert = this.removeToInsertLater(navbar);
    2412             }
    2413
    2414             _copyCanvasToImg = function () {
    2415                 // Show image in img tag
    2416                 img.src = canvas.toDataURL("image/png");
    2417
    2418                 // Remove canvas node
    2419                 if (!isDebug) {
    2420                     parent.removeChild(canvas);
    2421                 }
    2422             };
    2423
    2424             // Create screenshot in image element
    2425             if ("Promise" in window) {
    2426                 this.dumpToCanvas(id, w, h, ignoreTexts).then(_copyCanvasToImg);
    2427             } else {
    2428                 // IE
    2429                 this.dumpToCanvas(id, w, h, ignoreTexts);
    2430                 window.setTimeout(_copyCanvasToImg, 200);
    2431             }
    2432
    2433             // Reinsert navigation bar in board
    2434             if (Type.exists(navbar)) {
    2435                 navbar.style.display = navbarDisplay;
    2436                 insert();
    2437             }
    2438
    2439             return this;
    2440         }
    2441     }
    2442 );
    2443
    2444 export default JXG.SVGRenderer;
    2445