Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ dependency is **Chart.js** (the chart result view), inlined into that one file.
Refactored from a single-file SPA into a fully modular, test-first codebase
held at **100% test coverage**.

## Demo & examples

Try it live on the Antalya demo cluster: **https://antalya.demo.altinity.cloud/sql**.
The [**ontime chart demo**](docs/ONTIME-CHART-DEMO.md) is a ready-made library of 10
queries (load [`examples/ontime-charts.json`](examples/ontime-charts.json) via
**File ▾ → Replace**) that walks through every chart type and feature against the public
`ontime` flight dataset.

## How it works

```
Expand Down
65 changes: 65 additions & 0 deletions docs/ONTIME-CHART-DEMO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Chart demo — the `ontime` flight dataset

A ready-made **Library** of 10 analytical queries that show off every chart type and
feature in the Altinity SQL Browser, running against the public US flight-history
dataset (`ontime`, ~230M rows, 1987–2025) on the Antalya demo cluster.

- **Live demo:** **https://antalya.demo.altinity.cloud/sql**
- **The library file:** [`examples/ontime-charts.json`](../examples/ontime-charts.json)
([raw download](https://raw.githubusercontent.com/Altinity/altinity-sql-browser/main/examples/ontime-charts.json))
- **Reproduce it:** [`examples/build-ontime-charts.mjs`](../examples/build-ontime-charts.mjs)
regenerates the JSON (it derives each chart's schema key live with
`clickhouse-client --connection antalya`).

## Load it (≈30 seconds)

1. Open **https://antalya.demo.altinity.cloud/sql** and sign in (**Continue with Google**,
or use the credentials box).
2. Download [`ontime-charts.json`](https://raw.githubusercontent.com/Altinity/altinity-sql-browser/main/examples/ontime-charts.json)
(right-click → Save link as…).
3. In the header, click **File ▾ → Replace…** and pick the file. The library is renamed
**ontime-charts** and fills with 10 saved queries (confirm the replace if you already
had queries saved).
4. Click any query in the **Library** panel — it runs and opens straight into its chart.
Switch **Table / JSON / Chart** at the top of the results, or change the **Type / X / Y /
Series** dropdowns to re-encode any chart live.

## What each query demonstrates

| # | Query | Chart | Feature |
|---|-------|-------|---------|
| 1 | Busiest origin airports — 2023 | Bar (horizontal) | categorical axis; joined to `dim_airports` for readable names; hover any bar (long or short) for its exact value |
| 2 | Flights by month — 2023 | Column | numeric `month` auto-detected as an ordinal axis; K/M-humanised value ticks |
| 3 | Daily flights — 2023 | Line | `Date` axis auto-detected as a time series (~365 points) |
| 4 | Daily on-time rate — 2023 | Area | filled time series (a percentage measure) |
| 5 | Cancellation reasons — 2023 | Pie | share of a small category set, with a legend |
| 6 | Monthly flights by carrier — 2023 | Grouped bars | a **Series** column (carrier) splits each month into per-carrier bars |
| 7 | Average delay breakdown by carrier — 2023 | Multi-measure columns | four measures plotted together (“All measures”) |
| 8 | Daily flights since 2022 | Line | result exceeds the chart cap → a **“first 500 of 1.5K rows”** note (the table keeps them all) |
| 9 | Flights by day of week — 2023 | Column | ordinal `dayofweek` axis |
| 10 | Worst average departure delay by airport — 2023 | Bar (horizontal) | a non-count measure (avg minutes), joined for names |

Each saved query stores its chart configuration, so it reopens exactly as designed. (Charts
plot the first 500 rows; the full result is always available in the Table view.)

## Direct links

Every query is also reachable as a single shareable link — open one and the SQL **and** its
chart configuration are pre-loaded; press **Run**, then the **Chart** tab. (Inside the app,
the **Share** button copies the same kind of link for whatever you're looking at.)

- **Bar** — [Busiest origin airports — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgYS5EaXNwbGF5QWlycG9ydE5hbWUgQVMgYWlycG9ydCxcbiAgICBjb3VudCgpIEFTIGZsaWdodHNcbkZST00gb250aW1lLmZhY3Rfb250aW1lIEFTIGZcbklOTkVSIEpPSU4gb250aW1lLmRpbV9haXJwb3J0cyBBUyBhXG4gICAgT04gYS5BaXJwb3J0Q29kZSA9IGYuT3JpZ2luQ29kZSBBTkQgYS5Jc0xhdGVzdCA9IDFcbldIRVJFIGYuWWVhciA9IDIwMjNcbkdST1VQIEJZIGFpcnBvcnRcbk9SREVSIEJZIGZsaWdodHMgREVTQ1xuTElNSVQgMTUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImhiYXIiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6ImFpcnBvcnQ6U3RyaW5nfGZsaWdodHM6VUludDY0In19)
- **Column** — [Flights by month — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIE1vbnRoIEFTIG1vbnRoLCBjb3VudCgpIEFTIGZsaWdodHNcbkZST00gb250aW1lLmZhY3Rfb250aW1lXG5XSEVSRSBZZWFyID0gMjAyM1xuR1JPVVAgQlkgbW9udGhcbk9SREVSIEJZIG1vbnRoIiwiY2hhcnQiOnsiY2ZnIjp7InR5cGUiOiJiYXIiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6Im1vbnRoOlVJbnQ4fGZsaWdodHM6VUludDY0In19)
- **Line** — [Daily flights — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIEZsaWdodERhdGUgQVMgZGF0ZSwgY291bnQoKSBBUyBmbGlnaHRzXG5GUk9NIG9udGltZS5mYWN0X29udGltZVxuV0hFUkUgWWVhciA9IDIwMjNcbkdST1VQIEJZIGRhdGVcbk9SREVSIEJZIGRhdGUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImxpbmUiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6ImRhdGU6RGF0ZXxmbGlnaHRzOlVJbnQ2NCJ9fQ==)
- **Area** — [Daily on-time rate — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgRmxpZ2h0RGF0ZSBBUyBkYXRlLFxuICAgIHJvdW5kKDEwMCAqIGNvdW50SWYoQXJyRGVsMTUgPSAwKSAvIGNvdW50KCksIDEpIEFTIG9uX3RpbWVfcGN0XG5GUk9NIG9udGltZS5mYWN0X29udGltZVxuV0hFUkUgWWVhciA9IDIwMjNcbkdST1VQIEJZIGRhdGVcbk9SREVSIEJZIGRhdGUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImFyZWEiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6ImRhdGU6RGF0ZXxvbl90aW1lX3BjdDpGbG9hdDY0In19)
- **Pie** — [Cancellation reasons — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgbXVsdGlJZihDYW5jZWxsYXRpb25Db2RlID0gJ0EnLCAnQ2FycmllcicsXG4gICAgICAgICAgICBDYW5jZWxsYXRpb25Db2RlID0gJ0InLCAnV2VhdGhlcicsXG4gICAgICAgICAgICBDYW5jZWxsYXRpb25Db2RlID0gJ0MnLCAnTmF0aW9uYWwgQWlyIFN5c3RlbScsXG4gICAgICAgICAgICBDYW5jZWxsYXRpb25Db2RlID0gJ0QnLCAnU2VjdXJpdHknLCAnT3RoZXInKSBBUyByZWFzb24sXG4gICAgY291bnQoKSBBUyBjYW5jZWxsYXRpb25zXG5GUk9NIG9udGltZS5mYWN0X29udGltZVxuV0hFUkUgWWVhciA9IDIwMjMgQU5EIENhbmNlbGxlZCA9IDFcbkdST1VQIEJZIHJlYXNvblxuT1JERVIgQlkgY2FuY2VsbGF0aW9ucyBERVNDIiwiY2hhcnQiOnsiY2ZnIjp7InR5cGUiOiJwaWUiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6InJlYXNvbjpTdHJpbmd8Y2FuY2VsbGF0aW9uczpVSW50NjQifX0=)
- **Grouped columns** — [Monthly flights by carrier — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgTW9udGggQVMgbW9udGgsXG4gICAgQ2FycmllciBBUyBjYXJyaWVyLFxuICAgIGNvdW50KCkgQVMgZmxpZ2h0c1xuRlJPTSBvbnRpbWUuZmFjdF9vbnRpbWVcbldIRVJFIFllYXIgPSAyMDIzIEFORCBDYXJyaWVyIElOICgnV04nLCAnQUEnLCAnREwnLCAnVUEnKVxuR1JPVVAgQlkgbW9udGgsIGNhcnJpZXJcbk9SREVSIEJZIG1vbnRoLCBjYXJyaWVyIiwiY2hhcnQiOnsiY2ZnIjp7InR5cGUiOiJiYXIiLCJ4IjowLCJ5IjpbMl0sInNlcmllcyI6MX0sImtleSI6Im1vbnRoOlVJbnQ4fGNhcnJpZXI6TG93Q2FyZGluYWxpdHkoU3RyaW5nKXxmbGlnaHRzOlVJbnQ2NCJ9fQ==)
- **Multi-measure columns** — [Average delay breakdown by carrier — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgQ2FycmllciBBUyBjYXJyaWVyLFxuICAgIHJvdW5kKGF2ZyhDYXJyaWVyRGVsYXkpLCAxKSBBUyBjYXJyaWVyX2RlbGF5LFxuICAgIHJvdW5kKGF2ZyhXZWF0aGVyRGVsYXkpLCAxKSBBUyB3ZWF0aGVyX2RlbGF5LFxuICAgIHJvdW5kKGF2ZyhOQVNEZWxheSksIDEpIEFTIG5hc19kZWxheSxcbiAgICByb3VuZChhdmcoTGF0ZUFpcmNyYWZ0RGVsYXkpLCAxKSBBUyBsYXRlX2FpcmNyYWZ0X2RlbGF5XG5GUk9NIG9udGltZS5mYWN0X29udGltZVxuV0hFUkUgWWVhciA9IDIwMjMgQU5EIEFyckRlbDE1ID0gMVxuR1JPVVAgQlkgY2FycmllclxuT1JERVIgQlkgY2Fycmllcl9kZWxheSBERVNDXG5MSU1JVCAxMiIsImNoYXJ0Ijp7ImNmZyI6eyJ0eXBlIjoiYmFyIiwieCI6MCwieSI6WzEsMiwzLDRdLCJzZXJpZXMiOm51bGx9LCJrZXkiOiJjYXJyaWVyOkxvd0NhcmRpbmFsaXR5KFN0cmluZyl8Y2Fycmllcl9kZWxheTpOdWxsYWJsZShGbG9hdDY0KXx3ZWF0aGVyX2RlbGF5Ok51bGxhYmxlKEZsb2F0NjQpfG5hc19kZWxheTpOdWxsYWJsZShGbG9hdDY0KXxsYXRlX2FpcmNyYWZ0X2RlbGF5Ok51bGxhYmxlKEZsb2F0NjQpIn19)
- **Line (capped)** — [Daily flights since 2022](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIEZsaWdodERhdGUgQVMgZGF0ZSwgY291bnQoKSBBUyBmbGlnaHRzXG5GUk9NIG9udGltZS5mYWN0X29udGltZVxuV0hFUkUgRmxpZ2h0RGF0ZSA+PSAnMjAyMi0wMS0wMSdcbkdST1VQIEJZIGRhdGVcbk9SREVSIEJZIGRhdGUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImxpbmUiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6ImRhdGU6RGF0ZXxmbGlnaHRzOlVJbnQ2NCJ9fQ==)
- **Column** — [Flights by day of week — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIERheU9mV2VlayBBUyBkYXlvZndlZWssIGNvdW50KCkgQVMgZmxpZ2h0c1xuRlJPTSBvbnRpbWUuZmFjdF9vbnRpbWVcbldIRVJFIFllYXIgPSAyMDIzXG5HUk9VUCBCWSBkYXlvZndlZWtcbk9SREVSIEJZIGRheW9md2VlayIsImNoYXJ0Ijp7ImNmZyI6eyJ0eXBlIjoiYmFyIiwieCI6MCwieSI6WzFdLCJzZXJpZXMiOm51bGx9LCJrZXkiOiJkYXlvZndlZWs6VUludDh8ZmxpZ2h0czpVSW50NjQifX0=)
- **Bar** — [Worst average departure delay by airport — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgYS5EaXNwbGF5QWlycG9ydE5hbWUgQVMgYWlycG9ydCxcbiAgICByb3VuZChhdmcoZi5EZXBEZWxheU1pbnV0ZXMpLCAxKSBBUyBhdmdfZGVwX2RlbGF5XG5GUk9NIG9udGltZS5mYWN0X29udGltZSBBUyBmXG5JTk5FUiBKT0lOIG9udGltZS5kaW1fYWlycG9ydHMgQVMgYVxuICAgIE9OIGEuQWlycG9ydENvZGUgPSBmLk9yaWdpbkNvZGUgQU5EIGEuSXNMYXRlc3QgPSAxXG5XSEVSRSBmLlllYXIgPSAyMDIzXG5HUk9VUCBCWSBhaXJwb3J0XG5IQVZJTkcgY291bnQoKSA+PSAxMDAwMFxuT1JERVIgQlkgYXZnX2RlcF9kZWxheSBERVNDXG5MSU1JVCAxNSIsImNoYXJ0Ijp7ImNmZyI6eyJ0eXBlIjoiaGJhciIsIngiOjAsInkiOlsxXSwic2VyaWVzIjpudWxsfSwia2V5IjoiYWlycG9ydDpTdHJpbmd8YXZnX2RlcF9kZWxheTpOdWxsYWJsZShGbG9hdDY0KSJ9fQ==)

## Tables used

- `ontime.fact_ontime` — one row per US domestic flight (dates, carrier, origin/dest, delays, cancellations, …).
- `ontime.dim_airports` — airport reference data; joined on `AirportCode = OriginCode AND IsLatest = 1` for human-readable airport names.
198 changes: 198 additions & 0 deletions examples/build-ontime-charts.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Generator for examples/ontime-charts.json — a saved-queries "Library" file for
// the Altinity SQL Browser that demonstrates every chart feature against the
// public `ontime` flights dataset on the antalya cluster.
//
// Why a generator: the browser only restores a saved chart config when the
// entry's `chart.key` exactly equals schemaKey(resultColumns) = "name:type|…"
// (see src/ui/results.js chartCfgFor / src/core/chart-data.js schemaKey).
// Hand-writing those type strings is error-prone, so we derive each key live
// from `DESCRIBE (<query>)` against the real cluster.
//
// Run: node examples/build-ontime-charts.mjs (needs `clickhouse-client --connection antalya`)
// Out: examples/ontime-charts.json

import { execFileSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';

const here = dirname(fileURLToPath(import.meta.url));
const CONNECTION = 'antalya';

// Each spec: a query + the chart we want it to open with. `cfg` matches the
// app's shape { type, x, y:[...], series }; x/series are column indices, y a
// list of measure-column indices. `view:'chart'` makes a click open the chart.
const SPECS = [
{
name: 'Busiest origin airports — 2023',
description: 'Top 15 departure airports by flight count (joined to dim_airports for readable names). Horizontal Bar — hover any bar, long or short, to read its exact value.',
cfg: { type: 'hbar', x: 0, y: [1], series: null },
sql: `SELECT
a.DisplayAirportName AS airport,
count() AS flights
FROM ontime.fact_ontime AS f
INNER JOIN ontime.dim_airports AS a
ON a.AirportCode = f.OriginCode AND a.IsLatest = 1
WHERE f.Year = 2023
GROUP BY airport
ORDER BY flights DESC
LIMIT 15`,
},
{
name: 'Flights by month — 2023',
description: 'Monthly US flight volume. A numeric column named "month" is detected as an ordinal axis → vertical Column chart, with K/M-humanised value ticks.',
cfg: { type: 'bar', x: 0, y: [1], series: null },
sql: `SELECT Month AS month, count() AS flights
FROM ontime.fact_ontime
WHERE Year = 2023
GROUP BY month
ORDER BY month`,
},
{
name: 'Daily flights — 2023',
description: 'One point per day across 2023 (~365 rows). A Date X axis is auto-detected as a time series → Line chart.',
cfg: { type: 'line', x: 0, y: [1], series: null },
sql: `SELECT FlightDate AS date, count() AS flights
FROM ontime.fact_ontime
WHERE Year = 2023
GROUP BY date
ORDER BY date`,
},
{
name: 'Daily on-time rate — 2023',
description: 'Share of flights arriving on time (< 15 min late) per day, as a percentage. Rendered as a filled Area chart.',
cfg: { type: 'area', x: 0, y: [1], series: null },
sql: `SELECT
FlightDate AS date,
round(100 * countIf(ArrDel15 = 0) / count(), 1) AS on_time_pct
FROM ontime.fact_ontime
WHERE Year = 2023
GROUP BY date
ORDER BY date`,
},
{
name: 'Cancellation reasons — 2023',
description: 'Why flights were cancelled in 2023 (carrier / weather / national air system / security). A small categorical breakdown → Pie chart with a legend.',
cfg: { type: 'pie', x: 0, y: [1], series: null },
sql: `SELECT
multiIf(CancellationCode = 'A', 'Carrier',
CancellationCode = 'B', 'Weather',
CancellationCode = 'C', 'National Air System',
CancellationCode = 'D', 'Security', 'Other') AS reason,
count() AS cancellations
FROM ontime.fact_ontime
WHERE Year = 2023 AND Cancelled = 1
GROUP BY reason
ORDER BY cancellations DESC`,
},
{
name: 'Monthly flights by carrier — 2023',
description: 'Flights per month split across four major carriers (WN, AA, DL, UA). The "carrier" column is used as the Series, producing grouped bars with a per-carrier legend.',
cfg: { type: 'bar', x: 0, y: [2], series: 1 },
sql: `SELECT
Month AS month,
Carrier AS carrier,
count() AS flights
FROM ontime.fact_ontime
WHERE Year = 2023 AND Carrier IN ('WN', 'AA', 'DL', 'UA')
GROUP BY month, carrier
ORDER BY month, carrier`,
},
{
name: 'Average delay breakdown by carrier — 2023',
description: 'Mean minutes of each delay cause (carrier, weather, NAS, late aircraft) for delayed flights, per carrier. Four measures plotted at once ("All measures") as grouped columns.',
cfg: { type: 'bar', x: 0, y: [1, 2, 3, 4], series: null },
sql: `SELECT
Carrier AS carrier,
round(avg(CarrierDelay), 1) AS carrier_delay,
round(avg(WeatherDelay), 1) AS weather_delay,
round(avg(NASDelay), 1) AS nas_delay,
round(avg(LateAircraftDelay), 1) AS late_aircraft_delay
FROM ontime.fact_ontime
WHERE Year = 2023 AND ArrDel15 = 1
GROUP BY carrier
ORDER BY carrier_delay DESC
LIMIT 12`,
},
{
name: 'Daily flights since 2022',
description: 'Every day from 2022 onward (~1,460 points). The chart plots the first 500 and shows a "first 500 of N rows" note — the table view still has them all.',
cfg: { type: 'line', x: 0, y: [1], series: null },
sql: `SELECT FlightDate AS date, count() AS flights
FROM ontime.fact_ontime
WHERE FlightDate >= '2022-01-01'
GROUP BY date
ORDER BY date`,
},
{
name: 'Flights by day of week — 2023',
description: 'Volume by day of week (1 = Monday … 7 = Sunday). "dayofweek" is recognised as an ordinal axis → Column chart.',
cfg: { type: 'bar', x: 0, y: [1], series: null },
sql: `SELECT DayOfWeek AS dayofweek, count() AS flights
FROM ontime.fact_ontime
WHERE Year = 2023
GROUP BY dayofweek
ORDER BY dayofweek`,
},
{
name: 'Worst average departure delay by airport — 2023',
description: 'Airports with the highest mean departure delay (minutes) among those with ≥ 10,000 departures in 2023. Horizontal Bar of a non-count measure, joined for names.',
cfg: { type: 'hbar', x: 0, y: [1], series: null },
sql: `SELECT
a.DisplayAirportName AS airport,
round(avg(f.DepDelayMinutes), 1) AS avg_dep_delay
FROM ontime.fact_ontime AS f
INNER JOIN ontime.dim_airports AS a
ON a.AirportCode = f.OriginCode AND a.IsLatest = 1
WHERE f.Year = 2023
GROUP BY airport
HAVING count() >= 10000
ORDER BY avg_dep_delay DESC
LIMIT 15`,
},
];

const ch = (query) =>
execFileSync('clickhouse-client', ['--connection', CONNECTION, '--query', query], {
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024,
});

// schemaKey == columns.map(c => c.name + ':' + c.type).join('|'), derived from
// DESCRIBE so it matches exactly what the app receives at run time.
function schemaKey(sql) {
const out = ch(`DESCRIBE (${sql})`);
return out
.split('\n')
.filter((l) => l.trim())
.map((l) => { const [name, type] = l.split('\t'); return `${name}:${type}`; })
.join('|');
}

const resultRows = (sql) => Number(ch(`SELECT count() FROM (${sql})`).trim());

const queries = SPECS.map((s, i) => {
const key = schemaKey(s.sql);
const rows = resultRows(s.sql);
console.log(`#${i + 1} ${s.cfg.type.padEnd(4)} rows=${String(rows).padStart(5)} key=${key}`);
return {
id: 's' + (i + 1),
name: s.name,
sql: s.sql,
favorite: false,
description: s.description,
chart: { cfg: s.cfg, key },
view: 'chart',
};
});

const doc = {
format: 'altinity-sql-browser/saved-queries',
version: 1,
exportedAt: new Date().toISOString(),
queries,
};

const outPath = resolve(here, 'ontime-charts.json');
writeFileSync(outPath, JSON.stringify(doc, null, 2) + '\n');
console.log(`\nwrote ${outPath} (${queries.length} queries)`);
Loading
Loading