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