Render SVG to PNG using JavaScript - javascript

The following HTML element is on my page:
<canvas id="canvas" width=400 height=400 />
And I am trying to render the following tag as a PNG.
<div style="font-size=40px">
Fun with d3.js
</div>
My JavaScript function is as follows. It should be rendering the tag containing the text "Fun with d3.js" as an image in the canvas, however, it is not being rendered. But, if I insert the raw HTML elements using devtools, the SVG renders as legitimate. Somewhere in translation the Javascript seems to be losing the html element, but for the life of me cannot figure out why. TThe img.onerror event is being called but provides very little in terms of useful information.
renderToPNG: function() {
var myCanvas = document.getElementById("canvas");
var context = myCanvas.getContext("2d");
var data =
'<svg width=\"400\" height=\"400\">' +
' <foreignObject width=\"100%\" height=\"100%\">' +
' <div style=\"font-size=40px\">' +
' Fun with d3.js' +
' </div>' +
' </foreignObject>' +
'</svg>';
var domURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg = new Blob([data], {type:"image/svg+xml;charset=utf-8"});
var url = domURL.createObjectURL(svg);
img.onload = function() {
context.drawImage(img, 0, 0);
domURL.revokeObjectURL(url);
}
img.src = url;
}

I tried running your code and it turns out the foreignObject doesn't work for some reason. So that might be at the root of the problem.
It also depends on which browser you are using. Take a look at the snippet, it shows the SVG, the Canvas, and the Image, and you can see each of them having different problems (I see the canvas distorted on Chrome, and on Safari it doesn't even show).
function renderToPNG() {
var myCanvas = document.getElementById("canvas");
var context = myCanvas.getContext("2d");
var data =
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' +
'<svg version="1.1"' +
' baseProfile="full"' +
' width="200" height="200"' +
' xmlns="http://www.w3.org/2000/svg">' +
' <foreignObject width="100%" height="100%">' +
' <div style="font-size=40px; color: black">' +
' Fun with d3.js' +
' </div>' +
' </foreignObject>' +
' <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />' +
'</svg>';
var domURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg = new Blob([data], {
type: "image/svg+xml;charset=utf-8"
});
var url = domURL.createObjectURL(svg);
img.onload = function() {
context.drawImage(img, 0, 0);
domURL.revokeObjectURL(url);
}
img.src = url;
console.debug(img);
$('#image').append(img);
}
renderToPNG();
#svg,
#canvas,
#image {
border: 1px solid;
width: 200px;
height: 200px;
display: inline-block;
vertical-align: bottom;
}
#svg {
border-color: red
}
#canvas {
border-color: green
}
#image {
border-color: blue
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg id="svg" version="1.1" baseProfile="full" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<foreignObject width="100%" height="100%">
<div style="font-size:40px; color: black">
Fun with d3.js
</div>
</foreignObject>
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>
<canvas id="canvas" width="200" height="200"></canvas>
<div id="image"></div>
One thing to notice: I added the SVG doctype declaration on the JS code since that's how it's supposed to work when using the SVG as source for an Image (it acts as if it was an external file).
Just my two cents. Hoping for a better answer...

Related

dynamically setAttribute Style FILL using data variable

