After playing with interactive data visualization using D3.js and React, I wanted to have a "Download" function so that I can easily save the visualization as an image rather than taking a screenshot of a browser window:
- Practicing D3 Interactive Data Visualization with Fitbit Activity/Sleep Log
- How Much CO2 Emissions Have Your Flights Made?
In short, it can be achieved by writing a JavaScript snippet that does the following job:
<svg>toBlobBlobtoImageImageto<canvas>
Eventually, HTML Canvas enables you to save the content in the form of image file.
For the future reference, let me walk-through the process line-by-line since coming up with a complete snippet wasn't straightforward; there were some "tricky" parts that aren't fully covered by a piece of online resources e.g.:
Assume we have an SVG drawing index.html and its stylesheet e.g., style.css:
<svg xmlns="http://www.w3.org/2000/svg">
<rect width=64 height=64 />
</svg>
svg rect {
fill: blue;
}
SVG to Blob
The first and most confusing step is to convert SVG into a Blob object represented by XML:
const svg = document.querySelector('svg');
// CSS must be explicitly embedded
const style = createStyleElementFromCSS();
svg.insertBefore(style, svg.firstChild);
const data = (new XMLSerializer()).serializeToString(svg);
const svgBlob = new Blob([data], {
type: 'image/svg+xml;charset=utf-8'
});
// remove the temporarily injected CSS
style.remove();
To unlock the conversion, we have to keep the following points in our mind:
- Define
<svg>withxmlns="http://www.w3.org/2000/svg" - Embed
styleinside of the<svg>tag
Here, the inserted style element can be dynamically constructed as follows:
const createStyleElementFromCSS = () => {
// assume index.html loads only one CSS file in <header></header>
const sheet = document.styleSheets[0];
const styleRules = [];
for (let i = 0; i < sheet.cssRules.length; i++)
styleRules.push(sheet.cssRules.item(i).cssText);
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(styleRules.join(' ')))
return style;
};
Although we defined the style in the separate .css file, it's important for <svg> to explicitly contain the information. Otherwise, a saved image loses all the information about color, font, shape, etc.
Consequently, the svgBlob object has the XML representation of an SVG element below:
<svg xmlns="http://www.w3.org/2000/svg">
<style type="text/css">
svg rect { fill: blue; }
</style>
<rect width=64 height=64 />
</svg>
Blob to Image
Next, loading the Blob object to Image allows the application to treat the original SVG in a more handy way:
// convert the blob object to a dedicated URL
const url = URL.createObjectURL(svgBlob);
// load the SVG blob to a flesh image object
const img = new Image();
img.addEventListener('load', () => {
// (Next step: Image to Canvas)
});
img.src = url;
For instance, url denotes a reference to the object as: blob:http://localhost:3000/a1c7704c-09a5-46f5-a102-7bc84d8ecbce
Image to Canvas
Once Image recognizes the Blob URL, we finally draw the image on an HTML Canvas and trigger a download operation.
img.addEventListener('load', () => {
// draw the image on an ad-hoc canvas
const bbox = svg.getBBox();
const canvas = document.createElement('canvas');
canvas.width = bbox.width;
canvas.height = bbox.height;
const context = canvas.getContext('2d');
context.drawImage(img, 0, 0, bbox.width, bbox.height);
URL.revokeObjectURL(url);
// trigger a synthetic download operation with a temporary link
const a = document.createElement('a');
a.download = 'image.png';
document.body.appendChild(a);
a.href = canvas.toDataURL();
a.click();
a.remove();
});
Notice that the event handler cleanses the environment by removing url and a, an ad-hoc download link. Meanwhile, in case you want to obtain an image in a particular type, canvas.toDataURL() takes an optional parameter like canvas.toDataURL('image/jpeg').
That's it. Now we're capable of extracting an image file from an HTML-coded SVG illustration. My flight emission calculator app already has a Download button for the desired use case.
A set of code snippets below is a complete, compact example of things I explained in this article.
Support
Gift a cup of coffeeCategories
See also
- March 13, 2021
- How Much CO2 Emissions Have Your Flights Made?
- February 3, 2021
- Practicing D3 Interactive Data Visualization with Fitbit Activity/Sleep Log
- January 23, 2021
- Datavis 2020: A Free Online Course About D3.js & React
Author: Takuya Kitazawa
I am a product builder, mentor, and advocate for sustainable technology development with a decade of experience in AI/ML products, data systems, and digital transformation. Based in Canada and originally from Japan, I have lived and worked globally, including part-time residence in Malawi, Africa. Visit my portfolio to learn more about my work, or reach out to me at [email protected].
NowDisclaimer
- Opinions are my own and do not represent the views of organizations I am/was belonging to.
- I use Grammarly and LanguageTool for correcting grammatical errors, but I do not rely on any other generative AI tools to create my blog content.
- I am doing my best to ensure the accuracy and fair use of the information. However, there might be some errors, outdated information, or biased subjective statements due to the nature of a personal website. Visitors understand the limitations and rely on any information at their own risk.
- If there are any issues with the content, please contact me so I can take the necessary action.