Skip to content
Docs

Charts from SQL Aggregate Queries

An aggregate chart, saved and placed on a dashboard — Table and dimension pick the GROUP BY, the chart type changes only the drawing, and the saved chart reopens with its configuration intact.

The chart panel (recharts) builds charts from SQL aggregate queries: bar, line, pie, area, and sankey charts bound to the server aggregate surface. A chart’s configuration saves as a saved object of type: query.

A chart configuration is JSON: { source, dimensions, measures: [{ column, op }], chartType }. The panel translates it into a grouped aggregate GraphQL query against the table’s <table>Aggregate root field, through the query-builder helpers (schema-derived names only — no user string is interpolated into GraphQL). Dimension values map to the category axis and measures to series.

One dimension per chart — two for a sankey

Section titled “One dimension per chart — two for a sankey”

dimensions is an array in the stored JSON. Bar, line, pie, and area charts group by exactly one column (the query builder reads dimensions[0]; extra slots are ignored, so a saved sankey switched to a bar chart degrades cleanly). Save a chart with an empty dimensions and the builder raises Choose a chart dimension.

A sankey is the two-dimension exception: it draws the flow between a source and a target category (the builder’s Flow to picker), and the aggregate query groups by both columns. The source and target sides get separate nodes even when a category name appears on both — collapsing them would draw a cycle a sankey cannot lay out. Flow width comes from the first measure; null or non-positive flow values are dropped, and a NULL target category renders as the explicit null node, so unconverted flows stay visible.

A sankey of search-to-purchase category flow — Two categorical dimensions, one grouped aggregate query: each band is a flow from the searched category to the purchased one, with unconverted searches flowing to the null node.

Each measure selects _count or a _sum / _avg / _min / _max group over one column. The aggregate surface takes filter and groupBy only — it has no pagination arguments, so a chart query carries no limit.

Exploration and charting are one loop, in both directions:

  • Grid → chart. The grid toolbar’s Visualize button opens the chart panel already pointed at the current table, and the chart’s aggregate query carries the grid’s active column filters — the totals match the filtered grid you just left. The query builder has the same button for one-table, join-free designs, carrying the designer’s criteria.
  • Chart → grid (drill-through). Click a bar, a pie slice, or a sankey band and the data grid opens at /<table>?cf=… filtered to what you clicked — the same cf URL parameter the grid already treats as its filter source of truth. A sankey band drills to both dimensions at once; the (null) node drills to the grid’s _null operator, since it is a label for SQL NULL, not a text value.
A filtered grid becomes a chart with the same filter — Filter the posts grid from the column menu, click Visualize, and the chart builder opens pre-populated — the aggregate carries the grid's filter, nothing is retyped.

Every value a chart renders comes from a server grouped-aggregate query; the panel performs no client-side summation over fetched page rows. Clicking Visualize on the grid with an active column filter produces a chart whose aggregate query carries that same filter, so the rendered totals match the filtered SQL.

  • High cardinality. A dimension with thousands of distinct values does not freeze the UI. The result-mapping step caps categories at MAX_CHART_CATEGORIES (100) and raises Too many categories (maximum 100). Refine the chart filter. before any renderer receives a row. The guard raises a hard error; tighten the filter to bring the group count under the cap.
  • NULL dimensions render with an explicit label, distinct from the empty string.
  • Empty result renders a friendly empty state, not a blank canvas.

Chart colors resolve from theme tokens, so charts render correctly in both light and dark themes rather than from hard-coded hex values.