I have a set of arrays to set the start position, weidth and name of some chart elements in a SVG. What I haven't been able to achieve is setting the colour of each of those elements using HEX values from an array called chart_color.
Currently you'll see I've set the value of fill to green. What I tried and failed with is fill: chart_color[num].
<!DOCTYPE html>
<body>
<svg id="mysvg" width="1000" height="800"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<text x="50" y="60" fill="black"
font-family="Arial, Helvetica, sans-serif"
font-size="28">Revenue and Expenses</text>
<line x1="150" y1="80" x2="150" y2="320"
style="stroke:rgb(155, 144, 144);stroke-width:5" />
<script type="application/ecmascript"> <![CDATA[
var mysvg = document.getElementById("mysvg");
var chart_start = [152, 84, 152]
var chart_width = [100,64,36]
var chart_names = ["Revenue", "Expenses","Profit"]
var chart_color = ["#28CE6D","#DF3456","#4DC7EC"]
var num = 3;
while (num-- > 0)
{
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", chart_start[num]);
rect.setAttribute("y", [num] * 70 + 100);
rect.setAttribute("width", chart_width[num]);
rect.setAttribute("height", "50");
rect.setAttribute("style", "fill:green;stroke:black;stroke-width:0;opacity:1");
mysvg.appendChild(rect);
}
]]></script>
</svg>
</body>
</html>
With style attributes you can actually assign them a value without using the setAttribute function:
rect.style.fill = chart_color[num]
But use this method consistently so that you don't override the style attribute, like setting color to "red" then setting style to "width:100px".
You just need to get the index (num) of the chart_color array and apply that as a variable within in your style declaration (breaking out of the string).
Note that you are also setting the border to stroke: black - but hte stroke width to 0 - So it can be seen I have amended that to stroke-width: 1 so that the border on each shows up.
<!DOCTYPE html>
<body>
<svg id="mysvg" width="1000" height="800"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<text x="50" y="60" fill="black"
font-family="Arial, Helvetica, sans-serif"
font-size="28">Revenue and Expenses</text>
<line x1="150" y1="80" x2="150" y2="320"
style="stroke:rgb(155, 144, 144);stroke-width:5" />
<script type="application/ecmascript"> <![CDATA[
var mysvg = document.getElementById("mysvg");
var chart_start = [152, 84, 152]
var chart_width = [100,64,36]
var chart_names = ["Revenue", "Expenses","Profit"]
var chart_color = ["#28CE6D","#DF3456","#4DC7EC"]
var num = 3;
while (num-- > 0)
{
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", chart_start[num]);
rect.setAttribute("y", [num] * 70 + 100);
rect.setAttribute("width", chart_width[num]);
rect.setAttribute("height", "50");
rect.setAttribute("style", "fill:" + chart_color[num] + ";stroke:black;stroke-width:1;opacity:1");
mysvg.appendChild(rect);
}
]]></script>
</svg>
</body>
</html>

Tweak SVG viewBox behaviour via javascript

I am using vanilla javascript to navigate the pages of a comic book. However, I need to setup a condition that checks if the points in the current polygon intersects with the points in the next polygon. If true, I want the viewBox to animate from the current points to the next points, if false let nothing happen (use the default fade transition).
Here is a section of my code
var DELAY = 400;
function nextArea() {
if (isFirstPage() || areaIndex >= areas.length - 1) {
changePage(true);
changeArea();
} else {
fade();
areaIndex++;
setTimeout(changeArea, DELAY);
}
}
function prevArea() {
if (isLastPage() || areaIndex <= 0) {
changePage(false);
changeArea();
} else {
fade();
areaIndex--;
setTimeout(changeArea, DELAY);
}
}
function changeArea() {
if (isFirstPage() || isLastPage()) {
return;
}
var activeArea = areas[areaIndex];
var points = activeArea.getAttribute('points').split(' ');
var xy1 = points[0].split(',');
var xy2 = points[1].split(',');
var xy3 = points[2].split(',');
var box = [xy1[0], xy1[1], xy2[0] - xy1[0], xy3[1] - xy2[1]];
activePage.classList.remove('fade');
activePage.setAttribute('viewBox', box.join(' '));
activeRect = rects[pageIndex - 1];
activeRect.setAttribute('x', xy1[0]);
activeRect.setAttribute('y', xy1[1]);
}
My code repository is here: https://github.com/cnario/svg-carousel
Here is what I have thus far: https://cnario.github.io/svg-carousel/
Here is how I expect it to act: https://read.marvel.com/#book/41323
I suppose this is what you may need: a way to transition the viewBox from one value to another so that every time you have only one part of the svg in the viewBox.
let BB = {};
BB.tomato = tomato.getBBox();
BB.skyblue = skyblue.getBBox();
BB.gold = gold.getBBox();
let radios = document.querySelectorAll("#controls input");
radios.forEach(r =>{
let color = r.dataset.color;
let bb = BB[color];
r.addEventListener("change",()=>{
svg.setAttributeNS(null,"viewBox", `${bb.x} ${bb.y} ${bb.width} ${bb.height}`)
svg.style.height = `${bb.height * 300 / bb.width}px`;
})
})
svg {
width: 300px;
border: 1px solid;
height: 600px;
transition: height 1s;
}
<p id="controls">
<label>tomato: <input type="radio" name="selector" data-color="tomato" /></label>
<label>skyblue: <input type="radio" name="selector" data-color="skyblue" /></label>
<label>gold: <input type="radio" name="selector" data-color="gold" /></label>
</p>
<svg id="svg" viewBox="0 0 100 200">
<g id="tomato">
<circle cx="35" cy="70" r="25" fill="tomato" />
</g>
<g id="skyblue">
<ellipse cx="75" cy="160" rx="15" ry="35" fill="skyblue" />
</g>
<g id="gold">
<polygon fill="gold" points="75,15 60,30 90,30" />
</g>
</svg>

