Conversation
Adds a new plot type for visualising multivariate categorical and continuous data on a grid, where each cell shows up to 6 dots arranged in a canonical die-face layout. Ports the rendering logic from the ggdiceplot R package (v1.2.0), including column-major grid positions, tight-packing pip radius calculation, and pip_scale offset shrinkage. Three input modes: - Categorical (with_records): per-dot CSS colour, absent dots omitted - Continuous (with_points): colourmap tile background, hollow absent dots - Per-dot continuous (with_dot_data): per-dot colourmap + radius scaling Legend support: spatial-position legend (mini die faces), categorical colour legend, and size legend sections, stacked vertically.
- docs/src/plots/diceplot.md: full documentation page with usage examples for categorical, per-dot continuous, and ZEBRA domino modes - docs/src/assets/diceplot/: three SVG examples - docs/src/SUMMARY.md: added Dice Plot entry after Dot Plot - docs/src/gallery.md: gallery card with mirna_compound preview - CHANGELOG.md: entry under [Unreleased]
11 tests covering all three rendering modes (categorical, continuous tile, per-dot continuous), legend variants (position, colour, size, colorbar, stacked), edge cases (empty data, absent dots omitted), and all ndots variants (1-6).
Adds **DicePlot**, a new plot type based on the dice-face grid design from the R package [ggdiceplot](https://github.com/matthias-da/ggdiceplot) v1.2.0. It is intended for compact visualization of multivariate categorical and continuous data, with each grid cell showing up to 6 dots in a canonical die-face layout. The implementation supports three input modes: - **Categorical** (`with_records`): per-dot CSS colour, absent dots omitted - **Continuous** (`with_points`): colourmap tile background, hollow absent dots - **Per-dot continuous** (`with_dot_data`): per-dot colourmap + proportional radius (ZEBRA/domino style) I’m happy to adapt the implementation if there is a preferred approach for the legend or rendering integration. **Note on `render.rs` size:** the diff is relatively large (+505 lines) because DicePlot requires a custom stacked legend (position + colour + size sections) that does not fit the existing generic legend path. I kept this in `render.rs` rather than introducing a separate file only for this logic, but I’m happy to restructure it if that would fit the codebase better. **Note on example data:** the examples and tests use fairly verbose inline data. These datasets are only used for tests and doc asset generation. ## Type of change - [x] New plot type - [ ] New feature / API addition - [ ] Bug fix - [ ] Documentation / assets only - [ ] Refactor / housekeeping --- ## Checklist ### Library (new plot type) - [x] `src/plot/<name>.rs` — struct + builder methods - [x] `src/plot/mod.rs` — `pub mod` + re-export - [x] `src/render/plots.rs` — `Plot` enum variant + `bounds()` / `colorbar_info()` / `set_color()` - [x] `src/render/render.rs` — `render_<name>()`, added to `render_multiple()` match, `skip_axes` if pixel-space - [x] `src/render/layout.rs` — `auto_from_plots()` extended if categories needed ### Tests - [x] New test file in `tests/` with ≥ basic render + SVG content + legend tests - [x] `cargo test --features cli,full` — all existing tests still pass ### CLI (if applicable) - [ ] `src/bin/kuva/<name>.rs` — Args struct (with `/// doc comment`) + `run()` — N/A, input too structured for CLI - [ ] `src/bin/kuva/main.rs` — module, Commands variant, match arm — N/A - [ ] `scripts/smoke_tests.sh` — at least one invocation — N/A - [ ] `tests/cli_basic.rs` — SVG output test + content verification test — N/A - [ ] `docs/src/cli/index.md` — subcommand entry — N/A - [ ] `man/kuva.1` — regenerated (`./target/debug/kuva man > man/kuva.1`) — N/A ### Documentation - [x] `examples/<name>.rs` — Rust example for doc asset generation - [x] `scripts/gen_docs.sh` — invocations added; `bash scripts/gen_docs.sh` runs clean - [x] `docs/src/plots/<name>.md` — documentation page with embedded SVGs - [x] `docs/src/SUMMARY.md` — link added - [x] `docs/src/gallery.md` — gallery card added - [ ] `README.md` — plot types table updated ### Visual inspection - [x] Opened `test_outputs/` — new plot SVGs look correct - [x] Scanned neighbouring plots in `test_outputs/` for layout regressions - [x] `bash scripts/smoke_tests.sh` — all existing smoke test outputs still look correct - [x] No text clipped, no legend overlap, no spurious axes on pixel-space plots ### Housekeeping - [x] `CHANGELOG.md` — entry added under `## [Unreleased]` - [ ] `README.md` — item marked done in TODO section if applicable
- Rotated text (e.g. Z-axis ticks) was hardcoded to column 0; now uses actual x position via to_cx(). - Fill paths with a stroke were skipping the fill; render fill first, then stroke. - Relax ylabel test assertion to check first 5 columns instead of column 0 only.
Introduces Scatter3DPlot and Surface3DPlot — the first 3D plot types in kuva. Built on a shared orthographic projection module and open-box wireframe renderer. New modules: - projection.rs: 3D→2D orthographic projection with configurable azimuth/elevation, front-corner detection, depth sorting - plot3d.rs: shared Box3DConfig and DataRanges3D types - scatter3d.rs: depth-sorted point rendering with z-colormap, per-point colors/sizes, depth shading, marker shapes - surface3d.rs: quad mesh with painter's algorithm, z-colormap, wireframe overlay, NaN-safe grid handling Also includes: - CLI subcommands (scatter3d, surface3d) with --resolution upsampling - Consolidate parse_colormap into shared data.rs (was duplicated 6x) - ColorMap::map_rgb() fast path avoiding String allocation - Manual Debug impl for ColorMap - Examples, docs, tests, smoke tests, changelog, man page
## Description Adds **Scatter3DPlot** and **Surface3DPlot** — the first 3D plot types in kuva. Introduces a reusable orthographic projection module (`projection.rs`), shared 3D infrastructure (open-box wireframe, grid, rotated tick/axis labels), and two complete plot types with CLI subcommands, docs, examples, and tests. Also includes: - Consolidation of `parse_colormap` into shared `data.rs` (was duplicated 6×) - `ColorMap::map_rgb()` fast path to avoid String allocation per face/point - Manual `Debug` impl for `ColorMap` - Fix for terminal backend: rotated text position + fill paths with stroke (pre-existing bugs surfaced by 3D rendering) ## Type of change - [x] New plot type - [x] New feature / API addition - [x] Bug fix - [ ] Documentation / assets only - [x] Refactor / housekeeping --- ## Checklist ### Library (new plot type) - [x] `src/plot/<name>.rs` — struct + builder methods (`scatter3d.rs`, `surface3d.rs`, `plot3d.rs`) - [x] `src/plot/mod.rs` — `pub mod` + re-export - [x] `src/render/plots.rs` — `Plot` enum variant + `bounds()` / `colorbar_info()` / `set_color()` - [x] `src/render/render.rs` — `render_<name>()`, added to `render_multiple()` match, `skip_axes` if pixel-space - [x] `src/render/layout.rs` — `auto_from_plots()` extended if categories needed ### Tests - [x] New test file in `tests/` with ≥ basic render + SVG content + legend tests (`scatter3d_basic.rs`: 10 tests, `surface3d_basic.rs`: 10 tests) - [x] `cargo test --features cli,full` — all existing tests still pass ### CLI (if applicable) - [x] `src/bin/kuva/<name>.rs` — Args struct (with `/// doc comment`) + `run()` - [x] `src/bin/kuva/main.rs` — module, Commands variant, match arm - [x] `scripts/smoke_tests.sh` — at least one invocation - [x] `tests/cli_basic.rs` — SVG output test + content verification test (5 CLI tests) - [x] `docs/src/cli/index.md` — subcommand entry - [x] `man/kuva.1` — regenerated ### Documentation - [x] `examples/<name>.rs` — Rust example for doc asset generation (`scatter3d.rs`, `surface3d.rs`) - [x] `scripts/gen_docs.sh` — invocations added; `bash scripts/gen_docs.sh` runs clean - [x] `docs/src/plots/<name>.md` — documentation page with embedded SVGs - [x] `docs/src/SUMMARY.md` — link added - [x] `docs/src/gallery.md` — gallery card added - [x] `README.md` — plot types table updated ### Visual inspection - [x] Opened `test_outputs/` — new plot SVGs look correct - [x] Scanned neighbouring plots in `test_outputs/` for layout regressions - [x] `bash scripts/smoke_tests.sh` — all existing smoke test outputs still look correct - [x] No text clipped, no legend overlap, no spurious axes on pixel-space plots ### Housekeeping - [x] `CHANGELOG.md` — entry added under `## [Unreleased]` - [x] `README.md` — item marked done in TODO section if applicable Terminal rendering examples: ### Scatter3DPlot <img width="633" height="539" alt="image" src="https://github.com/user-attachments/assets/27718821-9348-4818-b014-93eae41349fe" /> ### Surface3DPlot <img width="705" height="550" alt="image" src="https://github.com/user-attachments/assets/7dd36671-13d8-49de-8472-a26420a67553" />
…d_font for cli (#71)
The embedded TTF is now stored as a gzip stream and inflated on first
use, cached for the process lifetime via OnceLock. Saves ~400 KB in the
published crate (757 KB raw -> 358 KB compressed) at the cost of a
one-time ~5-10 ms inflate when any backend first loads the font.
Uses flate2 rather than calling miniz_oxide directly so that:
- the on-disk asset is round-trippable with standard tools
(gunzip DejaVuSans.ttf.gz works), and
- we don't own a hand-rolled gzip header parser.
flate2 adds no new transitive crates under the png/pdf features (it's
already pulled in by tiny-skia/usvg); under default features it adds
flate2 + crc32fast, ~30 KB compiled.
The raw .ttf is kept in the repo for regenerating the .gz asset but is
excluded from the published crate.
## Description The embedded TTF is now stored as a gzip stream and inflated on first use, cached for the process lifetime via OnceLock. Saves ~400 KB in the published crate (757 KB raw -> 358 KB compressed) at the cost of a one-time ~5-10 ms inflate when any backend first loads the font. Uses flate2 rather than calling miniz_oxide directly so that: - the on-disk asset is round-trippable with standard tools (gunzip DejaVuSans.ttf.gz works), and - we don't own a hand-rolled gzip header parser. flate2 adds no new transitive crates under the png/pdf features (it's already pulled in by tiny-skia/usvg); under default features it adds flate2 + crc32fast, ~30 KB compiled. The raw .ttf is kept in the repo for regenerating the .gz asset but is excluded from the published crate. @Psy-Fer: I won't be offended in the least if you decide to reject this PR, hold it until after your next release, or decide to re-work it yourself. I made several decisions that could easily be flipped: 1. Kept the original TTF in the repo in addition to the gzipped one. No harm in doing this, but also not a lot of value since `gunzip DejaVuSans.ttf.gz` regenerates it. 2. Used gzip-framing on the file to make it easy to uncompress on the command line should anyone want to 3. But miniz_oxide supports DEFLATE compression, but not with gzip framing ... so I pulled in flate2 (with it's default miniz_oxide backend). It in turns, I believe, only pulls in miniz_oxide and a CRC calculation library that have no further transitive dependencies (also, these are, I think, all pulled in when PNG or PDF features are on) 4. Did _not_ feature gate it. Sine the font byte including isn't feature gated, this felt a bit weird to feature gate and support both compressed/uncompressed fonts embedding but not "no font embedding". The upside is it's simpler to maintain, and the binary is always smaller; the downside is now 3 extra crates always on the compile pathway. On the plus side, the gzipped (using libdeflate-gzip -12) font goes from 757,076 bytes down to 357,932. Even after adding back 30-50kb of flate/crc/miniz object code, we're will shaving around 360kb off the total size. ## Type of change - [ ] New plot type - [ ] New feature / API addition - [ ] Bug fix - [ ] Documentation / assets only - [x] Refactor / housekeeping --- ## Checklist ### Housekeeping - [ ] `CHANGELOG.md` — entry added under `## [Unreleased]` - [ ] `README.md` — item marked done in TODO section if applicable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release merge for v0.2.0