# Chart spec

> Each option the render_chart tool takes, with its values, its limits, and the chart types that accept it.

Updated: 2026-11-09

An agent, the model in an AI client that acts for a person, calls `render_chart` to draw charts from read-only SQL. Bonnard refuses an unknown option, or one that does not fit the chart type, and gives the valid options.

Verified 9 Nov 2026.

Table: Options by chart type

| Option | `line` | `bar` | `area` | `pie` | `scatter` | `funnel` | `waterfall` | `table` |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| [`encode`](#encode) | `x`, `y`, `series`, `y2` | `x`, `y`, `series`, `line`, `y2` | `x`, `y`, `series`, `y2` | `x`, `y` | `x`, `y`, `series`, `size` | `x`, `y` | `x`, `y` | No |
| [`stacking`](#stacking) | Yes | Yes | Yes | No | No | No | No | No |
| [`horizontal`](#horizontal) | No | Yes | No | No | No | No | No | No |
| [`funnelStyle`](#funnelstyle) | No | No | No | No | No | Yes | No | No |
| [`xAxisType`](#xaxistype) | Yes | `categorical`, `time` | Yes | No | No | No | No | No |
| [`axes`](#axes) | `x`, `y`, `y2` (range on `y`, `y2`) | `x`, `y`, `y2` (range on `y`, `y2`) | `x`, `y`, `y2` (range on `y`, `y2`) | No | `x`, `y` (range on `x`, `y`) | No | `x`, `y` (range on `y`) | No |
| [`fields`](#fields) | `format`, `label`, `additive` | `format`, `label`, `additive` | `format`, `label`, `additive` | `format` | `format`, `label` | `format` | `format`, `label` | `format`, `label` |
| [`fillGaps`](#fillgaps) | Yes | Yes | Yes | No | No | No | No | No |
| [`series`](#series) | `color`, `line`, `marker` | `color`, `line`, `marker` | `color`, `line`, `marker` | `color` | `color`, `marker` | No | No | No |
| [`markers`](#markers) | Yes | Yes | Yes | No | No | No | No | No |
| [`highlight`](#highlight) | Yes | Yes | Yes | Yes | Yes | No | No | No |
| [`legend`](#legend) | Yes | Yes | Yes | Yes | Yes | No | No | No |
| [`reference`](#reference) | Yes, with `y2` | Yes, with `y2` | Yes, with `y2` | No | Yes, with `x` | No | No | No |
| [`overlay`](#overlay) | Yes | Yes | Yes | No | No | No | No | No |

Bonnard also accepts `fields[].label` and `fields[].additive` on all chart types, and they have no effect where the table omits them. It accepts a list in `encode.y` on all types too, and `pie`, `scatter`, `funnel`, and `waterfall` draw its first column.

## `charts`

An array of 1 to 10 entries, needed on each call. An entry is a saved chart, `{ "chart_id": "<id>" }`, or a chart drawn from a query, with `sql`. The two kinds can mix. One entry shows one chart. Two or more show a carousel in the order given.

```json
{"charts":[{"sql":"SELECT region, COUNT(*) AS orders FROM orders GROUP BY region ORDER BY orders DESC","chartType":"bar","title":"Orders by region","encode":{"x":"region","y":"orders"}}]}
```

## `preview_rows`

An integer from 0 to 150, default 10, set beside `charts`. It sets how many rows of each chart the reply shows the agent. The person gets the whole chart.

## `sql`

A string, needed for a chart drawn from a query. It holds one read-only `SELECT`. Charts take no params, so write literal values.

## `chartType`

One of `line`, `bar`, `area`, `pie`, `scatter`, `funnel`, `waterfall`, or `table`. Omit it and the chart selects a type from the shape of the result. A `waterfall` needs ordered steps with signed changes, and totals on the start and end rows.

## `title`

A string, the chart's display name.

## `encode`

An object that maps result columns to the chart. Set `x` when more than one column can be the x-axis, so the chart does not plot the wrong one.

Table: The keys of encode

| Key | Value | Effect |
| --- | --- | --- |
| `x` | One column | The x-axis. On `scatter`, the numeric x measure. On `funnel` and `waterfall`, the stage or step label |
| `y` | One column, or a list on `line`, `bar`, and `area` | The measures on the left axis. A list draws one series for each column |
| `series` | One column | One series for each distinct value. It needs exactly one `y` measure |
| `line` | One column, or a list | Measures drawn as a line over the bars, on the same axis |
| `y2` | One column, or a list | Measures on a second axis at the right, drawn as a line |
| `size` | One column | A third numeric column that sets the point size |

## `stacking`

`stacked`, `stacked100` for a 100% stack, or `grouped`. The default is `grouped`: bars sit side by side, and lines and areas do not stack. Stacking needs more than one series.

## `horizontal`

A boolean. `true` runs the bars horizontally, with categories on the y-axis. Bars are vertical by default.

## `funnelStyle`

`bars` or `dropoff`, default `bars`. `dropoff` draws one column for each stage, with the part lost since the previous stage above it. A `dropoff` chart too narrow for its columns is drawn as `bars`.

## `xAxisType`

Omit it and the chart infers the scale. On `bar`, the default is `categorical`.

Table: xAxisType values

| Value | Use it for |
| --- | --- |
| `categorical` | A numeric column that is a grouping (a year, a rating, a bucket), drawn as evenly spaced labels |
| `continuous` | A measured quantity whose gaps carry meaning |
| `time` | Dates on a chronological timeline, so a two-week gap is twice as wide as a one-week gap |

## `axes`

An object with `x`, `y`, and `y2`. Each takes `title`, up to 60 characters, and `min` and `max`, each a number or `"auto"`. `min` must be less than `max`. `x` takes a range on `scatter` only.

## `fields`

An array of up to 100 entries, one for each result column. Bonnard never infers a format from a column name, so declare each format.

Table: The keys of fields

| Key | Value | Effect |
| --- | --- | --- |
| `name` | Needed, up to 256 characters | The column's result name. A name can be in the array one time |
| `label` | Up to 60 characters | Replaces the column's display name |
| `format` | `number`, `percent`, `currency`, or `compact` | How values are written |
| `currency` | An ISO 4217 code, three capital letters | The currency, with `format: "currency"` only |
| `additive` | Boolean | `true` shows missing time buckets as 0, and `false` as gaps. Unset leaves [`fillGaps`](#fillgaps) at `none` |

## `fillGaps`

How the chart draws periods that have no row. Returned rows never change. A measure's `additive` sets the default: `true` is `zero`, `false` is `gap`, and unset is `none`.

Table: fillGaps values

| Value | Effect |
| --- | --- |
| `none` | Draws only the returned rows, and a line joins across missing periods |
| `zero` | Draws the period as 0, for counts and sums |
| `previous` | Keeps the last known value, for balances and stock levels |
| `gap` | Breaks the line, for rates, ratios, and averages |

## `series`

A map keyed by series name, for up to 24 series. A series name is its legend label, or else its column name. On `pie`, and on `bar` with one series, a key can name a category value.

Table: The keys of series

| Key | Value |
| --- | --- |
| `color` | `blue`, `sky`, `green`, `amber`, `red`, `violet`, `pink`, `teal`, or `muted` |
| `line` | `solid`, `dashed`, or `dotted` |
| `width` | 1 to 6, where `line` applies |
| `marker` | `circle`, `emptyCircle`, `rect`, `roundRect`, `triangle`, `diamond`, or `none` |

## `markers`

`auto`, `all`, or `none`. The default is `auto`, which marks only a point with no neighbour.

## `highlight`

One series name, or up to 3 in an array, each up to 60 characters. The chart draws them in colour and the rest in grey. On `pie`, and on `bar` with one series, a name is a category value.

## `legend`

`auto`, `top`, `bottom`, or `none`. The default is `auto`, which draws a legend at the bottom when there are two series. `top` and `bottom` draw one even for a single series.

## `reference`

Lines and bands across the value axis. It takes `{"target": 40, "average": true}`, or an array of up to 6 entries. An entry takes exactly one of `y`, `x`, or `band`.

Table: The keys of reference

| Key | Value | Effect |
| --- | --- | --- |
| `y` | A number, `"average"`, or `"median"` | A line across the value axis |
| `x` | A number, `"average"`, or `"median"` | A vertical line, on `scatter` only |
| `band` | `[low, high]`, with low below high | A shaded range |
| `label` | Up to 60 characters | Drawn as written |
| `axis` | `"y"` or `"y2"` | `"y2"` puts a `y` line or a `band` on the right axis |

## `overlay`

A computed dashed line over each left-axis series. A 100% stack takes no overlay.

Table: overlay forms

| Form | Effect |
| --- | --- |
| `{"kind":"movingAverage","window":4}` | The trailing average of the last `window` points, 2 to 52 |
| `{"kind":"trend"}` | The least-squares line |
| `"series":["<name>"]` | Up to 12 series that get the line. Omit it for all left-axis series |

## KPI tile

A KPI is a `kind: "kpi"` tile in `save_dashboard`, and `render_chart` has no KPI type. Bonnard reads only the first row of its query. A KPI counts toward the 16 chart and KPI tiles on a dashboard.

Table: KPI tile fields

| Field | Value | Effect |
| --- | --- | --- |
| `kind` | Needed, `"kpi"` | The tile type |
| `id` | Needed, up to 64 characters | Lowercase letters, digits, `_`, and `-`, starting with a letter |
| `sql` | Needed, up to 8,000 characters | A read-only `SELECT` |
| `title` | Needed, up to 200 characters | The display name. It can hold a declared `{{param}}` |
| `valueColumn` | Up to 256 characters | The column with the value. Without it, the first numeric column |
| `deltaColumn` | Up to 256 characters | The change to show beside the value: this period minus the previous one, as a difference, written in the KPI's `format`. Up is always green and down always red. A change of 0 shows no arrow |
| `format` | `number`, `currency`, `percent`, or `compact` | How the value is written |
| `currency` | Three capital letters | The currency, with `format: "currency"` only |
| `caption` | Up to 2,000 characters | A short line under the value. It can hold a declared `{{param}}` |
| `span` | 1 to 4, default 1 | The width on the 4-column grid |

## What charts do not draw

The `charts` guide page names these items. Each row quotes what the agent reads.

Table: What charts do not draw

| Item | Guide page text |
| --- | --- |
| Sankey diagrams | "There is no sankey chart type." |
| Maps | "There is no map or geographic chart type." |
| Gauges | "There is no gauge chart type." |
| Treemaps | "There is no treemap chart type." |
| Heatmaps | "There is no heatmap chart type." |
| Box plots | "There is no box plot chart type." |
| Pivot tables | "There is no pivot table type; encode.series pivots tall rows into one series per value." |
| Bullet and progress charts | "There is no bullet or progress chart type." |
| Radar charts | "There is no radar chart type." |
| Candlestick charts | "There is no candlestick chart type." |
| Data labels | "Values are not printed on bars or points; pie, funnel and waterfall label themselves." |
| Hex and brand colours | "Colours are named slots from the house palette; hex and brand colours cannot be set." |
| Log scale | "A log scale cannot be set; value axes are linear." |
| Smoothed lines | "Lines are drawn straight between points." |
| Zooming | "Charts cannot be zoomed or drilled into; draw a narrower query instead." |
