Charts
Tables in your AI client
Bonnard draws a table to show the rows themselves, with a label and a format per column.


The examples use Acme, a demo retailer that reports in pounds. Each variant is a few words in the question. A follow-up changes a table already drawn.
How an agent draws it
The chat above ran three tools, in this order:
explore_schemafinds the tables and their columns: hereorders, one row per order, withorder_date,regionandrevenue.sql_queryruns a read-only query, so the agent can check the numbers first.render_charttakes the SQL and the chart options. It runs the query and returns the table the chat draws.
The render_chart call behind the table above:
JSON
{
"charts": [
{
"sql": "SELECT region, SUM(revenue) / 1000 AS revenue, COUNT(*) AS orders FROM orders WHERE order_date >= DATE '2026-07-01' AND order_date < DATE '2026-10-01' GROUP BY region ORDER BY revenue DESC",
"chartType": "table",
"title": "Revenue and orders by region, Q3 2026",
"fields": [
{ "name": "region", "label": "Region" },
{ "name": "revenue", "label": "Revenue (£k)", "format": "number" },
{ "name": "orders", "label": "Orders", "format": "number" }
]
}
]
}A call takes up to 10 charts, shown as a carousel. Each example below shows its one entry. A connected agent reads the full option reference with explore_docs, topic charts/table.
Currency
Revenue and average order value by region last quarter, in pounds.


JSON
{
"sql": "SELECT region, SUM(revenue) AS revenue, SUM(revenue) / COUNT(*) AS average_order FROM orders WHERE order_date >= DATE '2026-07-01' AND order_date < DATE '2026-10-01' GROUP BY region ORDER BY revenue DESC",
"chartType": "table",
"title": "Revenue and average order by region, Q3 2026",
"fields": [
{ "name": "region", "label": "Region" },
{ "name": "revenue", "label": "Revenue", "format": "currency", "currency": "GBP" },
{ "name": "average_order", "label": "Average order", "format": "currency", "currency": "GBP" }
]
}currency takes an ISO 4217 code, here GBP, and shows its symbol. A whole amount has no decimals, and an amount with pence has two.
Percentages
September revenue by category, with each category's share of the total.


JSON
{
"sql": "SELECT category, SUM(revenue) / 1000 AS revenue, SUM(revenue) / SUM(SUM(revenue)) OVER () AS share FROM order_lines WHERE order_date >= DATE '2026-09-01' AND order_date < DATE '2026-10-01' GROUP BY category ORDER BY revenue DESC",
"chartType": "table",
"title": "Revenue by category, September 2026",
"fields": [
{ "name": "category", "label": "Category" },
{ "name": "revenue", "label": "Revenue (£k)", "format": "number" },
{ "name": "share", "label": "Share", "format": "percent" }
]
}The query returns each share as a fraction, and percent shows 0.2176 as 21.8%. A column of 0 to 100 values reads the same.
Short numbers
Revenue by channel for each quarter this year, shortened to K and M.


JSON
{
"sql": "SELECT channel, SUM(revenue) FILTER (WHERE order_date < DATE '2026-04-01') AS q1, SUM(revenue) FILTER (WHERE order_date >= DATE '2026-04-01' AND order_date < DATE '2026-07-01') AS q2, SUM(revenue) FILTER (WHERE order_date >= DATE '2026-07-01') AS q3 FROM orders WHERE order_date >= DATE '2026-01-01' AND order_date < DATE '2026-10-01' GROUP BY channel ORDER BY q3 DESC",
"chartType": "table",
"title": "Revenue by channel and quarter, 2026 (£)",
"fields": [
{ "name": "channel", "label": "Channel" },
{ "name": "q1", "label": "Q1", "format": "compact" },
{ "name": "q2", "label": "Q2", "format": "compact" },
{ "name": "q3", "label": "Q3", "format": "compact" }
]
}compact writes 3,209,800 as 3.2M and 893,200 as 893.2K. Without it, a table shows every digit.
Every row
Revenue, orders and conversion for every week of last quarter.


JSON
{
"sql": "SELECT week_start AS week, revenue / 1000 AS revenue, orders, conversion FROM weekly_trading WHERE week_start >= DATE '2026-06-29' AND week_start < DATE '2026-09-28' ORDER BY week_start",
"chartType": "table",
"title": "Weekly trading, Q3 2026",
"fields": [
{ "name": "week", "label": "Week" },
{ "name": "revenue", "label": "Revenue (£k)", "format": "number" },
{ "name": "orders", "label": "Orders", "format": "number" },
{ "name": "conversion", "label": "Conversion", "format": "percent" }
]
}The table holds every row the query returns. A long one scrolls inside the chat and pages on a phone. A tap on a header sorts by that column, descending first, and a third tap restores the query’s order.
Options and limits
| Ask for | What Bonnard draws |
|---|---|
| ”as a table” | Every column and row, in the query’s order |
| ”in pounds” or “in euros” | Amounts with the currency’s symbol |
| ”as a percentage” | Values with one decimal, from 0 to 1 fractions or 0 to 100 |
| ”shortened to K and M” | Numbers such as 1.2M and 893.2K |
| ”call the column Net revenue” | A label in place of the column’s SQL name |
| ”the top 10” | The 10 rows the query returns |
| ”sorted by orders” | Rows in that order, which the reader can sort again |
- A table takes
fieldsalone: a label and a format per column.encode, axes, stacking, reference lines and styling are refused, with the options a table takes. - Formats are never inferred from column names. Money and ratios show as plain numbers until a format is set.
- Without a label, a snake_case name shows with spaces and a capital:
average_orderreads “Average order”. - Every row the query returns is shown. A chart query returns up to 5,000 rows by default.
- When the rows run past what one reply can carry, the table keeps the first rows and tells the agent how many.
- Dates show as 29 Jun 2026, and date-times as 29 Jun 26, 14:05.
- There is no pivot table. A column per period comes from the SQL, as the quarters above do.
- On a dashboard, a table is a chart tile with
chartType: "table".