Search Shortcut cmd + k | ctrl + k
duckfn_kuva

Statistical plotting in SQL - a thin wrapper around the Rust library kuva, with the statistical semantics of seaborn and ggplot2 but no Python or R environment, on every platform DuckDB runs (console, JVM, Python, Node, browser)

Maintainer(s): shijianjs

Installing and Loading

INSTALL duckfn_kuva FROM community;
LOAD duckfn_kuva;

Example

-- One function: a JSON chart spec in, an SVG document out
SELECT kuva_render('{"series":[{"type":"scatter","data":[{"x":1,"y":2},{"x":3,"y":4},{"x":5,"y":3}]}]}');
-- <svg xmlns="http://www.w3.org/2000/svg" ...> ... </svg>

-- Several series overlay on one set of axes
SELECT kuva_render('{"series":[{"type":"line","data":[{"x":0,"y":1},{"x":1,"y":2},{"x":2,"y":1.5}],"legend":"signal"},{"type":"scatter","data":[{"x":0,"y":1.2},{"x":1,"y":1.8},{"x":2,"y":1.6}],"legend":"observed"}]}');
-- <svg xmlns="http://www.w3.org/2000/svg" ...> ... </svg>

-- Or a multi-panel figure grid
SELECT kuva_render('{"figure":{"rows":1,"cols":2,"panels":[{"series":[{"type":"scatter","data":[{"x":1,"y":2},{"x":2,"y":3}]}]},{"series":[{"type":"histogram","values":[1,2,2,3,3,3,4],"bins":4}]}]}}');
-- <svg xmlns="http://www.w3.org/2000/svg" ...> ... </svg>

-- The chart config and the data can be separate: pass a dataset registry as the second argument
SELECT kuva_render('{"series":[{"type":"scatter","dataset":"d","encode":{"x":"a","y":"b"}}]}',
                   '{"d":[{"a":1,"b":2},{"a":3,"b":4}]}');
-- <svg xmlns="http://www.w3.org/2000/svg" ...> ... </svg>

About duckfn_kuva

Renders statistical charts from SQL. The extension wraps the Rust plotting library kuva (SVG backend only) and exposes it through three scalar functions, so charts work anywhere DuckDB runs - the CLI, a Python or R session, the JVM, or the browser build (DuckDB-Wasm) - from one and the same extension binary. There is no matplotlib or ggplot2 in the loop, and no plotting environment to install.

Function Kind Input -> output
kuva_render(spec) scalar VARCHAR (a JSON chart spec) -> VARCHAR (an SVG document)
kuva_render_terminal(spec) scalar VARCHAR -> VARCHAR (terminal text: braille dots and ANSI colour)
kuva_render_file(spec) scalar VARCHAR -> VARCHAR (the path of the SVG file it wrote; native builds only)

All three take the same JSON spec, and all three also take an optional second argument: a JSON object mapping dataset names to arrays of rows, so the chart config and the data can be built by different things - the config by a host SDK, the data by SQL. A series names a dataset with dataset and maps its columns with encode (roles like x, y, value, name, group); the one-argument form is unchanged.

Their own options stay in the JSON: kuva_render_terminal reads a top-level terminal object (cols / rows / print) and kuva_render_file reads a top-level file object (dir / name / open), which is what lets one statement write a chart to disk and optionally open it in a browser.

The JSON spec uses snake_case keys and describes the drawing rather than one particular chart. The top-level keys are title, x_axis, y_axis, grid, legend, theme, palette, font, annotations, width, height and series. Every series entry carries a type from the table below and declares only the fields that type needs, so a scatter takes data as {"x":…, "y":…} objects with optional x_err / y_err, a bar takes categories plus values (or several named series for grouped and stacked bars), a histogram takes values with optional bins and a kde overlay, a box takes groups of raw values, and a pie takes slices.

