Reimplementation of svg line not functioning - javascript

I've drawn a line before with this exact code but when reimplementing it into another project something isn't working.
let main = document.getElementById('main');
let svg = document.createElement('svg');
let newLine = document.createElement('line');
svg.setAttribute('style', `position: fixed;display: block;`);
newLine.setAttribute('x1', 0);
newLine.setAttribute('y1', 0);
newLine.setAttribute('x2', 500);
newLine.setAttribute('y2', 500);
newLine.setAttribute('style', `stroke:red;stroke-width:100;`);
svg.appendChild(newLine);
main.appendChild(svg);
Before I was running an express server and JSDOM to fill a div in the document with svg's then sending the innerhtml of the document element as the body when routing to '/', not the most performant way to do it but I was just playing around around with tools we were learning in class. When I put the code below into my html a black line is displayed as it should so I feel I'm missing some small piece...
<svg width="500" height="500">
<line x1="50" y1="50" x2="350" y2="350" stroke="black" />
</svg>

You need to use createElementNS when creating your svg and line elements, otherwise it just thinks they're made up tags and not actual SVG.
Also, since you're not using variables in your setAttribute, you should avoid string interpolation and just use single-quotes instead of backticks.
Solution
let main = document.getElementById('main');
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
let newLine = document.createElementNS('http://www.w3.org/2000/svg','line');
svg.setAttribute('style', 'position: fixed;display: block;');
newLine.setAttribute('x1', 0);
newLine.setAttribute('y1', 80);
newLine.setAttribute('x2', 100);
newLine.setAttribute('y2', 20);
newLine.setAttribute('style', 'stroke:red;stroke-width:100;');
svg.appendChild(newLine);
main.appendChild(svg);
<div id="main"></div>
Documentation
https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS

Figured it out, needed to use the code below when declaring my svg
const svgTop = document.createElementNS('http://www.w3.org/2000/svg', 'svg')

Related

Loading javascript libraries included in an appended SVG file

