prismatica
The universal compile-time scientific colormap library for Rust
[!IMPORTANT] v0.1.0 — core types, registry, matplotlib (8 maps) and Crameri (40 maps) are implemented. The API may still change before v1.0.
Status
| Component | Status |
|---|---|
Core types (Color, Colormap, ColormapMeta) |
Done |
| Registry (discovery, filtering) | Done |
| matplotlib (8 maps) | Done |
| Crameri (40 maps) | Done |
| CET (60+ maps) | Planned |
| CMOcean (22 maps) | Planned |
| ColorBrewer (35 palettes) | Planned |
| CMasher (30+ maps) | Planned |
| NCAR NCL (40+ maps) | Planned |
| CartoColors (15+ maps) | Planned |
| Moreland (6 maps) | Planned |
| d3 (d3-scale-chromatic) | Planned |
| Framework integrations | Planned |
Highlights
- 260+ scientific colormaps from 10+ established collections
- Compile-time constants -- zero runtime file I/O, zero parsing
- Zero dependencies -- the core crate has no external dependencies
- Continuous sampling -- linear interpolation between 256-step LUT entries for any
tin[0, 1] - Rich metadata -- every colormap carries its kind, perceptual uniformity flag, CVD safety, and citation
- Feature-gated collections -- include only the maps you need; ~1 KB per colormap
- Framework integrations -- optional support for plotters, egui, image, and serde
- Broad coverage -- matplotlib, Crameri, CET, CMOcean, ColorBrewer, CMasher, NCAR NCL, CartoColors, Moreland, d3
For Everyone
What are scientific colormaps?
A colormap is a function that converts a scalar value (like temperature, elevation, or density) into a color. When you see a weather map where blue means cold and red means hot, that is a colormap at work.
Perceptual uniformity means that equal steps in data produce equal steps in perceived color difference. Without it, your visualization can create false features -- bright bands or phantom boundaries that exist in the colormap but not in the data. The classic "rainbow" and "jet" colormaps are notorious for this.
Prismatica provides every established scientific colormap collection as compile-time Rust data. A researcher writes BATLOW.eval(0.5) and gets a perceptually uniform, colorblind-safe color. No files to load, no dependencies to install.
Quick start
Add prismatica to your project:
[]
= "0.1.0"
Use a colormap:
use BATLOW;
let color = BATLOW.eval;
println!;
Supported collections
| Collection | Author / Organization | Maps | Type |
|---|---|---|---|
| Matplotlib | van der Walt, Smith, Firing | 8 | Sequential, perceptually uniform |
| Crameri | Fabio Crameri | 35+ | Sequential, diverging, multi-sequential, cyclic |
| CET | Peter Kovesi | 60+ | Sequential, diverging, cyclic, rainbow, isoluminant |
| CMOcean | Kristen Thyng | 22 | Oceanographic sequential, diverging |
| ColorBrewer | Cynthia Brewer | 35 | Sequential, diverging, qualitative (discrete) |
| CMasher | Ellert van der Velden | 30+ | Sequential, diverging (astrophysics) |
| NCAR NCL | NCAR | 40+ | Geoscience sequential, diverging |
| CartoColors | CARTO | 15+ | Cartographic sequential, diverging, qualitative |
| Moreland | Kenneth Moreland | 6 | Cool-warm diverging, black body, Kindlmann |
| d3 | Mike Bostock | varies | d3-scale-chromatic |
| Total | ~260+ |
Choosing the right colormap
| Data type | Recommended maps | Why |
|---|---|---|
| Sequential (temperature, elevation) | batlow, viridis, oslo, thermal |
Monotonic luminance, perceptually uniform |
| Diverging (anomalies, residuals) | berlin, vik, balance, cool_warm |
Neutral center, symmetric extremes |
| Cyclic (phase, direction, time-of-day) | romaO, phase, twilight |
End color equals start color |
| Categorical (labels, classes) | SET2, DARK2, PAIRED |
Maximally distinct, non-interpolated |
What prismatica is not
- Not a color manipulation library (use
palettefor that) - Not a gradient builder (use
colorgradfor custom gradients) - Not a rendering engine
- Not a data visualization framework
Prismatica is the data layer. It answers one question: given a colormap name and a scalar t in [0, 1], what RGB color should I use?
For Researchers
The Color type
Methods: new(r, g, b), from_hex(0xFF8800), to_css_hex(), to_f32(), lerp(other, t).
Colormap sampling
use BATLOW;
// Continuous: sample at any float in [0, 1]
let color = BATLOW.eval;
// Rational: the 30th of 100 evenly-spaced samples
let color = BATLOW.eval_rational;
// Reversed direction (zero allocation)
let rev = BATLOW.reversed;
let color = rev.eval; // equivalent to BATLOW.eval(0.8)
// Extract N discrete colors
let legend_colors = BATLOW.colors;
Values outside [0, 1] are clamped. Interpolation is linear in sRGB space, matching matplotlib, ParaView, and most scientific tools.
Colormap discovery
use ;
// Find all perceptually uniform diverging colormaps
let diverging: = all_colormaps
.iter
.filter
.collect;
// Look up by name
let viridis = find_by_name.unwrap;
// Filter by collection
let crameri_maps = filter_by_collection;
Discrete palettes
ColorBrewer qualitative palettes provide distinct, non-interpolated colors for categorical data:
use SET2;
for i in 0..SET2.len
Feature flags
| Feature | Maps | Description |
|---|---|---|
core (default) |
~43 | matplotlib + Crameri -- the maps journals recommend |
matplotlib |
8 | viridis, inferno, magma, plasma, cividis, twilight, mako, rocket |
crameri |
35+ | batlow, berlin, roma, oslo, tokyo, hawaii, and more |
cet |
60+ | CET-L*, CET-D*, CET-C*, CET-R* perceptually uniform maps |
cmocean |
22 | thermal, haline, solar, ice, deep, and 17 more |
colorbrewer |
35 | Blues, RdBu, Set2, Spectral, and more |
cmasher |
30+ | ember, ocean, gothic, neon, and more |
ncar |
40+ | NCAR NCL geoscience colour tables |
cartocolors |
15+ | CARTO cartographic colour schemes |
moreland |
6 | cool-warm, black body, Kindlmann, extended variants |
d3 |
varies | d3-scale-chromatic maps |
all |
~260+ | All collections |
Framework integrations
| Feature flag | Framework | Conversion |
|---|---|---|
plotters-integration |
plotters | Color to RGBColor, palette closures |
egui-integration |
egui | Color to Color32 |
image-integration |
image | Color to Rgb<u8> |
serde-support |
serde | Serialization for ColormapMeta |
Binary size
Each colormap is a 256x3 = 768-byte LUT plus ~200 bytes of metadata. Approximately 1 KB per colormap.
| Feature | Maps | Size |
|---|---|---|
core (default) |
~43 | ~43 KB |
all |
~260 | ~260 KB |
Even with all 260+ colormaps enabled, the total is smaller than a single PNG image.
Metadata
Every colormap carries structured metadata for programmatic filtering:
Citation
If you use prismatica in academic work, please cite the upstream colormap authors. Each colormap's meta.citation field contains the appropriate reference. Key citations:
- Crameri: Crameri, F. (2018). Scientific colour maps. Zenodo. doi:10.5281/zenodo.1243862
- CET: Kovesi, P. (2015). Good Colour Maps: How to Design Them. arXiv:1509.03700
- CMOcean: Thyng, K. M. et al. (2016). True colors of oceanography. Oceanography, 29(3), 10.
- CMasher: van der Velden, E. (2020). CMasher: Scientific colormaps for making accessible, informative and 'cmashing' plots. JOSS, 5(46), 2004.
For Developers [Stubs Available]
Architecture
Prismatica's core data model is simple:
- A lookup table (LUT) of 256 evenly-spaced RGB values, stored as
static [[u8; 3]; 256] - A metadata struct describing the colormap's properties
- A sampling function that interpolates between LUT entries for any input
tin[0, 1]
All colormap data is compiled into the binary as const/static arrays. There is no runtime I/O, no parsing, and no allocation for basic operations.
Module structure
src/
├── lib.rs # Crate-level docs, re-exports, feature gates
├── types.rs # Color, Colormap, ColormapMeta, ColormapKind, DiscretePalette
├── traits.rs # Framework conversion traits (feature-gated)
├── registry.rs # all_colormaps(), find_by_name(), filter functions
│
├── matplotlib/ # Feature: "matplotlib" — 8 maps
│ └── mod.rs
├── crameri/ # Feature: "crameri" — 35+ maps
│ └── mod.rs
├── cet/ # Feature: "cet" — 60+ maps
│ └── mod.rs
├── cmocean/ # Feature: "cmocean" — 22 maps
│ └── mod.rs
├── colorbrewer/ # Feature: "colorbrewer" — 35 palettes
│ └── mod.rs
├── cmasher/ # Feature: "cmasher" — 30+ maps
│ └── mod.rs
├── ncar/ # Feature: "ncar" — 40+ maps
│ └── mod.rs
├── cartocolors/ # Feature: "cartocolors" — 15+ maps
│ └── mod.rs
├── moreland/ # Feature: "moreland" — 6 maps
│ └── mod.rs
└── d3/ # Feature: "d3"
└── mod.rs
Each collection module will contain one .rs file per colormap, auto-generated from upstream LUT data.
Code generation pipeline
Colormaps are not written by hand. A two-stage pipeline generates all colormap source code:
-
Fetch (
cargo xtask fetch): Downloads upstream data (ZIP archives, Python source files) and normalizes todata/{collection}/{name}.csv(256 rows ofR,G,Bas uint8) plusdata/{collection}/{name}.json(metadata). -
Generate (
cargo xtask generate): Reads normalized CSV + JSON and emits Rust source files withconstcolormap definitions andstaticLUT arrays.
Upstream data → cargo xtask fetch → data/*.csv + data/*.json → cargo xtask generate → src/{collection}/*.rs
Data sources
| Collection | Source | Format | License |
|---|---|---|---|
| Crameri | Zenodo | 256x3 CSV (floats 0-1) | MIT |
| CET | colorcet.com | MATLAB .m or CSV |
CC-BY |
| CMOcean | GitHub | Python/NumPy arrays | MIT |
| ColorBrewer | colorbrewer2.org | JSON (3-12 discrete steps) | Apache-2.0 |
| Matplotlib | BIDS/colormap | Python 256x3 float arrays | CC0 |
| CMasher | GitHub | .txt LUT files |
BSD-3 |
| Moreland | kennethmoreland.com | CSV | Public domain / BSD |
| NCAR NCL | ncl.ucar.edu | .rgb integer tables |
Apache-2.0 |
| CartoColors | CARTO | JSON | CC-BY-3.0 |
Testing strategy
- LUT integrity: every LUT has exactly 256 entries with valid RGB values
- Perceptual monotonicity: sequential + perceptually uniform maps have monotonic luminance
- Boundary clamping:
eval(-1.0)==eval(0.0),eval(2.0)==eval(1.0) - Reference values: spot-checks against upstream data (e.g.,
viridis(0.0)==(68, 1, 84)) - No duplicate names: every colormap has a unique canonical name
- Snapshot tests: catalog snapshot via
instato catch regressions
Code quality
Adding new colormaps
To add a new collection:
- Add a fetch function in
xtask/src/main.rsthat normalizes upstream data to 256x3 uint8 CSV + metadata JSON - Run
cargo xtask fetchto populatedata/{collection}/ - Run
cargo xtask generateto emitsrc/{collection}/*.rs - Add a feature flag in
Cargo.toml - Add the
#[cfg(feature = "...")]module declaration inlib.rs - Add the collection to the
allfeature - Update the registry in
registry.rs
Competitive positioning
| Crate | Colormaps | LUT-based | Metadata | Collections | no_std |
|---|---|---|---|---|---|
| colorous | ~40 | Partial | None | d3 only | Yes |
| colorgrad | ~40 | No (interpolated) | None | d3 + custom | No |
| scarlet | ~5 | No (computed) | None | Basic only | No |
| prismatica | 260+ | Yes (256-step const) | Full | 10+ collections | Planned |
Roadmap
| Version | Milestone | Maps |
|---|---|---|
| v0.1 | Core types + matplotlib + Crameri | ~43 |
| v0.2 | CET + CMOcean + ColorBrewer | ~160 |
| v0.3 | CMasher + NCAR + CartoColors + Moreland | ~260+ |
| v0.4 | Framework integrations, serde, gallery generator | ~260+ |
| v1.0 | Stable API, upstream sync CI, WASM, benchmarks | ~260+ |
Minimum supported Rust version
Rust edition 2024, targeting stable Rust 1.85+.
Support
If chromata is useful to your projects, consider supporting development via thanks.dev.
License
This project is licensed under the GNU General Public License v3.0.
Prismatica bundles colormap data from multiple upstream sources, each with its own license. All upstream licenses are permissive (MIT, Apache-2.0, CC-BY, CC0, BSD-3, public domain) and compatible with GPL-3.0. See individual collection module docs for source URLs and license details.