Chart Dialog
The chart dialog edits a typed chart — column, bar, stacked column, stacked bar, line, area, stacked area, pie, donut, scatter, radar, or waterfall — with a data grid plus styling controls, and saves a versioned uhuu.chart.v2 payload.
A uhuu.chart.v2 value separates spec (how the chart looks) from data (the values it draws). Templates render live from those two objects with the same renderer the dialog previews with, so the dialog preview and the printed page are the same picture, and a later brand-color change reaches charts that were saved long ago.
Use chart when the field is a visualization the user should edit as a chart. For raw tabular rows the user pastes/imports, use the Table dialog; for the richer spreadsheet editor, use Spreadsheet.
Parameters
type(enum, required:"chart"): Opens the Chart dialog.path(string, required): Payload path where the dialog response is stored.state(object, optional): The current chart to reopen — auhuu.chart.v2value ({ schema, spec, data }). Pass this so the dialog opens with the existing chart instead of an empty default.value(optional): Current chart value (same shape accepted asstate).config.brand(object, optional): Resolved brand colors, supplied by the host —{ chart: string[], ink, line, muted, surface }, all plain hex.chartis the series palette;ink,line,muted, andsurfacetheme the text, axes, gridlines, and background. The dialog resolves nothing itself: it draws with exactly the colors it is handed, which is what keeps its preview identical to the page. Omitted ⇒ a neutral fallback palette.config.bound(object, optional): Who owns the values —{ isBound: boolean, sourcePath?: string, allowOverride?: boolean }. See Bound and unbound charts. Omitted ⇒ treated as unbound.
The host supplies more than five series colors when a chart needs them; the extra stops are derived from the five brand anchors by the same rule everywhere, so series #6 gets the same color in the dialog and on the page.
Example Usage
export default ({ payload }) => {
const { sales_chart } = payload; // { schema: 'uhuu.chart.v2', spec: {...}, data: {...} }
return (
<div
onClick={() =>
$uhuu.editDialog({
path: 'sales_chart',
type: 'chart',
state: sales_chart,
config: {
brand: {
chart: ['#2f6db4', '#6ea3a1', '#c9a227', '#8a6f9e', '#b46a55'],
ink: '#18181b',
line: '#e4e4e7',
muted: '#71717a',
surface: '#ffffff',
},
bound: { isBound: false },
},
})
}
>
{/* render live from spec + data */}
</div>
);
};Example Response
After saving, the payload at path receives a versioned chart document:
{
"sales_chart": {
"schema": "uhuu.chart.v2",
"spec": {
"version": 2,
"type": "column",
"title": "ARR by quarter",
"subtitle": "Group total",
"options": {
"showGrid": true,
"showXAxis": true,
"showYAxis": true,
"showLegend": true,
"showLabels": true,
"smoothLine": false,
"innerRadiusRatio": 0
},
"format": { "kind": "currency", "currency": "CHF", "precision": 0 },
"axis": { "valueTitle": "ARR", "categoryTitle": "Quarter" }
},
"data": {
"categories": ["Q1", "Q2", "Q3", "Q4"],
"series": [
{ "name": "New business", "values": [120000, 148000, 151000, 190000] },
{ "name": "Renewals", "values": [86000, 91000, 99000, 104000] }
]
}
}
}What the two halves hold:
spec—type,title/subtitle,options,format(one locale-aware number format used by both axis ticks and data labels),axis(titles plus optionalmin/max), and optional color overrides.optionsalways carriesshowGrid,showXAxis,showYAxis,showLegend,showLabels,smoothLine, andinnerRadiusRatio(0 for a pie, ~0.6 for a donut). Type-dependent extras appear when relevant:stackedandhorizontaloverride what the type implies, pluslegendPosition,barPadding,showPoints,pointRadius,areaOpacity, andwaterfallTotals.palette(whole-chart) andseriesColors(keyed by series name, or by category for pie/donut/radar) hold resolved color strings. Omit them and the chart followsconfig.brand, which is what lets a later brand change repaint it; a literal hex freezes that series.
data—categories: string[]plusseries: [{ name, values }], one value per category. A pie or donut is a single-seriesdata. This is the only data shape the dialog reads or writes.
There is no pre-rendered SVG in the response. Render from spec + data.
Bound and unbound charts
A chart's values can come from a bound data source or be typed into the dialog. config.bound tells the dialog which, so the two editors never fight over the same numbers.
| Slot state | Data grid | Presentation controls | What save writes |
|---|---|---|---|
Bound (isBound: true) | Read-only, with a note naming the source: "Values come from finance.quarters — edit the data source" | Live | spec only; the values stay owned by the data source |
Unbound (isBound: false or omitted) | Editable — this is the calibration surface for the chart | Live | spec + data |
Bound with allowOverride: true | Editable | Live | spec + data, written as a document-level override; the source is untouched |
sourcePath is display text for that note — pass the path the user would recognize, not an internal identifier.
Schema-bound fields (Chart / DataTable)
A template can bind a field to a uhuu schema primitive (Chart or DataTable from uhuu-schemas/common) instead of storing the dialog's own document. The schema Chart is meaning-shaped — { type, categories, series: [{ name, data }], valueAxis } — and a schema DataTable is { columns, rows } of strings.
For charts this is now a rename, not a bridge to a third shape: the schema Chart's categories and series[].data map straight onto ChartData's categories and series[].values, and type maps onto spec.type. The editor host still converts, but nothing is reshaped in between.
- On open —
categories/seriesbecome the dialog'sdata,typeseedsspec.type, and the value is supplied as the dialogstate. A schemaDataTableis converted to the Table dialog'sstate.datacell matrix. - On save — the dialog's output is merged onto the original value, never substituted for it, so anything the dialog did not set survives the round trip:
valueAxis.min/maxleft to the template on aChart, acaptionon aDataTable. Only the keys the dialog actually owns are replaced.
So both patterns are valid, and you choose per template:
| Pattern | Payload stores | Render reads | Round-trip |
|---|---|---|---|
| Wire-shaped | uhuu.chart.v2 / { data } matrix | .spec + .data · .data | native — no conversion |
| Schema-bound | Chart / DataTable primitive | .categories/.series · .columns/.rows | host renames fields to/from the dialog |
Notes
- Source data does not have to be
ChartDataalready. A[{ label, value }]array, a[{ name, value }]array, or a record array plus aspec.encodingfield mapping are all normalized tocategories+serieson the way in — malformed input yields an empty chart rather than an error.ChartDatais what gets stored, though; the other shapes are inputs only. spec.formatis the single number format for the chart. Axis ticks and data labels always agree, and both follow the document's locale.- Charts are print output, not dashboards: there are no tooltips, hover states, or drill-downs.