I'm trying to download an image of a visualization created in d3, in Svelte. I've created a repl to show what I've tried so far. It's just a throwaway chart-- so don't mind how ugly and useless it is, this is just an attempt to get a simple working example.
Any idea what's going wrong? I'm not getting any errors in the console. Help very much appreciated.
App.svelte:
<script>
import Chart from "./Chart.svelte";
import saveAs from "./FileSaver.min.js";
async function downloadChart() {
const chart = document.querySelector("#chart");
const svgString = new XMLSerializer().serializeToString(chart);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const data = new Blob([svgString], {type: "image/svg+xml"});
const url = URL.createObjectURL(data);
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0);
canvas.toBlob((blob) => {
saveAs(blob, "chart.jpg");
});
};
img.src = url;
}
</script>
<Chart/>
<button on:click={downloadChart}>Download Image</button>
Chart.svelte
<script>
import { onMount } from "svelte";
import * as d3 from "d3";
let data = [10, 20, 30, 40, 50];
onMount(() => {
const svg = d3
.select("#chart")
.append("svg")
.attr("width", 500)
.attr("height", 500);
svg
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", (d, i) => i * 50)
.attr("y", (d, i) => 500 - d)
.attr("width", 50)
.attr("height", (d) => d)
.attr("fill", "teal");
});
</script>
<div id="chart" />
FileSaver.min.js is from https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js
There are several issues.
You serialize a
divrather than an SVG, which then cannot be loaded in the image. I would recommend adding anonerrorhandler to the image to be notified of such issues.Generally one should not query the DOM when using Svelte, it is prone to causing errors and violates the independence of the components. Use prop bindings and
bind:thisoruse:actionto interact with DOM nodes.Here, as the SVG is created by D3, it can be exposed as a property of
Chart, e.g.The canvas size should be set to the image size:
toBlobreturns a PNG by default, a second parameter has to be added to specify'image/jpeg'to get a JPG blob. (Would recommend using PNG, though. It has transparency and is a more suitable format for charts.)FileSaveris on NPM and can be imported like this:(Even if you keep the file as a copy, the import gives you the module, not the
saveAsfunction.)It also will not work in the REPL, because that does not allow opening new tabs. To test whether the logic works you can add a
aelement with a link to the file blob URL instead. The link then can be opened manually via the right click menu.Updated REPL