Command Palette

Search for a command to run...

Hectal
PHASE 16Advanced ~42 min· topic 6 of 8

Topic 16.6

Exporting and Printing

In one line

People want the chart in a slide, the numbers in a spreadsheet and the page on paper. An SVG file needs its styles written into it, a PNG goes through a canvas, a CSV needs careful quoting, and print needs its own CSS.

Think of it like this

Lending someone a recipe. You can photocopy the page (a PNG: fixed, easy to paste), hand over the original card (an SVG: sharp at any size, but it needs the same pen colours), or copy the ingredient list on a sheet (a CSV: the numbers, to reuse). Each is right for a different person.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

serialise
Turn a live object (an SVG element) into text that can be saved.
Blob
A chunk of file-like data in memory, with a type such as text/csv.
object URL
A temporary address (blob:…) for a Blob, made by URL.createObjectURL.
CSV
Comma-separated values: a text table, one row per line.
byte order mark
Three bytes at the start of a UTF-8 file (the character \ufeff) that tell Excel the file is UTF-8.
tainted canvas
A canvas that has drawn another site's image without permission, so the browser refuses to read it back.
CORS
Cross-Origin Resource Sharing: headers by which a server allows other sites to use its files.

Step by step

01Serialise and compare

The first example draws five bars styled by a stylesheet and by var(--chart-accent). It serialises them twice: once plainly, once with svgText. It counts how many bars carry their own fill, and shows both files as pictures, as an image viewer would show them.

Then svgToPng draws the picture on a canvas and returns a PNG blob. The log shows the type, the starting bytes of a PNG file and the size, which is the SVG's size times the scale.

02Make the CSV and the download

The second example prints what csvCell does with ten awkward values, builds a small CSV from real stations plus one row of test text, and shows the exact text with \r\n and the byte order mark made visible.

A stand-in for the link click logs the file name and that the address is a blob: address. In your project the same call starts the download.

Make the CSV and the downloaddiagram
Rendering diagram…

03Print

Print CSS goes in the same stylesheet, inside @media print: ink and paper colours, no controls, one column, charts that stay whole. Test it with the browser's print preview (Ctrl+P) or with the Rendering tab's Emulate CSS media type: print.

