Chart Query Pipeline#
Superset's chart rendering pipeline is a three-phase flow: a frontend query construction phase that converts chart form data into a SQL-agnostic QueryContext, a backend SQL execution phase that returns flat query results, and a frontend transform phase that converts those results into the final visualization.
All chart plugins communicate with the backend exclusively via POST /api/v1/chart/data . Each plugin is defined by three coordinated files:
| File | Role |
|---|---|
buildQuery.ts | Constructs the QueryContext POSTed to the backend |
transformProps.ts | Converts flat query results + form data into renderer-ready props |
controlPanel.tsx | Declares the chart editor UI controls |
Phase 1: buildQuery → QueryContext#
Each chart's buildQuery.ts calls buildQueryContext(formData, buildQuery?) from @superset-ui/core . The helper's two-step logic:
-
buildQueryObject(formData)— Extractsmetrics,columns,groupby,time_range, andadhoc_filtersinto a baseQueryObject. Filters are split into WHERE/HAVING clauses viaprocessFilters(). Time extras (granularity, time grain) are pulled viaextractExtras(). -
Chart-specific
buildQuerycallback — Receives the baseQueryObjectand returns a modified array. The Timeseries family uses this to injectpost_processingoperators (pivot, rolling window, time comparison, resample, contribution) and to add anextraMetricsentry for the series-limit sort metric .
The returned QueryContext contains the datasource, force flag, result_format, and the final queries array . A single chart can issue multiple queries in parallel by returning multiple QueryObject items from its callback .
Phase 2: Backend SQL Generation and GROUP BY#
The backend receives the QueryContext and routes each QueryObject through get_sqla_query() in superset/models/helpers.py.
Aggregation is triggered automatically by the presence of any metric :
need_groupby = bool(metrics is not None or groupby)
When need_groupby is True, the query builder:
- Collects groupby columns into
groupby_all_columnsand adds them toselect_exprs - Builds metric SQL expressions into
metrics_exprs— resolving saved metrics viaSqlMetric.get_sqla_col()and adhoc metrics viaadhoc_metric_to_sqla() - Merges and deduplicates both lists into the final
SELECT - Applies
GROUP BY *groupby_all_columns.values()
Adhoc metrics come in two expressionType flavors :
SIMPLE— wraps a column in a named aggregation function (e.g.,func.sum(...))SQL— passes a raw SQL string throughliteral_column()after Jinja processing
When no metrics are present, the query falls back to COUNT(*) and omits GROUP BY entirely .
Phase 3: transformProps → ECharts Options#
After the backend returns flat row data, the frontend transformProps.ts converts it into chart-ready props. For the Timeseries family (Line, Area, Step, Bar), a single shared Timeseries/transformProps.ts handles all variants . The Bar chart simply forces seriesType: 'bar' before delegating to this shared function .
The pipeline stages are :
- Data preparation — merges form data defaults, calls
rebaseForecastDatum(), runsextractSeries()to produce flatrawSeries - Per-series transform — maps each series through
transformSeries()intransformers.ts, which convertsseriesType→ EChartstypeand applies color, opacity, stacking, and forecast styling - Annotation injection — converts annotation layers into ECharts
SeriesOptionentries - Axis & layout construction — builds x/y axes, swaps if orientation is horizontal, computes padding
- Option assembly — outputs the final
EChartsCoreOptionplus cross-filter callbacks (setDataMask), context-menu hooks, andselectedValues
The assembled echartOptions passes to the <Echart> wrapper component, which merges Ant Design theme tokens and calls echartsInstance.setOption() .
Known Gotcha: Extra Metrics and Stacked Totals#
The buildQuery for Timeseries charts may inject a timeseries_limit_metric (sort-by metric) into the query via extractExtraMetrics() — a column that appears in the backend result but is not rendered as a visible series. In stacked bar charts with "Only Total" enabled, extractDataTotalValues() in utils/series.ts incorrectly sums this hidden metric into the displayed total, because extraMetricLabels is not passed as an exclusion filter . The fix is to compute extraMetricLabels before calling extractDataTotalValues and pass them via an extraMetricLabels opt-out list .
Key Source Files#
| File | Purpose |
|---|---|
superset-ui-core/src/query/buildQueryContext.ts | Assembles QueryContext from form data |
superset-ui-core/src/query/buildQueryObject.ts | Extracts metrics/columns/filters into base QueryObject |
superset/models/helpers.py → get_sqla_query() | Backend SQL assembly + automatic GROUP BY |
superset/connectors/sqla/models.py → adhoc_metric_to_sqla() | SIMPLE/SQL adhoc metric resolution |
plugin-chart-echarts/src/Timeseries/buildQuery.ts | Timeseries chart-specific query builder |
plugin-chart-echarts/src/Timeseries/transformProps.ts | Shared Timeseries/Bar transform pipeline |
plugin-chart-echarts/src/Timeseries/transformers.ts | transformSeries() + annotation transforms |
superset/charts/api.py | Backend POST /api/v1/chart/data handler |