---
url: https://chartbuddy.io/embed/docs/api/chart-data.md
---
# chartData schema

`chartData` (also called `cd`) is the serializable chart document. You pass it into `new Insight()`, `setChartData()`, and `update()`. You read the resolved form with `getChartData()`.

Two shapes matter:

| Shape | When | Notes |
|---|---|---|
| **Input** (`ChartDataInput`) | What you author or patch | Partial OK. `donut` / `bubble` accepted. Merged over defaults. |
| **Resolved** (`ChartData`) | What `getChartData()` returns | Resolved `chartType`. Full trees seeded. Safe to round-trip into `setChartData()`. |

Machine-readable rules (same registry the runtime uses): [`chart-schema.json`](https://unpkg.com/@chartbuddy.io/embed/chart-schema.json).

***

## Minimal

```js
{
  chartType: 'clusteredBar',
  isDataTransposed: true,
  seriesData: [
    ['', 'Q1', 'Q2', 'Q3'],
    ['Revenue', 100, 112, 125],
  ],
}
```

`chartType` + `seriesData` are enough to mount. Everything else deep-merges from defaults (or stays unset until you need it).

***

## Recommended shell

```js
{
  chartType: 'clusteredBar',
  isDataTransposed: true,
  seriesData: [/* … */],
  orientation: 'vertical', // or 'horizontal'
  title: { visible: true, text: 'Revenue' },
  subtitle: { visible: false, text: '' }, // hide default placeholder
  legend: { visible: true, colors: ['#2563eb', '#64748b'] },
  backgroundColor: '#ffffff',
}
```

Add `axes`, `annotations`, type bags (`bar`, `line`, …), and furniture only when you need them – or export a full tree from the editor and trim.

***

## Top-level map

| Field | Role | See |
|---|---|---|
| `chartType` | Chart type id (`donut` / `bubble` also accepted) | [Chart types](/chart-types/) · below |
| `seriesData` | 2D grid (layout depends on type) | below |
| `isDataTransposed` | Rows = series when `true` (default for most types) | below |
| `orientation` | `'vertical'` | `'horizontal'` | [Orientation](/concepts/orientation) · [Axes](/axes/) |
| `title` / `subtitle` / `footnote` | Furniture text blocks | [Title, subtitle & footnote](/configuration/title-subtitle-footnote) |
| `legend` | Visibility, placement, **`colors` palette** | [Legend](/configuration/legend) |
| `backgroundColor` | Chart underlay (PNG export still needs its own `background` option) | [Visual QA](/quality-assurance/visual-qa) |
| `axes` | Sides, roles, ticks, bounds, formats, breaks | [Axes](/axes/) |
| `annotations` | Arrows, level lines, totals, data-label formats | [Arrows](/configuration/arrows) · [Level lines](/configuration/level-lines) · [Number formats](/configuration/number-formats) |
| `multilines` | Free text boxes | [Text boxes](/configuration/text-boxes) |
| `canvas` | Internal width/height snapshot | [Canvas & sizing](/concepts/canvas) |
| `bar` / `line` / `area` / `pie` / `scatter` / `waterfall` / `mekko` / `barMekko` / `combo` | Type-specific option bags | [Chart types](/chart-types/) · below |
| `seriesLabels` | Per-series / per-point label chrome | full export |
| `chartPositionPercentages` | Plot margins inside the canvas | full export |
| `id` / `version` | Document identity / version | runtime |

Unknown top-level keys are **allowed** (forward-compatible). Wrong types, foreign option bags, and unknown `chartType` values are **rejected**.

***

## chartType

Resolved values (`cd.chartType` from `getChartData()`):

`clusteredBar` · `stackedBar` · `stackedBar100` · `line` · `stackedArea` · `stackedArea100` · `pie` · `scatter` · `waterfall` · `mekko` · `barMekko` · `combo`

### Donut & bubble

`donut` and `bubble` are shortcuts that seed defaults so the chart looks like what you asked for:

| You pass | Resolves to | Seeded defaults |
|---|---|---|
| `donut` | `pie` | `pie.innerRadiusRatio: 0.5` |
| `pie` | `pie` | `pie.innerRadiusRatio: 0` |
| `bubble` | `scatter` | larger point diameter |

```js
{ chartType: 'donut', seriesData: [['Category', 'Value'], ['North', 45]] }
```

Do not also set `innerRadiusRatio` unless you want a different hole. `getChartData()` returns `pie` with `innerRadiusRatio: 0.5`. That round-trips correctly.

***

## seriesData layouts

Layout is fixed by `chartType` (see the registry / `chart-schema.json`). Cells are `string | number | boolean | null`.

### Bar / line / area / stacked / waterfall / mekko / combo (`seriesRows`)

With `isDataTransposed: true` (usual): row 0 is the category header; later rows are series.

```js
[
  ['', 'Q1', 'Q2', 'Q3'],
  ['Revenue', 100, 112, 125],
  ['Costs', 60, 66, 70],
]
```

Minimum: 2 rows × 2 columns (header included).

### Pie / donut (`categoryValue`)

```js
[
  ['Category', 'Value'],
  ['North', 45],
  ['South', 30],
]
```

### Scatter / bubble (`pointRows`)

No transpose. Row 0 names metrics; later rows are points (`x`, `y`, optional `size`, optional `group`).

```js
[
  ['', 'Metric X', 'Metric Y', 'Size', 'Group'],
  ['Point 1', 10, 15, 8, 'A'],
]
```

Minimum: 2 rows × 3 columns.

### Bar Mekko (`widthRowThenHeightRows`)

Row 1 after the header sets **widths** (not drawn as a series). Later rows stack as **heights** on a real value axis. See [Bar Mekko](/chart-types/bar-mekko).

```js
[
  ['', 'Enterprise', 'Mid-market', 'SMB'],
  ['Accounts', 120, 85, 200],       // width
  ['Core revenue', 48, 16, 8],      // height
  ['Add-ons', 14, 12, 10],
]
```

Minimum: 3 rows × 2 columns.

### Ragged grids

Unequal row lengths **warn** and still load. Pad short rows when you care about clean columns.

***

## isDataTransposed

| Value | Meaning |
|---|---|
| `true` (typical) | Rows after the header are series; columns are categories |
| `false` | Opposite spreadsheet orientation |

Scatter / bubble ignore transpose (`pointRows`). When unsure, keep `true` for bar/line/area/waterfall/combo and match the samples.

***

## Type-specific option bags

Each chart type reads **one** bag:

| Types | Bag |
|---|---|
| `clusteredBar`, `stackedBar`, `stackedBar100` | `bar` |
| `line` | `line` |
| `stackedArea`, `stackedArea100` | `area` |
| `pie`, `donut` | `pie` |
| `scatter`, `bubble` | `scatter` |
| `waterfall` | `waterfall` |
| `mekko` | `mekko` |
| `barMekko` | `barMekko` |
| `combo` | `combo` |

Putting options in the wrong bag (e.g. `chartType: 'pie'` with `bar: {…}`) **throws** (`foreign-option-bag`). Resolved snapshots from `getChartData()` may carry every bag (defaults seed them); that is normal when round-tripping.

Examples:

```js
{ chartType: 'donut', pie: { innerRadiusRatio: 0.7 } }

{ chartType: 'combo', combo: { seriesTypes: { 0: 'bar', 1: 'line' } } }

{ chartType: 'barMekko', barMekko: { sort: 'heightDesc' } }
```

Per-type fields and recipes: [Chart types](/chart-types/).

***

## Waterfall: do not invent totals

Total columns are computed. Mark the column and leave its cell empty. A value typed into an `isTotal` column is ignored. A closing figure typed **without** `isTotal` becomes another contribution bar (the bridge then ends near ~2×).

```js
{
  chartType: 'waterfall',
  isDataTransposed: true,
  seriesData: [
    ['', 'Start', 'Price', 'Volume', 'Mix', 'End'],
    //  ^0       ^1       ^2        ^3     ^4   <- waterfall.columns keys
    ['Bridge', 100, 18, -8, 5, null], // End carries NO value
  ],
  waterfall: {
    columns: {
      4: { isTotal: true }, // closing bar = 115
    },
  },
}
```

| Rule | Detail |
|---|---|
| Column keys | 0-based **data-column** indices: header cells **after** the row-label cell. `'End'` is header index 5 but data column **4**. |
| `isTotal: true` | Show running total; ignore this column's own value |
| `startBar: true` | Reset to zero and drop the connector (new sequence). A mid-chart opening bar needs **both** `isTotal` and `startBar`. |
| `showSegments: true` | Draw a total as per-series segments instead of a solid bar |
| Column 0 | Always forced to `isTotal` + `startBar`; unlike other totals it **does** use its own value (opening balance) |

Full type page: [Waterfall](/chart-types/waterfall).

***

## Partial patches and merging

```js
insight.setData(seriesData);                 // data only
insight.update({ title: { text: 'FY26' } }); // any partial
insight.setChartData({ seriesData: […] });   // chartType optional
insight.update();                            // redraw only
```

| Behaviour | Detail |
|---|---|
| Omit `chartType` | Keeps the current type |
| Deep-merge keys | `title`, `subtitle`, `footnote`, `legend`, `axes`, `canvas`, `annotations` |
| Other top-level keys | Typically **replace** (including arrays such as `multilines`) |
| Empty / useless patch | Rejected (`empty-patch`) when there is nothing to apply |

See [Defaults & merging](/concepts/defaults).

***

## Validation

`new Insight({ chartData })`, `setChartData`, `setData`, and `update` validate at the API boundary and **throw** `ChartDataValidationError` with structured issues.

| Input | Behaviour |
|---|---|
| Unknown `chartType` | Throws (did-you-mean / `allowed`) |
| Wrong type / enum / range | Throws (lists every path) |
| Foreign option bag | Throws |
| Ragged `seriesData` | Warns, still accepts |
| Unknown keys | Always allowed |

Prefer checking before mount:

```js
import { validateChartData } from '@chartbuddy.io/embed';

const { valid, errors, warnings } = validateChartData(candidate);
// issue: { path, code, message, severity, expected?, received?, allowed?, suggestion? }
```

Branch on `code`, not `message` (messages may change):

| Code | Fix |
|---|---|
| `unknown-chart-type` / `not-in-enum` | Use `suggestion` or pick from `allowed` |
| `wrong-type` | Coerce to `expected` |
| `out-of-range` | Clamp to the bound in `expected` |
| `series-data-shape` | Reshape; `expected` names row/column minimums |
| `ragged-series-data` | Warning – pad short rows |
| `foreign-option-bag` | Move options into the bag named in `suggestion` |
| `empty-patch` | Include `chartType` and/or `seriesData` |

Full reference: [Validation](/quality-assurance/validation).

***

## Full document workflow

Hand-authoring every axis tick and annotation is painful. Prefer:

1. Mount with a minimal or recommended shell
2. Polish in edit mode (`editable: true`)
3. Snapshot:

```js
await insight.ready;
const full = insight.getChartData();
// or insight.exportConfig() for a JSON download
```

Expect trees such as `canvas`, `axes`, `annotations`, `multilines`, type bags, `seriesLabels`, and `chartPositionPercentages`. Round-trip that object with `setChartData(full)` when you need a complete starting point, then patch.

***

## Pitfalls

* Generating a chart type that is not in the registry (`bubbleChart3D`) – validate first
* Putting `bar` options on a `pie` (foreign bag)
* Wrong `seriesData` layout for the type (pie as series-rows, scatter transposed)
* Waterfall closing value without `isTotal` (double-counts the bridge)
* Wrong waterfall column index (header position vs data-column index)
* Expecting `backgroundColor` alone to appear in PNG export – pass `toPngBase64({ background })`
* Expecting `getChartData()` to keep `chartType: 'donut'` / `'bubble'` — it returns `pie` / `scatter` with the seeded defaults
* Deep-merging arrays by hand – `multilines` and similar usually **replace**

***

## Related

* [Data model](/concepts/data-model) – layouts overview
* [Defaults & merging](/concepts/defaults)
* [Insight API](/api/) – mount / update / events
* [Visual QA](/quality-assurance/visual-qa) – observe after mount
* Package index: [llms.txt](https://unpkg.com/@chartbuddy.io/embed/llms.txt) · schema: [chart-schema.json](https://unpkg.com/@chartbuddy.io/embed/chart-schema.json)
