1 /*
  2     Copyright 2008,2009
  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 JXG.SVGRenderer = function(container) {
 27     var i;
 28     this.constructor();
 29 
 30     /* 
 31         Enable easy test which renderer is used.
 32     */
 33     this.type = 'svg';
 34 
 35     this.svgRoot = null;
 36     this.suspendHandle = null;
 37     
 38     this.svgNamespace = 'http://www.w3.org/2000/svg';
 39     this.xlinkNamespace ='http://www.w3.org/1999/xlink';
 40 
 41     this.container = container;
 42     this.container.style.MozUserSelect = 'none';
 43 
 44     this.container.style.overflow = 'hidden';
 45     if (this.container.style.position=='') {
 46         this.container.style.position = 'relative';
 47     }
 48     
 49     this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg");
 50     this.svgRoot.style.overflow = 'hidden';
 51     this.svgRoot.style.width = this.container.style.width;
 52     this.svgRoot.style.height = this.container.style.height;
 53     this.container.appendChild(this.svgRoot);
 54 
 55     this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace,'defs');
 56     this.svgRoot.appendChild(this.defs);
 57     this.filter = this.container.ownerDocument.createElementNS(this.svgNamespace,'filter');
 58     this.filter.setAttributeNS(null, 'id', this.container.id+'_'+'f1');
 59     this.filter.setAttributeNS(null, 'width', '300%');
 60     this.filter.setAttributeNS(null, 'height', '300%');
 61     this.feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace,'feOffset');
 62     this.feOffset.setAttributeNS(null, 'result', 'offOut');
 63     this.feOffset.setAttributeNS(null, 'in', 'SourceAlpha');
 64     this.feOffset.setAttributeNS(null, 'dx', '5');
 65     this.feOffset.setAttributeNS(null, 'dy', '5');
 66     this.filter.appendChild(this.feOffset);
 67     this.feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace,'feGaussianBlur');
 68     this.feGaussianBlur.setAttributeNS(null, 'result', 'blurOut');
 69     this.feGaussianBlur.setAttributeNS(null, 'in', 'offOut');
 70     this.feGaussianBlur.setAttributeNS(null, 'stdDeviation', '3');
 71     this.filter.appendChild(this.feGaussianBlur);
 72     this.feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace,'feBlend');
 73     this.feBlend.setAttributeNS(null, 'in', 'SourceGraphic');
 74     this.feBlend.setAttributeNS(null, 'in2', 'blurOut');
 75     this.feBlend.setAttributeNS(null, 'mode', 'normal');
 76     this.filter.appendChild(this.feBlend);
 77     this.defs.appendChild(this.filter);    
 78     
 79     /* 
 80     * ~ 10 Layers. highest number = highest visibility
 81     */
 82     this.layer = [];
 83     for (i=0;i<JXG.Options.layer.numlayers;i++) {
 84         this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace,'g');
 85         this.svgRoot.appendChild(this.layer[i]);
 86     }
 87     
 88     // um Dashes zu realisieren
 89     this.dashArray = ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5'];
 90 };
 91 
 92 JXG.SVGRenderer.prototype = JXG.AbstractRenderer();
 93 
 94 JXG.SVGRenderer.prototype.setShadow = function(el) {
 95     if (el.visPropOld['shadow']==el.visProp['shadow']) {
 96         return;
 97     }
 98     if(el.rendNode != null) {
 99         if(el.visProp['shadow']) {
100             el.rendNode.setAttributeNS(null,'filter','url(#'+this.container.id+'_'+'f1)');
101         }
102         else {
103             el.rendNode.removeAttributeNS(null,'filter');
104         }    
105     }
106     el.visPropOld['shadow']=el.visProp['shadow'];
107 };
108 
109 JXG.SVGRenderer.prototype.setGradient = function(el) {
110     var fillNode = el.rendNode, col, op,
111         node, node2, node3, x1, x2, y1, y2;
112     
113     if (typeof el.visProp['fillOpacity']=='function') {
114         op = el.visProp['fillOpacity']();
115     } else {
116         op = el.visProp['fillOpacity'];
117     }
118     op = (op>0)?op:0;
119     if (typeof el.visProp['fillColor']=='function') {
120         col = el.visProp['fillColor']();
121     } else {
122         col = el.visProp['fillColor'];
123     }
124 
125     if(el.visProp['gradient'] == 'linear') {
126         node = this.createPrim('linearGradient', el.id+'_gradient');
127         x1 = '0%'; // TODO: get x1,x2,y1,y2 from el.visProp['angle']
128         x2 = '100%';
129         y1 = '0%';
130         y2 = '0%'; //means 270 degrees
131 
132         node.setAttributeNS(null,'x1',x1);
133         node.setAttributeNS(null,'x2',x2);
134         node.setAttributeNS(null,'y1',y1);
135         node.setAttributeNS(null,'y2',y2);
136         node2 = this.createPrim('stop',el.id+'_gradient1');
137         node2.setAttributeNS(null,'offset','0%');
138         node2.setAttributeNS(null,'style','stop-color:'+col+';stop-opacity:'+op);     
139         node3 = this.createPrim('stop',el.id+'_gradient2');
140         node3.setAttributeNS(null,'offset','100%');
141         node3.setAttributeNS(null,'style','stop-color:'+el.visProp['gradientSecondColor']+';stop-opacity:'+el.visProp['gradientSecondOpacity']);
142         node.appendChild(node2);
143         node.appendChild(node3);     
144         this.defs.appendChild(node);
145         fillNode.setAttributeNS(null, 'style', 'fill:url(#'+this.container.id+'_'+el.id+'_gradient)');      
146         el.gradNode1 = node2;
147         el.gradNode2 = node3;
148     }
149     else if (el.visProp['gradient'] == 'radial') {
150         node = this.createPrim('radialGradient',el.id+'_gradient');
151 
152         node.setAttributeNS(null, 'cx', '50%');
153         node.setAttributeNS(null, 'cy', '50%');
154         node.setAttributeNS(null, 'r', '50%');
155         node.setAttributeNS(null, 'fx', el.visProp['gradientPositionX']*100+'%');
156         node.setAttributeNS(null, 'fy', el.visProp['gradientPositionY']*100+'%');
157 
158         node2 = this.createPrim('stop',el.id+'_gradient1');
159         node2.setAttributeNS(null,'offset','0%');
160         node2.setAttributeNS(null,'style','stop-color:'+el.visProp['gradientSecondColor']+';stop-opacity:'+el.visProp['gradientSecondOpacity']);
161         node3 = this.createPrim('stop',el.id+'_gradient2');
162         node3.setAttributeNS(null,'offset','100%');
163         node3.setAttributeNS(null,'style','stop-color:'+col+';stop-opacity:'+op);         
164 
165         node.appendChild(node2);
166         node.appendChild(node3);     
167         this.defs.appendChild(node);
168         fillNode.setAttributeNS(null, 'style', 'fill:url(#'+this.container.id+'_'+el.id+'_gradient)'); 
169         el.gradNode1 = node2;
170         el.gradNode2 = node3;
171     }
172     else {
173         fillNode.removeAttributeNS(null,'style');
174     }
175 };
176 
177 JXG.SVGRenderer.prototype.updateGradient = function(el) {
178     var node2 = el.gradNode1, 
179         node3 = el.gradNode2, 
180         col, op;
181 
182     if (node2==null || node3==0) {
183         return;
184     }
185     if (typeof el.visProp['fillOpacity']=='function') {
186         op = el.visProp['fillOpacity']();
187     } else {
188         op = el.visProp['fillOpacity'];
189     }
190     op = (op>0)?op:0;
191     if (typeof el.visProp['fillColor']=='function') {
192         col = el.visProp['fillColor']();
193     } else {
194         col = el.visProp['fillColor'];
195     }
196     
197     if(el.visProp['gradient'] == 'linear') {
198         node2.setAttributeNS(null,'style','stop-color:'+col+';stop-opacity:'+op);     
199         node3.setAttributeNS(null,'style','stop-color:'+el.visProp['gradientSecondColor']+';stop-opacity:'+el.visProp['gradientSecondOpacity']);
200     } else if (el.visProp['gradient'] == 'radial') {
201         node2.setAttributeNS(null,'style','stop-color:'+el.visProp['gradientSecondColor']+';stop-opacity:'+el.visProp['gradientSecondOpacity']);
202         node3.setAttributeNS(null,'style','stop-color:'+col+';stop-opacity:'+op);         
203     }
204 }; 
205 
206 JXG.SVGRenderer.prototype.displayCopyright = function(str,fontsize) {
207     var node = this.createPrim('text','licenseText'),
208         t;
209     node.setAttributeNS(null,'x','20');
210     node.setAttributeNS(null,'y',2+fontsize);
211     node.setAttributeNS(null, "style", "font-family:Arial,Helvetica,sans-serif; font-size:"+fontsize+"px; fill:#356AA0;  opacity:0.3;");
212     t = document.createTextNode(str);
213     node.appendChild(t);
214     this.appendChildPrim(node,0);
215 };
216 
217 JXG.SVGRenderer.prototype.drawInternalText = function(el) {
218     var node = this.createPrim('text',el.id);
219     node.setAttributeNS(null, "class", "JXGtext");
220     //node.setAttributeNS(null, "style", "fill:"+ el.visProp['strokeColor']); not available at that time
221     node.setAttributeNS(null, "style", "'alignment-baseline:middle;"); 
222     el.rendNodeText = document.createTextNode('');
223     node.appendChild(el.rendNodeText);
224     this.appendChildPrim(node,9);
225     return node;
226 };
227 
228 JXG.SVGRenderer.prototype.updateInternalText = function(/** JXG.Text */ el) { 
229     el.rendNode.setAttributeNS(null, 'x', (el.coords.scrCoords[1])+'px'); 
230     el.rendNode.setAttributeNS(null, 'y', (el.coords.scrCoords[2])+'px'); 
231     el.updateText();
232     if (el.htmlStr!= el.plaintextStr) {
233         el.rendNodeText.data = el.plaintextStr;
234         el.htmlStr = el.plaintextStr;
235     }
236     this.transformImage(el, el.transformations);
237 };
238 
239 JXG.SVGRenderer.prototype.drawTicks = function(axis) {
240     var node = this.createPrim('path', axis.id);
241     //node.setAttributeNS(null, 'shape-rendering', 'crispEdges');
242     this.appendChildPrim(node,axis.layer);
243     this.appendNodesToElement(axis,'path'); 
244 };
245 
246 JXG.SVGRenderer.prototype.updateTicks = function(axis,dxMaj,dyMaj,dxMin,dyMin) {
247     var tickStr = "",
248         i, c, node, 
249         len = axis.ticks.length;
250         
251     for (i=0; i<len; i++) {
252         c = axis.ticks[i].scrCoords;
253         if (axis.ticks[i].major) {
254             if ((axis.board.needsFullUpdate||axis.needsRegularUpdate) && axis.labels[i].visProp['visible']) {
255                 this.drawText(axis.labels[i]);
256             }
257             tickStr += "M " + (c[1]+dxMaj) + " " + (c[2]-dyMaj) + " L " + (c[1]-dxMaj) + " " + (c[2]+dyMaj) + " ";
258         }
259         else
260             tickStr += "M " + (c[1]+dxMin) + " " + (c[2]-dyMin) + " L " + (c[1]-dxMin) + " " + (c[2]+dyMin) + " ";
261 
262     }
263     
264     node = this.getElementById(axis.id);
265     if(node == null) {
266         node = this.createPrim('path', axis.id);
267         //node.setAttributeNS(null, 'shape-rendering', 'crispEdges');
268         this.appendChildPrim(node,axis.layer);
269         this.appendNodesToElement(axis,'path');
270     }
271     node.setAttributeNS(null, 'stroke', axis.visProp['strokeColor']);    
272     node.setAttributeNS(null, 'stroke-opacity', axis.visProp['strokeOpacity']);
273     node.setAttributeNS(null, 'stroke-width', axis.visProp['strokeWidth']);
274     this.updatePathPrim(node, tickStr, axis.board);
275 };
276 
277 JXG.SVGRenderer.prototype.drawImage = function(el) {
278     var node = this.createPrim('image',el.id);
279 
280     node.setAttributeNS(null, 'preserveAspectRatio', 'none'); 
281     this.appendChildPrim(node,el.layer);
282     el.rendNode = node;
283     
284     this.updateImage(el);
285 };
286 
287 JXG.SVGRenderer.prototype.updateImageURL = function(el) {
288     var url;
289     if (JXG.isFunction(el.url)) {
290         url = el.url();
291     } else {
292         url = el.url;
293     }
294     el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url);
295 };
296 
297 JXG.SVGRenderer.prototype.transformImage = function(el,t) {
298     var node = el.rendNode, m,
299         str = "", //node.getAttributeNS(null, 'transform'),
300         s, len = t.length;
301 
302     if (len>0) {
303         m = this.joinTransforms(el,t);
304         s = m[1][1]+','+m[2][1]+','+m[1][2]+','+m[2][2]+','+m[1][0]+','+m[2][0];
305         str += ' matrix('+s+') ';
306         node.setAttributeNS(null, 'transform', str);
307         //node.style.MozTransform = str;
308     }
309 };
310 
311 /*
312 JXG.SVGRenderer.prototype.removeGrid = function(board) { 
313     var c = this.layer[board.options.layer['grid']];
314     board.hasGrid = false;
315     while (c.childNodes.length>0) {
316         c.removeChild(c.firstChild);
317     }
318 };
319 */
320 
321 JXG.SVGRenderer.prototype.setArrowAtts = function(node, c, o) {
322     if (!node) return;
323     node.setAttributeNS(null, 'stroke', c);
324     node.setAttributeNS(null, 'stroke-opacity', o);
325     node.setAttributeNS(null, 'fill', c);
326     node.setAttributeNS(null, 'fill-opacity', o);             
327 };
328 
329 JXG.SVGRenderer.prototype.setObjectStrokeColor = function(el, color, opacity) {
330     var c = this.evaluate(color),
331         o = this.evaluate(opacity),
332         node;
333 
334     o = (o>0)?o:0;
335 
336     if (el.visPropOld['strokeColor']==c && el.visPropOld['strokeOpacity']==o) {
337         return;
338     }
339     node = el.rendNode;
340     if(el.type == JXG.OBJECT_TYPE_TEXT) {
341         if (el.display=='html') {
342             node.style.color = c; // Schriftfarbe
343         } else {
344             node.setAttributeNS(null, "style", "fill:"+ c); 
345         }
346     }
347     else {
348         node.setAttributeNS(null, 'stroke', c);
349         node.setAttributeNS(null, 'stroke-opacity', o);          
350     }
351     if(el.type == JXG.OBJECT_TYPE_ARROW) {
352          this.setArrowAtts(el.rendNodeTriangle,c,o);
353     } else if (el.elementClass == JXG.OBJECT_CLASS_CURVE || el.elementClass == JXG.OBJECT_CLASS_LINE) {
354         if(el.visProp['firstArrow']) {
355             this.setArrowAtts(el.rendNodeTriangleStart,c,o);
356         }
357         if(el.visProp['lastArrow']) {
358             this.setArrowAtts(el.rendNodeTriangleEnd,c,o);
359         }                
360     }     
361     el.visPropOld['strokeColor'] = c;
362     el.visPropOld['strokeOpacity'] = o;
363 };
364 
365 JXG.SVGRenderer.prototype.setObjectFillColor = function(el, color, opacity) {
366     var node, c = this.evaluate(color),
367         o = this.evaluate(opacity);
368 
369     o = (o>0)?o:0;
370 
371     if (el.visPropOld['fillColor']==c && el.visPropOld['fillOpacity']==o) {
372         return;
373     }
374     node = el.rendNode;
375     node.setAttributeNS(null, 'fill', c);  
376     if (el.type==JXG.OBJECT_TYPE_IMAGE) {
377         node.setAttributeNS(null, 'opacity', o); 
378     } else {
379         node.setAttributeNS(null, 'fill-opacity', o);                   
380     }
381     
382     if (el.visProp['gradient']!=null) {
383         this.updateGradient(el);
384     }
385     el.visPropOld['fillColor'] = c;
386     el.visPropOld['fillOpacity'] = o;
387 } ;
388 
389 /**
390  * Sets an elements stroke width.
391  * @param {Object} el Reference to the geometry element.
392  * @param {int} width The new stroke width to be assigned to the element.
393  */
394 JXG.SVGRenderer.prototype.setObjectStrokeWidth = function(el, width) {
395     var w = this.evaluate(width), 
396         node;
397     //w = (w>0)?w:0;
398     try {
399         if (el.visPropOld['strokeWidth']==w) {
400             return;
401         }
402     } catch (e){
403         //alert(el.id);
404     }
405     
406     node = el.rendNode;
407     this.setPropertyPrim(node,'stroked', 'true');
408     if (w!=null) { 
409         this.setPropertyPrim(node,'stroke-width',w);    
410     }
411     el.visPropOld['strokeWidth'] = w;
412 };
413 
414 JXG.SVGRenderer.prototype.hide = function(el) {
415     var node;
416 
417     if (!JXG.exists(el))
418         return;
419     node = el.rendNode;
420     if(JXG.exists(node)) {
421         node.setAttributeNS(null, 'display', 'none');
422         node.style.visibility = "hidden";
423     }
424 };
425 
426 JXG.SVGRenderer.prototype.show = function(el) {
427     var node;
428 
429     if (!JXG.exists(el))
430         return;
431     node = el.rendNode;
432     if(JXG.exists(node)) {
433         node.setAttributeNS(null, 'display', 'inline');
434         node.style.visibility = "inherit";
435     }
436 };
437 
438 JXG.SVGRenderer.prototype.remove = function(shape) {
439     if(shape!=null && shape.parentNode != null)
440         shape.parentNode.removeChild(shape);
441 };
442 
443 JXG.SVGRenderer.prototype.suspendRedraw = function() {
444     // It seems to be important for the Linux version of firefox
445     this.suspendHandle = this.svgRoot.suspendRedraw(10000);
446 };
447 
448 JXG.SVGRenderer.prototype.unsuspendRedraw = function() {
449     this.svgRoot.unsuspendRedraw(this.suspendHandle);
450     this.svgRoot.forceRedraw();
451 };
452 
453 JXG.SVGRenderer.prototype.setDashStyle = function(el,visProp) {
454     var dashStyle = el.visProp['dash'], node = el.rendNode;
455     if(el.visProp['dash'] > 0) {
456         node.setAttributeNS(null, 'stroke-dasharray', this.dashArray[dashStyle-1]);
457     }
458     else {
459         if(node.hasAttributeNS(null, 'stroke-dasharray')) {
460             node.removeAttributeNS(null, 'stroke-dasharray');
461         }
462     }    
463 };
464 
465 JXG.SVGRenderer.prototype.setGridDash = function(id) {
466     var node = this.getElementById(id);
467     this.setPropertyPrim(node,'stroke-dasharray', '5, 5'); 
468 };
469 
470 JXG.SVGRenderer.prototype.createPrim = function(type,id) {
471     var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type);
472     node.setAttributeNS(null, 'id', this.container.id+'_'+id);
473     node.style.position = 'absolute';
474     if (type=='path') {
475         node.setAttributeNS(null, 'stroke-linecap', 'butt');
476         node.setAttributeNS(null, 'stroke-linejoin', 'round');
477         //node.setAttributeNS(null, 'shape-rendering', 'geometricPrecision'); // 'crispEdges'
478     }
479     return node;
480 };
481 
482 JXG.SVGRenderer.prototype.createArrowHead = function(el,idAppendix) {
483     var id = el.id+'Triangle',
484         node2, node3;
485         
486     if (idAppendix!=null) { id += idAppendix; }
487     node2 = this.createPrim('marker',id);
488 /*
489     node2.setAttributeNS(null, 'viewBox', '0 0 10 6');
490     node2.setAttributeNS(null, 'refY', '3');
491     node2.setAttributeNS(null, 'markerHeight', '6');
492     node2.setAttributeNS(null, 'markerWidth', '6');
493     if (idAppendix=='End') {
494         node2.setAttributeNS(null, 'refX', '0');
495         node3.setAttributeNS(null, 'd', 'M 0 3 L 10 6 L 10 0 z');
496     } else {
497         node2.setAttributeNS(null, 'refX', '10');
498         node3.setAttributeNS(null, 'd', 'M 0 0 L 10 3 L 0 6 z');
499     }
500 */    
501     node2.setAttributeNS(null, 'viewBox', '0 0 10 6');
502     node2.setAttributeNS(null, 'refY', '3');
503     node2.setAttributeNS(null, 'markerUnits', 'strokeWidth');
504     node2.setAttributeNS(null, 'markerHeight', '12');
505     node2.setAttributeNS(null, 'markerWidth', '10');
506     node2.setAttributeNS(null, 'orient', 'auto');
507     node2.setAttributeNS(null, 'stroke', el.visProp['strokeColor']);
508     node2.setAttributeNS(null, 'stroke-opacity', el.visProp['strokeOpacity']);            
509     node2.setAttributeNS(null, 'fill', el.visProp['strokeColor']);
510     node2.setAttributeNS(null, 'fill-opacity', el.visProp['strokeOpacity']);    
511     node3 = this.container.ownerDocument.createElementNS(this.svgNamespace,'path');
512     if (idAppendix=='End') {
513         node2.setAttributeNS(null, 'refX', '0');
514         node3.setAttributeNS(null, 'd', 'M 0 3 L 10 6 L 10 0 z');
515     } else {
516         node2.setAttributeNS(null, 'refX', '10');
517         node3.setAttributeNS(null, 'd', 'M 0 0 L 10 3 L 0 6 z');
518     }
519     node2.appendChild(node3);
520     return node2;
521 };
522 
523 /*
524 // seems to be unused
525 JXG.SVGRenderer.prototype.makeArrow = function(node,el,idAppendix) {
526     var node2 = this.createArrowHead(el,idAppendix);
527     this.defs.appendChild(node2);
528     node.setAttributeNS(null, 'marker-end', 'url(#'+this.container.id+'_'+el.id+'Triangle)');
529     el.rendNodeTriangle = node2;
530 };
531 */
532 
533 JXG.SVGRenderer.prototype.makeArrows = function(el) {
534     var node2;
535     if (el.visPropOld['firstArrow']==el.visProp['firstArrow'] && el.visPropOld['lastArrow']==el.visProp['lastArrow']) {
536         return;
537     }
538     if(el.visProp['firstArrow']) {
539         node2 = el.rendNodeTriangleStart;
540         if(node2 == null) {
541             node2 = this.createArrowHead(el,'End');
542             this.defs.appendChild(node2);            
543             el.rendNodeTriangleStart = node2;
544             el.rendNode.setAttributeNS(null, 'marker-start', 'url(#'+this.container.id+'_'+el.id+'TriangleEnd)');    
545         }    
546     }
547     else {
548         node2 = el.rendNodeTriangleStart;
549         if(node2 != null) {
550             this.remove(node2);
551         }
552     }
553     if(el.visProp['lastArrow']) {
554         node2 = el.rendNodeTriangleEnd;
555         if(node2 == null) {
556             node2 = this.createArrowHead(el,'Start');
557             this.defs.appendChild(node2);            
558             el.rendNodeTriangleEnd = node2;
559             el.rendNode.setAttributeNS(null, 'marker-end', 'url(#'+this.container.id+'_'+el.id+'TriangleStart)'); 
560         }    
561     }
562     else {
563         node2 = el.rendNodeTriangleEnd;
564         if(node2 != null) {
565             this.remove(node2);
566         }        
567     }
568     el.visPropOld['firstArrow'] = el.visProp['firstArrow'];
569     el.visPropOld['lastArrow'] = el.visProp['lastArrow'];
570 };
571 
572 JXG.SVGRenderer.prototype.updateLinePrim = function(node,p1x,p1y,p2x,p2y) {
573     node.setAttributeNS(null, 'x1', p1x);
574     node.setAttributeNS(null, 'y1', p1y);
575     node.setAttributeNS(null, 'x2', p2x);
576     node.setAttributeNS(null, 'y2', p2y);    
577 };
578 
579 JXG.SVGRenderer.prototype.updateCirclePrim = function(node,x,y,r) {
580     node.setAttributeNS(null, 'cx', (x));
581     node.setAttributeNS(null, 'cy', (y));
582     node.setAttributeNS(null, 'r', (r));
583 };
584 
585 JXG.SVGRenderer.prototype.updateEllipsePrim = function(node,x,y,rx,ry) {
586     node.setAttributeNS(null, 'cx', (x));
587     node.setAttributeNS(null, 'cy', (y));
588     node.setAttributeNS(null, 'rx', (rx));
589     node.setAttributeNS(null, 'ry', (ry));
590 };
591 
592 JXG.SVGRenderer.prototype.updateRectPrim = function(node,x,y,w,h) {
593     node.setAttributeNS(null, 'x', (x));
594     node.setAttributeNS(null, 'y', (y));
595     node.setAttributeNS(null, 'width', (w));
596     node.setAttributeNS(null, 'height', (h));
597 };
598 
599 JXG.SVGRenderer.prototype.updatePathPrim = function(node, pointString, board) {  // board not necessary in SVG
600     /*
601     node.setAttributeNS(null, 'stroke-linecap', 'butt');
602     node.setAttributeNS(null, 'stroke-linejoin', 'round');
603     //node.setAttributeNS(null, 'shape-rendering', 'geometricPrecision');
604     //node.setAttributeNS(null, 'shape-rendering', 'crispEdges');
605     */
606     node.setAttributeNS(null, 'd', pointString);
607 };
608 
609 JXG.SVGRenderer.prototype.updatePathStringPrim = function(el) {
610     var symbm = ' M ',
611         symbl = ' L ',
612         nextSymb = symbm,
613         maxSize = 5000.0,
614         pStr = '',
615         //h = 3*el.board.canvasHeight,
616         //w = 100*el.board.canvasWidth,
617         i, scr, 
618         isNoPlot = (el.curveType!='plot'),
619         //isFunctionGraph = (el.curveType=='functiongraph'),
620         len;
621 
622     if (el.numberPoints<=0) { return ''; }
623     
624     if (isNoPlot && el.board.options.curve.RDPsmoothing) {
625         el.points = this.RamenDouglasPeuker(el.points,0.5);
626     }
627     len = Math.min(el.points.length,el.numberPoints);
628     for (i=0; i<len; i++) {
629         scr = el.points[i].scrCoords;
630         //if (isNaN(scr[1]) || isNaN(scr[2]) /*|| Math.abs(scr[1])>w || (isFunctionGraph && (scr[2]>h || scr[2]<-0.5*h))*/ ) {  // PenUp
631         if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
632             nextSymb = symbm;
633         } else {
634             // Chrome has problems with values  being too far away.
635             if (scr[1]>maxSize) { scr[1] = maxSize; }
636             else if (scr[1]<-maxSize) { scr[1] = -maxSize; }
637             if (scr[2]>maxSize) { scr[2] = maxSize; }
638             else if (scr[2]<-maxSize) { scr[2] = -maxSize; }
639             
640             pStr += [nextSymb,scr[1],' ',scr[2]].join(''); // Attention: first coordinate may be inaccurate if far way
641             nextSymb = symbl;
642         }
643     }
644     return pStr;
645 };
646 
647 JXG.SVGRenderer.prototype.updatePathStringPoint = function(el, size, type) {
648     var s = '',
649         scr = el.coords.scrCoords,
650         sqrt32 = size*Math.sqrt(3)*0.5,
651         s05 = size*0.5;
652         
653     if(type == 'x') {
654         s = 'M ' + (scr[1]-size) + ' ' + (scr[2]-size) + ' L ' + 
655         (scr[1]+size) + ' ' + (scr[2]+size) + ' M ' + 
656         (scr[1]+size) + ' ' + (scr[2]-size) + ' L ' +
657         (scr[1]-size) + ' ' + (scr[2]+size);
658     }
659     else if(type == '+') {
660         s = 'M ' + (scr[1]-size) + ' ' + (scr[2]) + ' L ' + 
661         (scr[1]+size) + ' ' + (scr[2]) + ' M ' + 
662         (scr[1]) + ' ' + (scr[2]-size) + ' L ' +
663         (scr[1]) + ' ' + (scr[2]+size);    
664     }
665     else if(type == '<>') {
666         s = 'M ' + (scr[1]-size) + ' ' + (scr[2]) + ' L ' + 
667         (scr[1]) + ' ' + (scr[2]+size) + ' L ' + 
668         (scr[1]+size) + ' ' + (scr[2]) + ' L ' +
669         (scr[1]) + ' ' + (scr[2]-size) + ' Z ';
670     }
671     else if(type == '^') {
672         s = 'M ' + (scr[1]) + ' ' + (scr[2]-size) + ' L ' + 
673         (scr[1]-sqrt32) + ' ' + (scr[2]+s05) + ' L ' + 
674         (scr[1]+sqrt32) + ' ' + (scr[2]+s05) + ' Z ';
675     } 
676     else if(type == 'v') {
677         s = 'M ' + (scr[1]) + ' ' + (scr[2]+size) + ' L ' + 
678         (scr[1]-sqrt32) + ' ' + (scr[2]-s05) + ' L ' + 
679         (scr[1]+sqrt32) + ' ' + (scr[2]-s05) + ' Z ';
680     }   
681     else if(type == '>') {
682         s = 'M ' + (scr[1]+size) + ' ' + (scr[2]) + ' L ' + 
683         (scr[1]-s05) + ' ' + (scr[2]-sqrt32) + ' L ' + 
684         (scr[1]-s05) + ' ' + (scr[2]+sqrt32) + ' Z ';
685     }
686     else if(type == '<') {
687         s = 'M ' + (scr[1]-size) + ' ' + (scr[2]) + ' L ' + 
688         (scr[1]+s05) + ' ' + (scr[2]-sqrt32) + ' L ' + 
689         (scr[1]+s05) + ' ' + (scr[2]+sqrt32) + ' Z ';
690     }
691     return s;
692 };
693 
694 JXG.SVGRenderer.prototype.updatePolygonPrim = function(node, el) {
695     var pStr = '', 
696         scrCoords, i,
697         len = el.vertices.length;
698         
699     node.setAttributeNS(null, 'stroke', 'none');
700     for(i=0; i<len-1; i++) {
701         scrCoords = el.vertices[i].coords.scrCoords;
702         pStr = pStr + scrCoords[1] + "," + scrCoords[2];
703         if(i<len-2) { pStr += " "; }
704     }
705     node.setAttributeNS(null, 'points', pStr);
706 };
707 
708 JXG.SVGRenderer.prototype.appendChildPrim = function(node,level) {
709     if (typeof level=='undefined') { // trace nodes have level not set
710         level = 0;                         
711     } else if (level>=JXG.Options.layer.numlayers) { 
712         level = JXG.Options.layer.numlayers-1;
713     }
714     this.layer[level].appendChild(node);
715 };
716 
717 JXG.SVGRenderer.prototype.setPropertyPrim = function(node,key,val) {
718     if (key=='stroked') {
719         return;
720     }
721     node.setAttributeNS(null, key, val);
722 };
723 
724 JXG.SVGRenderer.prototype.drawVerticalGrid = function(topLeft, bottomRight, gx, board) {
725     var node = this.createPrim('path', 'gridx'),
726         gridArr = '';
727         
728     while(topLeft.scrCoords[1] < bottomRight.scrCoords[1] + gx - 1) { 
729         gridArr += ' M ' + topLeft.scrCoords[1] + ' ' + 0 + ' L ' + topLeft.scrCoords[1] + ' ' + board.canvasHeight+' ';
730         topLeft.setCoordinates(JXG.COORDS_BY_SCREEN, [topLeft.scrCoords[1] + gx, topLeft.scrCoords[2]]);   
731     }
732     this.updatePathPrim(node, gridArr, board);
733     return node;
734 };
735 
736 JXG.SVGRenderer.prototype.drawHorizontalGrid = function(topLeft, bottomRight, gy, board) {
737     var node = this.createPrim('path', 'gridy'),
738         gridArr = '';
739         
740     while(topLeft.scrCoords[2] <= bottomRight.scrCoords[2] + gy - 1) {
741         gridArr += ' M ' + 0 + ' ' + topLeft.scrCoords[2] + ' L ' + board.canvasWidth + ' ' + topLeft.scrCoords[2]+' ';
742         topLeft.setCoordinates(JXG.COORDS_BY_SCREEN, [topLeft.scrCoords[1], topLeft.scrCoords[2] + gy]);
743     }
744     this.updatePathPrim(node, gridArr, board);
745     return node;
746 };
747 
748 JXG.SVGRenderer.prototype.appendNodesToElement = function(element, type) {
749     element.rendNode = this.getElementById(element.id);
750 };
751 
752 /**
753  * Sets the buffering as recommended by SVGWG. Until now only Opera supports this and will be ignored by other browsers.
754  * @param {String} type Either 'auto', 'dynamic', or 'static'. For an explanation see {@link http://www.w3.org/TR/SVGTiny12/painting.html#BufferedRenderingProperty}.
755  */
756 JXG.SVGRenderer.prototype.setBuffering = function(el, type) {
757     el.rendNode.setAttribute('buffered-rendering', type);
758 };
759