Charts from SQL Aggregate Queries
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.
How a chart is built
Section titled “How a chart is built”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.
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.
From the grid to a chart — and back
Section titled “From the grid to a chart — and back”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 samecfURL 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_nulloperator, since it is a label for SQLNULL, not a text value.
Server-side values only
Section titled “Server-side values only”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.
Edge handling
Section titled “Edge handling”- 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 raisesToo 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.
Theming
Section titled “Theming”Chart colors resolve from theme tokens, so charts render correctly in both light and dark themes rather than from hard-coded hex values.
Related
Section titled “Related”- Dashboards — reuse a chart as a read-only dashboard tile.
- Pivot UI — cross-tab the same data across two axes instead of one.
- Grid grouping — the same server
GROUP BY, rendered as grid header rows. - Aggregate queries
- Data workbench overview