Export
PNG, SVG, CSV and PDF export, the report options, and when an export is still.
@workspace/active-charts 6.0.0Status unknowncontent differs from the recorded release 6.0.0baseline ce081dc3
This page is the package's docs/, included unchanged from
the package as it is in Foundation. Links to the package's internal design
documents keep their text.
Selected: nothing
// apps/docs/examples/charts/interaction.tsx'use client';
import {useRef, useState} from 'react';import {MONTHLY_SALES, SALES_SERIES} from '@/lib/examples/chart-data';
import { BarChart, englishChartTranslator, type ChartHandle, type ChartItemRef,} from '@workspace/active-charts';import {Button} from '@workspace/active-ui/components/primitives/button';
/** A readable name for a selected bar: its series and its month. */function describe(ref: ChartItemRef): string { if ('seriesId' in ref) return `${ref.seriesId} @ ${String(ref.key)}`; return JSON.stringify(ref);}
/** * Selection under the host's control, the shared tooltip and legend, and * the export of the imperative handle. Click a bar, or move to it with the * arrow keys and press Space, to select it; Shift+F10 opens the chart's menu * with copy, the data table and export. */export default function ChartInteraction() { const chart = useRef<ChartHandle>(null); const [selected, setSelected] = useState<ChartItemRef[]>([]); const [exported, setExported] = useState<string | null>(null); return ( <div className="flex w-full flex-col gap-4"> <BarChart ref={chart} data={MONTHLY_SALES} xKey="month" xAxis="category" series={SALES_SERIES} legend tooltip={{enabled: true, mode: 'shared'}} selection selectedItems={selected} onSelectionChange={(next) => setSelected(next)} height={300} locale="en-GB" t={englishChartTranslator} /> <div className="flex flex-wrap items-center gap-2"> <Button variant="outline" size="sm" onClick={async () => { const result = await chart.current?.exportAsCsv('sales.csv'); setExported( result ? `${result.filename}: ${result.blob.size} bytes` : null, ); }} > Export CSV </Button> <Button variant="outline" size="sm" onClick={async () => { const result = await chart.current?.exportAsPng('sales.png', 2); setExported( result ? `${result.filename}: ${result.blob.size} bytes` : null, ); }} > Export PNG </Button> <Button variant="ghost" size="sm" disabled={selected.length === 0} onClick={() => setSelected([])} > Clear selection </Button> </div> <p className="text-sm" aria-live="polite" data-chart-selection> Selected:{' '} {selected.length > 0 ? selected.map(describe).join(', ') : 'nothing'} </p> {exported ? ( <p className="text-muted-foreground text-sm">{exported}</p> ) : null} </div> );}Images (SVG, PNG, PDF) and data (CSV) from a chart's handle. The contract
is ../ ยง ChartHandle and ADR-040 (W11).
Await the result#
Every export returns a promise. It resolves once the file has been handed to the browser and rejects when no file could be produced. An image export waits until the chart is still, so an export started during an entrance or zoom animation captures the final frame.
import {useRef} from 'react';import {LineChart, type ChartHandle} from '@workspace/active-charts';
function FundChart({rows}: {rows: {date: Date; nav: number}[]}) { const chartRef = useRef<ChartHandle>(null);
async function download(): Promise<void> { try { await chartRef.current?.exportAsPng('nav.png'); } catch { // Show the host's own failure message. } }
return ( <> <LineChart ref={chartRef} data={rows} xKey="date" yKey="nav" xAxis={{temporalSemantic: 'calendarDate'}} height={300} /> <button type="button" onClick={() => void download()}> PNG </button> </> );}The same outcome is reported by on and
on, also for exports the reader starts from the
context menu. Without on, a failed menu export is written to the
developer console; it is never swallowed.
Capture without a download#
Every export resolves with the file it produced. With download: false
nothing is handed to the browser: the host gets the same file, captured the
same way, and can store it (ADR-105).
async function attachToCase(): Promise<void> { const result = await chartRef.current?.exportAsPdf('funding-ratio', { download: false, }); if (!result) return; // `result.filename` is 'funding-ratio.pdf'; `result.blob.type` is // 'application/pdf'. The upload is the host's own. await fetch('/api/case/42/attachments', { method: 'POST', headers: {'X-File-Name': result.filename}, body: result.blob, });}A report heading#
The host may put a title, a subtitle and a caption into an image export. These are the host's words, in the host's language; the chart never writes them.
async function exportReport(): Promise<void> { await chartRef.current?.exportAsPdf('funding-ratio.pdf', { report: { title: t('report.fundingRatio'), subtitle: t('report.lastYear'), caption: t('report.source'), }, });}Fonts#
Which script each format carries, and the host routes for what the PDF refuses: EXPORT-GUARANTEES.md.
SVG keeps the host font by name: it shows where that font is installed.
PNG embeds the host's web font when its
@font-files are readable (same origin); otherwise the image uses a system font.face PDF draws text only in a font it embeds, so the host supplies its fonts as TrueType data (ADR-058). Each text element draws in the first family that covers it. Without export fonts the PDF draws in Helvetica and refuses any character outside WinAnsi; complex scripts (Arabic, Hebrew, the Indic scripts, Thai, โฆ), emoji and supplementary-plane characters are refused whatever the fonts. For those, export SVG or PNG, or print from a browser.
Supply the fonts once, for every chart and for the context menu:
const exportFonts: ChartExportFonts = { families: [ { family: 'Corporate Sans', faces: [ // Loaded only when a PDF export needs it; the loader is the host's. { source: () => fetch('/fonts/corporate-regular.ttf').then((r) => r.arrayBuffer()), }, { source: () => fetch('/fonts/corporate-bold.ttf').then((r) => r.arrayBuffer()), weight: 700, }, ], }, { family: 'Noto Sans CJK', faces: [ { source: () => fetch('/fonts/noto-sans-cjk.ttf').then((r) => r.arrayBuffer()), }, ], }, ],};
function ChartsWithExportFonts() { return ( <ChartDefaults locale="en-GB" t={englishChartTranslator} exportFonts={exportFonts} > <AppShell /> </ChartDefaults> );}A single export can name its own set, or skip the defaults, and a refusal names why:
declare const exportFonts: ChartExportFonts;
async function exportPdf(): Promise<void> { try { await chartRef.current?.exportAsPdf('chart.pdf', {fonts: exportFonts}); } catch (err) { if ( err instanceof ChartExportError && err.code === 'EXPORT_TEXT_UNSUPPORTED' ) { // err.reason, err.script and err.codePoint say what; offer PNG instead. await chartRef.current?.exportAsPng('chart.png'); } }}Data#
export writes the host's raw rows: never the reduced, grouped or
percentage values the chart displays. A standalone family writes its own
data model: a treemap its node paths and values, a sankey its links, an
organisation chart name, parent and title, a gauge its value and range.