# kuva: a Rust plotting library with 64 plot types and a terminal renderer

> kuva is a scientific plotting library in Rust that produces SVG by default with optional PNG and PDF backends, ships a CLI that renders plots from a shell, and can draw a box plot straight into a terminal. The library is young but the design is careful, with a deliberate minimum supported Rust version policy and a developer note about how the work was produced.

**Psy-Fer/kuva** — A scientific plotting library in Rust

- Repository: https://github.com/Psy-Fer/kuva
- Website: https://psy-fer.github.io/kuva/
- Stars: 887 · Forks: 42
- Language: Rust
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/psy-fer-kuva

## SVG is the format, and PNG and PDF are the extras

The opening line of the README is a specification rather than a slogan. kuva is a scientific plotting library in Rust, with 64 plot types, SVG output, optional PNG and PDF backends, and a CLI binary that renders plots directly from the shell, including in the terminal itself.

The ordering matters. SVG is the base format, and the raster and vector alternatives are feature flags on top of it. The library is distributed on crates.io, documented on docs.rs, and there is a conda-forge package as well, which is a detail worth knowing if your environment is a conda environment rather than a cargo one.

The 64 plot types are the substance of the library, and the repository has an example file for each of them, with generated SVG outputs checked in alongside the source. The example list reads like a taxonomy of what a scientific plotting library is expected to cover: band, bar, boxplot, brick, brick_pop, bump, calendar, candlestick, chord, clustermap, contour, coverage, datetime, density, diceplot, dotplot and ecdf are the ones visible in the repository listing, alongside figure, and the complex and simple overview examples that render everything at once.

The genres tell you the audience. A volcano plot, a ridgeline plot, a Sankey diagram, a brick plot and a bump chart are the vocabulary of genomics, of statistics and of exploratory analysis rather than of dashboards, and the CLI examples in the README are drawn from the same field. The package keywords confirm it, listing plotting, bioinformatics, svg, visualization and science.

So the scope is a scientific figure library, not a charting library for a web application.

## The API is a builder over plain Rust data

The library example in the README is short enough to see the whole design.

```rust
use kuva::prelude::*;

let plot = ScatterPlot::new()
    .with_data(vec![(1.0_f64, 2.0), (3.0, 5.0), (5.0, 4.0)])
    .with_color("steelblue")
    .with_legend("samples");

let plots: Vec<Plot> = vec![plot.into()];
let layout = Layout::auto_from_plots(&plots)
    .with_title("My Plot")
    .with_x_label("X")
    .with_y_label("Y");

let svg = render_to_svg(plots, layout);
std::fs::write("my_plot.svg", svg).unwrap();
```

Three things are worth noticing. The data is plain Rust, a vector of tuples of f64, with no dataframe type and no column store, so the caller owns the data and the library only draws. The builder is method chaining with a `with_` prefix on every setter, which makes a figure read as a sequence of decisions. And the rendering call is a free function taking the plots and the layout, returning the SVG as a string that you write to a file yourself, so the library never touches the filesystem.

That last point is the one that matters for adoption. A plotting library that returns bytes rather than writing files composes with anything, including a web server that serves the SVG, a build script that generates a figure, or a test that compares the output.

The layout is separate from the plot and is derived from the plots, with Layout::auto_from_plots taking the set and returning a layout you can then title and label. So the axis ranges and placement are derived from the data, and the labels and title are separate decorations on top.

## The CLI is the part that changes how you work

The command line interface is what separates kuva from a library you call, and the four examples in the README are the whole manual in miniature.

```bash
kuva scatter data.tsv --x x --y y -o plot.svg
kuva volcano gene_stats.tsv --name-col gene --x-col log2fc --y-col pvalue --top-n 20
kuva ridgeline samples.tsv --group-by group --value expression --overlap 0.6
kuva box samples.tsv --group-col group --value-col expression --terminal
```

A plot type becomes a subcommand, and the columns are named with flags, so the same call reads like a sentence. The volcano example is the clearest statement of the intended domain, where the x and y columns are a log2 fold change and a p value, the names come from a gene column, and the top N labels are requested explicitly.

The input handling is unusually accommodating and it is stated in one paragraph. Input is auto-detected as TSV or CSV, parquet files are also supported, columns can be selected by name or by 0-based index, and you can pipe from standard input by omitting the file argument. Output defaults to SVG on standard output, and to write a file you use -o with a .svg, .png or .pdf extension, with extensions case-insensitive and other extensions rejected.

