Skip to content

Data dialogs and exports

4 min read · Updated 27 Oct 2026

This page belongs to Data App developer guide. The menu that offers these actions is in Object menu.

The visualization packages do not expose the query or the rows behind a mounted object, and they have no export API at the moment:

  • Offer data actions — show as table, CSV, Excel — only for data your app queries itself, such as a figure card or a hand-built chart, and read those rows from the engine, not from the DOM.
  • Do not present a query you rebuilt from a visualization’s fields as that visualization’s data. Top N, sorting, grouping and the object’s own filters live inside the visualization, so the rows can differ from what it shows.

Open the dialog immediately with a loading indicator and disable exports until the data arrives.

Create a new request token every time the dialog opens and invalidate it on close. Apply a result only if its token is still current, including when the same object is reopened.

Display query errors in the dialog.

For CSV files intended to be opened in Excel:

  • Start with a UTF-8 byte-order mark. Without it, Excel reads UTF-8 as the system’s legacy code page and Förbrukning arrives as Förbrukning.
  • Put sep=; on the first line and separate with semicolons. Excel reads a comma-separated file as one column in locales that use the comma as a decimal separator.
  • Quote every field that contains a semicolon, a quote or a newline, and double the quotes inside it. An unquoted semicolon shifts every later column.
  • Neutralize text that a spreadsheet would run as a formula. Model values are data, and a text value starting with =, +, -, @, a tab or a carriage return can be evaluated when the file is opened. Prefix such text with an apostrophe; leave real numbers as numbers.
const csvField = (value: unknown) => {
if (typeof value === 'number') return String(value);
let text = value == null ? '' : String(value);
if (/^[=+\-@\t\r]/.test(text)) text = `'${text}`;
return /[;"\r\n]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
};
const csv = '\uFEFF' + ['sep=;', ...rows.map((row) => row.map(csvField).join(';'))].join('\r\n');

Offer a real .xlsx as well as CSV, so numbers arrive as numbers and the file opens without an import dialog. Use a library you trust, or write the file directly — an .xlsx is a zip of a few XML parts. If you write it yourself:

  • Write numbers as numeric cells (<c r="B2"><v>1234.5</v></c>) so Excel can sum them, and text as inline strings.
  • Remove control characters from text. XML 1.0 cannot carry them, and Excel refuses a file that contains one.
  • Storing the zip entries uncompressed avoids needing a deflate implementation; you still need a correct CRC-32 for each entry.
  • A fixed timestamp in the zip entries makes the output byte-identical between runs.

Check the archive’s CRCs and load it with a spreadsheet library to verify cell values and types.

Create the file as a Blob, point a temporary link at an object URL and click it. Revoke the URL on the next animation frame, not in the same tick; the click is handled asynchronously and an immediately revoked URL can be gone before the browser reads it.

To capture a region, clone its subtree, wrap it in an SVG foreignObject, draw that through an <img> onto a canvas, and export the canvas at twice its size for sharpness. Preserve the following when cloning:

  • Styles. An SVG image fetches no stylesheets, so inline every node’s computed style. Large markup is normal.
  • Canvas pixels. A <canvas> serializes as an empty element, and charts are drawn to canvases. Replace each with an <img> holding its own toDataURL().
  • Pseudo-elements. ::before and ::after are not cloned. Re-create any that carry content — an arrow on a delta, for example — as real elements.
  • Scroll position. A clone starts at the top. For each scrolled container, shift its contents by a transform instead.
  • Icon fonts. An SVG image loads no external fonts, and a cross-origin stylesheet cannot be read back to inline. Replace icon glyphs with inline SVG or a font you embed, mapping by code point rather than ligature name.
  • Virtualized content. A table renders only the rows and columns in view. To capture more of it, resize the live object, wait for it to re-render, capture, then restore it; widening the clone gives headings with no cells.
  • Serialize with XMLSerializer, not innerHTML, and set the XHTML namespace on the clone. An SVG image parses its content as XML, and HTML serialization loads as nothing.
  • Paint a background. A transparent image of a dark panel disappears on a white slide; fill with the page’s own background colour.

Canvas export works only while the canvas is not tainted. A cross-origin image in the region makes toDataURL() throw, and a WebGL canvas without a preserved drawing buffer reads back blank. Report those cases rather than saving a blank or partial image.