src/styles/production.css (print block)whole filecss
@media print {
  :root:root, :root[data-theme="dark"] { --page-bg: #fff; --chart-bg: #fff; --chart-ink: #000; --chart-muted: #444; }
  header button, .presets, .exports, .state button, select { display: none; }
  .grid { display: block; }
  .box { break-inside: avoid; margin-bottom: 12px; }
}

04Meet the tainted canvas

This stage draws an image from the site onto a canvas and tries to read it. It fails with the real browser error: SecurityError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data. With crossOrigin = "anonymous" the same read works, because the server allows it.

Try it yourself

  1. 1

    Export at three times the size

    In the first example change svgToPng(svg, 2) to svgToPng(svg, 3). The log says pixels 900 x 360: the picture is the SVG's size times the scale.

  2. 2

    Drop the byte order mark

    In the second example, call toCsv(rows, columns, { bom: false }). The text no longer starts with \ufeff. Excel on Windows may then show the accents wrongly if a name has one. Keep the mark unless your readers use another tool.

Code & diagrams

An SVG with its styles, and a PNG from it New tab

The plain file has no fill on its bars, so a viewer paints them black. The second has every style written in. Both pictures are the real exported files, shown as images.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

plain: bars with a fill of their own: 0 of 5
svgText: bars with a fill of their own: 5 of 5
svgText starts with: <?xml version="1.0" encoding="UTF-8"?>
svgText size attributes: true
png: image/png | starts with 137,80,78,71 | pixels 600 x 240
scale 30: The picture would be 9000 x 3600 pixels, which is more than a browser canvas should be asked for. Use a smaller scale.
CSV: quoting, the file text, and the download call New tab

The first three rows are real stations (S01 Quay, S02 Market, S03 Chapel, all in Harbour); the fourth is made-up test text. A formula-looking text gets a ' in front, and a negative number does not.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

"Quay"                           -> "Quay"
"Old Town, East"                 -> "\"Old Town, East\""
"He said \"go\""                 -> "\"He said \"\"go\"\"\""
"line one\nline two"             -> "\"line one\nline two\""
"=SUM(A1:A9)"                    -> "'=SUM(A1:A9)"
"-5 degrees"                     -> "'-5 degrees"
-3                               -> "-3"
null                             -> ""
NaN                              -> ""
Date 2025-06-01T23:30:00.000Z    -> "2025-06-01"
"<BOM>Station,Name,District\r\nS01,Quay,Harbour\r\nS02,Market,Harbour\r\nS03,Chapel,Harbour\r\nTEST,\"Test, \"\"quoted\"\" =HYPERLINK(\"\"x\"\")\",\r\n"
download: lumen-rides-2025-06-01-2025-06-30.csv | address starts with blob: true
odd names: lumen-rides-ardenvale-quay-caf-1.svg

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Serialise without the styles

In the first example, delete the from.forEach(…) block inside the pasted svgText (the four lines that copy the computed styles).

terminal
$ Console, and in the pictures
── what you'll see ──
svgText: bars with a fill of their own: 0 of 5
(the middle picture and the PNG show five black bars, though the page shows teal bars)

Myth vs fact

Myth

A screenshot is a good enough export.

Fact

It is blurry on a big screen, includes the browser, cannot be edited and holds no numbers. Export the SVG, the PNG and the CSV.

Myth

An SVG file looks the same wherever it is opened.

Fact

Only if its styles are inside it. CSS from your page and CSS variables do not travel with serializeToString.

Myth

A CSV is just join(",").

Fact

A comma, quote or line break in a value breaks the columns, and a value that starts with = can run as a formula in a spreadsheet.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A formula-looking value in a CSV (=HYPERLINK(...)) can make a spreadsheet send data or run a command when someone opens the file (CSV injection). Prefixing a ' defuses it. Data from users or other systems must always go through csvCell. The cost: a spreadsheet shows the ' as a visible character in that cell, so tell readers that a leading apostrophe is a safety mark.

  • ▸

    Fonts: an SVG used as an image cannot fetch web fonts, so a chart that uses your brand font exports in the computer's default font. To keep the font, embed it as base64 in a <style> inside the SVG, or accept the fallback and name a safe font stack.

  • ▸

    canvas.toBlob is asynchronous and gives null when the canvas is too big (browsers limit the width, height and area). Check the size first, as svgToPng does, and tell the person to choose a smaller scale.

  • ▸

    <use href="#icon"> points into the page's <defs>. A copy of just the chart has no <defs>, so the icon is missing in the file. Copy the symbols you need into the exported SVG or draw them in place.

Remember this

  1. 1

    The problem this solves: the board wants the chart in a slide, the analyst wants the numbers in Excel, and the office wants a printout. A screenshot is blurry, loses the numbers and takes a person's time. The dashboard can make every one of them with a button.

  2. 2

    SVG to a file. new XMLSerializer().serializeToString(svg) turns the live element into text. But the text carries only the element's own attributes. The page's stylesheet, var(--chart-accent) and inherited currentColor are left behind, so the file opened alone shows black shapes. svgText copies the computed fill, stroke and font values into each element, adds the xmlns, and fixes the width and height (a file with width="100%" fills whatever opens it). The first example shows the difference as pictures.

  3. 3

    SVG to PNG, through a canvas. Make a Blob of the SVG text, give it an address with URL.createObjectURL, load it into an Image, drawImage it on a canvas and call canvas.toBlob. Scale the canvas by 2 for a sharp image on a phone or in a slide, fill the background first (a PNG is transparent otherwise, and dark text on a transparent PNG vanishes on a dark slide), and refuse silly sizes (a canvas has a maximum). An SVG loaded as an image cannot load fonts or pictures from the web, so it uses the fonts the computer already has.

  4. 4

    CSV, carefully. A CSV is text with rules: a cell with a comma, a quote or a line break is wrapped in quotes and inner quotes are doubled ("Quay, ""North"""). Lines end with \r\n. A byte order mark at the start (\ufeff) lets Excel read accents correctly. Empty and NaN values become empty cells. And a text that starts with =, +, - or @ can run as a formula when opened in a spreadsheet, so csvCell puts a ' in front. Numbers are not touched, so -3 stays -3.

  5. 5

    The download itself. Make a Blob, URL.createObjectURL it, put it in a link with a download name, click the link from code, and release the address a moment later. The name should be safe and tell what is inside, including the filter (lumen-rides-2025-06-01-2025-06-30.csv). A preview cannot download, so the second example logs the name and the text instead. Some browsers ask the person for permission when a page starts several downloads in a row, so offer one file per click.

  6. 6

    Export what the person sees. If a filter is on, export the filtered rows and put the filter in the file name. Exporting everything when the screen shows June is a surprise, and exporting a filter with no label is a mystery.

  7. 7

    Print. Paper has no dark theme, no buttons and no hover. A @media print block resets the colour tokens to dark ink on white, hides buttons and controls, turns the grid into one column, and uses break-inside: avoid so a chart is never cut by a page break. The project's production.css does this, and the Print step below shows the block. Browsers also leave out CSS background colours when printing unless the person ticks "Background graphics", while SVG fills are always drawn: keep meaning in the marks, not in a div background (or add print-color-adjust: exact where a background is data).

  8. 8

    Tainted canvas. If a canvas draws an image from another origin that did not send permission headers, the browser marks the canvas as tainted and refuses getImageData and toBlob. The last walkthrough stage shows the real errors and the fix: ask for the image with crossOrigin = "anonymous", and the server must send CORS headers. A chart drawn from your own SVG never taints.

Explain it without notes

01

Why does an SVG made with serializeToString lose its colours when opened as a file?

02

How does a chart get from SVG to a PNG file in the browser?

03

Which values in a CSV need quoting, and what is CSV injection?

04

What is a tainted canvas and how do you avoid it?

Practice

01

Write exportName(kind, filter) that gives lumen-rides-saturdays-june.png for kind = "png" and a Saturday filter in June. What does it return for an empty filter?

02

Make toCsv write dates as 2025-06-01 and numbers with . for decimals, whatever the visitor's language. Why is toLocaleString the wrong tool here?

Trade-offs

  • ↔

    SVG export stays sharp and editable but depends on fonts and on styles being copied in. PNG is easy to paste everywhere but fixed in size.

  • ↔

    Writing computed styles into every element makes files bigger. For a chart with thousands of marks, export a PNG or a canvas image instead.

Done when you can

  • I can export an SVG that keeps its colours and fonts.

  • I can make a PNG from an SVG at a chosen scale, with a background.

  • I can write a CSV that survives commas, quotes, line breaks and formulas.

  • I can start a download and name the file from the filter.

  • I can write print CSS that hides controls and keeps charts whole.

  • I can explain a tainted canvas and its fix.