The terminal rendering is the standout. A box plot drawn with half-block characters into a terminal is not a gimmick, it is the difference between looking at data on a build server and not being able to. The README shows a Sankey diagram rendered in the terminal as a GIF, so the approach extends well beyond box plots.

And because the CLI reads standard input, a plot of a parquet file or a database dump becomes a pipe away, which is what makes it usable inside a data pipeline rather than only in a notebook.

## Two Rust version floors, and the reason for one of them

The install and feature documentation contains a piece of engineering judgement that is unusual to see explained at all, and it is worth reading closely before you pin a toolchain.

The crate declares edition 2021 and rust-version 1.87 as its own minimum supported Rust version. The PDF feature, however, needs more. It depends on krilla, the maintained successor to the now-archived svg2pdf, and krilla requires Rust 1.92 or newer. The note is explicit about why that floor is not reflected in the crate-level rust-version: doing so would force every user, including those who never enable pdf, onto the newer toolchain through Cargo's rust-version-aware dependency resolver. If you do not enable pdf or full, kuva itself still only needs Rust 1.87.

The manifest carries the same information in machine readable form, with a package.metadata.msrv table that names the PDF feature's real minimum separately from the package rust-version, with a comment saying it should be bumped in lockstep with krilla's own MSRV. The typst-math feature has its own, lower floor of 1.89 as of typst 0.14.

The result is a feature matrix where the cheapest path is the default, SVG only, at Rust 1.87. Enabling raster or vector output beyond SVG raises the floor, and the extras are deliberately kept out of the full feature set. The README lists the opt-in extras separately from the optional backends, with the typst-math feature described as deliberately not in full, because it needs the Typst compiler and a newer toolchain than the base.

The typecheck script and the CI badge are also visible in the repository listing, along with a rust-toolchain.toml, a rustfmt.toml and a Cargo.lock, so the toolchain used in CI is pinned in the repository rather than left to the contributor.

## Packaging is the boring part, and it is handled well

Installing the CLI is a two step process, and the first step is the one everybody already knows.

```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# then restart your shell, or run:
source ~/.cargo/env
```

Then the crate itself, with the feature flags spelled out for the three different cases:

```bash
cargo install kuva --features cli
cargo install --path . --features cli
cargo install --path . --features cli,full
```

The comment on the first line is the detail that matters in practice, that after installation kuva is available anywhere in your shell, with no need to reference ./target/release/kuva or to set any paths manually. That is the difference between a tool you will actually use in a shell and one you will only run from a terminal in a repository.

For the library the Cargo.toml entries mirror the same choices:

```toml
[dependencies]
kuva = "0.6"

kuva = { version = "0.6", features = ["png"] }
kuva = { version = "0.6", features = ["full"] }
```

One packaging constraint is worth knowing about, because it shapes what you can find in the crate. The Cargo.toml excludes the raw TTF and OTF font files, the mdBook documentation tree, the generated example outputs and the helper scripts, and the comment gives the reason: only the gzip-compressed font files are needed at compile time because they are embedded with include_bytes, and excluding the rest keeps the package within the crates.io 10 MB limit. The consequence for a user is that the examples directory in the repository is richer than the one in the published crate.

## The developer note says who wrote what

There is a development note at the end of the README, and it is unusual enough that it deserves to be read rather than skimmed.

kuva was initially built by hand, with a working library and several plot types already in place before AI tooling was introduced. From that point, development was heavily assisted by Claude, accelerating the addition of new plot types, the CLI binary, tests and documentation. The architecture, domain knowledge and direction remain the author's own, and Claude was used as an accelerant rather than an author. The disclaimer itself says it was written by Claude as an honest assessment of its own role in the project.

That is a more informative disclosure than the usual vague acknowledgement, because it names which parts of the project are hand written and which were generated. The core library and the early plot types are the author's; the breadth of plot types, the CLI, the test suite and the documentation are where the tooling was applied.

It also changes how you should read the repository. The example directory with a file per plot type, the tests directory and the docs tree are the parts most likely to have been produced with assistance, so they are where you should look most carefully if you are going to contribute. The architecture is the part to read to understand the design, and the repository has an ARCHITECTURE.md at the root alongside a CHANGELOG.md, a CONTRIBUTING.md, a rustfmt.toml, a benches directory, a man directory and a scripts directory, plus a test_outputs directory that suggests generated output files are checked in for comparison.

