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