Appearance
Are you an LLM? You can read better optimized documentation at /embed/docs/api/chart-data.md for this page in Markdown format
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.
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 · below |
seriesData | 2D grid (layout depends on type) | below |
isDataTransposed | Rows = series when true (default for most types) | below |
orientation | 'vertical' | 'horizontal' | Orientation · Axes |
title / subtitle / footnote | Furniture text blocks | Title, subtitle & footnote |
legend | Visibility, placement, colors palette | Legend |
backgroundColor | Chart underlay (PNG export still needs its own background option) | Visual QA |
axes | Sides, roles, ticks, bounds, formats, breaks | Axes |
annotations | Arrows, level lines, totals, data-label formats | Labels · Arrows · Level lines · Number formats |
multilines | Free text boxes | Text boxes |
canvas | Internal width/height snapshot | Canvas & sizing |
bar / line / area / pie / scatter / waterfall / mekko / barMekko / combo | Type-specific option bags | 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. Not { name, data } series objects.
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.
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.
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.
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.
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 | Replace with a 2D array of string/number cells, not { name, data } objects. 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.
Full document workflow
Hand-authoring every axis tick and annotation is painful. Prefer:
- Mount with a minimal or recommended shell
- Polish in edit mode (
editable: true) - Snapshot:
js
await insight.ready;
const full = insight.getChartData();
// or insight.exportConfig() for a JSON downloadExpect 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
baroptions on apie(foreign bag) - Wrong
seriesDatalayout 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
backgroundColoralone to appear in PNG export – passtoPngBase64({ background }) - Expecting
getChartData()to keepchartType: 'donut'/'bubble'– it returnspie/scatterwith the seeded defaults - Deep-merging arrays by hand –
multilinesand similar usually replace
Related
- Data model – layouts overview
- Defaults & merging
- Insight API – mount / update / events
- Visual QA – observe after mount
- Docs index: llms.txt · schema: chart-schema.json