The licence is MIT, and the author is James M. Ferguson.

One more thing in the README points at the wider ecosystem, with a single entry: charton, a general purpose plotting library implementing the Grammar of Graphics in Rust, with a declarative API similar to ggplot2 or Altair, first class support for Polars, integration with the Jupyter and evcxr ecosystem, and the ability to interoperate with Matplotlib or Altair for hybrid workflows. That is the closest comparison in the documentation, and the difference in approach is worth noting, because charton builds charts by composing layers while kuva builds them by configuring one plot type and rendering it.

## Where kuva is the wrong choice

Three situations rule it out.

The first is interactive notebook work. The ecosystem section names charton as offering integration with the Jupyter and evcxr ecosystem, and kuva does not claim that. A scientist working in a notebook, calling a plot function and having the figure appear inline, needs a library with an in-process display path, and kuva's model is that you produce a file. It is a pipeline tool, not a notebook tool, and the difference shows up the moment you want to iterate on a figure without re-running the pipeline.

The second is a publication workflow that needs PDF and a toolchain below Rust 1.92. The PDF feature is the one most people building figures for a paper will want, and it is also the one that raises the compiler floor, because it pulls in krilla. A team pinned to an older Rust for other reasons cannot have PDF output without upgrading, and the README is explicit that this is a deliberate choice rather than an oversight.

The third is if you need annotations, interactivity or a charting library for a web page. The 64 plot types cover the static scientific figure vocabulary, and the output is a file. There is no mention of interactivity, tooltips or a web component, and there is no charting library in the ecosystem list that suggests the author is planning one.

The maintenance record is worth a look for a project this young. The recent releases are v0.6.0 on 2026-10-15, v0.5.0 on 2026-08-07 and v0.4.1 on the same day, and the last push to the repository was on 2026-10-19. So the project is moving fast, which is the right pace for something pre-1.0, and it also means the API can move with it. A team embedding kuva in a pipeline should expect to read the CHANGELOG.md before each upgrade rather than to bump a version and hope.

## Conclusion

kuva fits a Rust data pipeline that needs plots without pulling in a Python runtime or a browser, and it fits a scientist who wants a figure file they can commit next to the code that produced it. It is the wrong choice for interactive notebook work, where an in-process plotting API is what you want, and it is the wrong choice if you need PDF output on a toolchain below Rust 1.92, because the PDF backend raises the floor for everyone who enables that feature. Before adopting it, read ARCHITECTURE.md in the repository root, check the feature flags you actually need against the two different Rust version floors, and decide whether SVG is enough for your publication workflow, since it is the only backend with no additional dependency.

## FAQ

### What is kuva and what does it produce?

It is a scientific plotting library in Rust with 64 plot types. SVG is the default output, PNG and PDF are available as optional backends behind feature flags, and there is a CLI binary that renders plots from the shell, including directly in a terminal.

### How do I install the kuva CLI?

Install Rust with rustup first, then run cargo install kuva --features cli from crates.io, or cargo install --path . --features cli from a local clone for SVG output only, or with the full feature for PNG and PDF. After installation kuva is on your PATH, so you do not need to reference the built binary directly.

### What Rust version does kuva need?

kuva's own minimum supported Rust version is 1.87. The pdf feature depends on krilla and needs Rust 1.92 or newer, and the opt-in typst-math feature needs 1.89. The higher PDF floor is deliberately not set as the crate-level rust-version so that users who never enable it are not forced onto the newer toolchain.

### What input formats does the kuva CLI accept?

Input is auto-detected as TSV or CSV, and parquet files are also supported. Columns can be selected by name or by 0-based index, and you can pipe from standard input by omitting the file argument. Output goes to standard output as SVG unless you use -o with a .svg, .png or .pdf extension.

### Is kuva still being developed?

The last push to the repository was on 2026-10-19 and the repository is not archived, with v0.6.0 released on 2026-10-15 after v0.5.0 and v0.4.1 on 2026-08-07. The licence is MIT, and the documentation site is psy-fer.github.io/kuva.

## Sources

- [License: MIT](https://github.com/Psy-Fer/kuva/blob/main/LICENSE)
- [Project website](https://psy-fer.github.io/kuva/)
- [Psy-Fer/kuva on GitHub](https://github.com/Psy-Fer/kuva)
- [README](https://github.com/Psy-Fer/kuva/blob/main/README.md)
- [Releases](https://github.com/Psy-Fer/kuva/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/psy-fer-kuva
