Textplot
ASCII and Unicode plots inline in DuckDB query results β bars, sparklines, and density plots that fit in a single VARCHAR column. Useful for eyeballing distributions and trends in the DuckDB CLI without leaving the terminal.
Install
-- Install the extension
INSTALL textplot FROM community;
-- Load it into your session
LOAD textplot;
-- See the shape of a column without leaving the CLI
SELECT tp_density(array_agg(value)) AS distribution
FROM measurements;
-- One sparkline per row in a status table
SELECT host, tp_sparkline(cpu_history, width := 24) AS cpu
FROM hosts_status;
-- Inline progress bar
SELECT tp_bar(0.75) AS progress;
-- QR code (ASCII / Unicode)
SELECT tp_qr('https://query.farm'); Technical Overview
Why Use Textplot?
Textplot renders ASCII and Unicode charts as VARCHAR values right inside a SELECT. The point: when you're poking at data in the DuckDB CLI, you can see the shape of a distribution, the trend of a series, or the level of a metric without copying numbers into a notebook or a BI tool. Quick eyeballing β not a replacement for real visualization when you actually need axes, hover, or pixels.
π What this extension is for
Inline visuals for terminal-first SQL workflows. The output is plain text, so it travels through .mode line, log files, status emails, and READMEs the same way any other column does.
- β’ Eyeballing distributions: Wrap an aggregate in
tp_densityto see whether a column is roughly normal, bimodal, or has a long tail β in one row of CLI output, no matplotlib round-trip. - β’ Per-row trend cells: Drop a
tp_sparklineinto aSELECTlist against a table where each row carries an array of values; you get one sparkline per entity in the result table. - β’ Status indicators:
tp_barwith threshold-based coloring is the natural shape for SLA / disk / health columns in a fleet table. - β’ Scannable links:
tp_qrrenders a QR code as Unicode blocks for embedding in terminal output, dashboards, or docs.
βοΈ How it works
Every function returns a VARCHAR β there's no client-side renderer, no asset pipeline, no extra dependency in the query plan. Each function takes a positional value (or array) plus a small set of named parameters for width, style, and characters.
- β’ Charts as strings:
tp_bar,tp_sparkline, andtp_densityall return a singleVARCHARcell. Width is parameterized so columns line up across rows. - β’ Named-parameter styling:
shape,style,theme,width,on_color,off_color, andthresholdsare all passed via DuckDB's named-parameter syntax.onandoffcollide with SQL keywords β quote them as"on"and"off". - β’ Three sparkline modes:
tp_sparklinepicks a vocabulary based onmode:absolute(height β value),delta(direction of change), ortrend(signed magnitude). Each mode has multiple themes β blocks, arrows, faces, slopes. - β’ Density plot styles:
tp_densityships several character-set styles βshaded,ascii,dots,height,circles,rainbow_circleβ so you can pick one that fits the output medium (terminal, email, README).
π‘οΈ What it isn't
Textplot is a CLI ergonomics tool. It earns its place when the alternative is squinting at numbers β not when you actually need a real chart.
- β’ Not a visualization library: Character-grid resolution caps detail. For interactive dashboards, publication figures, or anything where pixels matter, use a real charting tool β Matplotlib, Vega-Lite, Observable Plot, Apache Superset, etc.
- β’ Not a statistical summary:
tp_densityshows the shape of a distribution; it doesn't compute moments, fit kernels, or give you confidence intervals. Pair it withquantile_cont,stddev_pop, ordatasketcheswhen you need numbers. - β’ Unicode rendering varies: Emoji and block characters depend on the terminal font. For maximum portability use
style := 'ascii'(tp_density),theme := 'ascii_basic'(tp_sparkline), or custom"on"/"off"characters withtp_barandtp_qr.
π― Common Use Cases
See the shape of a column inline
SELECT tp_density(array_agg(value)) FROM measurements; β one row of CLI output that tells you whether the data is roughly normal, bimodal, or skewed, without leaving SQL.
Sparklines per row in a status table
Pre-aggregate a series per entity into an array column, then render tp_sparkline in the SELECT. You get a fleet view that fits in a terminal window.
Threshold-colored health bars
tp_bar with a thresholds list is the classic SLA / disk-utilization / risk indicator. Sort by the underlying number; let the bar carry the visual.
Embed scannable links
tp_qr renders Unicode-block QR codes for terminal output, status emails, and README embeds. ecc := 'high' for tolerant rendering, custom characters for styling.
Deep Dive
Technical Details
What you can do with one query
The shortest path from βa column of numbersβ to βI can see whatβs going onβ β without leaving the DuckDB CLI:
SELECT tp_density(array_agg(value)) AS distribution
FROM measurements;
tp_density renders the array as a one-line Unicode density plot β enough to tell whether the data is roughly normal, bimodal, or has a long tail. No notebook, no matplotlib, no BI tool round-trip. The same shape works for sparklines and threshold-colored bars in any result table:
SELECT host,
tp_sparkline(cpu_history, theme := 'utf8_blocks', width := 24) AS cpu,
tp_bar(disk_pct, min := 0, max := 100, width := 12,
thresholds := [(90, 'red'), (75, 'yellow'), (0, 'green')]) AS disk
FROM hosts_status
ORDER BY disk_pct DESC;
One sparkline per row, one threshold-colored bar per row, all inline in the result table.
Textplot is a CLI ergonomics tool. Character-grid resolution caps the detail you can see β these plots are for quick visual checks during interactive SQL, not a replacement for Matplotlib, Vega-Lite, or a proper BI dashboard. When you need axes, hover, drilldown, or pixels, use a real charting tool. When you just want to know the shape of a column without leaving the terminal, use this.
How it fits the CLI workflow
Every function returns a single VARCHAR. Thatβs the whole API: any chart you produce is just another column in the result set. It composes with .mode line, .mode markdown, log redirection, tee to a file, or paste into a status email β same as any other string column.
The companion piece for the CLI is fuzzycomplete, which fixes tab-completion for table and column names. Together theyβre the two extensions worth installing for βI live in the DuckDB REPLβ workflows: fuzzycomplete makes typing queries painless, textplot makes reading their output painless.
Function map
| Function | Shape | Use for |
|---|---|---|
tp_bar | Single bar | Progress, percentages, SLA indicators |
tp_sparkline | One-line trend | Time series, KPIs in tables |
tp_density | Distribution plot | Spotting outliers, shape of a column |
tp_qr | QR code | Scannable links in terminal/docs |
Styling vocabulary
The named-parameter surface is shared in spirit across functions:
- Shapes / styles / themes β
shape := 'circle' | 'square' | 'heart'ontp_bar; named character-set styles ontp_density(shaded,ascii,dots,height,circles,rainbow_circle, β¦); per-mode themes ontp_sparkline(utf8_blocks,arrows,faces,slopes, β¦). - Colors β
on_color/off_colorontp_barandtp_density.thresholds := [(threshold, color), ...]gives value-conditional coloring; each block uses the highest threshold it satisfies. - Custom characters β every function with
on/offaccepts arbitrary strings (single chars, emoji, multi-char sequences). Theyβre SQL reserved words β quote them as"on"and"off". - Width β fixed character width keeps charts aligned in tables.
Portability: ASCII vs Unicode
Defaults use Unicode block characters and emoji, which look great in modern terminals but vary by font. For maximum portability:
-- ASCII-only density plot
SELECT tp_density([1, 5, 3, 8, 2], style := 'ascii');
-- ASCII sparkline
SELECT tp_sparkline([1, 3, 2, 5, 4, 6], theme := 'ascii_basic');
-- ASCII bar with custom characters
SELECT tp_bar(0.7, "on" := '#', "off" := '-', width := 15);
-- ASCII QR code
SELECT tp_qr('https://query.farm', "on" := '##', "off" := ' ');
For QR codes specifically, .mode ascii in the CLI gives the cleanest rendering β the default table borders interact poorly with multi-line block output.
When to reach for it (and when not to)
Reach for Textplot when:
- Youβre in the DuckDB CLI doing ad-hoc analysis and want to see a distribution or trend without copying values out.
- You need per-row visual cells in a result table β sparklines per host, bars per SLA, etc.
- The output goes to a terminal, log file, status email, or README.
Reach for a real charting tool when:
- Youβre building an interactive web dashboard with hover/zoom/drilldown.
- You need precise pixel control for publication-quality figures.
- The audience expects polished graphics and the data deserves them.
The two are complementary, not competing β Textplot is the βIβm at a SQL prompt right nowβ tool.
Reference
Extension Contents
Quick reference to all available functions and settings organized by category.
| Name | Description | |
|---|---|---|
| Charts Render numeric values and arrays as Unicode/ASCII charts directly in your query results β bars, sparklines, and density plots that fit in a single VARCHAR cell. | ||
| tp_bar() | Render a horizontal bar showing a numeric value as a fraction of a min/max range | |
| tp_density() | Render an array of numeric values as a density plot β a compact distribution visualization where the character intensity at each x-position reflects how many input values fall in that bin | |
| tp_sparkline() | Render an array of numeric values as a sparkline β a compact one-line trend chart | |
| QR Codes Encode strings as QR codes rendered as Unicode-block art. Useful for dashboards, terminal output, and embedding scannable links in docs. | ||
| tp_qr() | Encode a string as a QR code rendered with Unicode block characters | |
API Reference
Function Documentation
Detailed documentation for each function including signatures, parameters, and examples.
tp_bar
Signature
Parameters
| Parameter | Type | Mode | Description |
|---|---|---|---|
value | DOUBLE | Positional | Numeric value to visualize. |
min | DOUBLE | Named | Default: 0 Minimum of the value range. |
max | DOUBLE | Named | Default: 1.0 Maximum of the value range. |
width | INTEGER | Named | Default: 10 Bar width in characters. |
shape | VARCHAR | Named | Default: 'square' Block shape: 'square', 'circle', or 'heart'. |
on_color | VARCHAR | Named | Color name for filled portion (e.g. 'red', 'green', 'blue', 'yellow'). |
off_color | VARCHAR | Named | Color name for unfilled portion. |
on | VARCHAR | Named | Custom character for filled portion. Must be quoted in SQL β "on" β because on is a reserved keyword. |
off | VARCHAR | Named | Custom character for unfilled portion. Must be quoted as "off". |
filled | BOOLEAN | Named | Default: true Fill all blocks up to the value (true) or just mark the endpoint (false). |
thresholds | STRUCT(threshold DOUBLE, color VARCHAR)[] | Named | List of (threshold, color) pairs for value-dependent coloring; each block uses the highest threshold β€ value. |
Returns
Description
Render a horizontal bar showing a numeric value as a fraction of a min/max range. Configurable width, shape (square/circle/heart), foreground/background colors, custom characters, and threshold-based coloring. Returns a VARCHAR you can drop into any SELECT.
Examples
Simple progress bar (50% filled)
SELECT tp_bar(0.5); Output
| tp_bar(0.5) |
|---|
| π₯π₯π₯π₯π₯β¬β¬β¬β¬β¬ |
Custom width and range β 75 out of 100
SELECT tp_bar(75, min := 0, max := 100, width := 20); Output
| tp_bar(75, min := 0, max := 100, width := 20) |
|---|
| π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯β¬β¬β¬β¬β¬ |
Circle shape with explicit colors
SELECT tp_bar(0.8, shape := 'circle', on_color := 'green', off_color := 'white'); Output
| tp_bar(0.8, shape := 'circle', on_color := 'green', off_color := 'white') |
|---|
| π’π’π’π’π’π’π’π’βͺβͺ |
Threshold-based coloring (85 out of 100 β yellow zone)
SELECT tp_bar(85, min := 0, max := 100, thresholds := [{'threshold': 90, 'color': 'red'}, {'threshold': 70, 'color': 'yellow'}, {'threshold': 0, 'color': 'green'}]); Output
| tp_bar(85, min := 0, max := 100, thresholds := main.list_value(main.struct_pack(threshold := 90, color := 'red'), main.struct_pack(threshold := 70, color := 'yellow'), main.struct_pack(threshold := 0, color := 'green'))) |
|---|
| π¨π¨π¨π¨π¨π¨π¨π¨π¨β¬ |
Custom characters ("on" and "off" must be quoted)
SELECT tp_bar(0.7, "on" := 'β', "off" := 'β', width := 15); Output
| tp_bar(0.7, "on" := 'β', "off" := 'β', width := 15) |
|---|
| βββββββββββββββ |
Heart shape
SELECT tp_bar(0.6, shape := 'heart', on_color := 'red'); Output
| tp_bar(0.6, shape := 'heart', on_color := 'red') |
|---|
| β€οΈβ€οΈβ€οΈβ€οΈβ€οΈβ€οΈπ€π€π€π€ |
Related Functions
- tp_density() β Render an array of numeric values as a density plot β a compact distribution visualization where the character intensity at each x-position reflects how many input values fall in that bin
- tp_sparkline() β Render an array of numeric values as a sparkline β a compact one-line trend chart
tp_density
Signature
Parameters
| Parameter | Type | Mode | Description |
|---|---|---|---|
values | DOUBLE[] | Positional | Array of numeric values to plot the distribution of. |
width | INTEGER | Named | Default: 20 Plot width in characters. |
style | VARCHAR | Named |
Default: 'shaded' Character-set style: 'shaded' (default ββββ), 'ascii' ( .:+#@), 'dots' ( .β’β), 'height' ( βββββ
βββ), 'circles' (β«βͺπ‘π π΄), 'safety', 'rainbow_circle' (β«π€π£π΅π’π‘π π΄βͺ), 'rainbow_square', 'moon', 'sparse', or 'white'. |
graph_chars | VARCHAR[] | Named | Custom array of characters for density levels β overrides style. |
marker | VARCHAR | Named | Character used to highlight specific values inside the plot. |
Returns
Description
Render an array of numeric values as a density plot β a compact distribution visualization where the character intensity at each x-position reflects how many input values fall in that bin. Multiple character-set styles, including ASCII for terminal compat and emoji for visual punch.
Examples
Default shaded style
SELECT tp_density([1, 2, 3, 4, 5, 4, 3, 2, 1]); Output
| tp_density(main.list_value(1, 2, 3, 4, 5, 4, 3, 2, 1)) |
|---|
| β β β β β |
Wide height-style plot
SELECT tp_density([1, 2, 3, 2, 1], width := 30, style := 'height'); Output
| tp_density(main.list_value(1, 2, 3, 2, 1), width := 30, style := 'height') |
|---|
| β β β |
ASCII style for terminals without Unicode
SELECT tp_density([1, 5, 3, 8, 2], style := 'ascii'); Output
| tp_density(main.list_value(1, 5, 3, 8, 2), style := 'ascii') |
|---|
| @ @ @ @ @ |
Dot style for subtle visualization
SELECT tp_density([1, 5, 3, 8, 2], style := 'dots'); Output
| tp_density(main.list_value(1, 5, 3, 8, 2), style := 'dots') |
|---|
| β β β β β |
Rainbow emoji style
SELECT tp_density([1, 5, 3, 8, 2], style := 'rainbow_circle'); Output
| tp_density(main.list_value(1, 5, 3, 8, 2), style := 'rainbow_circle') |
|---|
| βͺβ«βͺβ«β«βͺβ«β«β«β«β«βͺβ«β«β«β«β«β«β«βͺ |
Related Functions
tp_qr
Signature
Parameters
| Parameter | Type | Mode | Description |
|---|---|---|---|
value | VARCHAR | Positional | String value to encode. |
ecc | VARCHAR | Named | Default: 'low' Error correction level: 'low', 'medium', 'quartile', or 'high'. Higher levels can survive more damage at the cost of larger QR codes. |
on | VARCHAR | Named |
Default: 'β¬' Character for filled modules. Must be quoted as "on" (reserved keyword). |
off | VARCHAR | Named |
Default: 'β¬' Character for empty modules. Must be quoted as "off". |
Returns
Description
Encode a string as a QR code rendered with Unicode block characters. Configurable error-correction level and custom on/off characters for styling.
Examples
Basic QR code
SELECT tp_qr('Hello, World!'); Output
| tp_qr('Hello, World!') |
|---|
β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ |
High error correction (more redundancy, larger code)
SELECT tp_qr('https://query.farm', ecc := 'high'); Output
| tp_qr('https://query.farm', ecc := 'high') |
|---|
β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬β¬ |
Custom emoji styling
SELECT tp_qr('https://query.farm', ecc := 'high', "on" := 'π‘', "off" := 'β«'); Output
| tp_qr('https://query.farm', ecc := 'high', "on" := 'π‘', "off" := 'β«') |
|---|
π‘π‘π‘π‘π‘π‘π‘β«π‘β«π‘β«π‘β«π‘β«β«β«π‘β«π‘β«π‘π‘π‘π‘π‘π‘π‘ π‘β«β«β«β«β«π‘β«π‘β«β«β«π‘β«π‘β«π‘β«β«π‘β«β«π‘β«β«β«β«β«π‘ π‘β«π‘π‘π‘β«π‘β«π‘π‘π‘π‘π‘π‘β«π‘π‘β«β«π‘π‘β«π‘β«π‘π‘π‘β«π‘ π‘β«π‘π‘π‘β«π‘β«β«β«β«π‘π‘π‘β«β«π‘π‘β«π‘β«β«π‘β«π‘π‘π‘β«π‘ π‘β«π‘π‘π‘β«π‘β«β«β«π‘π‘π‘π‘π‘π‘β«β«β«π‘π‘β«π‘β«π‘π‘π‘β«π‘ π‘β«β«β«β«β«π‘β«π‘β«β«π‘β«β«π‘β«π‘β«π‘β«π‘β«π‘β«β«β«β«β«π‘ π‘π‘π‘π‘π‘π‘π‘β«π‘β«π‘β«π‘β«π‘β«π‘β«π‘β«π‘β«π‘π‘π‘π‘π‘π‘π‘ β«β«β«β«β«β«β«β«π‘π‘β«π‘β«π‘π‘π‘π‘β«β«β«π‘β«β«β«β«β«β«β«β« β«β«π‘π‘π‘β«π‘β«π‘π‘π‘π‘β«β«π‘π‘π‘β«π‘β«π‘π‘π‘π‘β«β«π‘π‘π‘ π‘π‘π‘π‘β«π‘β«π‘β«π‘π‘π‘β«π‘β«π‘β«β«β«β«β«π‘β«β«π‘π‘π‘β«π‘ π‘π‘π‘β«β«π‘π‘π‘π‘β«β«π‘β«β«π‘β«π‘π‘π‘β«π‘π‘π‘π‘π‘β«π‘β«β« π‘β«β«β«β«β«β«β«π‘π‘β«π‘β«π‘β«β«β«β«β«β«β«π‘π‘π‘β«π‘β«π‘β« β«π‘π‘π‘π‘π‘π‘β«β«β«β«π‘π‘π‘π‘π‘β«β«π‘β«π‘π‘β«π‘β«β«π‘π‘π‘ π‘β«π‘β«π‘π‘β«β«β«β«β«β«π‘π‘π‘β«π‘π‘β«π‘π‘π‘π‘β«π‘π‘β«π‘π‘ β«β«β«π‘β«β«π‘β«π‘π‘β«π‘π‘β«β«β«π‘β«β«β«π‘β«π‘π‘β«β«β«π‘β« β«β«π‘β«β«π‘β«β«π‘β«π‘π‘β«β«π‘π‘π‘β«π‘β«β«π‘π‘β«π‘π‘β«β«β« β«π‘β«β«π‘π‘π‘π‘β«β«β«β«π‘β«β«β«β«π‘π‘β«β«β«π‘β«β«π‘π‘π‘π‘ π‘π‘π‘β«β«π‘β«π‘π‘β«π‘β«β«β«π‘π‘β«π‘β«β«π‘π‘β«π‘π‘π‘β«π‘π‘ π‘β«β«π‘β«π‘π‘π‘β«β«π‘π‘β«β«β«π‘π‘β«π‘π‘β«β«π‘β«β«β«π‘β«β« π‘β«π‘β«β«β«β«β«π‘π‘π‘π‘π‘π‘π‘β«β«β«β«π‘π‘β«π‘β«π‘π‘β«π‘π‘ π‘β«π‘π‘β«π‘π‘β«β«β«π‘β«π‘π‘β«β«β«β«β«π‘π‘π‘π‘π‘π‘β«π‘π‘β« β«β«β«β«β«β«β«β«π‘π‘β«β«π‘β«β«β«π‘π‘π‘π‘π‘β«β«β«π‘π‘β«β«π‘ π‘π‘π‘π‘π‘π‘π‘β«β«β«β«β«β«β«π‘β«β«β«π‘π‘π‘β«π‘β«π‘β«β«β«β« π‘β«β«β«β«β«π‘β«β«π‘β«β«π‘β«π‘π‘π‘π‘β«π‘π‘β«β«β«π‘π‘β«π‘π‘ π‘β«π‘π‘π‘β«π‘β«π‘π‘π‘π‘β«π‘β«π‘β«π‘β«β«π‘π‘π‘π‘π‘π‘π‘π‘β« π‘β«π‘π‘π‘β«π‘β«π‘π‘β«π‘β«π‘π‘β«π‘π‘π‘π‘π‘π‘β«π‘β«β«β«β«β« π‘β«π‘π‘π‘β«π‘β«π‘π‘β«β«β«β«π‘π‘β«β«β«π‘π‘π‘π‘π‘π‘π‘β«π‘β« π‘β«β«β«β«β«π‘β«β«β«β«β«π‘π‘β«π‘π‘β«π‘π‘β«π‘β«β«π‘π‘β«π‘β« π‘π‘π‘π‘π‘π‘π‘β«β«π‘π‘β«π‘π‘β«β«π‘π‘β«β«β«β«β«β«π‘π‘π‘β«β« |
tp_sparkline
Signature
Parameters
| Parameter | Type | Mode | Description |
|---|---|---|---|
values | DOUBLE[] | Positional | Array of numeric values, ordered (typically by time). |
mode | VARCHAR | Named | Default: 'absolute' Visualization mode: 'absolute' (height β value), 'delta' (up/same/down), or 'trend' (signed magnitude). |
theme | VARCHAR | Named | Character-set theme. Options vary by mode β absolute: utf8_blocks, ascii_basic, hearts, faces. delta: arrows, triangles, ascii_arrows, math, faces, thumbs, trends, simple. trend: arrows, ascii, slopes, intensity, faces, chart. |
width | INTEGER | Named | Default: 20 Sparkline width in characters. |
Returns
Description
Render an array of numeric values as a sparkline β a compact one-line trend chart. Three modes: absolute (height β value), delta (direction of change), trend (direction with magnitude). Each mode has multiple themes: arrows, faces, slopes, hearts, and more.
Examples
Absolute mode (default)
SELECT tp_sparkline([1, 3, 2, 5, 4, 6, 2, 1]); Output
| tp_sparkline(main.list_value(1, 3, 2, 5, 4, 6, 2, 1)) |
|---|
| ββββββββ β β βββββ |
Stock prices with explicit theme and width
SELECT tp_sparkline([45.2, 47.1, 46.8, 49.3, 52.1, 48.7], width := 20, theme := 'utf8_blocks'); Output
| tp_sparkline(main.list_value(45.2, 47.1, 46.8, 49.3, 52.1, 48.7), width := 20, theme := 'utf8_blocks') |
|---|
| βββββββ β β β ββββββ |
Delta mode with arrow theme
SELECT tp_sparkline([100, 105, 102, 108, 95], mode := 'delta'); Output
| tp_sparkline(main.list_value(100, 105, 102, 108, 95), "mode" := 'delta') |
|---|
| ββββββββββββββββββββ |
Delta mode with face theme
SELECT tp_sparkline([72, 75, 75, 78, 71], mode := 'delta', theme := 'faces'); Output
| tp_sparkline(main.list_value(72, 75, 75, 78, 71), "mode" := 'delta', theme := 'faces') |
|---|
| ππππππππππππππππππππ |
Trend mode with slope theme
SELECT tp_sparkline([10, 12, 11, 15, 8], mode := 'trend', theme := 'slopes'); Output
| tp_sparkline(main.list_value(10, 12, 11, 15, 8), "mode" := 'trend', theme := 'slopes') |
|---|
| /////\\\\\/////\\\\\\\\\\ |
Trend mode with intensity theme
SELECT tp_sparkline([100, 110, 105, 125, 90], mode := 'trend', theme := 'intensity'); Output
| tp_sparkline(main.list_value(100, 110, 105, 125, 90), "mode" := 'trend', theme := 'intensity') |
|---|
| +++++-----+++++---------- |
Related Functions
- tp_density() β Render an array of numeric values as a density plot β a compact distribution visualization where the character intensity at each x-position reflects how many input values fall in that bin
- tp_bar() β Render a horizontal bar showing a numeric value as a fraction of a min/max range
Practical Examples
Cookbook
Real-world recipes and patterns for common use cases.
Recipes for inline ASCII / Unicode plots in the DuckDB CLI β distributions, trends, bars, and QR codes that live as VARCHAR columns in your SELECT output.
See the shape of a column
The headline use β point an aggregate at tp_density and youβve got a one-line distribution plot in CLI output:
SELECT tp_density(array_agg(value)) AS distribution
FROM measurements;
For a wider plot or a different character set:
SELECT tp_density(array_agg(value), width := 40, style := 'height') AS distribution
FROM measurements;
Available styles: shaded (default), ascii, dots, height, circles, safety, rainbow_circle, rainbow_square, moon, sparse, white. See tp_density for the full parameter list.
Sparkline per row in a status table
The pattern that makes tp_sparkline most useful β pre-aggregate a series per entity, render a sparkline cell:
SELECT host,
tp_sparkline(cpu_history, theme := 'utf8_blocks', width := 24) AS cpu_24h
FROM hosts_status
ORDER BY host;
Three modes change what the sparkline encodes:
-- Absolute mode (default) β height β value
SELECT tp_sparkline([45.2, 47.1, 46.8, 49.3, 52.1, 48.7]);
-- Delta mode β direction of change between consecutive points
SELECT tp_sparkline([100, 105, 102, 108, 95], mode := 'delta');
-- Trend mode β direction with magnitude
SELECT tp_sparkline([100, 110, 105, 125, 90], mode := 'trend', theme := 'intensity');
Threshold-colored health bars
tp_bar with a thresholds list is the natural shape for SLA, disk-utilization, or risk indicators. Each block adopts the highest threshold β€ value:
SELECT host,
disk_pct,
tp_bar(disk_pct, min := 0, max := 100, width := 14,
thresholds := [
{'threshold': 90, 'color': 'red'},
{'threshold': 75, 'color': 'yellow'},
{'threshold': 0, 'color': 'green'}
]) AS disk
FROM hosts_status
ORDER BY disk_pct DESC;
Sort by the underlying number; let the bar carry the visual.
ASCII output for portable terminals
When the destination doesnβt render emoji or wide Unicode reliably (older terminals, log aggregators, plain-text email), pin every function to its ASCII vocabulary:
-- Density
SELECT tp_density([1, 5, 3, 8, 2], style := 'ascii');
-- Sparkline
SELECT tp_sparkline([1, 3, 2, 5, 4, 6], theme := 'ascii_basic');
-- Bar with custom characters ("on" / "off" are reserved β quote them)
SELECT tp_bar(0.7, "on" := '#', "off" := '-', width := 15);
-- QR code in ASCII blocks
SELECT tp_qr('https://query.farm', "on" := '##', "off" := ' ');
Per-row charts on a generated series
Useful when youβre prototyping or want to demo the styling vocabulary without a real dataset:
SELECT round(n, 4) AS value,
tp_bar(n,
width := 14,
shape := 'circle',
off_color := 'black',
filled := true,
thresholds := [
(0.8, 'red'), (0.7, 'orange'), (0.6, 'yellow'), (0.0, 'green')
]) AS bar
FROM (SELECT random() AS n FROM generate_series(1, 10));
QR codes for terminal output
-- Default monochrome
SELECT tp_qr('Hello, World!');
-- High error correction (more redundancy, slightly larger code)
SELECT tp_qr('https://query.farm', ecc := 'high');
-- Custom emoji styling
SELECT tp_qr('https://query.farm', ecc := 'high', "on" := 'π‘', "off" := 'β«');
In the DuckDB CLI, .mode ascii gives the cleanest rendering of QR output β the default table borders interact poorly with the multi-line block grid.
Pair with fuzzycomplete for a CLI-first workflow
Textplot makes CLI output readable; fuzzycomplete makes CLI input tolerable (VS-Code-style fuzzy tab-completion across schemas and databases). Install both if you live in the DuckDB REPL:
INSTALL fuzzycomplete FROM community;
LOAD fuzzycomplete;
INSTALL textplot FROM community;
LOAD textplot;
Tips
- Pick widths to match your output β width 20β30 for dashboards, 10β15 for compact reports, β₯40 for distribution shapes you want to actually read.
- Combine with raw numbers, donβt replace them β the visual is for shape; the number is for precision. Show both.
- Sparklines shine in tables β one cell per row is where they earn their keep.
- ASCII for portability, emoji for impact β emoji needs a Unicode-capable terminal and font.
- For real visualization, use a real tool β Textplot is for eyeballing in the CLI, not for the dashboard you ship to stakeholders.
Platform Support
Compatibility
Extension availability may vary by platform and DuckDB version. Check below to ensure this extension supports your environment before installation.
Quick Facts
Platforms
Supported platform architectures
Compiled binary sizes
| Platform | Architecture | Size |
|---|---|---|
| Linux | aarch64 | 2.72 MB |
| Linux | x86_64 | 3.09 MB |
| Linux (musl) | x86_64 | 2.95 MB |
| macOS | Apple Silicon | 1.88 MB |
| macOS | Intel | 2.19 MB |
| Windows | x86_64 | 7.54 MB |
| WASM | eh | 36.8 KB |
| WASM | mvp | 30.8 KB |
| WASM | threads | 30.8 KB |
Gzipped download size from the DuckDB community-extensions registry.
Charts in Your SELECT Statement
Install Textplot to render bars, sparklines, and density plots inline in any DuckDB query β no notebook, no BI tool, just SQL output.