Connecting a Database
Feed charts and views from live warehouse data with typed ChartData, covering why numbers must be numbers and how to catch a bad encoding before a host renders it.
The visualize tool takes SQL from the agent. But when you author
dashboards and views over live, unseen data, the
encoding is only as good as the types the data arrives with. This page shows how to feed a chart from a real database so it renders correctly the first time.
chart(rows) vs chart(ChartData)
chart (and chartCell) take either raw rows or a typed ChartData:
import { chart, buildChartData } from "@bonnard/mcp-charts";
// Raw rows: types are sniffed from a sample of the values.
const spec = chart(rows, { chartType: "line" });
// Typed ChartData: column types come from your driver, not a sniff.
const data = buildChartData({ rows, columns, mapKind }); // or an adapter's runSql(...)
const specTyped = chart(data, { chartType: "line" });Both call the same function. Pass raw Record<string, unknown>[] to let inference sniff, or pass a
{ rows, fields?, encode?, notes? } ChartData to trust declared types. When you build views
from live data, prefer the typed source: an adapter (or buildChartData)
hands the resolver column types from your driver, so the encoding is decided from types, not guessed
from a sample.
// In a view's render, ride the adapter's types:
render: async () => chart(await runSql("select month, revenue from ..."), { chartType: "line" }),Numbers must be numbers
This is the single most common way a live chart goes wrong. Some drivers stringify decimals and
bigints. Postgres NUMERIC / BIGINT, for example, arrive as revenue: "1234", not 1234.
When inference sees a column that's entirely numeric strings, it recovers it to a measure and adds an advisory note. But the robust fix is to not rely on recovery:
- Use an adapter (Warehouse Adapters). It maps the driver's column types
for you, so a
NUMERICcolumn is a measure regardless of how the driver serialises it. - Declare the column's
kindviafields(orbuildChartData). - Cast in SQL:
revenue::float8,count(*)::int.
fields and encode are trusted verbatim. A wrong declaration is honoured, not corrected: mark a measure as a
string dimension and you get a blank series (with a note). Declare types that match the data.
Typed fields
const spec = chart(rows, {
chartType: "bar",
fields: [
{ name: "region", kind: "string", role: "dimension" },
{ name: "revenue", kind: "number", role: "measure", format: "currency", currency: "USD" },
],
});A FieldMeta carries name, and optionally role (measure / dimension / time), kind
(number / string / time / boolean), format (number / currency / percent),
granularity (day…year), currency, and label.
Catch a bad encoding before it renders: explain()
Most data mistakes are invisible to tsc. Rows are Record<string, unknown>[], so the type system
can't see that sales is a numeric string or that a column is all-null. explain() diagnoses how
rows (or a typed ChartData) would be charted, without building the render payload: the inferred
field typing, the resolved chartType / x / series, and any notes.
Assert the encoding in a unit test or CI, so a broken chart fails your build instead of a host:
import { explain } from "@bonnard/mcp-charts";
const e = explain(sampleRows, { chartType: "bar" });
expect(e.series.length).toBeGreaterThan(0); // caught a blank chartstrict mode
Pass strict: true to promote encoding-failure advisories (zero series, an ignored encode column)
from notes to thrown errors, for loud failure in authoring and CI:
// Throws on a zero-series result or an ignored encode column, instead of returning a blank chart.
explain(sampleRows, { chartType: "bar", strict: true });Leave strict off in production (the default): there, a degenerate result renders with an advisory
note rather than crashing the tool call. strict is available on chart, chartCell, and explain
via ResolveOptions.
Zero-series notes
When a plotting chart (bar / line / area / pie / scatter) resolves to no measure series, the spec carries a prominent note rather than rendering a silently blank chart:
No measure column to plot — the chart has no data series. Check that value columns contain numbers (not strings), or declare types via
fields.
That single guard covers the whole "blank chart" family: numeric-string measures that weren't
recovered, an all-null column, a wrong fields declaration, or an encode.y naming a column that
isn't in the result.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Blank chart, "No measure column to plot" note | value column arrived as strings, or a fields/encode declaration left no measure | return numbers (not numeric strings), or declare the column kind: "number" |
| Bar/line over "dates" isn't sorted | dates are strings in a loose format (01/15/2026, Jan 2025) | return ISO dates (YYYY-MM-DD) so the axis sorts chronologically |
| Multi-slice pie / odd funnel, with a precondition note | forced a chart type whose shape needs a category + a measure | supply one dimension + one measure, or drop the forced chartType |
| "Ignored unknown encode column" note | encode.x / encode.y names a column not in the result | fix the column name (the note lists the available columns) |
A render_view param is rejected | the caller passed a param the view didn't declare, or the wrong type | declare it in the view's params, or fix the value. See Named views |
unknown item_id from render_view | the item_id names no chart cell in the view | pass an id set via chartCell(rows, { id }); the error lists the selectable ids |
Next
- Warehouse Adapters: the bundled adapters that supply driver types for free.
- API Reference:
buildChartDataand the adapter-authoring kit.