---
title: Charts
description: Charts written as a plain-English sentence or a few lines of JavaScript, and Plotly for everything else.
section: Frontend
order: 4
---

# Charts

<p class="lead">A chart is a sentence like <code>mean value by subject sorted</code>, run over a collection's rows as the viewer. For anything a sentence can't express, write a few lines of JavaScript, or return a Plotly figure.</p>

## Chart blocks

A chart block reads a collection's rows (as the viewer) and shapes them in one of two ways:

- **A sentence** like `mean value by subject sorted`. It's parsed, never executed, so it's safe and needs no worker. The language is described below.
- **A few lines of JavaScript** for anything a sentence can't express: `return count(rows, "status")`. It runs in an isolated worker with only the rows and some helpers (no page, no session) and is killed after two seconds.

The block detects which one it is: text starting with a measure and without code punctuation is a sentence.

```js title="Chart block, as JavaScript"
return groupBy(rows, "class").map(([label, list]) => ({ label, value: mean(list, "value") }));
```

Sentence examples:

```text title="Chart block"
count grades by subject
mean value from grades by subject sorted
percent by status

# stacked, one series per status
count by month of date and by status
count by subject top 5                            # the rest as "Other"
mean value by week of date rolling 4
sum (first_half + second_half) / 2 by month of date
values value against date where subject = Mathematics   # a scatter

# as of a past date
mean value by subject as of 2026-06-30
```

| Part | Forms |
|---|---|
| Measures | `count percent sum mean median min max distinct values` |
| Groupings | `by field`, `by month\|week\|weekday\|year of date`, `by n every 5`; a second grouping after "and" makes series: `by class and by subject` |
| Conditions | `where a = x and b >= 3 and roles has pupil` |
| Endings | `top n`, `sorted` (smallest first) or `sorted desc`, `cumulative`, `rolling n`, `as of 2026-06-30` |

Relations are shown by name. Time axes span first to last month with data, keeping empty months and bins: counts show 0, while an average over nothing is a gap in the line rather than a drop to 0. The language has its own unit tests, which run in Node without a build.

## Live tables, shared charts

Editors can make a table **Live** from its toolbar. Live tables read rows through [sync](/docs/sync), so everyone's changes appear in real time: what you want for a kanban board, trip planning or a sign-up sheet with several people editing. Small live tables (up to 2,000 rows) are also cached per user in IndexedDB, so reloads are instant; signing out clears the cache. Non-live tables load once when the page opens.

Charts on a page share data: each collection (or slice) is fetched once per page. Chart settings:

- **Only rows where** filters the first source with the filter language (`class = "class4a0000000"`), so only that slice is fetched.
- **Live** re-renders when the rows change anywhere, via sync. Off by default, since a chart that stays still is easier to read.

## Try it

Type or pick a sentence to chart a year of a small shop's orders (category, country, shipper, date, total, shipped), Northwind-style:

<sluurp-island src="chart-playground"></sluurp-island>

## Charts with Plotly

For charts beyond the sentence language (heatmaps, sunbursts, box plots, maps, 3D), a JavaScript chart block can return a Plotly figure, `{ plotly: { data, layout } }`, styled with the page's theme and palette. Plotly ships in the binary and is only loaded when such a chart scrolls into view. Any page or island can use it via `plot()` from `sluurp/plotly`; these three islands use the same orders data:

<sluurp-island src="plotly-chart" props='{"figure":"heatmap"}' client="visible"></sluurp-island>

<sluurp-island src="plotly-chart" props='{"figure":"sunburst"}' client="visible"></sluurp-island>

<sluurp-island src="plotly-chart" props='{"figure":"box"}' client="visible"></sluurp-island>
