---
id: "packages/active-charts/export"
title: "Export"
description: "PNG, SVG, CSV and PDF export, the report options, and when an export is still."
url: "/packages/active-charts/export"
locale: "en"
revision: "a67b3bacce8b2147"
---

# Export

> [!NOTE]
> **The package's own guide**
>
> This page is the package's `docs/recipes/EXPORT.md`, included unchanged from
> the package as it is in Foundation. Links to the package's internal design
> documents keep their text.

**Example: Selection, tooltip, legend and export**

Client: Runs entirely in your browser. Source: `examples/charts/interaction.tsx`.

```tsx
// 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 `../API.md` § 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.

```tsx
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 `onExportComplete(result)` and
`onExportError(format, error)`, also for exports the reader starts from the
context menu. Without `onExportError`, 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).

```tsx
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.

```tsx
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-face` files are
  readable (same origin); otherwise the image uses a system font.
- **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:

```tsx
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:

```tsx
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

`exportAsCsv` 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.
