charts · git:20260906.5482ae7 · 2026-09-06 · sha256 2a14262251efba3f
charts git:20260906.5482ae7A
Immutable. This exact content never changes and is served at /api/v1/blob/2a14262251efba3f.
---
name: charts
description: Draw and save charts, dashboards, and written reports from warehouse queries. Use whenever an answer involves a trend, a comparison, a distribution, or a headline number worth showing, when the user asks to chart, plot, graph, visualise, or build a dashboard, and when a visual needs custom Python because create_chart cannot express it.
---
# Charts
`create_chart` runs your SQL, draws the result, and saves it to the
conversation. Charts persist: reopening the thread later still shows them.
## Chart or not
Draw when the shape is the answer: a trend over time, a comparison across
categories, a distribution, a two-dimensional pattern.
Do not draw when a sentence is the answer. One number is a sentence, or a `kpi`
if it deserves emphasis. Three rows are a `table`. A chart of three bars is
noise.
## Choosing a kind
| Question shape | Kind | Mapping |
| --- | --- | --- |
| How did X move over time? | `line` | `x` = date, `y` = measure |
| Same, several measures or groups | `line` | plus `series`, or a list of `y` |
| Same, composition over time | `area` with `stacked=True` | `x`, `y`, `series` |
| How do categories compare? | `bar` | `x` = category, `y` = measure |
| Composition within each category | `bar` with `stacked=True` | plus `series` |
| Share of a whole, few slices | `pie` | `x` = labels, `y` = values |
| How is a value distributed? | `histogram` | `x` = the column to bin |
| Two dimensions against one value | `heatmap` | `x` = columns, `series` = rows, `y` = value |
| Exact figures matter | `table` | no mapping |
| One headline number | `kpi` | `y` = value, `x` = optional label |
Use `pie` only up to about six slices; beyond that a bar chart reads better.
## Getting it right
- Aggregate in SQL. The tool refuses more than a few thousand rows, and a chart
of raw rows is unreadable anyway.
- Order the query the way the chart should read: by date for a trend, by value
descending for a ranked bar chart.
- Give every chart a title that states the finding, not the mechanism: "LLM cost
per day by model", not "Query results".
- Put the caveat in `description`. Cost charts should say the figures are list
price estimates; anything over message dates should say the timestamps are
capture times.
- Set `y_format`: `currency` for dollars, `percent` for rates (pass the fraction,
not 0-100), `duration_s` for seconds.
- Reuse a `slug` to replace a chart you already made rather than accumulating
near-duplicates.
## After you draw one
`create_chart` puts the chart in the conversation itself — the user is already
looking at it, with its title, caveat and row count. So in your reply, write the
finding the chart shows; do not re-embed it with `` and do not
describe the chart as an artefact ("the chart has been generated and saved").
Embedding belongs in saved reports, below, where there is no card.
## Dashboards
A dashboard is several charts plus the narrative that ties them together:
1. Draw each chart with `create_chart`, giving each a stable `slug`.
2. Write `/reports/<thread id>/<slug>.md` with your findings, embedding each
chart where it belongs:
```markdown
## Reliability
Failures held at 3% of runs this week, all on Tuesday's deploy.

```
3. Tell the user the report path.
Lead each section with what the chart shows. A dashboard of unexplained charts
is a worse answer than three sentences.
## When create_chart cannot draw it
For a visual outside the supported kinds — a box plot, a funnel, dual axes,
annotations — build the figure yourself and publish it:
```
run_sql("SELECT ...", save_as="rows.csv") -> returns an absolute path
```
Then write a script and run it with `execute` (this pauses for approval):
```python
import pandas as pd, plotly.express as px
df = pd.read_csv("/tmp/nextrole-analytics/<thread id>/rows.csv")
fig = px.box(df, x="model", y="duration_s")
fig.write_json("/tmp/nextrole-analytics/<thread id>/duration-by-model.plotly.json")
```
Then publish it, and it renders like any other chart:
```
create_chart(kind="scatter", title="Run duration by model",
figure_path="/tmp/nextrole-analytics/<thread id>/duration-by-model.plotly.json")
```
Scripts have no database credentials by design — data reaches them only through
`save_as`. pandas and plotly are installed; do not install anything.
Do not set colors or a template in a custom figure. The interface themes every
chart for light and dark at render time, and hard-coded colors survive into the
wrong theme.