I am trying to build a browser-based SVG rasterizer. The SVG files can contain javascript code that affects the output (for example, randomly changes colors of elements) and use libraries (for example chroma.js) to do that.
The process I am trying to implement is:
load the SVG file
load libraries linked in the SVG file
execute the javascript included inside script tags
display on a canvas
save the canvas as PNG
This is necessary because appending the SVG to the HTML element does not run the JS included inside SVG.
Everything works well - except the loading of external libraries (point 2). The snippet that does that is as follows
$.get('/some_svg_file.svg', function(d) {
// this will be replaced with file drag/drop later
var getLibs = Array.from(d.querySelectorAll('script'))
.filter(p => p.getAttribute('xlink:href'))
.map(p => {
return axios.get(p.getAttribute('xlink:href'))
})
// only run embedded js after libraries have been downloaded
Promise.all(getLibs).then((values) => {
values.forEach(d => {
try {
eval(d.data);
} catch (e) {
console.log(e)
}
});
Array.from(d.querySelectorAll('script'))
.forEach(p => {
if (p.textContent.length) {
try {
eval(p.textContent)
} catch (e) {
console.log(e)
}
}
})
// code that writes to canvas etc goes here
})
})
So, if I put link chroma.js in the html file header, everything works ok. If I download it and eval it, the scripts inside the SVG file fail with ReferenceError: chroma is not defined
Another attempt I did was using the script tag technique like so:
$.get('/foo_with_js.svg', function(d) {
var getLibs = Array.from(d.querySelectorAll('script'))
.filter(p => p.getAttribute('xlink:href'))
.map(p => {
var s = document.createElement('script');
s.src = p.getAttribute('xlink:href');
s.type = "text/javascript";
s.async = false;
document.querySelector('head').appendChild(s);
})
// load scripts and save to canvas
})
which also failed in the same way (despite chroma.js being neatly included in the head section.
So how could I get this to work - so that I can load SVG, append it to HTML and run the scripts inside it without having to pre-link all the possible scripts in the HTML?
Ah - and if you're asking why not use some other conversion process, the answer is "lack of consistent support for SVG filters"
If you can make this work with any JavaScript library embedded in SVG is a good question, but here I have an example where I use chroma.js like you suggested.
A reference to the SVG document is added to the data attribute of <object>. Here it is static, but I guess it could be dynamically updated. Now the SVG is doing its thing, like setting a color on the <rect> and animating the cy attribute of the <circle>. At any point I can grab the contentDocument (contentDocument.documentElement.outerHTML) of <object>, that is the SVG document at a particular point. If values in the document is changing this will affect the contentDocument. Now I can take the string (outerHTML) and turn that into a data URI. The data URI can be set as src on an image object and the image can be rendered in the <canvas> element. In the outerHTML you can see that there are also references to the JavaScript library and the JavaScript code that I wrote, but this is not executed in the image when rendered into <canvas>.
SVG document:
<?xml version="1.0"?>
<svg viewBox="0 0 200 200" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="drop-shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/>
<feoffset in="blur" dx="4" dy="4" result="offsetBlur"/>
<feMerge>
<feMergeNode in="offsetBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<script href="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.2/chroma.min.js"/>
<rect x="0" y="0" width="200" height="200" />
<circle cx="40" cy="40" r="20" fill="blue" style="filter: url(#drop-shadow);"/>
<script>
// <![CDATA[
var cy = 40;
var speed = 5;
document.querySelector('rect').style.fill = chroma('hotpink');
setInterval(function(){
if(cy > 160 || cy < 40) speed *= -1;
cy += speed;
document.querySelector('circle').attributes['cy'].value = cy;
}, 500);
// ]]>
</script>
</svg>
HTML document:
<!DOCTYPE html>
<html>
<head>
<title>SVG</title>
<script type="text/javascript">
var object, canvas, img, loop;
document.addEventListener('DOMContentLoaded', e => {
object = document.querySelector('object');
canvas = document.querySelector('canvas');
img = new Image();
img.addEventListener('load', e => {
canvas.getContext('2d').drawImage(e.target, 0, 0);
});
object.addEventListener('load', e => {
loop = setInterval(function(){
var svg64 = btoa(e.target.contentDocument.documentElement.outerHTML);
var b64Start = 'data:image/svg+xml;base64,';
img.src = b64Start + svg64;
}, 500);
});
});
</script>
</head>
<body>
<object type="image/svg+xml" width="200" height="200" data="test.svg"></object>
<canvas width="200" height="200"></canvas>
</body>
</html>
These two documents need to run from a server, so not directly from the filesystem.
From what I have tested the SVG animations and CSS animations are not working in this setup. Only the "animations" in JavaScript where you manipulate the DOM is working.

adding svg rectangle to dom using js

I would add a rectangle as on each Node element
the rect is added only in the last element.
ngAfterViewInit() {
let rect : NodeList = this.synoptic.nativeElement.querySelectorAll("#stops g");
let rect2 = document.createElementNS('','rect');
rect2.setAttribute("fill","none");
rect.forEach((elm : Node) => {
console.log(elm.firstChild) #### Well selecting the first element
elm.insertBefore(rect2,elm.firstChild);
console.log(elm) ####rect added
})
### rect is awailable only in the last elm
}
Edit I'm using angular And my code is running inside ngAfterViewInit() hook
Here a stackblitz demo
Try it this way by adding namespace and attributes required for rect
(function() {
let rect = document.querySelectorAll("#stops g");
rect.forEach(e => {
let rect2 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect2.setAttribute("fill", "#000");
rect2.setAttribute('x', e.getAttribute('x'));
rect2.setAttribute('y', e.getAttribute('y'));
rect2.setAttribute('height', '50');
rect2.setAttribute('width', '50');
e.appendChild(rect2);
});
})();
<svg id="stops" xmlns="http://www.w3.org/2000/svg">
<g x="0" y="0"></g>
<g x="100" y="100"></g>
</svg>
Angular Working Fiddle
The SVG namespace is 'http://www.w3.org/2000/svg', and not the empty string.
Additionally a rect with fill none won't be visible, it either needs a fill or a stroke and appears to have neither specified.
Finally it needs a non-zero width and height.

Appending a div containing canvas is not working

I am making use of canvg to convert svg present in div into canvas(upto this it's working fine) and then copying the innerHTML of div to another div, but it's not working. Canvas is coming but nothing will be present in that canvas.
Thanks in advance
<div id="k">
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
Sorry, your browser does not support inline SVG.
</svg>
</div>
<div id="kk">
<p>Watch me</p>
</div>
var svgTag = document.querySelectorAll('#k svg');
svgTag = svgTag[0];
var c = document.createElement('canvas');
c.width = svgTag.clientWidth;
c.height = svgTag.clientHeight;
svgTag.parentNode.insertBefore(c, svgTag);
svgTag.parentNode.removeChild(svgTag);
var div = document.createElement('div');
div.appendChild(svgTag);
canvg(c, div.innerHTML);
setTimeout(function(){
var data = $("#k").html();
$("#kk").append($(''+data+''));
},5000);
JSFiddle
The content of a canvas element is held as binary data, much like the content of an image element. Canvas elements do not have an innerHTML text property that can be used to recreate the canvas.
The following example shows a method of cloning a canvas element using standard canvas 2D methods:
function canvasClone(c1) {
var c2 = document.createElement('canvas');
c2.width=c1.width;
c2.height=c1.height;
c2.getContext("2d").drawImage(c1, 0,0);
return c2;
}
You can demonstrate it by including the function in the fiddle and changing the timeout processing to:
setTimeout(function(){
kk.appendChild(canvasClone(c));
},5000);
Feel free to rewrite the timeout code in JQuery if you prefer.

SVG clipping using javascript

We are trying to use clippath for polyline using javascript.
We tried writing a sample HTML code which worked fine:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="parent">
<clippath id="cut-off-bottom">
<rect x="0" y="0" width="200" height="100" />
</clippath>
<circle cx="100" cy="100" r="100" clip-path="url(#cut-off-bottom)" />
</svg>
This works absolutely fine.
But when we try to generate the same code using javascript it doesn't work:
_svgNS = 'http://www.w3.org/2000/svg';
parent = document.getElementById('parent');
circle = document.createElementNS(_svgNS, 'circle');
circle.setAttributeNS(null, 'cx', '100');
circle.setAttributeNS(null, 'cy', '100');
circle.setAttributeNS(null, 'r', '100');
clippath = document.createElementNS(_svgNS, 'clippath');
clippath.setAttributeNS(null, 'id', 'clip');
r = document.createElementNS(_svgNS, 'rect');
r.setAttributeNS(null, 'x', '0');
r.setAttributeNS(null, 'y', '0');
r.setAttributeNS(null, 'width', '200');
r.setAttributeNS(null, 'height', '100');
clippath.appendChild(r);
circle.setAttributeNS(_svgNS, 'clip-path', 'url(#clip)');
parent.appendChild(clippath);
parent.appendChild(circle);
Can anyone please help us find the issue with the above code...
Thanks in advance.
After a while of fiddling it turns out that SVG is very case-sensitive and the clippath element needs to be a clipPath element.
See working fiddle: http://jsfiddle.net/F4mq9/2/
Very strange that the static sample worked however.
You set the id of the <clippath> to clip, but then you set the clip-path="url(#clippath). You need to match the id attribute you set.
Demo: http://jsfiddle.net/uMe4s/7/
I've tersified some of your code (appending the element as soon as it was created, instead of at the end). This has no effect on the solution.
Old question, however the fix was not mentioned in the approved fix. The reason it did not work was because of the namespace on the clippath attribute. Like in the jsfiddle, the namespace url should be set to null.

Append a path to SVG with javascript

I'm trying to append a path element to some inline svg using javascript but I'm getting the error: "Uncaught NotFoundError: An attempt was made to reference a Node in a context where it does not exist"
Can anyone explain why this javascript is not working with my HTML?
<body>
<svg height='500' width='500'></svg>
<script>
var svg = document.getElementsByTagName("svg")[0];
var pathElement = svg.appendChild("path");
</script>
</body>
appendChild takes an element and not a string so what you need is something like this...
var svg = document.getElementsByTagName("svg")[0];
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
// Use path.setAttribute("<attr>", "<value>"); to set any attributes you want
var pathElement = svg.appendChild(path);
Working example for anyone passing by who wants to see it in action:
http://jsfiddle.net/huvd0fju/
HTML
<svg id="mysvg" width="100" height="100">
<circle class="c" cx="50" cy="50" r="40" fill="#fc0" />
</svg>
JS
var mysvg = document.getElementById("mysvg");
//uncomment for example of changing existing svg element attributes
/*
var mycircle = document.getElementsByClassName("c")[0];
mycircle.setAttributeNS(null,"fill","#96f");
*/
var svgns = "http://www.w3.org/2000/svg";
var shape = document.createElementNS(svgns, "rect");
shape.setAttributeNS(null,"width",50);
shape.setAttributeNS(null,"height",80);
shape.setAttributeNS(null,"fill","#f00");
mysvg.appendChild(shape);

Categories