      1 /*
      2     Copyright 2008-2011
      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: you can redistribute it and/or modify
     13     it under the terms of the GNU Lesser General Public License as published by
     14     the Free Software Foundation, either version 3 of the License, or
     15     (at your option) any later version.
     16
     17     JSXGraph is distributed in the hope that it will be useful,
     18     but WITHOUT ANY WARRANTY; without even the implied warranty of
     19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     20     GNU Lesser General Public License for more details.
     21
     22     You should have received a copy of the GNU Lesser General Public License
     23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
     24 */
     25
     26 /*jshint bitwise: false, curly: true, debug: false, eqeqeq: true, devel: false, evil: false,
     27   forin: false, immed: true, laxbreak: false, newcap: false, noarg: true, nonew: true, onevar: true,
     28    undef: true, white: true, sub: false*/
     29 /*global JXG: true, AMprocessNode: true, MathJax: true, document: true */
     30
     31 /**
     32  * Uses SVG to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
     33  * @class JXG.AbstractRenderer
     34  * @augments JXG.AbstractRenderer
     35  * @param {Node} container Reference to a DOM node containing the board.
     36  * @see JXG.AbstractRenderer
     37  */
     38 JXG.SVGRenderer = function (container) {
     39     var i;
     40
     41     // docstring in AbstractRenderer
     42     this.type = 'svg';
     43
     44     /**
     45      * SVG root node
     46      * @type Node
     47      * @private
     48      */
     49     this.svgRoot = null;
     50
     51     /**
     52      * @private
     53      */
     54     //this.suspendHandle = null;
     55
     56     /**
     57      * The SVG Namespace used in JSXGraph.
     58      * @see http://www.w3.org/TR/SVG/
     59      * @type String
     60      * @default http://www.w3.org/2000/svg
     61      */
     62     this.svgNamespace = 'http://www.w3.org/2000/svg';
     63
     64     /**
     65      * The xlink namespace. This is used for images.
     66      * @see http://www.w3.org/TR/xlink/
     67      * @type String
     68      * @default http://www.w3.org/1999/xlink
     69      */
     70     this.xlinkNamespace = 'http://www.w3.org/1999/xlink';
     71
     72     // container is documented in AbstractRenderer
     73     this.container = container;
     74
     75     // prepare the div container and the svg root node for use with JSXGraph
     76     this.container.style.MozUserSelect = 'none';
     77
     78     this.container.style.overflow = 'hidden';
     79     if (this.container.style.position === '') {
     80         this.container.style.position = 'relative';
     81     }
     82
     83     this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg");
     84     this.svgRoot.style.overflow = 'hidden';
     85
     86     this.svgRoot.style.width = JXG.getStyle(this.container, 'width');
     87     this.svgRoot.style.height = JXG.getStyle(this.container, 'height');
     88
     89     this.container.appendChild(this.svgRoot);
     90
     91     /**
     92      * The <tt>defs</tt> element is a container element to reference reusable SVG elements.
     93      * @type Node
     94      * @see http://www.w3.org/TR/SVG/struct.html#DefsElement
     95      */
     96     this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, 'defs');
     97     this.svgRoot.appendChild(this.defs);
     98
     99     /**
    100      * Filters are used to apply shadows.
    101      * @type Node
    102      * @see http://www.w3.org/TR/SVG/filters.html#FilterElement
    103      */
    104     this.filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter');
    105     this.filter.setAttributeNS(null, 'id', this.container.id + '_' + 'f1');
    106     this.filter.setAttributeNS(null, 'width', '300%');
    107     this.filter.setAttributeNS(null, 'height', '300%');
    108     this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse');
    109
    110     this.feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset');
    111     this.feOffset.setAttributeNS(null, 'result', 'offOut');
    112     this.feOffset.setAttributeNS(null, 'in', 'SourceAlpha');
    113     this.feOffset.setAttributeNS(null, 'dx', '5');
    114     this.feOffset.setAttributeNS(null, 'dy', '5');
    115     this.filter.appendChild(this.feOffset);
    116
    117     this.feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur');
    118     this.feGaussianBlur.setAttributeNS(null, 'result', 'blurOut');
    119     this.feGaussianBlur.setAttributeNS(null, 'in', 'offOut');
    120     this.feGaussianBlur.setAttributeNS(null, 'stdDeviation', '3');
    121     this.filter.appendChild(this.feGaussianBlur);
    122
    123     this.feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend');
    124     this.feBlend.setAttributeNS(null, 'in', 'SourceGraphic');
    125     this.feBlend.setAttributeNS(null, 'in2', 'blurOut');
    126     this.feBlend.setAttributeNS(null, 'mode', 'normal');
    127     this.filter.appendChild(this.feBlend);
    128
    129     this.defs.appendChild(this.filter);
    130
    131     /**
    132      * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front
    133      * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented
    134      * there, too. The higher the number, the "more on top" are the elements on this layer.
    135      * @type Array
    136      */
    137     this.layer = [];
    138     for (i = 0; i < JXG.Options.layer.numlayers; i++) {
    139         this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g');
    140         this.svgRoot.appendChild(this.layer[i]);
    141     }
    142
    143     /**
    144      * Defines dash patterns. Defined styles are: <ol>
    145      * <li value="-1"> 2px dash, 2px space</li>
    146      * <li> 5px dash, 5px space</li>
    147      * <li> 10px dash, 10px space</li>
    148      * <li> 20px dash, 20px space</li>
    149      * <li> 20px dash, 10px space, 10px dash, 10px dash</li>
    150      * <li> 20px dash, 5px space, 10px dash, 5px space</li></ol>
    151      * @type Array
    152      * @default ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5']
    153      * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties
    154      */
    155     this.dashArray = ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5'];
    156 };
    157
    158 JXG.SVGRenderer.prototype = new JXG.AbstractRenderer();
    159
    160 JXG.extend(JXG.SVGRenderer.prototype, /** @lends JXG.SVGRenderer.prototype */ {
    161
    162     /**
    163      * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag.
    164      * @private
    165      * @param {JXG.GeometryElement} element A JSXGraph element, preferably one that can have an arrow attached.
    166      * @param {String} [idAppendix=''] A string that is added to the node's id.
    167      * @returns {Node} Reference to the node added to the DOM.
    168      */
    169     _createArrowHead: function (element, idAppendix) {
    170         var id = element.id + 'Triangle',
    171             node2, node3;
    172
    173         if (JXG.exists(idAppendix)) {
    174             id += idAppendix;
    175         }
    176         node2 = this.createPrim('marker', id);
    177
    178         node2.setAttributeNS(null, 'viewBox', '0 0 10 6');
    179         node2.setAttributeNS(null, 'refY', '3');
    180         node2.setAttributeNS(null, 'markerUnits', 'userSpaceOnUse'); //'strokeWidth');
    181         node2.setAttributeNS(null, 'markerHeight', '12');
    182         node2.setAttributeNS(null, 'markerWidth', '10');
    183         node2.setAttributeNS(null, 'orient', 'auto');
    184         node2.setAttributeNS(null, 'stroke', element.visProp.strokecolor);
    185         node2.setAttributeNS(null, 'stroke-opacity', element.visProp.strokeopacity);
    186         node2.setAttributeNS(null, 'fill', element.visProp.strokecolor);
    187         node2.setAttributeNS(null, 'fill-opacity', element.visProp.strokeopacity);
    188         node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, 'path');
    189
    190         if (idAppendix === 'End') {
    191             node2.setAttributeNS(null, 'refX', '0');
    192             node3.setAttributeNS(null, 'd', 'M 0 3 L 10 6 L 10 0 z');
    193         } else {
    194             node2.setAttributeNS(null, 'refX', '10');
    195             node3.setAttributeNS(null, 'd', 'M 0 0 L 10 3 L 0 6 z');
    196         }
    197         node2.appendChild(node3);
    198         return node2;
    199     },
    200
    201     /**
    202      * Updates an arrow DOM node.
    203      * @param {Node} node The arrow node.
    204      * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green.
    205      * @param {Number} opacity
    206      */
    207     _setArrowAtts: function (node, color, opacity) {
    208         if (node) {
    209             node.setAttributeNS(null, 'stroke', color);
    210             node.setAttributeNS(null, 'stroke-opacity', opacity);
    211             node.setAttributeNS(null, 'fill', color);
    212             node.setAttributeNS(null, 'fill-opacity', opacity);
    213         }
    214     },
    215
    216     /* ******************************** *
    217      *  This renderer does not need to
    218      *  override draw/update* methods
    219      *  since it provides draw/update*Prim
    220      *  methods except for some cases like
    221      *  internal texts or images.
    222      * ******************************** */
    223
    224     /* **************************
    225      *    Lines
    226      * **************************/
    227
    228     // documented in AbstractRenderer
    229     updateTicks: function (axis, dxMaj, dyMaj, dxMin, dyMin, minStyle, majStyle) {
    230         var tickStr = '',
    231             i, c, node,
    232             x, y,
    233             len = axis.ticks.length;
    234
    235         for (i = 0; i < len; i++) {
    236             c = axis.ticks[i];
    237             x = c[0];
    238             y = c[1];
    239             if (typeof x[0] != 'undefined' && typeof x[1] != 'undefined') {
    240                 tickStr += "M " + (x[0]) + " " + (y[0]) + " L " + (x[1]) + " " + (y[1]) + " ";
    241             }
    242         }
    243         // Labels
    244         for (i = 0; i < len; i++) {
    245             c = axis.ticks[i].scrCoords;
    246             if (axis.ticks[i].major
    247                 && (axis.board.needsFullUpdate || axis.needsRegularUpdate)
    248                 && axis.labels[i]
    249                 && axis.labels[i].visProp.visible) {
    250                     this.updateText(axis.labels[i]);
    251             }
    252         }
    253         node = this.getElementById(axis.id);
    254         if (!JXG.exists(node)) {
    255             node = this.createPrim('path', axis.id);
    256             this.appendChildPrim(node, axis.visProp.layer);
    257             this.appendNodesToElement(axis, 'path');
    258         }
    259         node.setAttributeNS(null, 'stroke', axis.visProp.strokecolor);
    260         node.setAttributeNS(null, 'stroke-opacity', axis.visProp.strokeopacity);
    261         node.setAttributeNS(null, 'stroke-width', axis.visProp.strokewidth);
    262         this.updatePathPrim(node, tickStr, axis.board);
    263     },
    264
    265     /* **************************
    266      *    Text related stuff
    267      * **************************/
    268
    269     // already documented in JXG.AbstractRenderer
    270     displayCopyright: function (str, fontsize) {
    271         var node = this.createPrim('text', 'licenseText'),
    272             t;
    273         node.setAttributeNS(null, 'x', '20px');
    274         node.setAttributeNS(null, 'y', (2 + fontsize) + 'px');
    275         node.setAttributeNS(null, "style", "font-family:Arial,Helvetica,sans-serif; font-size:" + fontsize + "px; fill:#356AA0;  opacity:0.3;");
    276         t = document.createTextNode(str);
    277         node.appendChild(t);
    278         this.appendChildPrim(node, 0);
    279     },
    280
    281     // already documented in JXG.AbstractRenderer
    282     drawInternalText: function (el) {
    283         var node = this.createPrim('text', el.id);
    284
    285         node.setAttributeNS(null, "class", el.visProp.cssclass);
    286         //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox
    287         el.rendNodeText = document.createTextNode('');
    288         node.appendChild(el.rendNodeText);
    289         this.appendChildPrim(node,  el.visProp.layer);
    290
    291         return node;
    292     },
    293
    294     // already documented in JXG.AbstractRenderer
    295     updateInternalText: function (el) {
    296         var content = el.plaintext;
    297
    298         // el.rendNode.setAttributeNS(null, "class", el.visProp.cssclass);
    299         if (!isNaN(el.coords.scrCoords[1]+el.coords.scrCoords[2])) {
    300             el.rendNode.setAttributeNS(null, 'x', el.coords.scrCoords[1] + 'px');
    301             el.rendNode.setAttributeNS(null, 'y', (el.coords.scrCoords[2] + this.vOffsetText*0.5) + 'px');
    302         }
    303         if (el.htmlStr !== content) {
    304             el.rendNodeText.data = content;
    305             el.htmlStr = content;
    306         }
    307         this.transformImage(el, el.transformations);
    308     },
    309
    310     /* **************************
    311      *    Image related stuff
    312      * **************************/
    313
    314     // already documented in JXG.AbstractRenderer
    315     drawImage: function (el) {
    316         var node = this.createPrim('image', el.id);
    317
    318         node.setAttributeNS(null, 'preserveAspectRatio', 'none');
    319         this.appendChildPrim(node, el.visProp.layer);
    320         el.rendNode = node;
    321
    322         this.updateImage(el);
    323     },
    324
    325     // already documented in JXG.AbstractRenderer
    326     transformImage: function (el, t) {
    327         var node = el.rendNode, m,
    328             str = "",
    329             s, len = t.length;
    330
    331         if (len > 0) {
    332             m = this.joinTransforms(el, t);
    333             s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(',');
    334             str += ' matrix(' + s + ') ';
    335             node.setAttributeNS(null, 'transform', str);
    336         }
    337     },
    338
    339     // already documented in JXG.AbstractRenderer
    340     updateImageURL: function (el) {
    341         var url = JXG.evaluate(el.url);
    342         el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url);
    343     },
    344
    345     /* **************************
    346      * Render primitive objects
    347      * **************************/
    348
    349     // already documented in JXG.AbstractRenderer
    350     appendChildPrim: function (node, level) {
    351         if (!JXG.exists(level)) { // trace nodes have level not set
    352             level = 0;
    353         } else if (level >= JXG.Options.layer.numlayers) {
    354             level = JXG.Options.layer.numlayers - 1;
    355         }
    356         this.layer[level].appendChild(node);
    357     },
    358
    359     // already documented in JXG.AbstractRenderer
    360     appendNodesToElement: function (element) {
    361         element.rendNode = this.getElementById(element.id);
    362     },
    363
    364     // already documented in JXG.AbstractRenderer
    365     createPrim: function (type, id) {
    366         var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type);
    367         node.setAttributeNS(null, 'id', this.container.id + '_' + id);
    368         node.style.position = 'absolute';
    369         if (type === 'path') {
    370             node.setAttributeNS(null, 'stroke-linecap', 'butt');
    371             node.setAttributeNS(null, 'stroke-linejoin', 'round');
    372         }
    373         return node;
    374     },
    375
    376     // already documented in JXG.AbstractRenderer
    377     remove: function (shape) {
    378         if (JXG.exists(shape) && JXG.exists(shape.parentNode)) {
    379             shape.parentNode.removeChild(shape);
    380         }
    381     },
    382
    383     // already documented in JXG.AbstractRenderer
    384     makeArrows: function (el) {
    385         var node2;
    386
    387         if (el.visPropOld.firstarrow === el.visProp.firstarrow && el.visPropOld.lastarrow === el.visProp.lastarrow) {
    388             return;
    389         }
    390
    391         if (el.visProp.firstarrow) {
    392             node2 = el.rendNodeTriangleStart;
    393             if (!JXG.exists(node2)) {
    394                 node2 = this._createArrowHead(el, 'End');
    395                 this.defs.appendChild(node2);
    396                 el.rendNodeTriangleStart = node2;
    397                 el.rendNode.setAttributeNS(null, 'marker-start', 'url(#' + this.container.id + '_' + el.id + 'TriangleEnd)');
    398             }
    399         } else {
    400             node2 = el.rendNodeTriangleStart;
    401             if (JXG.exists(node2)) {
    402                 this.remove(node2);
    403             }
    404         }
    405         if (el.visProp.lastarrow) {
    406             node2 = el.rendNodeTriangleEnd;
    407             if (!JXG.exists(node2)) {
    408                 node2 = this._createArrowHead(el, 'Start');
    409                 this.defs.appendChild(node2);
    410                 el.rendNodeTriangleEnd = node2;
    411                 el.rendNode.setAttributeNS(null, 'marker-end', 'url(#' + this.container.id + '_' + el.id + 'TriangleStart)');
    412             }
    413         } else {
    414             node2 = el.rendNodeTriangleEnd;
    415             if (JXG.exists(node2)) {
    416                 this.remove(node2);
    417             }
    418         }
    419         el.visPropOld.firstarrow = el.visProp.firstarrow;
    420         el.visPropOld.lastarrow = el.visProp.lastarrow;
    421     },
    422
    423     // already documented in JXG.AbstractRenderer
    424     updateEllipsePrim: function (node, x, y, rx, ry) {
    425         node.setAttributeNS(null, 'cx', x);
    426         node.setAttributeNS(null, 'cy', y);
    427         node.setAttributeNS(null, 'rx', Math.abs(rx));
    428         node.setAttributeNS(null, 'ry', Math.abs(ry));
    429     },
    430
    431     // already documented in JXG.AbstractRenderer
    432     updateLinePrim: function (node, p1x, p1y, p2x, p2y) {
    433         if (!isNaN(p1x+p1y+p2x+p2y)) {
    434             node.setAttributeNS(null, 'x1', p1x);
    435             node.setAttributeNS(null, 'y1', p1y);
    436             node.setAttributeNS(null, 'x2', p2x);
    437             node.setAttributeNS(null, 'y2', p2y);
    438         }
    439     },
    440
    441     // already documented in JXG.AbstractRenderer
    442     updatePathPrim: function (node, pointString) {
    443         if (pointString == '') {
    444             pointString = 'M 0 0';
    445         }
    446         node.setAttributeNS(null, 'd', pointString);
    447     },
    448
    449     // already documented in JXG.AbstractRenderer
    450     updatePathStringPoint: function (el, size, type) {
    451         var s = '',
    452             scr = el.coords.scrCoords,
    453             sqrt32 = size * Math.sqrt(3) * 0.5,
    454             s05 = size * 0.5;
    455
    456         if (type === 'x') {
    457             s = ' M ' + (scr[1] - size) + ' ' + (scr[2] - size) +
    458                 ' L ' + (scr[1] + size) + ' ' + (scr[2] + size) +
    459                 ' M ' + (scr[1] + size) + ' ' + (scr[2] - size) +
    460                 ' L ' + (scr[1] - size) + ' ' + (scr[2] + size);
    461         } else if (type === '+') {
    462             s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
    463                 ' L ' + (scr[1] + size) + ' ' + (scr[2]) +
    464                 ' M ' + (scr[1])        + ' ' + (scr[2] - size) +
    465                 ' L ' + (scr[1])        + ' ' + (scr[2] + size);
    466         } else if (type === '<>') {
    467             s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
    468                 ' L ' + (scr[1])        + ' ' + (scr[2] + size) +
    469                 ' L ' + (scr[1] + size) + ' ' + (scr[2]) +
    470                 ' L ' + (scr[1])        + ' ' + (scr[2] - size) + ' Z ';
    471         } else if (type === '^') {
    472             s = ' M ' + (scr[1])          + ' ' + (scr[2] - size) +
    473                 ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] + s05) +
    474                 ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] + s05) +
    475                 ' Z ';  // close path
    476         } else if (type === 'v') {
    477             s = ' M ' + (scr[1])          + ' ' + (scr[2] + size) +
    478                 ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] - s05) +
    479                 ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] - s05) +
    480                 ' Z ';
    481         } else if (type === '>') {
    482             s = ' M ' + (scr[1] + size) + ' ' + (scr[2]) +
    483                 ' L ' + (scr[1] - s05)  + ' ' + (scr[2] - sqrt32) +
    484                 ' L ' + (scr[1] - s05)  + ' ' + (scr[2] + sqrt32) +
    485                 ' Z ';
    486         } else if (type === '<') {
    487             s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
    488                 ' L ' + (scr[1] + s05)  + ' ' + (scr[2] - sqrt32) +
    489                 ' L ' + (scr[1] + s05)  + ' ' + (scr[2] + sqrt32) +
    490                 ' Z ';
    491         }
    492         return s;
    493     },
    494
    495     // already documented in JXG.AbstractRenderer
    496     updatePathStringPrim: function (el) {
    497         var symbm = ' M ',
    498             symbl = ' L ',
    499             nextSymb = symbm,
    500             maxSize = 5000.0,
    501             pStr = '',
    502             i, scr,
    503             isNotPlot = (el.visProp.curvetype !== 'plot'),
    504             len;
    505
    506         if (el.numberPoints <= 0) {
    507             return '';
    508         }
    509
    510         if (isNotPlot && el.board.options.curve.RDPsmoothing) {
    511             el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 0.5);
    512         }
    513
    514         len = Math.min(el.points.length, el.numberPoints);
    515         for (i = 0; i < len; i++) {
    516             scr = el.points[i].scrCoords;
    517             if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
    518                 nextSymb = symbm;
    519             } else {
    520                 // Chrome has problems with values being too far away.
    521                 if (scr[1] > maxSize) {
    522                     scr[1] = maxSize;
    523                 } else if (scr[1] < -maxSize) {
    524                     scr[1] = -maxSize;
    525                 }
    526
    527                 if (scr[2] > maxSize) {
    528                     scr[2] = maxSize;
    529                 } else if (scr[2] < -maxSize) {
    530                     scr[2] = -maxSize;
    531                 }
    532
    533                 // Attention: first coordinate may be inaccurate if far way
    534                 pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
    535                 nextSymb = symbl;
    536             }
    537         }
    538         return pStr;
    539     },
    540
    541     // already documented in JXG.AbstractRenderer
    542     updatePathStringBezierPrim: function (el) {
    543         var symbm = ' M ',
    544             symbl = ' C ',
    545             nextSymb = symbm,
    546             maxSize = 5000.0,
    547             pStr = '',
    548             i, j, scr,
    549             lx, ly, f = el.visProp.strokewidth,
    550             isNoPlot = (el.visProp.curvetype !== 'plot'),
    551             len;
    552
    553         if (el.numberPoints <= 0) {
    554             return '';
    555         }
    556
    557         if (isNoPlot && el.board.options.curve.RDPsmoothing) {
    558             el.points = JXG.Math.Numerics.RamerDouglasPeuker(el.points, 0.5);
    559         }
    560
    561         len = Math.min(el.points.length, el.numberPoints);
    562         for (j=1; j<3; j++) {
    563             nextSymb = symbm;
    564             for (i = 0; i < len; i++) {
    565                 scr = el.points[i].scrCoords;
    566                 if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
    567                     nextSymb = symbm;
    568                 } else {
    569                     // Chrome has problems with values being too far away.
    570                     if (scr[1] > maxSize) {
    571                         scr[1] = maxSize;
    572                     } else if (scr[1] < -maxSize) {
    573                         scr[1] = -maxSize;
    574                     }
    575
    576                     if (scr[2] > maxSize) {
    577                         scr[2] = maxSize;
    578                     } else if (scr[2] < -maxSize) {
    579                         scr[2] = -maxSize;
    580                     }
    581
    582                     // Attention: first coordinate may be inaccurate if far way
    583                     if (nextSymb == symbm) {
    584                         pStr += [nextSymb,
    585                             scr[1]+0*f*(2*j*Math.random()-j), ' ',
    586                             scr[2]+0*f*(2*j*Math.random()-j)].join('');
    587                     } else {
    588                         pStr += [nextSymb,
    589                             (lx + (scr[1]-lx)*0.333 + f*(2*j*Math.random()-j)), ' ',
    590                             (ly + (scr[2]-ly)*0.333 + f*(2*j*Math.random()-j)), ' ',
    591                             (lx + 2*(scr[1]-lx)*0.333 + f*(2*j*Math.random()-j)), ' ',
    592                             (ly + 2*(scr[2]-ly)*0.333 + f*(2*j*Math.random()-j)), ' ',
    593                             scr[1], ' ', scr[2]
    594                             ].join('');
    595                     }
    596                     nextSymb = symbl;
    597                     lx = scr[1];
    598                     ly = scr[2];
    599                 }
    600             }
    601         }
    602         return pStr;
    603     },
    604
    605     // already documented in JXG.AbstractRenderer
    606     updatePolygonPrim: function (node, el) {
    607         var pStr = '',
    608             scrCoords, i,
    609             len = el.vertices.length;
    610
    611         node.setAttributeNS(null, 'stroke', 'none');
    612         for (i = 0; i < len - 1; i++) {
    613              if (el.vertices[i].isReal) {
    614                 scrCoords = el.vertices[i].coords.scrCoords;
    615                 pStr = pStr + scrCoords[1] + "," + scrCoords[2];
    616             } else {
    617                 node.setAttributeNS(null, 'points', '');
    618                 return;
    619             }
    620             if (i < len - 2) {
    621                 pStr += " ";
    622             }
    623         }
    624         if (pStr.indexOf('NaN')==-1)
    625             node.setAttributeNS(null, 'points', pStr);
    626     },
    627
    628     // already documented in JXG.AbstractRenderer
    629     updateRectPrim: function (node, x, y, w, h) {
    630         node.setAttributeNS(null, 'x', x);
    631         node.setAttributeNS(null, 'y', y);
    632         node.setAttributeNS(null, 'width', w);
    633         node.setAttributeNS(null, 'height', h);
    634     },
    635
    636     /* **************************
    637      *  Set Attributes
    638      * **************************/
    639
    640     // documented in JXG.AbstractRenderer
    641     setPropertyPrim: function (node, key, val) {
    642         if (key === 'stroked') {
    643             return;
    644         }
    645         node.setAttributeNS(null, key, val);
    646     },
    647
    648     // documented in JXG.AbstractRenderer
    649     show: function (el) {
    650         var node;
    651
    652         if (el && el.rendNode) {
    653             node = el.rendNode;
    654             node.setAttributeNS(null, 'display', 'inline');
    655             node.style.visibility = "inherit";
    656         }
    657     },
    658
    659     // documented in JXG.AbstractRenderer
    660     hide: function (el) {
    661         var node;
    662
    663         if (el && el.rendNode) {
    664             node = el.rendNode;
    665             node.setAttributeNS(null, 'display', 'none');
    666             node.style.visibility = "hidden";
    667         }
    668     },
    669
    670     // documented in JXG.AbstractRenderer
    671     setBuffering: function (el, type) {
    672         el.rendNode.setAttribute('buffered-rendering', type);
    673     },
    674
    675     // documented in JXG.AbstractRenderer
    676     setDashStyle: function (el) {
    677         var dashStyle = el.visProp.dash, node = el.rendNode;
    678
    679         if (el.visProp.dash > 0) {
    680             node.setAttributeNS(null, 'stroke-dasharray', this.dashArray[dashStyle - 1]);
    681         } else {
    682             if (node.hasAttributeNS(null, 'stroke-dasharray')) {
    683                 node.removeAttributeNS(null, 'stroke-dasharray');
    684             }
    685         }
    686     },
    687
    688     // documented in JXG.AbstractRenderer
    689     setGradient: function (el) {
    690         var fillNode = el.rendNode, col, op,
    691             node, node2, node3, x1, x2, y1, y2;
    692
    693         op = JXG.evaluate(el.visProp.fillopacity);
    694         op = (op > 0) ? op : 0;
    695
    696         col = JXG.evaluate(el.visProp.fillcolor);
    697
    698         if (el.visProp.gradient === 'linear') {
    699             node = this.createPrim('linearGradient', el.id + '_gradient');
    700             x1 = '0%'; // TODO: get x1,x2,y1,y2 from el.visProp['angle']
    701             x2 = '100%';
    702             y1 = '0%';
    703             y2 = '0%'; //means 270 degrees
    704
    705             node.setAttributeNS(null, 'x1', x1);
    706             node.setAttributeNS(null, 'x2', x2);
    707             node.setAttributeNS(null, 'y1', y1);
    708             node.setAttributeNS(null, 'y2', y2);
    709             node2 = this.createPrim('stop', el.id + '_gradient1');
    710             node2.setAttributeNS(null, 'offset', '0%');
    711             node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
    712             node3 = this.createPrim('stop', el.id + '_gradient2');
    713             node3.setAttributeNS(null, 'offset', '100%');
    714             node3.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
    715             node.appendChild(node2);
    716             node.appendChild(node3);
    717             this.defs.appendChild(node);
    718             fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)');
    719             el.gradNode1 = node2;
    720             el.gradNode2 = node3;
    721         } else if (el.visProp.gradient === 'radial') {
    722             node = this.createPrim('radialGradient', el.id + '_gradient');
    723
    724             node.setAttributeNS(null, 'cx', '50%');
    725             node.setAttributeNS(null, 'cy', '50%');
    726             node.setAttributeNS(null, 'r', '50%');
    727             node.setAttributeNS(null, 'fx', el.visProp.gradientpositionx * 100 + '%');
    728             node.setAttributeNS(null, 'fy', el.visProp.gradientpositiony * 100 + '%');
    729
    730             node2 = this.createPrim('stop', el.id + '_gradient1');
    731             node2.setAttributeNS(null, 'offset', '0%');
    732             node2.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
    733             node3 = this.createPrim('stop', el.id + '_gradient2');
    734             node3.setAttributeNS(null, 'offset', '100%');
    735             node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
    736
    737             node.appendChild(node2);
    738             node.appendChild(node3);
    739             this.defs.appendChild(node);
    740             fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)');
    741             el.gradNode1 = node2;
    742             el.gradNode2 = node3;
    743         } else {
    744             fillNode.removeAttributeNS(null, 'style');
    745         }
    746     },
    747
    748     // documented in JXG.AbstractRenderer
    749     updateGradient: function (el) {
    750         var node2 = el.gradNode1,
    751             node3 = el.gradNode2,
    752             col, op;
    753
    754         if (!JXG.exists(node2) || !JXG.exists(node3)) {
    755             return;
    756         }
    757
    758         op = JXG.evaluate(el.visProp.fillopacity);
    759         op = (op > 0) ? op : 0;
    760
    761         col = JXG.evaluate(el.visProp.fillcolor);
    762
    763         if (el.visProp.gradient === 'linear') {
    764             node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
    765             node3.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
    766         } else if (el.visProp.gradient === 'radial') {
    767             node2.setAttributeNS(null, 'style', 'stop-color:' + el.visProp.gradientsecondcolor + ';stop-opacity:' + el.visProp.gradientsecondopacity);
    768             node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
    769         }
    770     },
    771
    772     // documented in JXG.AbstractRenderer
    773     setObjectFillColor: function (el, color, opacity) {
    774         var node, rgba = JXG.evaluate(color), c, rgbo,
    775             o = JXG.evaluate(opacity), oo;
    776
    777         o = (o > 0) ? o : 0;
    778
    779         if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
    780             return;
    781         }
    782         if (JXG.exists(rgba) && rgba !== false) {
    783             if (rgba.length!=9) {          // RGB, not RGBA
    784                 c = rgba;
    785                 oo = o;
    786             } else {                       // True RGBA, not RGB
    787                 rgbo = JXG.rgba2rgbo(rgba);
    788                 c = rgbo[0];
    789                 oo = o*rgbo[1];
    790             }
    791             node = el.rendNode;
    792             node.setAttributeNS(null, 'fill', c);
    793             if (el.type === JXG.OBJECT_TYPE_IMAGE) {
    794                 node.setAttributeNS(null, 'opacity', oo);
    795             } else {
    796                 node.setAttributeNS(null, 'fill-opacity', oo);
    797             }
    798             if (JXG.exists(el.visProp.gradient)) {
    799                 this.updateGradient(el);
    800             }
    801         }
    802         el.visPropOld.fillcolor = rgba;
    803         el.visPropOld.fillopacity = o;
    804     },
    805
    806     // documented in JXG.AbstractRenderer
    807     setObjectStrokeColor: function (el, color, opacity) {
    808         var rgba = JXG.evaluate(color), c, rgbo,
    809             o = JXG.evaluate(opacity), oo,
    810             node;
    811
    812         o = (o > 0) ? o : 0;
    813
    814         if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
    815             return;
    816         }
    817
    818         if (JXG.exists(rgba) && rgba !== false) {
    819             if (rgba.length!=9) {          // RGB, not RGBA
    820                 c = rgba;
    821                 oo = o;
    822             } else {                       // True RGBA, not RGB
    823                 rgbo = JXG.rgba2rgbo(rgba);
    824                 c = rgbo[0];
    825                 oo = o*rgbo[1];
    826             }
    827             node = el.rendNode;
    828             if (el.type === JXG.OBJECT_TYPE_TEXT) {
    829                 if (el.visProp.display === 'html') {
    830                     node.style.color = c;
    831 					node.style.opacity = oo;
    832                 } else {
    833                     node.setAttributeNS(null, "style", "fill:" + c);
    834                     node.setAttributeNS(null, "style", "fill-opacity:" + oo);
    835                 }
    836             } else {
    837                 node.setAttributeNS(null, 'stroke', c);
    838                 node.setAttributeNS(null, 'stroke-opacity', oo);
    839             }
    840             if (el.type === JXG.OBJECT_TYPE_ARROW) {
    841                 this._setArrowAtts(el.rendNodeTriangle, c, oo);
    842             } else if (el.elementClass === JXG.OBJECT_CLASS_CURVE || el.elementClass === JXG.OBJECT_CLASS_LINE) {
    843                 if (el.visProp.firstarrow) {
    844                     this._setArrowAtts(el.rendNodeTriangleStart, c, oo);
    845                 }
    846                 if (el.visProp.lastarrow) {
    847                     this._setArrowAtts(el.rendNodeTriangleEnd, c, oo);
    848                 }
    849             }
    850         }
    851
    852         el.visPropOld.strokecolor = rgba;
    853         el.visPropOld.strokeopacity = o;
    854     },
    855
    856     // documented in JXG.AbstractRenderer
    857     setObjectStrokeWidth: function (el, width) {
    858         var w = JXG.evaluate(width),
    859             node;
    860
    861         if (el.visPropOld.strokewidth === w) {
    862             return;
    863         }
    864
    865         node = el.rendNode;
    866         this.setPropertyPrim(node, 'stroked', 'true');
    867         if (JXG.exists(w)) {
    868
    869             this.setPropertyPrim(node, 'stroke-width', w + 'px');
    870         }
    871         el.visPropOld.strokewidth = w;
    872     },
    873
    874     // documented in JXG.AbstractRenderer
    875     setShadow: function (el) {
    876         if (el.visPropOld.shadow === el.visProp.shadow) {
    877             return;
    878         }
    879
    880         if (JXG.exists(el.rendNode)) {
    881             if (el.visProp.shadow) {
    882                 el.rendNode.setAttributeNS(null, 'filter', 'url(#' + this.container.id + '_' + 'f1)');
    883             } else {
    884                 el.rendNode.removeAttributeNS(null, 'filter');
    885             }
    886         }
    887         el.visPropOld.shadow = el.visProp.shadow;
    888     },
    889
    890     /* **************************
    891      * renderer control
    892      * **************************/
    893
    894     // documented in JXG.AbstractRenderer
    895     suspendRedraw: function () {
    896         // It seems to be important for the Linux version of firefox
    897         //this.suspendHandle = this.svgRoot.suspendRedraw(10000);
    898     },
    899
    900     // documented in JXG.AbstractRenderer
    901     unsuspendRedraw: function () {
    902         //this.svgRoot.unsuspendRedraw(this.suspendHandle);
    903         //this.svgRoot.unsuspendRedrawAll();
    904         //this.svgRoot.forceRedraw();
    905     },
    906
    907     // document in AbstractRenderer
    908     resize: function (w, h) {
    909         this.svgRoot.style.width = parseFloat(w) + 'px';
    910         this.svgRoot.style.height = parseFloat(h) + 'px';
    911     }
    912
    913 });
    914