Group type values
Distributions histogram histogram2d density ridgeline ecdf qq box violin strip raincloud hexbin heatmap
Relationships & correlation scatter line series band jointplot contour parallel polar ternary quiver
Categorical & comparison bar pie waffle funnel pareto pyramid lollipop slope dot_plot dice_plot venn mosaic rose radar upset
Time series stacked_area streamgraph candlestick waterfall horizon calendar gantt bump
Statistical & model evaluation roc pr survival forest volcano manhattan
Hierarchical & network treemap sunburst network sankey chord phylo clustermap
3D scatter3d surface3d
Composite & utility brick synteny text legend_plot

JSON rather than a STRUCT is deliberate: the series of one figure are heterogeneous, and a STRUCT list cannot hold a mix of them.

Composition works two ways. Several series in one series array are overlaid on a shared set of axes - a line with its scatter points on top, for instance. A top-level figure with rows, cols and panels switches to a multi-panel grid with shared axes and an optional shared legend.

theme selects light, dark, minimal or solarized (or overrides individual colours), palette selects one of the named palettes or an explicit colour list, and annotations adds reference lines, shaded regions and text callouts. grid and legend cover the axis chrome, tick formats, grayscale (accessibility) mode and legend placement; width, height and scale size the canvas.

A malformed spec fails the query with an actionable message rather than returning NULL, so a typo is never silent - for example a bar whose values and categories differ in length is rejected before anything is drawn.

Requires DuckDB 1.3 or newer: the extension is built against DuckDB 1.5.6 headers but declares the C API floor it needs (v1.2.0), so one binary loads into 1.3.2 through 1.5.6 alike. The full field reference and the development notes live in the repository: https://github.com/shijianjs/duckfn-kuva.

Added Functions

function_name function_type description comment examples
kuva_render scalar Renders a complete chart described by a JSON string into an SVG document The full-featured, JSON-based entry point: one JSON object describes canvas, axes, legend, annotations and a heterogeneous list of series; an optional second argument is a JSON object mapping dataset names to arrays of rows, which a series names with dataset and maps with encode [SELECT kuva_render('{"series":[{"type":"scatter","dataset":"sales","encode":{"x":"revenue","y":"profit"}}]}', '{"sales":[{"revenue":10,"profit":3},{"revenue":12,"profit":5}]}'); SELECT kuva_render('{"series":[{"type":"scatter","data":[{"x":1,"y":2},{"x":3,"y":4},{"x":5,"y":3}]}]}')]
kuva_render_file scalar Renders a chart described by a JSON string to an SVG file and returns the file path; the JSON's own file object sets the directory, the file name and whether to open it in a browser Native builds only: writes the SVG to disk (default: a file in the system temp directory, created if the directory is missing) and optionally opens it in the system default browser; file: {dir, name, open} rides along in the JSON, and an optional second argument supplies the data as a JSON dataset registry [SELECT kuva_render_file('{"series":[{"type":"scatter","dataset":"d","encode":{"x":"a","y":"b"}}]}', '{"d":[{"a":1,"b":2},{"a":3,"b":4}]}'); SELECT kuva_render_file('{"file":{"dir":"/tmp/charts","name":"scatter.svg","open":true},"series":[{"type":"scatter","data":[{"x":1,"y":2},{"x":3,"y":4}]}]}')]
kuva_render_terminal scalar Renders a chart described by a JSON string as terminal text (braille dots and ANSI colour); the JSON's own terminal object sets the character grid and whether to print straight to stdout Same JSON as kuva_render, different backend: terminal: {cols, rows, print} — a braille cell is 2x4 dots, defaults are 100x30, and print: true writes to stdout and returns NULL; an optional second argument supplies the data as a JSON dataset registry [SELECT kuva_render_terminal('{"series":[{"type":"bar","dataset":"t","encode":{"name":"k","value":"v"}}]}', '{"t":[{"k":"a","v":4},{"k":"b","v":7}]}'); SELECT kuva_render_terminal('{"terminal":{"cols":100,"rows":30},"series":[{"type":"line","data":[{"x":1,"y":2},{"x":2,"y":3}]}]}')]

Overloaded Functions

This extension does not add any function overloads.

Added Types

This extension does not add any types.

Added Settings

This extension does not add any settings.