Skip to content

Commit 15ef62d

Browse files
authored
Add sparkline and bar visualization function docs (#418)
## Summary - New **Visualization functions** page documenting `bar` (scalar) and `sparkline` (aggregate) - Aggregation page updated with a Visualization aggregates category linking to the new page - Sidebar updated with the new page entry ## Dependencies - questdb/questdb#6975
1 parent 8fe10a0 commit 15ef62d

3 files changed

Lines changed: 281 additions & 0 deletions

File tree

documentation/query/functions/aggregation.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ min/max position-by-position — see the
104104
| [array_elem_min](/docs/query/functions/array/#array_elem_min) | Element-wise minimum across arrays |
105105
| [array_elem_sum](/docs/query/functions/array/#array_elem_sum) | Element-wise sum across arrays |
106106

107+
### Visualization aggregates
108+
109+
| Function | Description |
110+
| :------- | :---------- |
111+
| [sparkline](/docs/query/functions/visualization/#sparkline) | Vertical block chart of values within a group |
112+
113+
See the [visualization functions](/docs/query/functions/visualization/) page for
114+
full reference, including the scalar [bar](/docs/query/functions/visualization/#bar)
115+
function.
116+
107117
---
108118

109119
QuestDB supports implicit `GROUP BY`. When aggregate functions are used with
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
---
2+
title: Visualization functions
3+
sidebar_label: Visualization
4+
description: SQL functions for rendering inline charts in query results using Unicode block characters.
5+
---
6+
7+
Visualization functions render numeric data as compact Unicode block charts
8+
directly in query results. The output is a `varchar` cell that works everywhere:
9+
psql, the web console, JDBC clients, CSV exports.
10+
11+
| Function | Type | Description |
12+
| :------- | :--- | :---------- |
13+
| [bar](#bar) | Scalar | Horizontal bar proportional to a value within a range |
14+
| [sparkline](#sparkline) | Aggregate | Vertical block chart of values within a group |
15+
16+
## bar
17+
18+
`bar(value, min, max, width)` - Renders a single numeric value as a horizontal
19+
bar. The bar is made of full block characters with a fractional block at the end
20+
for sub-character precision.
21+
22+
Characters used (varying width):
23+
24+
```
25+
▏▎▍▌▋▊▉█
26+
```
27+
28+
Characters range from `` (1/8 fill, U+258F) to `` (full fill, U+2588). A
29+
`width` of 20 characters gives 160 discrete levels of resolution (20 x 8).
30+
31+
Since `bar` is a scalar function, it can wrap aggregates like `sum()`, `avg()`,
32+
or `count()` to visualize their results inline.
33+
34+
#### Parameters
35+
36+
All four arguments are required:
37+
38+
- `value` is any numeric value. Implicitly cast to `double`. `NULL` produces
39+
`NULL` output.
40+
- `min` (`double`): the value that maps to an empty bar (zero length).
41+
- `max` (`double`): the value that maps to a full bar (`width` characters).
42+
- `width` (`int`): the number of characters at `max` value.
43+
44+
Values below `min` are clamped to an empty bar. Values above `max` are clamped
45+
to a full bar of `width` characters. If `min`, `max`, or `width` are `NULL`, or
46+
if `min >= max`, the function returns `NULL`.
47+
48+
#### Return value
49+
50+
Return value type is `varchar`.
51+
52+
#### Examples
53+
54+
```questdb-sql demo title="Visualize aggregated volume per minute"
55+
SELECT timestamp, symbol,
56+
round(sum(amount), 2) total,
57+
bar(sum(amount), 0, 50, 30)
58+
FROM trades
59+
WHERE symbol IN ('BTC-USDT', 'ETH-USDT')
60+
SAMPLE BY 1m
61+
LIMIT -10;
62+
```
63+
64+
```questdb-sql demo title="Per-symbol scaling with window functions"
65+
SELECT timestamp, symbol, round(total, 2) total,
66+
bar(total, min(total) OVER (PARTITION BY symbol),
67+
max(total) OVER (PARTITION BY symbol), 30)
68+
FROM (
69+
SELECT timestamp, symbol, sum(amount) total
70+
FROM trades
71+
WHERE symbol IN ('BTC-USDT', 'ETH-USDT')
72+
SAMPLE BY 1m
73+
)
74+
LIMIT -10;
75+
```
76+
77+
| timestamp | symbol | total | bar |
78+
| :-------------------------- | :------- | :----- | :----------------- |
79+
| 2026-03-06T17:18:00.000000Z | ETH-USDT | 72.94 | ██ |
80+
| 2026-03-06T17:18:00.000000Z | BTC-USDT | 6.76 | ██████ |
81+
| 2026-03-06T17:19:00.000000Z | ETH-USDT | 118.19 | ███ |
82+
| 2026-03-06T17:19:00.000000Z | BTC-USDT | 1.59 ||
83+
| 2026-03-06T17:20:00.000000Z | ETH-USDT | 246.87 | ███████ |
84+
| 2026-03-06T17:20:00.000000Z | BTC-USDT | 14.36 | █████████████ |
85+
| 2026-03-06T17:21:00.000000Z | BTC-USDT | 2.9 | ██ |
86+
| 2026-03-06T17:21:00.000000Z | ETH-USDT | 375.3 | ██████████ |
87+
| 2026-03-06T17:22:00.000000Z | BTC-USDT | 8.07 | ███████ |
88+
| 2026-03-06T17:22:00.000000Z | ETH-USDT | 529.74 | ███████████████ |
89+
90+
Each symbol's bars scale independently because `PARTITION BY symbol` gives each
91+
its own min/max range.
92+
93+
```questdb-sql demo title="Global scaling across all symbols"
94+
SELECT timestamp, symbol, round(total, 2) total,
95+
bar(total, min(total) OVER (),
96+
max(total) OVER (), 30)
97+
FROM (
98+
SELECT timestamp, symbol, sum(amount) total
99+
FROM trades
100+
WHERE symbol IN ('BTC-USDT', 'ETH-USDT')
101+
SAMPLE BY 1m
102+
)
103+
LIMIT -10;
104+
```
105+
106+
All symbols share the same min/max, making bars comparable across groups.
107+
108+
```questdb-sql demo title="Inline with row-level data"
109+
SELECT symbol, price,
110+
bar(price, 0, 100000, 25)
111+
FROM trades
112+
LATEST ON timestamp PARTITION BY symbol;
113+
```
114+
115+
#### See also
116+
117+
- [sparkline](#sparkline) - Aggregate trend chart
118+
119+
## sparkline
120+
121+
`sparkline(value)` or `sparkline(value, min, max, width)` - Collects numeric
122+
values within a group and renders them as a compact vertical block chart. Each
123+
value maps to one character. Best for showing trends, cycles, and spikes.
124+
125+
Characters used (varying height):
126+
127+
```
128+
▁▂▃▄▅▆▇█
129+
```
130+
131+
Characters range from `` (lowest, U+2581) to `` (highest, U+2588), giving 8
132+
levels of vertical resolution per character.
133+
134+
Since `sparkline` is an aggregate, it pairs naturally with
135+
[SAMPLE BY](/docs/query/sql/sample-by/) to show intra-bucket trends.
136+
137+
The input can be any numeric type (`double`, `int`, `long`, `short`, `float`) -
138+
it is implicitly cast to `double`.
139+
140+
#### Parameters
141+
142+
- `value` is any numeric value. Each value produces one character in the output.
143+
- `min` (optional, `double`): lower bound for scaling. Pass `NULL` to
144+
auto-compute from data. Values below `min` are clamped to the lowest
145+
character.
146+
- `max` (optional, `double`): upper bound for scaling. Pass `NULL` to
147+
auto-compute from data. Values above `max` are clamped to the highest
148+
character.
149+
- `width` (optional, `int`, constant): maximum number of output characters. When
150+
the group has more values than `width`, the function sub-samples by dividing
151+
values into equal buckets and averaging each bucket. Must be a positive
152+
integer.
153+
154+
`min` and `max` can each independently be `NULL`, allowing partial auto-scaling.
155+
For example, `sparkline(price, 0, NULL, 24)` fixes the floor at 0 but
156+
auto-computes the ceiling from the data.
157+
158+
#### Return value
159+
160+
Return value type is `varchar`.
161+
162+
#### Null handling
163+
164+
- `NULL` input values are silently skipped.
165+
- If all values in a group are `NULL`, the function returns `NULL`.
166+
- An empty group (no rows) also returns `NULL`.
167+
- When all values are identical (`min` equals `max`), every character renders as
168+
``, signaling "no variation".
169+
170+
#### Examples
171+
172+
```questdb-sql demo title="Hourly price trends with sub-sampling"
173+
SELECT timestamp, symbol,
174+
round(avg(price), 0) avg_price,
175+
sparkline(price, NULL, NULL, 20)
176+
FROM trades
177+
WHERE symbol IN ('BTC-USDT', 'ETH-USDT')
178+
AND timestamp IN '2026-03-07'
179+
SAMPLE BY 1h
180+
LIMIT 10;
181+
```
182+
183+
| timestamp | symbol | avg_price | sparkline |
184+
| :-------------------------- | :------- | :-------- | :------------------- |
185+
| 2026-03-07T00:00:00.000000Z | BTC-USDT | 68229 | ▄▄▄▄▄▄▃▂▁▁▂▃▃▄▆▇▇▇▇▇ |
186+
| 2026-03-07T00:00:00.000000Z | ETH-USDT | 1981 | ▆▅▄▅▅▄▅▅▆▆▆▄▂▂▂▄▇▆▇▇ |
187+
| 2026-03-07T01:00:00.000000Z | BTC-USDT | 68239 | ▇▅▃▃▂▃▃▂▂▂▂▁▁▁▂▃▃▃▅▅ |
188+
| 2026-03-07T01:00:00.000000Z | ETH-USDT | 1979 | ▇▅▃▃▃▃▂▁▁▃▄▃▂▂▃▂▂▁▂▅ |
189+
| 2026-03-07T02:00:00.000000Z | BTC-USDT | 68182 | ▇▇▇▆▆▆▄▃▂▃▂▂▂▁▂▄▅▆▆▆ |
190+
| 2026-03-07T02:00:00.000000Z | ETH-USDT | 1978 | ▆▆▅▄▃▃▃▃▃▂▂▂▃▅▅▆▆▇▇▇ |
191+
| 2026-03-07T03:00:00.000000Z | BTC-USDT | 68286 | ▇▆▆▆▅▅▅▅▅▄▄▃▂▂▃▃▃▁▁▁ |
192+
| 2026-03-07T03:00:00.000000Z | ETH-USDT | 1986 | ▁▄▇▇▇▆▆▅▅▅▅▅▃▃▃▄▃▂▂▂ |
193+
| 2026-03-07T04:00:00.000000Z | ETH-USDT | 1973 | ▁▁▂▂▃▃▃▄▄▅▇▇▇▇▇▇▆▆▆▆ |
194+
| 2026-03-07T04:00:00.000000Z | BTC-USDT | 68026 | ▁▃▃▃▃▄▄▄▄▅▅▅▇▇▇▇▇▇▆▆ |
195+
196+
The `width` of 20 sub-samples each hour's tick data into 20 characters,
197+
regardless of how many ticks exist within each bucket.
198+
199+
```questdb-sql demo title="Compare intra-day trends across symbols"
200+
SELECT symbol, sparkline(price)
201+
FROM trades
202+
WHERE timestamp IN '2026-03-07'
203+
SAMPLE BY 1h;
204+
```
205+
206+
```questdb-sql demo title="Fixed scale for cross-symbol comparison"
207+
SELECT symbol, sparkline(amount, 0, 1000000, 24)
208+
FROM trades
209+
SAMPLE BY 1d
210+
LIMIT -5;
211+
```
212+
213+
This ensures 0 is always `` and 1,000,000 is always `` across all symbols,
214+
making the sparklines visually comparable.
215+
216+
```questdb-sql demo title="Partial auto-scaling with fixed floor"
217+
SELECT symbol, sparkline(price, 0, NULL, 24)
218+
FROM trades
219+
SAMPLE BY 1d
220+
LIMIT -5;
221+
```
222+
223+
Fixes the floor at 0 but auto-computes the ceiling from the data.
224+
225+
#### Clamping
226+
227+
When explicit `min`/`max` are provided, out-of-range values are clamped:
228+
229+
- A value below `min` renders as `` (clamped to floor)
230+
- A value above `max` renders as `` (clamped to ceiling)
231+
- Values are never silently dropped
232+
233+
#### Limitations
234+
235+
- **Sub-sampling averages buckets.** When `width` is smaller than the number of
236+
collected values, the function divides values into equal buckets and averages
237+
each. This smooths spikes. If preserving peaks is important, use a `width`
238+
equal to or greater than the expected number of values.
239+
240+
- **Limited FILL support.** When used with `SAMPLE BY`, `sparkline` supports
241+
`FILL(NULL)`, `FILL(NONE)`, and `FILL(PREV)`. `FILL(LINEAR)` and
242+
`FILL(value)` are not supported.
243+
244+
#### See also
245+
246+
- [bar](#bar) - Scalar horizontal bar
247+
- [Aggregate functions](/docs/query/functions/aggregation/) - Full aggregate
248+
reference
249+
- [SAMPLE BY](/docs/query/sql/sample-by/) - Time-series aggregation
250+
251+
## Configuration
252+
253+
Both functions enforce a maximum output size controlled by an existing server
254+
property:
255+
256+
```ini
257+
# server.conf
258+
cairo.sql.string.function.buffer.max.size=1048576
259+
```
260+
261+
Default is 1,048,576 bytes (1 MB). This is the same property used by
262+
`string_agg()`, `lpad()`, and `rpad()`.
263+
264+
Each output character is 3 bytes in UTF-8, so the default allows up to 349,525
265+
characters of output. For `sparkline`, this limits the number of values
266+
accumulated per group. For `bar`, this limits the `width` parameter. If the
267+
limit is exceeded, the function throws a non-critical error.
268+
269+
In practice these limits are generous - a sparkline or bar of 349K characters
270+
would be unusable. The limit exists to prevent accidental memory exhaustion.

documentation/sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ module.exports = {
467467
"query/functions/touch",
468468
"query/functions/trigonometric",
469469
"query/functions/uuid",
470+
"query/functions/visualization",
470471
{
471472
type: "category",
472473
label: "Window Functions",

0 commit comments

Comments
 (0)