HTML input of type date or text not working in SVG foreignObject

I'm trying to convert HTML to image using SVG foreignObject. When I include input element of type date or text it does not work. Removing input tag it works.
var canvas = document.getElementById("mycanvas");
var ctx = canvas.getContext("2d");
var myDiv = document.getElementById("mydiv");
canvas.setAttribute('width', myDiv.clientWidth );
canvas.setAttribute('height', myDiv.clientHeight);
const tempImg = document.createElement('img');
tempImg.addEventListener('load', onTempImageLoad);
tempImg.src = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="'+myDiv.clientWidth+'" height="'+myDiv.clientHeight+'"><foreignObject x="0" y="0" width="'+myDiv.clientWidth+'" height="'+myDiv.clientHeight+'">'+'<style>h1{color3:red}</style>'+'<div xmlns="http://www.w3.org/1999/xhtml">'+myDiv.outerHTML+'</div></foreignObject></svg>')
function onTempImageLoad(e){
ctx.drawImage(e.target, 0, 0)
}
html,body,svg { height:100% }
div{
border-style: solid;
border-width: 2px;
}
<div id="mydiv">
<input type="date" name="bday" />
<input type="text" name="name" />
<h1 >My Div</h1>
</div>
<canvas id="mycanvas"></canvas>
Added a little Fix:
function callfunction() {
var canvas = document.getElementById("mycanvas");
var ctx = canvas.getContext("2d");
var myDiv = document.getElementById("mydiv");
canvas.setAttribute('width', myDiv.clientWidth);
canvas.setAttribute('height', myDiv.clientHeight);
const tempImg = document.createElement('img');
tempImg.addEventListener('load', onTempImageLoad);
debugger
tempImg.src = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="' + myDiv.clientWidth + '" height="' + myDiv.clientHeight + '"><foreignObject x="0" y="0" width="' + myDiv.clientWidth + '" height="' + myDiv.clientHeight + '">' + '<style>h1{color3:red}</style>' + '<div xmlns="http://www.w3.org/1999/xhtml">' + myDiv.outerHTML.replace(/(<input(.+?\/?)>)/g, "<input $2 />") + '</div></foreignObject></svg>')
function onTempImageLoad(e) {
ctx.drawImage(e.target, 0, 0)
}
}
html,
body,
svg {
height: 100%
}
div {
border-style: solid;
border-width: 2px;
}
<button onclick="callfunction()">
Click Me
</button>
<div id="mydiv">
<input type="date" name="bday">
<input type="text" name="name">
<h1>My Div</h1>
</div>
<canvas id="mycanvas"></canvas>

Hover on svg for affecting the other element property doesn't work

