      1 /*
      2     Copyright 2008-2015
      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 <http://www.gnu.org/licenses/>
     29     and <http://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 /* depends:
     36  jxg
     37  options
     38  renderer/abstract
     39  base/constants
     40  utils/type
     41  utils/env
     42  utils/color
     43  math/numerics
     44 */
     45
     46 define([
     47     'jxg', 'options', 'renderer/abstract', 'base/constants', 'utils/type', 'utils/env', 'utils/color', 'math/numerics'
     48 ], function (JXG, Options, AbstractRenderer, Const, Type, Env, Color, Numerics) {
     49
     50     "use strict";
     51
     52     /**
     53      * Uses SVG to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
     54      * @class JXG.AbstractRenderer
     55      * @augments JXG.AbstractRenderer
     56      * @param {Node} container Reference to a DOM node containing the board.
     57      * @param {Object} dim The dimensions of the board
     58      * @param {Number} dim.width
     59      * @param {Number} dim.height
     60      * @see JXG.AbstractRenderer
     61      */
     62     JXG.SVGRenderer = function (container, dim) {
     63         var i;
     64
     65         // docstring in AbstractRenderer
     66         this.type = 'svg';
     67
     68         this.isIE = navigator.appVersion.indexOf("MSIE") !== -1 || navigator.userAgent.match(/Trident\//);
     69
     70         /**
     71          * SVG root node
     72          * @type Node
     73          */
     74         this.svgRoot = null;
     75
     76         /**
     77          * The SVG Namespace used in JSXGraph.
     78          * @see http://www.w3.org/TR/SVG/
     79          * @type String
     80          * @default http://www.w3.org/2000/svg
     81          */
     82         this.svgNamespace = 'http://www.w3.org/2000/svg';
     83
     84         /**
     85          * The xlink namespace. This is used for images.
     86          * @see http://www.w3.org/TR/xlink/
     87          * @type String
     88          * @default http://www.w3.org/1999/xlink
     89          */
     90         this.xlinkNamespace = 'http://www.w3.org/1999/xlink';
     91
     92         // container is documented in AbstractRenderer
     93         this.container = container;
     94
     95         // prepare the div container and the svg root node for use with JSXGraph
     96         this.container.style.MozUserSelect = 'none';
     97
     98         this.container.style.overflow = 'hidden';
     99         if (this.container.style.position === '') {
    100             this.container.style.position = 'relative';
    101         }
    102
    103         this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg");
    104         this.svgRoot.style.overflow = 'hidden';
    105
    106         this.svgRoot.style.width = dim.width + 'px';
    107         this.svgRoot.style.height = dim.height + 'px';
    108
    109         this.container.appendChild(this.svgRoot);
    110
    111         /**
    112          * The <tt>defs</tt> element is a container element to reference reusable SVG elements.
    113          * @type Node
    114          * @see http://www.w3.org/TR/SVG/struct.html#DefsElement
    115          */
    116         this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, 'defs');
    117         this.svgRoot.appendChild(this.defs);
    118
    119         /**
    120          * Filters are used to apply shadows.
    121          * @type Node
    122          * @see http://www.w3.org/TR/SVG/filters.html#FilterElement
    123          */
    124         this.filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter');
    125         this.filter.setAttributeNS(null, 'id', this.container.id + '_' + 'f1');
    126         /*
    127         this.filter.setAttributeNS(null, 'x', '-100%');
    128         this.filter.setAttributeNS(null, 'y', '-100%');
    129         this.filter.setAttributeNS(null, 'width', '400%');
    130         this.filter.setAttributeNS(null, 'height', '400%');
    131         //this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse');
    132         */
    133         this.filter.setAttributeNS(null, 'width', '300%');
    134         this.filter.setAttributeNS(null, 'height', '300%');
    135         this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse');
    136
    137         this.feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset');
    138         this.feOffset.setAttributeNS(null, 'result', 'offOut');
    139         this.feOffset.setAttributeNS(null, 'in', 'SourceAlpha');
    140         this.feOffset.setAttributeNS(null, 'dx', '5');
    141         this.feOffset.setAttributeNS(null, 'dy', '5');
    142         this.filter.appendChild(this.feOffset);
    143
    144         this.feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur');
    145         this.feGaussianBlur.setAttributeNS(null, 'result', 'blurOut');
    146         this.feGaussianBlur.setAttributeNS(null, 'in', 'offOut');
    147         this.feGaussianBlur.setAttributeNS(null, 'stdDeviation', '3');
    148         this.filter.appendChild(this.feGaussianBlur);
    149
    150         this.feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend');
    151         this.feBlend.setAttributeNS(null, 'in', 'SourceGraphic');
    152         this.feBlend.setAttributeNS(null, 'in2', 'blurOut');
    153         this.feBlend.setAttributeNS(null, 'mode', 'normal');
    154         this.filter.appendChild(this.feBlend);
    155
    156         this.defs.appendChild(this.filter);
    157
    158         /**
    159          * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front
    160          * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented
    161          * there, too. The higher the number, the "more on top" are the elements on this layer.
    162          * @type Array
    163          */
    164         this.layer = [];
    165         for (i = 0; i < Options.layer.numlayers; i++) {
    166             this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g');
    167             this.svgRoot.appendChild(this.layer[i]);
    168         }
    169
    170         /**
    171          * Defines dash patterns. Defined styles are: <ol>
    172          * <li value="-1"> 2px dash, 2px space</li>
    173          * <li> 5px dash, 5px space</li>
    174          * <li> 10px dash, 10px space</li>
    175          * <li> 20px dash, 20px space</li>
    176          * <li> 20px dash, 10px space, 10px dash, 10px dash</li>
    177          * <li> 20px dash, 5px space, 10px dash, 5px space</li></ol>
    178          * @type Array
    179          * @default ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5']
    180          * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties
    181          */
    182         this.dashArray = ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5'];
    183     };
    184
    185     JXG.SVGRenderer.prototype = new AbstractRenderer();
    186
    187     JXG.extend(JXG.SVGRenderer.prototype, /** @lends JXG.SVGRenderer.prototype */ {
    188
    189         /**
    190          * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag.
    191          * @private
    192          * @param {JXG.GeometryElement} element A JSXGraph element, preferably one that can have an arrow attached.
    193          * @param {String} [idAppendix=''] A string that is added to the node's id.
    194          * @returns {Node} Reference to the node added to the DOM.
    195          */
    196         _createArrowHead: function (element, idAppendix) {
    197             var node2, node3,
    198                 id = element.id + 'Triangle',
    199                 s, d;
    200
    201             if (Type.exists(idAppendix)) {
    202                 id += idAppendix;
    203             }
    204             node2 = this.createPrim('marker', id);
    205
    206             node2.setAttributeNS(null, 'stroke', Type.evaluate(element.visProp.strokecolor));
    207             node2.setAttributeNS(null, 'stroke-opacity', Type.evaluate(element.visProp.strokeopacity));
    208             node2.setAttributeNS(null, 'fill', Type.evaluate(element.visProp.strokecolor));
    209             node2.setAttributeNS(null, 'fill-opacity', Type.evaluate(element.visProp.strokeopacity));
    210             node2.setAttributeNS(null, 'stroke-width', 0);  // this is the stroke-width of the arrow head.
    211                                                             // Should be zero to make the positioning easy
    212
    213             node2.setAttributeNS(null, 'orient', 'auto');
    214             node2.setAttributeNS(null, 'markerUnits', 'strokeWidth'); // 'strokeWidth' 'userSpaceOnUse');
    215
    216             /*
    217             * Changes here are also necessary in _setArrowAtts()
    218             */
    219             s = parseInt(element.visProp.strokewidth, 10);
    220             //node2.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 12 + ' ' + s * 12);
    221             node2.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 10 + ' ' + s * 10);
    222
    223             /*
    224                The arrow head is an equilateral triangle with base length 10 and height 10.
    225                This 10 units are scaled to strokeWidth*3 pixels or minimum 10 pixels.
    226                See also abstractRenderer.updateLine() where the line path is shortened accordingly.
    227             */
    228             d = Math.max(s * 3, 10);
    229             node2.setAttributeNS(null, 'markerHeight', d);
    230             node2.setAttributeNS(null, 'markerWidth', d);
    231
    232             node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, 'path');
    233
    234             if (idAppendix === 'End') {     // First arrow
    235                 node2.setAttributeNS(null, 'refY', 5);
    236                 node2.setAttributeNS(null, 'refX', 10);
    237                 node3.setAttributeNS(null, 'd', 'M 10 0 L 0 5 L 10 10 z');
    238             } else {                        // Last arrow
    239                 node2.setAttributeNS(null, 'refY', 5);
    240                 node2.setAttributeNS(null, 'refX', 0);
    241                 node3.setAttributeNS(null, 'd', 'M 0 0 L 10 5 L 0 10 z');
    242             }
    243
    244             node2.appendChild(node3);
    245             return node2;
    246         },
    247
    248         /**
    249          * Updates an arrow DOM node.
    250          * @param {Node} node The arrow node.
    251          * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green.
    252          * @param {Number} opacity
    253          * @param {Number} width
    254          */
    255         _setArrowAtts: function (node, color, opacity, width, parentNode) {
    256             var s, d;
    257
    258             if (node) {
    259                 node.setAttributeNS(null, 'stroke', color);
    260                 node.setAttributeNS(null, 'stroke-opacity', opacity);
    261                 node.setAttributeNS(null, 'fill', color);
    262                 node.setAttributeNS(null, 'fill-opacity', opacity);
    263
    264                 // This is the stroke-width of the arrow head.
    265                 // Should be zero to make the positioning easy
    266                 node.setAttributeNS(null, 'stroke-width', 0);
    267
    268                 // The next lines are important if the strokeWidth of the line is changed.
    269                 s = width;
    270                 node.setAttributeNS(null, 'viewBox', (-s) + ' ' + (-s) + ' ' + s * 10 + ' ' + s * 10);
    271                 d = Math.max(s * 3, 10);
    272
    273                 node.setAttributeNS(null, 'markerHeight', d);
    274                 node.setAttributeNS(null, 'markerWidth', d);
    275
    276                 if (this.isIE) {
    277                     parentNode.parentNode.insertBefore(parentNode, parentNode);
    278                 }
    279             }
    280
    281         },
    282
    283         /* ******************************** *
    284          *  This renderer does not need to
    285          *  override draw/update* methods
    286          *  since it provides draw/update*Prim
    287          *  methods except for some cases like
    288          *  internal texts or images.
    289          * ******************************** */
    290
    291         /* **************************
    292          *    Lines
    293          * **************************/
    294
    295         // documented in AbstractRenderer
    296         updateTicks: function (ticks) {
    297             var i, c, node, x, y,
    298                 tickStr = '',
    299                 len = ticks.ticks.length;
    300
    301             for (i = 0; i < len; i++) {
    302                 c = ticks.ticks[i];
    303                 x = c[0];
    304                 y = c[1];
    305
    306                 if (typeof x[0] === 'number' && typeof x[1] === 'number') {
    307                     tickStr += "M " + (x[0]) + " " + (y[0]) + " L " + (x[1]) + " " + (y[1]) + " ";
    308                 }
    309             }
    310
    311             node = ticks.rendNode;
    312
    313             if (!Type.exists(node)) {
    314                 node = this.createPrim('path', ticks.id);
    315                 this.appendChildPrim(node, ticks.visProp.layer);
    316                 ticks.rendNode = node;
    317             }
    318
    319             node.setAttributeNS(null, 'stroke', ticks.visProp.strokecolor);
    320             node.setAttributeNS(null, 'stroke-opacity', ticks.visProp.strokeopacity);
    321             node.setAttributeNS(null, 'stroke-width', ticks.visProp.strokewidth);
    322             this.updatePathPrim(node, tickStr, ticks.board);
    323         },
    324
    325         /* **************************
    326          *    Text related stuff
    327          * **************************/
    328
    329         // already documented in JXG.AbstractRenderer
    330         displayCopyright: function (str, fontsize) {
    331             var node = this.createPrim('text', 'licenseText'),
    332                 t;
    333             node.setAttributeNS(null, 'x', '20px');
    334             node.setAttributeNS(null, 'y', (2 + fontsize) + 'px');
    335             node.setAttributeNS(null, "style", "font-family:Arial,Helvetica,sans-serif; font-size:" + fontsize + "px; fill:#356AA0;  opacity:0.3;");
    336             t = this.container.ownerDocument.createTextNode(str);
    337             node.appendChild(t);
    338             this.appendChildPrim(node, 0);
    339         },
    340
    341         // already documented in JXG.AbstractRenderer
    342         drawInternalText: function (el) {
    343             var node = this.createPrim('text', el.id);
    344
    345             node.setAttributeNS(null, "class", el.visProp.cssclass);
    346             //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox
    347
    348             // Preserve spaces
    349             node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "space", "preserve");
    350
    351             el.rendNodeText = this.container.ownerDocument.createTextNode('');
    352             node.appendChild(el.rendNodeText);
    353             this.appendChildPrim(node,  el.visProp.layer);
    354
    355             return node;
    356         },
    357
    358         // already documented in JXG.AbstractRenderer
    359         updateInternalText: function (el) {
    360             var content = el.plaintext, v;
    361
    362             // el.rendNode.setAttributeNS(null, "class", el.visProp.cssclass);
    363             if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
    364
    365                 // Horizontal
    366                 v = el.coords.scrCoords[1];
    367                 if (el.visPropOld.left !== (el.visProp.anchorx + v)) {
    368                     el.rendNode.setAttributeNS(null, 'x', v + 'px');
    369
    370                     if (el.visProp.anchorx === 'left') {
    371                         el.rendNode.setAttributeNS(null, 'text-anchor', 'start');
    372                     } else if (el.visProp.anchorx === 'right') {
    373                         el.rendNode.setAttributeNS(null, 'text-anchor', 'end');
    374                     } else if (el.visProp.anchorx === 'middle') {
    375                         el.rendNode.setAttributeNS(null, 'text-anchor', 'middle');
    376                     }
    377                     el.visPropOld.left = el.visProp.anchorx + v;
    378                 }
    379
    380                 // Vertical
    381                 v = el.coords.scrCoords[2];
    382                 if (el.visPropOld.top !== (el.visProp.anchory + v)) {
    383                     el.rendNode.setAttributeNS(null, 'y', (v + this.vOffsetText * 0.5) + 'px');
    384
    385                     if (el.visProp.anchory === 'bottom') {
    386                         el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-after-edge');
    387                     } else if (el.visProp.anchory === 'top') {
    388                         el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-before-edge');
    389                     } else if (el.visProp.anchory === 'middle') {
    390                         el.rendNode.setAttributeNS(null, 'dominant-baseline', 'middle');
    391                     }
    392                     el.visPropOld.top = el.visProp.anchory + v;
    393                 }
    394             }
    395             if (el.htmlStr !== content) {
    396                 el.rendNodeText.data = content;
    397                 el.htmlStr = content;
    398             }
    399             this.transformImage(el, el.transformations);
    400         },
    401
    402         /**
    403          * Set color and opacity of internal texts.
    404          * SVG needs its own version.
    405          * @private
    406          * @see JXG.AbstractRenderer#updateTextStyle
    407          * @see JXG.AbstractRenderer#updateInternalTextStyle
    408          */
    409         updateInternalTextStyle: function (element, strokeColor, strokeOpacity) {
    410             this.setObjectFillColor(element, strokeColor, strokeOpacity);
    411         },
    412
    413         /* **************************
    414          *    Image related stuff
    415          * **************************/
    416
    417         // already documented in JXG.AbstractRenderer
    418         drawImage: function (el) {
    419             var node = this.createPrim('image', el.id);
    420
    421             node.setAttributeNS(null, 'preserveAspectRatio', 'none');
    422             this.appendChildPrim(node, el.visProp.layer);
    423             el.rendNode = node;
    424
    425             this.updateImage(el);
    426         },
    427
    428         // already documented in JXG.AbstractRenderer
    429         transformImage: function (el, t) {
    430             var s, m,
    431                 node = el.rendNode,
    432                 str = "",
    433                 len = t.length;
    434
    435             if (len > 0) {
    436                 m = this.joinTransforms(el, t);
    437                 s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(',');
    438                 str += ' matrix(' + s + ') ';
    439                 node.setAttributeNS(null, 'transform', str);
    440             }
    441         },
    442
    443         // already documented in JXG.AbstractRenderer
    444         updateImageURL: function (el) {
    445             var url = Type.evaluate(el.url);
    446
    447             el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url);
    448         },
    449
    450         // already documented in JXG.AbstractRenderer
    451         updateImageStyle: function (el, doHighlight) {
    452             var css = doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass;
    453
    454             el.rendNode.setAttributeNS(null, 'class', css);
    455         },
    456
    457         /* **************************
    458          * Render primitive objects
    459          * **************************/
    460
    461         // already documented in JXG.AbstractRenderer
    462         appendChildPrim: function (node, level) {
    463             if (!Type.exists(level)) { // trace nodes have level not set
    464                 level = 0;
    465             } else if (level >= Options.layer.numlayers) {
    466                 level = Options.layer.numlayers - 1;
    467             }
    468
    469             this.layer[level].appendChild(node);
    470
    471             return node;
    472         },
    473
    474         // already documented in JXG.AbstractRenderer
    475         createPrim: function (type, id) {
    476             var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type);
    477             node.setAttributeNS(null, 'id', this.container.id + '_' + id);
    478             node.style.position = 'absolute';
    479             if (type === 'path') {
    480                 node.setAttributeNS(null, 'stroke-linecap', 'round');
    481                 node.setAttributeNS(null, 'stroke-linejoin', 'round');
    482             }
    483             return node;
    484         },
    485
    486         // already documented in JXG.AbstractRenderer
    487         remove: function (shape) {
    488             if (Type.exists(shape) && Type.exists(shape.parentNode)) {
    489                 shape.parentNode.removeChild(shape);
    490             }
    491         },
    492
    493         // already documented in JXG.AbstractRenderer
    494         makeArrows: function (el) {
    495             var node2;
    496
    497             if (el.visPropOld.firstarrow === el.visProp.firstarrow && el.visPropOld.lastarrow === el.visProp.lastarrow) {
    498                 if (this.isIE && el.visProp.visible && (el.visProp.firstarrow || el.visProp.lastarrow)) {
    499                     el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode);
    500                 }
    501                 return;
    502             }
    503
    504             if (el.visProp.firstarrow) {
    505                 node2 = el.rendNodeTriangleStart;
    506                 if (!Type.exists(node2)) {
    507                     node2 = this._createArrowHead(el, 'End');
    508                     this.defs.appendChild(node2);
    509                     el.rendNodeTriangleStart = node2;
    510                     el.rendNode.setAttributeNS(null, 'marker-start', 'url(#' + this.container.id + '_' + el.id + 'TriangleEnd)');
    511                 } else {
    512                     this.defs.appendChild(node2);
    513                 }
    514             } else {
    515                 node2 = el.rendNodeTriangleStart;
    516                 if (Type.exists(node2)) {
    517                     this.remove(node2);
    518                 }
    519             }
    520             if (el.visProp.lastarrow) {
    521                 node2 = el.rendNodeTriangleEnd;
    522                 if (!Type.exists(node2)) {
    523                     node2 = this._createArrowHead(el, 'Start');
    524                     this.defs.appendChild(node2);
    525                     el.rendNodeTriangleEnd = node2;
    526                     el.rendNode.setAttributeNS(null, 'marker-end', 'url(#' + this.container.id + '_' + el.id + 'TriangleStart)');
    527                 } else {
    528                     this.defs.appendChild(node2);
    529                 }
    530             } else {
    531                 node2 = el.rendNodeTriangleEnd;
    532                 if (Type.exists(node2)) {
    533                     this.remove(node2);
    534                 }
    535             }
    536             el.visPropOld.firstarrow = el.visProp.firstarrow;
    537             el.visPropOld.lastarrow = el.visProp.lastarrow;
    538         },
    539
    540         // already documented in JXG.AbstractRenderer
    541         updateEllipsePrim: function (node, x, y, rx, ry) {
    542             var huge = 1000000;
    543
    544             // webkit does not like huge values if the object is dashed
    545             x = Math.abs(x) < huge ? x : huge * x / Math.abs(x);
    546             y = Math.abs(y) < huge ? y : huge * y / Math.abs(y);
    547             rx = Math.abs(rx) < huge ? rx : huge * rx / Math.abs(rx);
    548             ry = Math.abs(ry) < huge ? ry : huge * ry / Math.abs(ry);
    549
    550             node.setAttributeNS(null, 'cx', x);
    551             node.setAttributeNS(null, 'cy', y);
    552             node.setAttributeNS(null, 'rx', Math.abs(rx));
    553             node.setAttributeNS(null, 'ry', Math.abs(ry));
    554         },
    555
    556         // already documented in JXG.AbstractRenderer
    557         updateLinePrim: function (node, p1x, p1y, p2x, p2y) {
    558             var huge = 1000000;
    559
    560             if (!isNaN(p1x + p1y + p2x + p2y)) {
    561                 // webkit does not like huge values if the object is dashed
    562                 p1x = Math.abs(p1x) < huge ? p1x : huge * p1x / Math.abs(p1x);
    563                 p1y = Math.abs(p1y) < huge ? p1y : huge * p1y / Math.abs(p1y);
    564                 p2x = Math.abs(p2x) < huge ? p2x : huge * p2x / Math.abs(p2x);
    565                 p2y = Math.abs(p2y) < huge ? p2y : huge * p2y / Math.abs(p2y);
    566
    567                 node.setAttributeNS(null, 'x1', p1x);
    568                 node.setAttributeNS(null, 'y1', p1y);
    569                 node.setAttributeNS(null, 'x2', p2x);
    570                 node.setAttributeNS(null, 'y2', p2y);
    571             }
    572         },
    573
    574         // already documented in JXG.AbstractRenderer
    575         updatePathPrim: function (node, pointString) {
    576             if (pointString === '') {
    577                 pointString = 'M 0 0';
    578             }
    579             node.setAttributeNS(null, 'd', pointString);
    580         },
    581
    582         // already documented in JXG.AbstractRenderer
    583         updatePathStringPoint: function (el, size, type) {
    584             var s = '',
    585                 scr = el.coords.scrCoords,
    586                 sqrt32 = size * Math.sqrt(3) * 0.5,
    587                 s05 = size * 0.5;
    588
    589             if (type === 'x') {
    590                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2] - size) +
    591                     ' L ' + (scr[1] + size) + ' ' + (scr[2] + size) +
    592                     ' M ' + (scr[1] + size) + ' ' + (scr[2] - size) +
    593                     ' L ' + (scr[1] - size) + ' ' + (scr[2] + size);
    594             } else if (type === '+') {
    595                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
    596                     ' L ' + (scr[1] + size) + ' ' + (scr[2]) +
    597                     ' M ' + (scr[1])        + ' ' + (scr[2] - size) +
    598                     ' L ' + (scr[1])        + ' ' + (scr[2] + size);
    599             } else if (type === '<>') {
    600                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
    601                     ' L ' + (scr[1])        + ' ' + (scr[2] + size) +
    602                     ' L ' + (scr[1] + size) + ' ' + (scr[2]) +
    603                     ' L ' + (scr[1])        + ' ' + (scr[2] - size) + ' Z ';
    604             } else if (type === '^') {
    605                 s = ' M ' + (scr[1])          + ' ' + (scr[2] - size) +
    606                     ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] + s05) +
    607                     ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] + s05) +
    608                     ' Z ';  // close path
    609             } else if (type === 'v') {
    610                 s = ' M ' + (scr[1])          + ' ' + (scr[2] + size) +
    611                     ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] - s05) +
    612                     ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] - s05) +
    613                     ' Z ';
    614             } else if (type === '>') {
    615                 s = ' M ' + (scr[1] + size) + ' ' + (scr[2]) +
    616                     ' L ' + (scr[1] - s05)  + ' ' + (scr[2] - sqrt32) +
    617                     ' L ' + (scr[1] - s05)  + ' ' + (scr[2] + sqrt32) +
    618                     ' Z ';
    619             } else if (type === '<') {
    620                 s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
    621                     ' L ' + (scr[1] + s05)  + ' ' + (scr[2] - sqrt32) +
    622                     ' L ' + (scr[1] + s05)  + ' ' + (scr[2] + sqrt32) +
    623                     ' Z ';
    624             }
    625             return s;
    626         },
    627
    628         // already documented in JXG.AbstractRenderer
    629         updatePathStringPrim: function (el) {
    630             var i, scr, len,
    631                 symbm = ' M ',
    632                 symbl = ' L ',
    633                 symbc = ' C ',
    634                 nextSymb = symbm,
    635                 maxSize = 5000.0,
    636                 pStr = '';
    637                 // isNotPlot = (el.visProp.curvetype !== 'plot');
    638
    639             if (el.numberPoints <= 0) {
    640                 return '';
    641             }
    642
    643             len = Math.min(el.points.length, el.numberPoints);
    644
    645             if (el.bezierDegree === 1) {
    646                 /*
    647                 if (isNotPlot && el.visProp.rdpsmoothing) {
    648                     el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
    649                     el.numberPoints = el.points.length;
    650                 }
    651                 */
    652
    653                 for (i = 0; i < len; i++) {
    654                     scr = el.points[i].scrCoords;
    655                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
    656                         nextSymb = symbm;
    657                     } else {
    658                         // Chrome has problems with values being too far away.
    659                         scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize);
    660                         scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize);
    661
    662                         // Attention: first coordinate may be inaccurate if far way
    663                         //pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
    664                         pStr += nextSymb + scr[1] + ' ' + scr[2];   // Seems to be faster now (webkit and firefox)
    665                         nextSymb = symbl;
    666                     }
    667                 }
    668             } else if (el.bezierDegree === 3) {
    669                 i = 0;
    670                 while (i < len) {
    671                     scr = el.points[i].scrCoords;
    672                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
    673                         nextSymb = symbm;
    674                     } else {
    675                         pStr += nextSymb + scr[1] + ' ' + scr[2];
    676                         if (nextSymb === symbc) {
    677                             i += 1;
    678                             scr = el.points[i].scrCoords;
    679                             pStr += ' ' + scr[1] + ' ' + scr[2];
    680                             i += 1;
    681                             scr = el.points[i].scrCoords;
    682                             pStr += ' ' + scr[1] + ' ' + scr[2];
    683                         }
    684                         nextSymb = symbc;
    685                     }
    686                     i += 1;
    687                 }
    688             }
    689             return pStr;
    690         },
    691
    692         // already documented in JXG.AbstractRenderer
    693         updatePathStringBezierPrim: function (el) {
    694             var i, j, k, scr, lx, ly, len,
    695                 symbm = ' M ',
    696                 symbl = ' C ',
    697                 nextSymb = symbm,
    698                 maxSize = 5000.0,
    699                 pStr = '',
    700                 f = el.visProp.strokewidth,
    701                 isNoPlot = (el.visProp.curvetype !== 'plot');
    702
    703             if (el.numberPoints <= 0) {
    704                 return '';
    705             }
    706
    707             if (isNoPlot && el.board.options.curve.RDPsmoothing) {
    708                 el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
    709             }
    710
    711             len = Math.min(el.points.length, el.numberPoints);
    712             for (j = 1; j < 3; j++) {
    713                 nextSymb = symbm;
    714                 for (i = 0; i < len; i++) {
    715                     scr = el.points[i].scrCoords;
    716
    717                     if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
    718                         nextSymb = symbm;
    719                     } else {
    720                         // Chrome has problems with values being too far away.
    721                         scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize);
    722                         scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize);
    723
    724                         // Attention: first coordinate may be inaccurate if far way
    725                         if (nextSymb === symbm) {
    726                             //pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
    727                             pStr += nextSymb + scr[1] + ' ' + scr[2];   // Seems to be faster now (webkit and firefox)
    728                         } else {
    729                             k = 2 * j;
    730                             pStr += [nextSymb,
    731                                 (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)), ' ',
    732                                 (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)), ' ',
    733                                 (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)), ' ',
    734                                 (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)), ' ',
    735                                 scr[1], ' ', scr[2]].join('');
    736                         }
    737
    738                         nextSymb = symbl;
    739                         lx = scr[1];
    740                         ly = scr[2];
    741                     }
    742                 }
    743             }
    744             return pStr;
    745         },
    746
    747         // already documented in JXG.AbstractRenderer
    748         updatePolygonPrim: function (node, el) {
    749             var i,
    750                 pStr = '',
    751                 scrCoords,
    752                 len = el.vertices.length;
    753
    754             node.setAttributeNS(null, 'stroke', 'none');
    755
    756             for (i = 0; i < len - 1; i++) {
    757                 if (el.vertices[i].isReal) {
    758                     scrCoords = el.vertices[i].coords.scrCoords;
    759                     pStr = pStr + scrCoords[1] + "," + scrCoords[2];
    760                 } else {
    761                     node.setAttributeNS(null, 'points', '');
    762                     return;
    763                 }
    764
    765                 if (i < len - 2) {
    766                     pStr += " ";
    767                 }
    768             }
    769             if (pStr.indexOf('NaN') === -1) {
    770                 node.setAttributeNS(null, 'points', pStr);
    771             }
    772         },
    773
    774         // already documented in JXG.AbstractRenderer
    775         updateRectPrim: function (node, x, y, w, h) {
    776             node.setAttributeNS(null, 'x', x);
    777             node.setAttributeNS(null, 'y', y);
    778             node.setAttributeNS(null, 'width', w);
    779             node.setAttributeNS(null, 'height', h);
    780         },
    781
    782         /* **************************
    783          *  Set Attributes
    784          * **************************/
    785
    786         // documented in JXG.AbstractRenderer
    787         setPropertyPrim: function (node, key, val) {
    788             if (key === 'stroked') {
    789                 return;
    790             }
    791             node.setAttributeNS(null, key, val);
    792         },
    793
    794         // documented in JXG.AbstractRenderer
    795         show: function (el) {
    796             var node;
    797
    798             if (el && el.rendNode) {
    799                 node = el.rendNode;
    800                 node.setAttributeNS(null, 'display', 'inline');
    801                 node.style.visibility = "inherit";
    802             }
    803         },
    804
    805         // documented in JXG.AbstractRenderer
    806         hide: function (el) {
    807             var node;
    808
    809             if (el && el.rendNode) {
    810                 node = el.rendNode;
    811                 node.setAttributeNS(null, 'display', 'none');
    812                 node.style.visibility = "hidden";
    813             }
    814         },
    815
    816         // documented in JXG.AbstractRenderer
    817         setBuffering: function (el, type) {
    818             el.rendNode.setAttribute('buffered-rendering', type);
    819         },
    820
    821         // documented in JXG.AbstractRenderer
    822         setDashStyle: function (el) {
    823             var dashStyle = el.visProp.dash, node = el.rendNode;
    824
    825             if (el.visProp.dash > 0) {
    826                 node.setAttributeNS(null, 'stroke-dasharray', this.dashArray[dashStyle - 1]);
    827             } else {
    828                 if (node.hasAttributeNS(null, 'stroke-dasharray')) {
    829                     node.removeAttributeNS(null, 'stroke-dasharray');
    830                 }
    831             }
    832         },
    833
    834         // documented in JXG.AbstractRenderer
    835         setGradient: function (el) {
    836             var fillNode = el.rendNode, col, op,
    837                 node, node2, node3, x1, x2, y1, y2;
    838
    839             op = Type.evaluate(el.visProp.fillopacity);
    840             op = (op > 0) ? op : 0;
    841
    842             col = Type.evaluate(el.visProp.fillcolor);
    843
    844             if (el.visProp.gradient === 'linear') {
    845                 node = this.createPrim('linearGradient', el.id + '_gradient');
    846                 x1 = '0%';
    847                 x2 = '100%';
    848                 y1 = '0%';
    849                 y2 = '0%';
    850
    851                 node.setAttributeNS(null, 'x1', x1);
    852                 node.setAttributeNS(null, 'x2', x2);
    853                 node.setAttributeNS(null, 'y1', y1);
    854                 node.setAttributeNS(null, 'y2', y2);
    855                 node2 = this.createPrim('stop', el.id + '_gradient1');
    856                 node2.setAttributeNS(null, 'offset', '0%');
    857                 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
    858                 node3 = this.createPrim('stop', el.id + '_gradient2');
    859                 node3.setAttributeNS(null, 'offset', '100%');
    860                 node3.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
    861                 node.appendChild(node2);
    862                 node.appendChild(node3);
    863                 this.defs.appendChild(node);
    864                 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)');
    865                 el.gradNode1 = node2;
    866                 el.gradNode2 = node3;
    867             } else if (el.visProp.gradient === 'radial') {
    868                 node = this.createPrim('radialGradient', el.id + '_gradient');
    869
    870                 node.setAttributeNS(null, 'cx', '50%');
    871                 node.setAttributeNS(null, 'cy', '50%');
    872                 node.setAttributeNS(null, 'r', '50%');
    873                 node.setAttributeNS(null, 'fx', el.visProp.gradientpositionx * 100 + '%');
    874                 node.setAttributeNS(null, 'fy', el.visProp.gradientpositiony * 100 + '%');
    875
    876                 node2 = this.createPrim('stop', el.id + '_gradient1');
    877                 node2.setAttributeNS(null, 'offset', '0%');
    878                 node2.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
    879                 node3 = this.createPrim('stop', el.id + '_gradient2');
    880                 node3.setAttributeNS(null, 'offset', '100%');
    881                 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
    882
    883                 node.appendChild(node2);
    884                 node.appendChild(node3);
    885                 this.defs.appendChild(node);
    886                 fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)');
    887                 el.gradNode1 = node2;
    888                 el.gradNode2 = node3;
    889             } else {
    890                 fillNode.removeAttributeNS(null, 'style');
    891             }
    892         },
    893
    894         // documented in JXG.AbstractRenderer
    895         updateGradient: function (el) {
    896             var col, op,
    897                 node2 = el.gradNode1,
    898                 node3 = el.gradNode2;
    899
    900             if (!Type.exists(node2) || !Type.exists(node3)) {
    901                 return;
    902             }
    903
    904             op = Type.evaluate(el.visProp.fillopacity);
    905             op = (op > 0) ? op : 0;
    906
    907             col = Type.evaluate(el.visProp.fillcolor);
    908
    909             if (el.visProp.gradient === 'linear') {
    910                 node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
    911                 node3.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
    912             } else if (el.visProp.gradient === 'radial') {
    913                 node2.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
    914                 node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
    915             }
    916         },
    917
    918         // documented in JXG.AbstractRenderer
    919         setObjectFillColor: function (el, color, opacity) {
    920             var node, c, rgbo, oo,
    921                 rgba = Type.evaluate(color),
    922                 o = Type.evaluate(opacity);
    923
    924             o = (o > 0) ? o : 0;
    925
    926             if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
    927                 return;
    928             }
    929             if (Type.exists(rgba) && rgba !== false) {
    930                 if (rgba.length !== 9) {          // RGB, not RGBA
    931                     c = rgba;
    932                     oo = o;
    933                 } else {                       // True RGBA, not RGB
    934                     rgbo = Color.rgba2rgbo(rgba);
    935                     c = rgbo[0];
    936                     oo = o * rgbo[1];
    937                 }
    938
    939                 node = el.rendNode;
    940
    941                 if (c !== 'none') {               // problem in firefox 17
    942                     node.setAttributeNS(null, 'fill', c);
    943                 } else {
    944                     oo = 0;
    945                 }
    946
    947                 if (el.type === JXG.OBJECT_TYPE_IMAGE) {
    948                     node.setAttributeNS(null, 'opacity', oo);
    949                     //node.style['opacity'] = oo;  // This would overwrite values set y CSS class.
    950                 } else {
    951                     node.setAttributeNS(null, 'fill-opacity', oo);
    952                 }
    953
    954                 if (Type.exists(el.visProp.gradient)) {
    955                     this.updateGradient(el);
    956                 }
    957             }
    958             el.visPropOld.fillcolor = rgba;
    959             el.visPropOld.fillopacity = o;
    960         },
    961
    962         // documented in JXG.AbstractRenderer
    963         setObjectStrokeColor: function (el, color, opacity) {
    964             var rgba = Type.evaluate(color), c, rgbo,
    965                 o = Type.evaluate(opacity), oo,
    966                 node;
    967
    968             o = (o > 0) ? o : 0;
    969
    970             if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
    971                 return;
    972             }
    973
    974             if (Type.exists(rgba) && rgba !== false) {
    975                 if (rgba.length !== 9) {          // RGB, not RGBA
    976                     c = rgba;
    977                     oo = o;
    978                 } else {                       // True RGBA, not RGB
    979                     rgbo = Color.rgba2rgbo(rgba);
    980                     c = rgbo[0];
    981                     oo = o * rgbo[1];
    982                 }
    983
    984                 node = el.rendNode;
    985
    986                 if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
    987                     if (el.visProp.display === 'html') {
    988                         node.style.color = c;
    989                         node.style.opacity = oo;
    990                     } else {
    991                         node.setAttributeNS(null, "style", "fill:" + c);
    992                         node.setAttributeNS(null, "style", "fill-opacity:" + oo);
    993                     }
    994                 } else {
    995                     node.setAttributeNS(null, 'stroke', c);
    996                     node.setAttributeNS(null, 'stroke-opacity', oo);
    997                 }
    998
    999                 if (el.type === Const.OBJECT_TYPE_ARROW) {
    1000                     this._setArrowAtts(el.rendNodeTriangle, c, oo, el.visProp.strokewidth, el.rendNode);
    1001                 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE || el.elementClass === Const.OBJECT_CLASS_LINE) {
    1002                     if (el.visProp.firstarrow) {
    1003                         this._setArrowAtts(el.rendNodeTriangleStart, c, oo, el.visProp.strokewidth, el.rendNode);
    1004                     }
    1005
    1006                     if (el.visProp.lastarrow) {
    1007                         this._setArrowAtts(el.rendNodeTriangleEnd, c, oo, el.visProp.strokewidth, el.rendNode);
    1008                     }
    1009                 }
    1010             }
    1011
    1012             el.visPropOld.strokecolor = rgba;
    1013             el.visPropOld.strokeopacity = o;
    1014         },
    1015
    1016         // documented in JXG.AbstractRenderer
    1017         setObjectStrokeWidth: function (el, width) {
    1018             var node,
    1019                 w = Type.evaluate(width);
    1020
    1021             if (isNaN(w) || el.visPropOld.strokewidth === w) {
    1022                 return;
    1023             }
    1024
    1025             node = el.rendNode;
    1026             this.setPropertyPrim(node, 'stroked', 'true');
    1027             if (Type.exists(w)) {
    1028                 this.setPropertyPrim(node, 'stroke-width', w + 'px');
    1029
    1030                 if (el.type === Const.OBJECT_TYPE_ARROW) {
    1031                     this._setArrowAtts(el.rendNodeTriangle, el.visProp.strokecolor, el.visProp.strokeopacity, w, el.rendNode);
    1032                 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE || el.elementClass === Const.OBJECT_CLASS_LINE) {
    1033                     if (el.visProp.firstarrow) {
    1034                         this._setArrowAtts(el.rendNodeTriangleStart, el.visProp.strokecolor, el.visProp.strokeopacity, w, el.rendNode);
    1035                     }
    1036
    1037                     if (el.visProp.lastarrow) {
    1038                         this._setArrowAtts(el.rendNodeTriangleEnd, el.visProp.strokecolor, el.visProp.strokeopacity, w, el.rendNode);
    1039                     }
    1040                 }
    1041             }
    1042             el.visPropOld.strokewidth = w;
    1043         },
    1044
    1045         // documented in JXG.AbstractRenderer
    1046         setShadow: function (el) {
    1047             if (el.visPropOld.shadow === el.visProp.shadow) {
    1048                 return;
    1049             }
    1050
    1051             if (Type.exists(el.rendNode)) {
    1052                 if (el.visProp.shadow) {
    1053                     el.rendNode.setAttributeNS(null, 'filter', 'url(#' + this.container.id + '_' + 'f1)');
    1054                 } else {
    1055                     el.rendNode.removeAttributeNS(null, 'filter');
    1056                 }
    1057             }
    1058             el.visPropOld.shadow = el.visProp.shadow;
    1059         },
    1060
    1061         /* **************************
    1062          * renderer control
    1063          * **************************/
    1064
    1065         // documented in JXG.AbstractRenderer
    1066         suspendRedraw: function () {
    1067             // It seems to be important for the Linux version of firefox
    1068             //this.suspendHandle = this.svgRoot.suspendRedraw(10000);
    1069         },
    1070
    1071         // documented in JXG.AbstractRenderer
    1072         unsuspendRedraw: function () {
    1073             //this.svgRoot.unsuspendRedraw(this.suspendHandle);
    1074             //this.svgRoot.unsuspendRedrawAll();
    1075             //this.svgRoot.forceRedraw();
    1076         },
    1077
    1078         // documented in AbstractRenderer
    1079         resize: function (w, h) {
    1080             this.svgRoot.style.width = parseFloat(w) + 'px';
    1081             this.svgRoot.style.height = parseFloat(h) + 'px';
    1082         },
    1083
    1084         // documented in JXG.AbstractRenderer
    1085         createTouchpoints: function (n) {
    1086             var i, na1, na2, node;
    1087             this.touchpoints = [];
    1088             for (i = 0; i < n; i++) {
    1089                 na1 = 'touchpoint1_' + i;
    1090                 node = this.createPrim('path', na1);
    1091                 this.appendChildPrim(node, 19);
    1092                 node.setAttributeNS(null, 'd', 'M 0 0');
    1093                 this.touchpoints.push(node);
    1094
    1095                 this.setPropertyPrim(node, 'stroked', 'true');
    1096                 this.setPropertyPrim(node, 'stroke-width', '1px');
    1097                 node.setAttributeNS(null, 'stroke', '#000000');
    1098                 node.setAttributeNS(null, 'stroke-opacity', 1.0);
    1099                 node.setAttributeNS(null, 'display', 'none');
    1100
    1101                 na2 = 'touchpoint2_' + i;
    1102                 node = this.createPrim('ellipse', na2);
    1103                 this.appendChildPrim(node, 19);
    1104                 this.updateEllipsePrim(node, 0, 0, 0, 0);
    1105                 this.touchpoints.push(node);
    1106
    1107                 this.setPropertyPrim(node, 'stroked', 'true');
    1108                 this.setPropertyPrim(node, 'stroke-width', '1px');
    1109                 node.setAttributeNS(null, 'stroke', '#000000');
    1110                 node.setAttributeNS(null, 'stroke-opacity', 1.0);
    1111                 node.setAttributeNS(null, 'fill', '#ffffff');
    1112                 node.setAttributeNS(null, 'fill-opacity', 0.0);
    1113
    1114                 node.setAttributeNS(null, 'display', 'none');
    1115             }
    1116         },
    1117
    1118         // documented in JXG.AbstractRenderer
    1119         showTouchpoint: function (i) {
    1120             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
    1121                 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'inline');
    1122                 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'inline');
    1123             }
    1124         },
    1125
    1126         // documented in JXG.AbstractRenderer
    1127         hideTouchpoint: function (i) {
    1128             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
    1129                 this.touchpoints[2 * i].setAttributeNS(null, 'display', 'none');
    1130                 this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'none');
    1131             }
    1132         },
    1133
    1134         // documented in JXG.AbstractRenderer
    1135         updateTouchpoint: function (i, pos) {
    1136             var x, y,
    1137                 d = 37;
    1138
    1139             if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
    1140                 x = pos[0];
    1141                 y = pos[1];
    1142
    1143                 this.touchpoints[2 * i].setAttributeNS(null, 'd', 'M ' + (x - d) + ' ' + y + ' ' +
    1144                     'L ' + (x + d) + ' ' + y + ' ' +
    1145                     'M ' + x + ' ' + (y - d) + ' ' +
    1146                     'L ' + x + ' ' + (y + d));
    1147                 this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25);
    1148             }
    1149         }
    1150     });
    1151
    1152     return JXG.SVGRenderer;
    1153 });
    1154