SQL is great for querying data, but it does not produce charts on its own. The standard workflow is query then visualise: run a SQL query, extract the result columns, and pass them to a charting library.
In Python, this is what you do with pandas + matplotlib or Polars + matplotlib. In the browser, the equivalent is sql.js + Chart.js. The pattern is the same everywhere — SQL gives you the data, the charting library draws the picture.
This article walks through five interactive examples, starting with a raw data preview and building up to a fully styled grouped bar chart:
- Preview the dataset with
SELECT * - Simple bar chart — one series, one query
- Line chart with two series — EV vs diesel over time
- Polished chart — title, legend, axis labels, and grid styling
- Grouped bar chart with a computed column
The dataset
We will use a small dataset of UK car registrations comparing electric vehicles (EV) and diesel vehicles from 2018 to 2023. Each row records the year, the number of new EV registrations (ev_count), and the number of new diesel registrations (diesel_count).
Click Run on the first block to create the table and see the data:
The numbers tell a dramatic story: EV registrations grew from 2,216 in 2018 to 87,217 in 2023, while diesel fell from 58,456 to 24,590. But numbers in a table are hard to read at a glance — a chart makes the trend instantly visible.
How the data flows from SQL to Chart.js
Each chart block below has two editors: a SQL editor on top and a Chart.js editor below it. When you click Run & Plot, the SQL query executes first and the result columns are automatically passed to the Chart.js code as variables:
headers— an array of column names, e.g.["year", "ev_count", "diesel_count"]columns[0]— all values from the first column, e.g.[2018, 2019, 2020, …]columns[1]— all values from the second column, e.g.[2216, 6718, 7248, …]columns[N]— all values from the Nth column — works for any number of columnsctx— the canvas drawing context that Chart.js renders onto
This means the Chart.js code never contains hardcoded data. If you change the SQL query — add a WHERE, swap columns, or query a different table — the same Chart.js code adapts automatically because columns[0], columns[1], etc. always reflect whatever the query returned.
A simple bar chart
The first step is to query the data you want to plot. For a bar chart of EV registrations by year, all you need is:
The ORDER BY year is important. Without it, SQL does not guarantee the row order, and your chart labels could appear shuffled. Always sort time-series data before plotting.
The pattern is simple: the first column becomes the x-axis labels, and the second column becomes the bar heights. Chart.js handles the rendering — you just pass the arrays.
A line chart with two series
To compare EV and diesel trends on the same chart, query both columns and render them as two lines:
Each numeric column after the first becomes a separate line. The crossover point around 2022 is immediately visible on the chart but would be easy to miss in a table.
This is the power of the query-then-visualise pattern: SQL handles the data preparation (filtering, sorting, computing), and the chart library handles the presentation.
Polishing the chart
The default Chart.js output works, but real-world charts need a title, axis labels, a legend, and grid styling. The SQL query stays the same — all the polish happens in the Chart.js configuration:
In Python, this is equivalent to calling ax.set_title(), ax.set_xlabel(), and ax.legend() in matplotlib. In Polars or pandas, you would pass these as keyword arguments to the plot method. The concept is the same — only the syntax changes.
Grouped bar chart with a computed column
SQL can compute derived values before charting. Here we add a total column using arithmetic in the SELECT:
Computing the total in SQL rather than JavaScript keeps the logic in one place. This is a best practice: let SQL do the data work, and let the chart library do the drawing.
SQL vs Python: the query-then-visualise pattern
The pattern demonstrated here — query then visualise — is universal. The tools change, but the workflow is the same:
- SQL + Chart.js (this article) — browser-based, no installation required
- pandas + matplotlib —
df = pd.read_sql(query, conn)thendf.plot() - Polars + matplotlib —
df = pl.read_database(query, conn)then plot with matplotlib - BI tools (Tableau, Power BI, Metabase) — write a SQL query, drag columns onto a chart canvas
The advantage of doing it in the browser with sql.js and Chart.js is that there is nothing to install — the entire pipeline runs in a single HTML page. For production dashboards, you would typically use a BI tool or a Python script, but the underlying pattern remains the same.
Try editing the queries above — change ev_count to diesel_count, add a WHERE year >= 2020 filter, or compute a percentage column. The charts will update to reflect your new query.
References
- Chart.js documentation: Getting Started
- SQLite documentation: SELECT Statement
- sql.js: SQLite compiled to WebAssembly
- Pandas equivalent: How to Create a Graph in Python
- Polars equivalent: How to Create a Graph with Polars