I have svg images in my page:
And I use to convert to png images (and download them):
https://github.com/CogComp/apelles/blob/master/public/index.html#L370-L390
var mySVG = $("#" + id + " svg")[0];
var imgId = id + "-img";
$(document.body).append('<img height="' + mySVG.getBBox().height + '" width="' + mySVG.getBBox().width + '" id="' + imgId + '" style="visibility: hidden; "/>');
var tgtImage = document.getElementById(imgId); // Where to draw the result
var can = document.createElement('canvas'); // Not shown on page
var ctx = can.getContext('2d');
var loader = new Image;
loader.width = can.width = tgtImage.width;
loader.height = can.height = tgtImage.height;
loader.onload = function () {
ctx.drawImage(loader, 0, 0, loader.width, loader.height);
tgtImage.src = can.toDataURL();
};
var svgAsXML = (new XMLSerializer).serializeToString(mySVG);
loader.src = 'data:image/svg+xml,' + encodeURIComponent(svgAsXML);
sleep(500).then(() => {
var url = tgtImage.src.replace(/^data:image\/[^;]/, 'data:application/octet-stream');
window.open(url);
});
Here is the output. Why is the output turned dark?
I tried changing the background color but didn't help.
Here is the link to the page btw: http://nlp.cogcomp.org/
Update: Checking the SVG code I think I have a background:
Despite having a background, I tried adding a background to the picture, but doesn't seem to help. Here are a couple of variations I have tried:
var svgQ = $("#" + id + " svg");
var mySVG = svgQ[0];
var rect = document.createElementNS(svgQ,'rect');
rect.setAttribute('x',10);
rect.setAttribute('y',10);
rect.setAttribute('width',50);
rect.setAttribute('height',50);
rect.setAttribute('fill','#EFEFEF');
mySVG.appendChild(rect);
// or mySVG.append(rect);
$("svg").each(function(){
$(this).css({ fill: '#000' });
// or $(this).attr("fill", '#000');
// or $(this).attr("fill", '#000000');
});
svgQ.css({ fill: '#000' });
// or svgQ.attr("fill", '#000');
// or svgQ.attr("fill", '#000000');
Related
I am currently working on a Javascript project and I am struggling with exporting the entire SVG image on the canvas. So far I've been only able to export the visible part of the canvas, with out the "hidden" parts.
How do I capture the full canvas content?
Is there a way to do it without messing around with the original canvas size?
I am using D3.js V3
Screenshot of my project
Here's my code:
var svgString;
window.onload = function(){
setTimeout(function() {
exportSVG = document.getElementById("canvas");
document.getElementById("canvas").style.fontFamily= "lato";
document.getElementById("canvas").style.width= exportSVG.getBBox().width * 1;
document.getElementById("canvas").style.height= exportSVG.getBBox().height * 1;
svgString = getSVGString(exportSVG);
console.log(exportSVG.getBBox().width + " / " + exportSVG.getBBox().height);
svgString2Image(svgString, exportSVG.getBBox().width, exportSVG.getBBox().height, 'png', save); // passes Blob and filesize String to the callback
console.log("svg export code loaded");
// console.log(svgString.getBBox().width); document.getElementById("canvas").getBBox().width
}, 5000);
};
function save(dataBlob, filesize) {
saveAs(dataBlob, 'D3 vis exported to PNG.png'); // FileSaver.js function
}
// Below are the functions that handle actual exporting:
// getSVGString ( svgNode ) and svgString2Image( svgString, width, height, format, callback )
function getSVGString(svgNode) {
svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
var cssStyleText = getCSSStyles(svgNode);
appendCSS(cssStyleText, svgNode);
var serializer = new XMLSerializer();
var svgString = serializer.serializeToString(svgNode);
svgString = svgString.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace
svgString = svgString.replace(/NS\d+:href/g, 'xlink:href'); // Safari NS namespace fix
return svgString;
function getCSSStyles(parentElement) {
var selectorTextArr = [];
// Add Parent element Id and Classes to the list
selectorTextArr.push('#' + parentElement.id);
for (var c = 0; c < parentElement.classList.length; c++)
if (!contains('.' + parentElement.classList[c], selectorTextArr))
selectorTextArr.push('.' + parentElement.classList[c]);
// Add Children element Ids and Classes to the list
var nodes = parentElement.getElementsByTagName("*");
for (var i = 0; i < nodes.length; i++) {
var id = nodes[i].id;
if (!contains('#' + id, selectorTextArr))
selectorTextArr.push('#' + id);
var classes = nodes[i].classList;
for (var c = 0; c < classes.length; c++)
if (!contains('.' + classes[c], selectorTextArr))
selectorTextArr.push('.' + classes[c]);
}
// Extract CSS Rules
var extractedCSSText = "";
for (var i = 0; i < document.styleSheets.length; i++) {
var s = document.styleSheets[i];
try {
if (!s.cssRules) continue;
} catch (e) {
if (e.name !== 'SecurityError') throw e; // for Firefox
continue;
}
var cssRules = s.cssRules;
for (var r = 0; r < cssRules.length; r++) {
if (contains(cssRules[r].selectorText, selectorTextArr))
extractedCSSText += cssRules[r].cssText;
}
}
return extractedCSSText;
function contains(str, arr) {
return arr.indexOf(str) === -1 ? false : true;
}
}
function appendCSS(cssText, element) {
var styleElement = document.createElement("style");
styleElement.setAttribute("type", "text/css");
styleElement.innerHTML = cssText;
var refNode = element.hasChildNodes() ? element.children[0] : null;
element.insertBefore(styleElement, refNode);
}
}
function svgString2Image(svgString, width, height, format, callback) {
var format = format ? format : 'png';
var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString))); // Convert SVG string to data URL
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
var image = new Image();
image.onload = function() {
context.clearRect(0, 0, width, height);
context.drawImage(image, 0, 0, width, height);
canvas.toBlob(function(blob) {
var filesize = Math.round(blob.length / 1024) + ' KB';
if (callback) callback(blob, filesize);
});
};
image.src = imgsrc;
}
Simply change your <svg> viewBox attribute before you serialize it to a string so that it displays everything:
var svg = document.querySelector('svg');
var toExport = svg.cloneNode(true); // avoids having to reset everything afterward
// grab its inner content BoundingBox
var bb = svg.getBBox();
// update its viewBox so it displays all its inner content
toExport.setAttribute('viewBox', bb.x + ' ' + bb.y + ' ' + bb.width + ' ' + bb.height);
toExport.setAttribute('width', bb.width);
toExport.setAttribute('height', bb.height);
var svgAsStr = new XMLSerializer().serializeToString(toExport);
var blob = new Blob([svgAsStr], {type: 'image/svg+xml'});
var img = new Image();
img.onload = drawToCanvas;
img.src = URL.createObjectURL(blob);
function drawToCanvas(evt) {
var canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
canvas.getContext('2d').drawImage(this, 0,0);
document.body.appendChild(canvas);
}
svg{border: 1px solid blue}
canvas{border: 1px solid green}
<svg width="50" height="50" viewBox="0 0 50 50">
<rect x="0" y="0" width="200" height="50" fill="#CCC"/>
</svg>
I have data from a database that draws a graph using NVD3. I then want to work toward PDFing the report page with dompdf. It seems that the "basic SVG support" of dompdf does not work well with my bar chart. So I figure I will convert the SVG to a PNG fisrt.
This answer works well for displaying the SVG as a PNG without styling: https://stackoverflow.com/a/19269812
Code:
var el = $($('svg')[0]);
var svgMarkup = '<svg xmlns="http://www.w3.org/2000/svg"'
+ ' class="' + el.attr('class') +'"'
+ ' width="' + el.attr('width') +'"'
+ ' height="' + el.attr('height') +'"'
+ '>'
+ $('svg')[0].innerHTML.toString()+'</svg>';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = this.URL || this.webkitURL || this;
var img = new Image();
var svg = new Blob([svgMarkup], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
alert('ok');
DOMURL.revokeObjectURL(url);
};
img.src = url;
There is another answer on the same question that addresses the styling, but I can't get the image to open at the correct size on the same screen i.e. without opening a new tab (even then, the image is cropped). https://stackoverflow.com/a/38085847
Code:
var style = "\n";
var requiredSheets = ['phylogram_d3.css', 'open_sans.css']; // list of required CSS
for (var i=0; i<document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href) {
var sheetName = sheet.href.split('/').pop();
if (requiredSheets.indexOf(sheetName) != -1) {
var rules = sheet.rules;
if (rules) {
for (var j=0; j<rules.length; j++) {
style += (rules[j].cssText + '\n');
}
}
}
}
}
var svg = d3.select("svg"),
img = new Image(),
serializer = new XMLSerializer(),
// prepend style to svg
svg.insert('defs',":first-child")
d3.select("svg defs")
.append('style')
.attr('type','text/css')
.html(style);
// generate IMG in new tab
var svgStr = serializer.serializeToString(svg.node());
img.src = 'data:image/svg+xml;base64,'+window.btoa(unescape(encodeURIComponent(svgStr)));
window.open().document.write('<img src="' + img.src + '"/>');
So, using Javascript, how can I convert an SVG to a PNG? I'm trying combinations of the two, but I think my problem is that the d3 selector used in the second snippet is too different from the SVG markup method used in the first:
var style = "\n";
var requiredSheets = ['default-blue-white.css']; // list of required CSS
for (var i=0; i<document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href) {
var sheetName = sheet.href.split('/').pop();
if (requiredSheets.indexOf(sheetName) != -1) {
var rules = sheet.rules;
if (rules) {
for (var j=0; j<rules.length; j++) {
style += (rules[j].cssText + '\n');
}
}
}
}
}
var svgX = d3.select("svg");
// prepend style to svg
svgX.insert('defs',":first-child");
d3.select("svg defs")
.append('style')
.attr('type','text/css')
.html(style);
var el = $($('svg')[0]);
var svgMarkup = '<svg xmlns="http://www.w3.org/2000/svg"'
+ ' class="' + el.attr('class') +'"'
+ ' width="' + el.attr('width') +'"'
+ ' height="' + el.attr('height') +'"'
+ '>'
+ $('svg')[0].innerHTML.toString()+'</svg>';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = this.URL || this.webkitURL || this;
var img = new Image();
var svg = new Blob([svgMarkup], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
alert('ok');
DOMURL.revokeObjectURL(url);
};
img.src = url;
The production of a <svg> by cobbling up its attributes is much too naive, as you could see from the croppings and false sizes.
Going via a data URL is a viable way, but instead of opening that in a new window (where it would be a SVG image, not a PNG), write it to canvas.
Do not insert the stylesheets into the DOM with d3.js selection.html(), but use plain Javascript document.createCDATASection() and element.appendChild() to avoid ending up with invalid XML.
The following will replace the <svg> with a <canvas> in its place, and with the same size as it is rendered in the browser.
var styleRules = [];
var requiredSheets = [...]; // list of required CSS files
for (var sheet of document.styleSheet)) {
if (sheet.href) {
var sheetName = sheet.href.split('/').pop();
if (requiredSheets.indexOf(sheetName) != -1) {
var rules = Array.from(sheet.cssRules).map(rule => rule.cssText);
styleRules = styleRules.concat(rules);
}
}
}
var styleText = styleRules.join(' ');
var styleNode = document.createCDATASection(styleRules);
var svg = d3.select("svg"),
img = new Image(),
serializer = new XMLSerializer(),
// prepend style to svg
svg.insert('defs',":first-child")
var styleEl = d3.select("svg defs")
.append('style')
.attr('type','text/css');
styleEl.node().appendChild(styleNode);
var svgStr = serializer.serializeToString(svg.node());
img.src = 'data:image/svg+xml;base64,'+window.btoa(unescape(encodeURIComponent(svgStr)));
var bbox = svg.getBoundingClientRect();
var canvas = document.createElement("canvas");
canvas.width = bbox.width;
canvas.height = bbox.height;
canvas.getContext("2d").drawImage(img,0,0,bbox.width,bbox.width);
canvas.parentNode.replaceChild(canvas, svg);
I am Alex and this is my first stack overflow question.
I have the below script.
-This script gets images from an api.
-For each image received from the api, an element is created
-for each image, a class attribute is created
-for each image, an id attribute is created.
-for each image, a element is created. For each of the , I dynamically append an onclick event. each onclick is associated with its own function.
the anchor1,2,3,4 and 5 function are made so each time a user clicks a selected image, that image is then place into a canvas. When that is done, the color thief js kicks in and displays the color palette of the image selected.
All of that works perfectly.
What I would like to do is instead of having a function for each image, is to have 1 function that would place the image clicked into the canvas.
Basically i think my code can be made less redundant.
// search the collection using a JSON call
function search(query) {
return $.getJSON("https://www.rijksmuseum.nl/api/en/collection?q=Q&key=r4nzV2tL&imgonly=True&ps=5&format=json".replace("Q", query));
}
var searchBtn = document.getElementById("search");
searchBtn.addEventListener("click", doSearch);
var resultD = document.getElementById("result");
var searchField = document.getElementById("query");
//search function starts here
function doSearch() {
$("#result").show(); // result div to show when making new search
resultD.innerHTML = "";
var searchString = searchField.value;
if (searchString !== "") {
search(searchString).done(function(data) {
for (var artObj in data.artObjects) {
var rImg = document.createElement("img"); // create the image
rImg.setAttribute("crossOrigin", "Anonymous"); //needed so I can actually copy the image for later use
rImg.setAttribute("class", "imageClass"); //needed so I can actually copy the image for later use
var link = document.createElement("a"); // create the link
link.setAttribute('href', '#'); // set link path
// link.href = "www.example.com"; //can be done this way too
rImg.src = data.artObjects[artObj].webImage.url; // the source of the image element is the url from rijks api
link.appendChild(rImg); // append image to link
resultD.appendChild(link); // append link with image to div
resultD.innerHTML += data.artObjects[artObj].title; // this is the title from rijks api
$("#result img").each(function (i, image){ //for each image create a different id
image.id = "image" + (i + 1);
});
$("#result a").each(function (i, anchor){ //for each anchor create a different id
anchor.id = "anchor" + (i + 1);
anchor.setAttribute('onclick', "anchor" + (i + 1)+'();return false;'); // set link path
//return false needed so to avoid page jump
});
resultD.innerHTML += "<br> <br> <br> <br>";
}
});
}
}//search function ends here
//for each image create size matching canvas
function anchor1(){
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image1");
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
}
function anchor2(){
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image2");
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
}
function anchor3(){
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image3");
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
}
function anchor4(){
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image4");
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
}
function anchor5(){
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image5");
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
}
You want to utilize the .on event with jquery which allows you to deal with dynamic data which is basically what you are dealing with.
You are generating different <a> links with an individual id to call your different functions like (anchor1, anchor2...) with $("#result a").each(function (i, anchor){ //for each anchor create a different id anchor.id = "anchor" + (i + 1);
Instead of generating a different id for each <a> just give them the same value, but make it a class and not a id. Id's should always be unique and never repeat, classes can be repeated. For instance <a class="anchor"><...</a>
Then when you want to deal with the data from that particular record/image you would call it as such.
$(document).on('click','.anchor', function () {
});
So here is a working example you can start with.
<a class="anchor" data-id="1">...</a>
$(document).on('click','.anchor', function () {
var anchor_id = $(this).attr('data-id').val();
var c=document.getElementById("drawing1");
var ctx=c.getContext("2d");
var img=document.getElementById("image"+anchor_id); //this will attach the id for that image here
c.height = img.height ;
c.width = img.width ;
ctx.drawImage(img,0,0,c.width, c.height);
$("#result").hide();
setTimeout(function() { //timeout for image load to canvas - start
var colorThief = new ColorThief();
var color = colorThief.getPalette(img, 18);
var newHTML = $.map(color, function(value) {
return('<div style="background-color:rgb(' + value.join(', ') + ')"> </div>'+'<br>');
});
$("#colors").html(newHTML.join(''));
}, 500); //timeout for image load to canvas - ends
});
http://api.jquery.com/on/
https://learn.jquery.com/events/handling-events/
http://html-tuts.com/jquery-this-selector/
I tried using getImageURI method to extract image but no luck.
Below is my code:
var chart = new google.visualization.AnnotationChart(document.getElementById('temperature_chart'));
google.visualization.events.addListener(chart, 'ready', function () {
var imgUri = chart.getImageURI();
document.getElementById('chartImg').src = imgUri;
});
chart.draw(data, options);
I don't know PDF but image it's possible:
<script type="text/javascript" src="http://canvg.github.io/canvg/rgbcolor.js"></script>
<script type="text/javascript" src="http://canvg.github.io/canvg/StackBlur.js"></script>
<script type="text/javascript" src="http://canvg.github.io/canvg/canvg.js"></script>
function getImgData(chartContainer) {
var chartArea = chartContainer.getElementsByTagName('svg')[0].parentNode;
var svg = chartArea.innerHTML;
var doc = chartContainer.ownerDocument;
var canvas = doc.createElement('canvas');
canvas.setAttribute('width', chartArea.offsetWidth);
canvas.setAttribute('height', chartArea.offsetHeight);
canvas.setAttribute(
'style',
'position: absolute; ' +
'top: ' + (-chartArea.offsetHeight * 2) + 'px;' +
'left: ' + (-chartArea.offsetWidth * 2) + 'px;');
doc.body.appendChild(canvas);
canvg(canvas, svg);
var imgData = canvas.toDataURL("image/png");
canvas.parentNode.removeChild(canvas);
return imgData;
}
document.body.addEventListener("load", function() {
saveAsImg( document.getElementById("pie_div")); // or your ID
}, false );
for more information see this link: Save Google charts as a image
http://www.battlehorse.net/page/topics/charts/save_google_charts_as_image.html
https://github.com/google/google-visualization-issues/issues/731
Here is HTML and Code that draws an image into a canvas, after a user has used a file picker.
HTML
<form class='frmUpload'>
<input name="picOneUpload" type="file" accept="image/*" onchange="picUpload(this.files[0])" >
</form>
<button onclick='fncSaveAsJPG()'>Convert Img To JPEG</button>
<canvas id="cnvsForFormat" width="400" height="266"></canvas>
<img id='imgTwoForJPG' src="">
Script
<script>
window.picUpload = function(frmData) {
console.log("picUpload ran: " + frmData);
var cnvs=document.getElementById("cnvsForFormat");
console.log("cnvs: " + cnvs);
var ctx=cnvs.getContext("2d");
cnvs.style.border="1px solid #c3c3c3";
var img = new Image;
img.src = URL.createObjectURL(frmData);
console.log('img: ' + img);
img.onload = function() {
var picWidth = this.width;
var picHeight = this.height;
var wdthHghtRatio = picHeight/picWidth;
console.log('picWidth: ' + Number(picWidth));
console.log('picHeight: ' + Number(picHeight));
console.log('wdthHghtRatio: ' + wdthHghtRatio);
if (Number(picWidth) > 400) {
var newHeight = Math.round(Number(400) * wdthHghtRatio);
} else {
return false;
};
document.getElementById('cnvsForFormat').height = newHeight;
console.log('width: ' + picWidth + " h: " + picHeight);
console.log('width: 400 h: ' + newHeight);
//You must change the width and height settings in order to decrease the image size, but
//it needs to be proportional to the original dimensions.
ctx.drawImage(img,0,0, 400, newHeight);
};
cnvs.onload = function () {
console.log('Onload Canvas 2 Ran');
};
};
cnvsForFormat.onload = function () {
console.log('Onload Canvas Ran');
};
</script>
I've tried attaching the .onload() method to various elements and put the code in various places without any luck. How to I get some code to run with the <canvas> element is done with the drawImage event?