> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getnao.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Visualizations

> Chart types, chart editing, table formatting, and display settings

When the agent answers a question with data, it can render the result set as a chart or as a table. Both are produced by the `display_chart` tool from a previous `execute_sql` result, and both can be adjusted afterwards without re-running the query.

## Chart types

The agent picks a chart type from the result set, and you can change it later from the edit dialog.

| Type         | Use it for                                                             |
| ------------ | ---------------------------------------------------------------------- |
| Bar          | Comparing values across categories                                     |
| Stacked bar  | Composition of a total across categories                               |
| Line         | Trends over time                                                       |
| Area         | Time-series trends with filled emphasis                                |
| Stacked area | How multiple series compose a total over time                          |
| Pie          | How one measure splits across categories                               |
| Donut        | Same as pie, with a hollow centre                                      |
| KPI card     | A single headline number                                               |
| Scatter      | Correlation between two numeric variables                              |
| Radar        | Several metrics compared across a fixed set of categories              |
| Mixed        | Bars, lines, and areas combined in one chart, optionally on two Y-axes |

Stacked bar and stacked area charts also have a normalized variant that plots each series as a share of its category total on a 0-100% axis. See [Normalize to 100 percent](#normalize-to-100-percent) below.

For large numeric values, nao shortens Y-axis labels to values such as `100K`, `1.5M`, or `2B` to keep charts readable.

### Pie and donut charts

Pie and donut charts take exactly one series. Categories are shown in a legend rather than as labels drawn on the slices, and slices are separated by a small gap so adjacent categories stay distinguishable.

When a result set has more than 10 categories, nao keeps the 10 largest slices by value and sums the remainder into a single **Other** slice. If the data already contains a category called "Other", the remainder is merged into it rather than creating a duplicate.

## Editing a chart

Click the **pencil icon** next to a chart's download button to open the edit dialog. Changes apply to the existing chart config, so the query is not re-run.

The dialog lets you set:

* **Title**
* **Chart type**
* **X-axis type**: `Auto`, `Category`, `Date`, or `Number`
* **X-axis column**
* **X-axis label**: an optional custom label drawn under the axis
* **Series**: for each series, the data column, an optional label, a color, a [value format](#value-formatting) (number format, unit, and placement), and - on [mixed charts](#combo-charts-and-a-second-y-axis) - a series type and Y-axis side. Use **Add series** to plot another column, or the bin icon to remove one (at least one series is required).
* **Y-axis range**
* **Show data labels**

Click **Save** to persist the changes. Any cached chart image used for Slack, Teams, and WhatsApp previews is regenerated on the next request.

Chart editing is available on charts in a chat thread and on chart blocks inside a story. It is restricted to the chat or story owner, and hidden while the agent is running, on archived stories, in shared or read-only views, and when viewing older story versions.

### Normalize to 100 percent

When the selected chart type is a stacked bar or stacked area chart, the dialog shows a **Normalize to 100%** toggle. Turning it on switches the chart to its 100% stacked variant: each series is drawn as a share of the category total and the axis runs from 0 to 100%. Tooltips show percentages instead of absolute values.

Use it when the composition matters more than the absolute totals. Turning the toggle off returns the chart to absolute stacking.

### Y-axis range

By default the Y axis adapts to the values in the result set. Line and scatter charts auto-scale to a readable range rather than forcing a zero baseline.

To pin the scale, enter a **Min**, a **Max**, or both in the **Y-axis range** fields. Leaving a field on its `Auto` placeholder keeps that bound automatic. The minimum must be lower than the maximum, otherwise the dialog rejects the change.

The Y-axis range fields are not shown for pie, KPI card, and radar charts, which have no Y axis.

### Data labels

The **Show data labels** toggle prints the numeric value of each data point directly on the chart, instead of requiring a hover. You can also ask the agent for it in plain English (for example: *"show the values on the chart"*).

On line and area charts, labels are limited to the most significant points so a dense series stays legible.

### Combo charts and a second Y-axis

Pick the **Mixed** chart type to draw several series with different shapes in one chart - for example bars for revenue and a line for a conversion rate. Each series then gets two extra controls in its row of the edit dialog:

* **Series type** - draw that series as a `bar`, `line`, or `area`, independent of the others.
* **Y-axis side** - plot the series against the `left` or `right` axis. A right axis appears as soon as any series uses it, which is what lets you compare metrics on very different scales or units in a single chart.

The right axis has its own **range** and **label** fields, mirroring the left axis. You can also just ask the agent: *"plot revenue as bars and the conversion rate as a line on a second axis."*

### KPI comparison pills

A **KPI card** can show a small change pill under its number, comparing the latest value to the previous period. Turn it on with the **Comparison pill** control in the edit dialog (it appears only for KPI cards), or ask the agent for it. There are three styles:

* **Percentage** - the signed percentage change, colored green or red with an arrow.
* **Variation** - the signed absolute change, colored green or red with an arrow.
* **Absolute** - just the magnitude of the change, with no color or arrow.

The pill names the period it compares against (*"vs. last month / last week / last quarter / last year"*), inferred from the data's dates, and falls back to *"vs. previous period"* when the spacing is unclear. Comparison pills need the query to return **two or more time-ordered rows** (oldest to newest). Set the style to **none** to hide the pill.

### Hiding the tooltip total

When a chart stacks several series, the tooltip shows a **Total** row by default. That total is meaningless when the series can't be meaningfully added up - unrelated metrics, or different units or currencies. The agent can set a chart-wide `hide_total` option to drop the Total row in those cases; additive charts still show it, and percentage charts still show 100%.

## Value formatting

By default chart values render as plain numbers. Value formatting lets you show a currency symbol, a `%`, or any unit (V, kg, GB, …) and control how numbers are rounded and abbreviated. Formatting is set **per series**, so a chart can mix, for example, a dollar series and a percentage series.

A format applies everywhere the series' value appears: the value axis, tooltips, data labels, and KPI cards. It also carries through into [stories](/nao-agent/chat/capabilities/stories), including their HTML and PDF exports. The value axis automatically widens to fit longer labels (such as `$1,000,000`) so they don't clip.

A value format has three parts:

* A **number format** - a [d3-format](https://d3js.org/d3-format) specifier that controls rounding, thousands separators, and abbreviation of the raw number.
* A **unit** - free text such as a currency symbol (`$`, `€`, `¥`, `£`), a `%`, or a unit like `V`, `kg`, `GB`.
* A **placement** - whether the unit sits *before* the number (**Prefix**, for currencies) or *after* it (**Suffix**, for `%` and most units).

### Setting it in the editor

In the [chart edit dialog](#editing-a-chart), each series row has a **Number format**, a **Unit**, and a **Placement** (Prefix or Suffix). Any part can be left blank.

### Asking the agent

You usually don't need to open the editor - ask in plain English and the agent sets the format for you:

> *"Format revenue as US dollars."*
> *"Show the conversion rate as a percentage with one decimal."*
> *"Label the storage column in GB."*

## d3-format number cheat sheet

The **number format** field takes a raw [d3-format](https://d3js.org/d3-format) specifier and applies it to the value **as-is** (it does not multiply or divide). Copy-paste one of these:

| Format  | `1234.567` renders as | Use for                                 |
| ------- | --------------------- | --------------------------------------- |
| `,.0f`  | `1,235`               | Whole numbers with thousands separators |
| `,.1f`  | `1,234.6`             | 1 decimal place                         |
| `,.2f`  | `1,234.57`            | 2 decimals (money)                      |
| `.0f`   | `1235`                | Whole number, no separator              |
| `.2f`   | `1234.57`             | 2 decimals, no separator                |
| `,`     | `1,234.567`           | Separators, keep all decimals           |
| `.2s`   | `1.2K`                | Abbreviate to 2 significant digits      |
| `.3s`   | `1.23K`               | Abbreviate to 3 significant digits      |
| `+,.0f` | `+1,235`              | Always show the sign                    |

The pattern is `[,][.precision][type]`: a leading `,` adds thousands separators, `.N` sets precision, and the trailing letter is the type - `f` for fixed decimals or `s` for SI-prefix abbreviation. See the [full d3-format docs](https://d3js.org/d3-format) for more.

### Copy-paste recipes (format + unit)

| Goal                               | Number format | Unit | Placement |
| ---------------------------------- | ------------- | ---- | --------- |
| US dollars                         | `,.2f`        | `$`  | Prefix    |
| US dollars, no cents               | `,.0f`        | `$`  | Prefix    |
| Euros, no decimals                 | `,.0f`        | `€`  | Prefix    |
| Abbreviated dollars (`$1.2M`)      | `.2s`         | `$`  | Prefix    |
| Percentage (data stored as `42.5`) | `.1f`         | `%`  | Suffix    |
| Volts                              | `,.0f`        | ` V` | Suffix    |
| Bytes, abbreviated (`1.2M`)        | `.2s`         | `B`  | Suffix    |

### Percentages

Do **not** use d3's `%` format type - it multiplies the value by 100. If your data is already stored as a percentage (for example `42.5` meaning 42.5%), use a plain number format (`.1f`) with a `%` unit set to **Suffix** placement. That renders `42.5` as `42.5%`.

### Abbreviating large numbers

The `.2s` (SI-prefix) number format abbreviates large numbers. How the abbreviation letters display depends on the series' compact mode, which the agent picks based on the kind of value:

* **Financial** (the default) is meant for money and counts: `k` becomes `K` and `G` becomes `B`, while `M` and `T` are unchanged - so you get `1.2K`, `3.4M`, `5.6B`.
* **SI** keeps d3's scientific letters unchanged, which is what you want for scientific units such as bytes (`1.2k`, `3.4M`, `5.6G`).

<Note>
  The editor accepts number formats that render identically in the interactive chart and in static story exports - the `f` and `s` types, a bare `,`, and a precision (for example `,.2f`, `.2f`, `,`, and `.2s`). If you enter a format that would export differently, the dialog shows a warning and blocks saving until you adjust it. The agent is not limited to these when it sets a format directly.
</Note>

## Viewing the SQL and data behind a chart

Every chart is backed by a SQL query and its result set. Click the **View SQL query and data** action (the `<>` icon) on the chart toolbar to open a side panel with the `execute_sql` query on top and the result rows below, so you can verify exactly how a chart was built.

## Tables

Ask for a table instead of a chart and the agent renders the result set with `display_chart` using `chart_type: "table"`.

Tables include:

* **Right-aligned numeric columns** with tabular figures, so digits line up.
* **Sortable columns** - click a column header to sort by it. Each click cycles through ascending, descending, and back to the original order (the chevron shows the current direction). Column widths stay stable across sorting and paging. This works on tables in chat, in stories, and on `execute_sql` results.

### Exporting table data

Any table - in a chat thread, in a story, or from an `execute_sql` result - can be downloaded from its toolbar. The export menu offers two formats:

* **CSV** - a plain comma-separated file.
* **Excel** (`.xlsx`) - a native spreadsheet file.

Both export the full result set, not just the rows currently on screen.

### Conditional formatting

Numeric, boolean, and text columns can be colored by rule. Open the **Edit table formatting** dialog from the table toolbar and pick a rule per column - columns default to **None**.

Available rules depend on the column type:

<CardGroup cols={3}>
  <Card title="Numeric" icon="hashtag">
    **Color scale** shades each cell on a gradient between the column minimum and maximum. **Threshold** colors cells matching a comparison (`≥`, `>`, `≤`, `<`, `=`) against a value.
  </Card>

  <Card title="Boolean" icon="toggle-on">
    **Boolean** sets a background color for true cells, false cells, or both. Leave a color unset to skip that case.
  </Card>

  <Card title="Text" icon="font">
    **String** colors cells that match an operator: `equals` for an exact match, `in list` for one of several values, or `contains` for a case-insensitive substring.
  </Card>
</CardGroup>

For color scales, nao derives the gradient from a single color by default, running from a light tint at the low end to the full color at the high end. The bounds default to the column minimum and maximum.

**Asking the agent to format a table**

You do not have to open the dialog. Ask for the formatting in plain English and the agent applies the rules itself:

> *"Show revenue by region as a table and highlight anything under 10k in red."*
> *"Put a color scale on the conversion rate column."*

The agent is instructed to use real conditional formatting for these requests rather than faking it with emoji or by adding an extra status column to the data.

## Date format

Dates rendered by nao follow a project-level setting rather than each viewer's locale, so everyone on the project reads the same format.

Set it in **Settings -> Project** on the **Date format** card. This is an admin setting.

| Preset   | Pattern          |
| -------- | ---------------- |
| European | `DD/MM/YYYY`     |
| American | `MM/DD/YYYY`     |
| ISO 8601 | `YYYY-MM-DD`     |
| Custom   | Your own pattern |

European is the default. Choosing **Custom** lets you compose a pattern from the supported tokens: `YYYY`, `YY`, `MMMM`, `MMM`, `MM`, `M`, `DD`, `D`, `dddd`, and `ddd`. The card previews the result on a sample date as you type.

The setting applies to chart axes, tooltips, legends, and KPI cards, to SQL result tables, and to the charts and tables inside stories and their PDF and HTML exports.

## Maps

When a query returns geographic points (latitude/longitude), the agent can plot them on an interactive map instead of a chart, using the `display_map` tool. The map renders point data over an OpenStreetMap-based basemap, and you can pan and zoom it like any web map. Ask in plain English: *"map our customers by city."*

<Note>
  Maps are **experimental** and off by default. An admin enables them under **Settings -> Agent -> Experimental**. Until then the agent won't offer the map tool.
</Note>

## Custom charts

Beyond the built-in chart types, a project can ship its own **custom charts** - browser-rendered visualizations authored as small JavaScript modules in `agent/charts/`. Use them when a question needs a visual that nao's built-in types don't cover.

Each custom chart is a file `agent/charts/<type>.js` that exports a `render` function:

```js theme={null}
export function render(element, context) {
  const { data, config, colors, theme, libs } = context;
  // draw into `element` using the query's `data`
  return () => element.replaceChildren(); // optional cleanup
}
```

* `<type>` must start with a lowercase letter and must not collide with a built-in type (`bar`, `line`, `pie`, `table`, …); names that break the rules are ignored.
* `context` carries the `data` rows from the referenced `execute_sql` result, the chart `config`, the nao `colors` and `theme`, and a set of charting `libs`.
* Charts render in the browser only. Stories, exports, automations, MCP embeds, and messaging previews keep using the built-in chart types.

Active charts hot-reload as you edit the file, so you can iterate without restarting. See the `build-custom-charts` skill in your project for the full authoring reference.