HTML:
<div>
<svg class="velveti-grid-point" width="100" height="100" style="height: 120px; width: 625px;">
<circle class="myPoint" cx="500" cy="105" r="5" fill="#80E1EE" />
</svg>
<div class="theContainer">bg-color</div>
</div>
CSS:
.myPoint:hover + .theContainer {
background-color: black;
}
Problem: When i am hovering on the blue svg circle the background-color should be displayed on the text, but with svg it doesn't work. What is the problem? what do i need to do?
The demo: http://jsfiddle.net/wy6y66ox/
This has nothing to do with SVG per se.
+ is referred to as an adjacent selector. It will select only the element that is immediately preceded by the former element.
Because .myPoint is not a sibling of .theContainer your selector will not work.
You would need javascript in this instance.
Paulie_D is right.You have to use Javascript.
HTML
<div>
<svg class="velveti-grid-point" width="100" height="100" style="height: 120px; width: 625px;">
<circle id="svgid" class="myPoint" cx="500" cy="105" r="5" fill="#80E1EE" />
</svg>
<div id="divcolor" class="theContainer">bg-color</div>
</div>
JS
var svg = document.getElementById( 'svgid' );
var div = document.getElementById( 'divcolor' );
svg.onmouseover = function() {
div.style.backgroundColor = 'blue';
};
svg.onmouseout = function() {
div.style.backgroundColor = 'transparent';
};
Demo Here

Error html user tag canvas

I have a source code. I use tag canvas in code HTML. I have one tag div before 11 tag Canvas and two tag div behind tag canvas. When I run the code the error occurred.
If I set property of height for canvas < 50px then arrow hidden.
And if I don't set property of heigth of canvas. When I run the code, the display: tag div MISSSSS and MOMMMMM between tag canvas. Sorry I don't post image display because I do not has the right.
I want to MISSSSSS and MOMMMMMM behind canvas.
And I want set property of height for canvas = 30px don't hidden arrow.
Live example on the JSFiddle
HTML:
<div>LOLLL</div>
<div style="height:30px;">
<canvas id="text_1" width="120" ></canvas>
<canvas id="arrow_1" width="120" ></canvas>
<canvas id="text_1" width="120" ></canvas>
<canvas id="arrow_2" width="120" ></canvas>
<canvas id="text_1" width="120" ></canvas>
<canvas id="arrow_2" width="120" ></canvas>
<canvas id="text_1" width="120" ></canvas>
<canvas id="arrow_2" width="120" ></canvas>
<canvas id="text_1" width="120" ></canvas>
<canvas id="arrow_2" width="120" ></canvas>
<canvas id="arrow_2" width="120" ></canvas>
</div>
<div>MISSSSSS</div>
<div>MOMMMM</div>
Javascript:
if(navigator.userAgent.toLowerCase().indexOf('chrome') > -1)
var t = setTimeout("resize()", 200);
else
resize();
function resize() {
var innerWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
var innerHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
var targetWidth = 800;
var targetHeight = 600;
window.resizeBy(targetWidth-innerWidth, targetHeight-innerHeight);
}
function canvas_arrow(context, fromx, fromy, tox, toy){
var headlen = 20; // length of head in pixels
var dx = tox-fromx;
var dy = toy-fromy;
var angle = Math.atan2(dy,dx);
context.moveTo(fromx, fromy);
context.lineTo(tox, toy);
context.lineWidth=2;
//context.lineTo(tox-headlen*Math.cos(angle-Math.PI/6),toy-headlen*Math.sin(angle-Math.PI/6));
context.moveTo(tox, toy);
context.lineTo(tox-headlen*Math.cos(angle+Math.PI/6),toy-headlen*Math.sin(angle+Math.PI/6));
context.moveTo(tox, toy);
context.lineTo(tox-headlen*Math.cos(angle-Math.PI/6),toy-headlen*Math.sin(angle-Math.PI/6));
}
$('document').ready(function(){
var count= parseInt($("canvas").length);
for(var i=0; i< count; i++){
var ctx= $("canvas")[i].getContext('2d');
var temp= $("canvas")[i].id;
if(temp.indexOf("text") != -1){
ctx.font="15px Times New Roman";
ctx.fillText("I Love My Mom",10,10);
}
else{
if(temp.indexOf("arrow") != -1){
ctx.beginPath();
canvas_arrow(ctx,10,10,100,10);
ctx.stroke();
}
}
}
});
I'm confused about what your goal is, but try overflow:hidden; in addition to height: 30px;
https://jsfiddle.net/9j7vx5dz/2/

Categories

Resources