Skip to content

kuva v0.6.0

Latest

Choose a tag to compare

@Psy-Fer Psy-Fer released this 15 Oct 05:32
· 1 commit to main since this release
94a09b6

kuva v0.6.0 brings a new genome track to the library: stacked tracks sharing one genomic axis, so coverage depth, variants, amplicons and gene features line up column-for-column down the figure. Also in this release are real 2-D typeset math in labels, proper missing-data handling on the CLI, CIGAR annotation bars for BrickPlot, 15 new colour palettes, and a substantial numerical-stability sweep across the statistical plots (thanks to @eneskemalergin )

Highlights

  • Genome-browser layout (#2) — the new TrackStack primitive and the CoveragePlot preset, plus the kuva coverage subcommand. Tracks are an open extension seam, and PlotTrack turns all 64 plot types into a track.
  • Typst math in labels (opt-in typst-math) — stacked fractions, radicals, large operators, math italic, compiled in-process with no external binary. Plus a zero-dependency TypstBackend that emits CETZ markup for typst compile.
  • Missing, NA and non-finite values on the CLI (#108) — recognised centrally at ingestion, so every plot behaves the same. --na-values, --na-strategy <drop|zero|error>, --clamp-min/--clamp-max.
  • BrickPlot CIGAR bars — per-row CIGAR annotation drawn in read space, with run-dedup, and a CIGAR that does not match its row is rejected rather than drawn.
  • Version stamping — every rendered file records which kuva produced it (SVG comment, PNG Software chunk, PDF creator/producer), so a figure attached to a bug report identifies itself.
  • 15 LTC colour palettes (#112) and Layout::with_tick_font_family for monospace tick labels.
  • Numerical-stability sweep — invalid p-values, degenerate pie charts, unstable LOESS and regression fits, runaway tick generation, and non-deterministic UpSet ordering all fixed.

Breaking changes

  • BrickPlot STRIGAR input format now matches current bladerunner: bijective base-26 multi-character letters, and the | and @ separators are gone. Old-format strings are rejected rather than silently rendered wrong, and reported via the new BrickPlot::strigar_issues(). Regenerate STRIGAR strings from a current bladerunner, or build rows through the builder API.
  • kuva funnel --color-by is renamed --color-mode, resolving a collision with the data-column --color-by used everywhere else. --color-by stays as a hidden alias, so existing scripts keep working.

Upgrading

kuva = "0.6"

The typst-math feature is not part of full (it pulls a ~200-crate tree and needs Rust >= 1.89). The pre-built CLI binaries on this page do ship with it, since a downloaded binary cannot opt in afterwards.


[0.6.0] — 2026-10-15

Added

  • Genome-browser layout: the TrackStack primitive and the CoveragePlot preset (#2) — vertically stacked tracks that share one genomic x-axis, so coverage depth, variants, amplicons and gene features line up column-for-column down the figure. The load-bearing piece is a shared XScale (map/invert): every track is handed the same pixel mapping, and the stack's left gutter is the maximum of all tracks' own left margins, so panels align even when their y-tick labels differ in width. This is why it is a new composition type rather than a Figure mode: each Figure cell derives its own margin_left from its own tick labels, so shared-x panels drift apart. Tracks are an open extension seam (Track trait, not an enum, so a new lane type is one trait impl and no core edits): PlotTrack wraps any Vec<Plot>, turning all 64 plot types into a track, while IntervalTrack (labelled bands, greedy minimum-row tiling for overlapping intervals, per-row and per-item colours, labels omitted when they do not fit) and VariantTrack (typed tick groups) draw directly against the XScale. Plus TrackHeight::{Fixed,Flex} sizing, StackLayer under/overlays with RegionHighlight, explicit Entry::Axis placement, per-track legend sections, a title band, and XAxisFormat::{Numeric,Genomic,DateTime} where Genomic picks a single unit for the whole axis from its magnitude (bp/kb/Mb/Gb) so labels never mix. CoveragePlot is the first consumer and the thing #2 asks for: with_sample, with_variants, with_feature/with_features, with_region/with_regions, with_locus, with_overlaid_samples. CLI: kuva coverage. Features and genes sit above the axis, the x-axis label goes in the left gutter. The only new core plumbing is Layout::force_margin_left/right. CoveragePlot is a composite like Figure, not a Plot enum variant, so the plot-type count stays at 64 and --emit-code does not apply. See Plots → Coverage Plot.

  • BrickPopPlot — a population allele-frequency composite built on BrickPlot: motif bricks per haplotype row, an allele-frequency bar alongside each row (with_frequencies, with_frequency_range, with_freq_bar_color), and optional metric columns (with_metric, with_metric_column, with_metric_values) rendered as colormapped cells with an explicit missing-data colour (with_na_color, taking Vec<Option<f64>> so absent is distinct from zero). sorted_by_frequency() ranks rows by frequency. Like CoveragePlot it is a composite, not a Plot variant. See Plots → Brick Pop Plot.

  • Missing, NA and non-finite value handling on the CLI (#108) — missing values are now recognised centrally at ingestion in DataTable rather than per subcommand, so every plot gets the same behaviour. --na-values <TOKENS> sets the token set treated as missing (comma-separated, negative sentinels allowed, e.g. --na-values -999,NA) on top of the built-in empty/NA/NaN/null/N/A set, and --na-strategy <drop|zero|error> chooses what happens to a row with a missing value in a selected numeric column: drop the row (default), substitute 0, or fail outright (the pre-#108 behaviour). --clamp-min <N> / --clamp-max <N> clip values to a bound, which also gives -inf/inf somewhere finite to land, e.g. capping an infinite -log10(p) with --clamp-max 300. Extended beyond the numeric plots to bar and parallel-coordinates, where a missing value in any axis previously had no defined meaning.

  • Version stamping in output — rendered files now record which kuva produced them, for triaging an issue from an attached figure. All three file backends record it in their format's own metadata slot: SVG writes a <!-- Generated by kuva X.Y.Z --> comment, PNG writes a standard Software tEXt chunk reading kuva X.Y.Z, and PDF sets the document's creator/producer metadata. The version is exposed to library users as kuva::VERSION.

  • BrickPlot CIGAR bars — with_cigars([...]) draws a thin band above each row, coloured by that read's CIGAR operations, so a reader can see which bases are reference-supported and which are insertions or soft clips. Drawn in read space, the same coordinate as the bricks, so an operation always lines up with the bases beneath it: M/=/X/I/S consume read bases and get width, while D/N consume only reference and render as a zero-width caret rather than opening a gap in the row. Consecutive rows sharing a CIGAR collapse to a single bar (with_cigar_dedup, default on) bracketed with an xN count in the gutter (with_cigar_run_bracket), so a block of identical reads states its CIGAR once and a bar appears only where something changes; vertical space is charged only to rows that draw a bar, so an unchanging run keeps the normal pitch. with_sort(BrickSort::Cigar) groups identical CIGARs so dedup draws the fewest possible bars, and BrickSort::Custom(Vec<usize>) accepts an externally computed ranking (e.g. a caller's distance-from-consensus order) without kuva needing to know how it was derived. Op colours are reserved outside the motif palette so a motif brick can never be mistaken for an insertion, and are overridable via with_cigar_colors. The CIGAR covers every base the row draws, flanks included (left flank + STRIGAR + right flank for a with_flanked_strigars row), and the bar starts at the row's first drawn base, so flanking sequence is annotated too; row_base_len(i) returns the length a row's CIGAR must consume. A CIGAR whose read-consuming length disagrees with its row is rejected rather than drawn and reported by cigar_issues(): a bar that is short by a few bases misaligns every operation after the first while still looking like a valid annotation. Supply the CIGAR from whatever produced the row rather than re-deriving it from locus coordinates, since only that producer knows where its own span begins and ends. with_row_height and Figure row sizing both account for the space the bars take, so brick rows keep their requested pixel height. cigar_bar_count() reports how many bars will be drawn.

  • Layout::with_tick_font_family: set the font family of axis tick labels separately from titles, axis labels, and legends, e.g. .with_tick_font_family("monospace") for sequence-context category labels. Tick labels are wrapped in a <g font-family> group, so output without the option is unchanged. Coverage spans every place kuva draws a tick label, not just the cartesian axes: x/y/x2/y2 ticks and Manhattan chromosome labels, plus the pixel-space and composite plots that draw their own (polar ring and theta labels, ternary edge percentages, radar ring values and spoke names, parallel-coordinates per-axis values, 3D x/y/z ticks, and colorbar ticks). Titles stay in the main family throughout, including the ones that sit where a tick label looks like it should be: ternary corner labels, parallel axis names, 3D axis labels, and the rotated colorbar label. Width reservation follows the face: text_metrics gains a FontStyle::Mono table generated from the bundled DejaVu Sans Mono (cargo run --example gen_text_metrics, +509 RLE runs, with the same drift guards as the sans faces), and a tick label in a monospace family is measured against it. A fixed-width face is narrower than DejaVu Sans for digits (0.602 em vs 0.636 em) but nearly twice as wide for thin glyphs (. is 0.602 em vs 0.318 em), so sans-measured margins under-reserved a decimal axis and clipped it; with_font_family("monospace") now reserves tick labels the same way. The family decides, by word ("DejaVu Sans Mono", "ui-monospace") or by a list of common fixed-width faces that carry no mono in their name ("Consolas", "Fira Code", …); "Monotype Corsiva" and other proportional faces keep the sans metrics. The genome-browser composite carries the option itself, since it builds a Layout per track: TrackStack::with_tick_font_family / CoveragePlot::with_tick_font_family set the shared genomic axis and every track's y-axis ticks at once (new Track::set_tick_font_family trait method, defaulted to a no-op so annotation lanes are unaffected). The PDF backend now resolves the generic monospace family to the bundled DejaVu Sans Mono instead of a system font such as Courier New; PNG and terminal output ignore the option exactly as they already ignore with_font_family. See Reference → Layout & Axes.

  • Typst tier for math in labels (new opt-in typst-math feature) — labels containing $...$ may now be typeset with the Typst compiler (linked as a library — no external binary) for real 2-D math: stacked fractions, radicals with vinculum, large operators, math italic. The whole label is compiled and embedded as an SVG fragment (SVG backend), pixmap (PNG), or through the PDF pipeline, with math color following the label color. typst-math is not part of full: it pulls a ~200-crate dependency tree and needs Rust >= 1.89, so full stays the core backend set (embed_font + png + pdf) and the typesetter is enabled explicitly with cargo build --features typst-math. The pre-built CLI binaries on the releases page do ship with it, since a downloaded binary cannot opt in afterwards. Builds on the always-on inline-Unicode lookup tier from 0.3.0, which remains the fallback everywhere: the terminal backend, builds without typst-math, and any label Typst fails to compile (one warning per distinct label). Bundles New Computer Modern Math (~0.75 MB compressed) as the math font. Layout reserves the fragment's measured extent rather than one nominal text line, via a new ComputedLayout::x_label_math_extra (and ComputedLayout is now #[non_exhaustive], so future layout fields are not breaking additions), so a deep descender (the denominator of $\frac{-b \pm \sqrt{b^2-4ac}}{2a}$) clears the canvas bottom with the same gap ordinary text gets. Note: Typst math is not LaTeX (mc is one identifier — write m c). See Reference → Math in Labels.

  • Math in TextPlot bodies (typst tier) — $...$ inside a markdown body is spliced into the wrapped paragraph as a typeset fragment: kuva's own word-wrap treats each fragment as one unbreakable word (sized by its typeset width), surrounding words stay real selectable text in SVG/PDF, punctuation directly after math stays flush ($x^2$.), and a line holding a tall fragment (stacked fraction, radical) grows its leading so neighbouring lines never collide. Terminal output and builds without typst-math lower body math to inline Unicode exactly as before.

  • TypstBackend (opt-in typst feature, zero deps; not in full) — emit a CETZ-based Typst document for external compilation with typst compile; $...$ regions become native Typst math, typeset with fonts matching the surrounding document. Full primitive coverage: Path (arrowheads, chord ribbons, sankey flows, venn outlines) lowers to CETZ merge-path calls with arcs converted to cubic Béziers, and clip regions map to Typst's box(clip: true) — so quiver/network arrowheads and plot-area clipping survive the round trip. Library-only: the CLI's -o still accepts .svg/.png/.pdf only, so .typ is emitted by constructing a TypstBackend in Rust.

  • CoveragePlot coverage-threshold lines — with_coverage_threshold(depth) / with_coverage_threshold_labeled(depth, label) draw a dashed horizontal line at a given depth on every coverage track (e.g. a minimum-coverage cutoff). CLI: kuva coverage --min-coverage <depth> (repeatable). Built on new PlotTrack::with_hline / with_reference_line support in the TrackStack primitive, so any PlotTrack can carry horizontal reference lines in its own y-scale.

  • BrickPlot marker lines — with_vline(x) / with_vline_labeled(x, label) draw a dashed vertical line across all rows at a reference coordinate (locus, primer boundary, variant position), and with_marker_line(ReferenceLine) takes a fully-styled line. Drawn in the brick's own reference-coordinate system, so they respect with_x_offset/with_x_origin. (ReferenceLine/Orientation now derive Debug.)

  • Survival clinical figures. SurvivalPlot gains three survminer-style options for publication-quality Kaplan-Meier figures: .with_risk_table(true) draws a number-at-risk table below the plot (one row per group, counts at each x-axis tick; space is reserved automatically via a new Layout::risk_table_rows / ComputedLayout::risk_table_extra bottom-margin mechanism); .with_median_lines(true) draws dashed median-survival reference lines; and .with_logrank_pvalue(true) computes the log-rank test p-value from the raw data (exact Mantel-Haenszel for two groups, Σ (O−E)²/E approximation for 3+) via a new chi-square survival function, annotating it and overriding any with_pvalue_text. CLI: kuva survival --risk-table --median-lines --logrank.

  • Force-directed (ggrepel-style) label placement. A new LabelStyle::Repel variant runs a shared render_utils::repel_labels force-directed pass (labels pushed off each other and off the points in 2-D, with thin leader lines back to each). Wired into VolcanoPlot and ManhattanPlot (CLI --label-style repel), and ScatterPlot gains per-point labels for the first time: .with_labels(iter) + .with_label_style(…) / .with_repel_labels(), with CLI kuva scatter --label-col <COL> --label-style <nudge|exact|repel>. The Exact/Nudge/Arrow/Repel rendering is now one shared draw_labels path across all three plot types (deduplicating the previous per-plot copies).

  • Scatter marker shapes and LOESS smoothing. MarkerShape gains TriangleDown, Star, Pentagon, and Hexagon (now 10 shapes), lifting every plot that reuses the marker set; a MarkerShape::parse helper and the CLI kuva scatter --marker <shape> flag expose them. A new TrendLine::Loess { span } variant (plus ScatterPlot::with_loess() / with_loess_span(f) and the shared render_utils::loess locally-weighted-regression helper) overlays a LOESS/LOWESS smoother for non-linear trends; CLI --loess / --loess-span <F>. LOESS takes precedence over --trend when both are given.

  • StripPlot::with_horizontal(bool) — horizontal orientation (categories on the Y axis, values on X; seaborn orient='h', ggplot2 coord_flip), completing the horizontal-orientation set alongside BoxPlot/ViolinPlot. Jitter and swarm spread run vertically within each row. The renderer already supported the swap for all three layout styles; this wires the field, bounds(), category-axis labels, and the CLI kuva strip --horizontal (both --y and --group-col modes) through to it.

  • kuva strip --opacity <0..1> — CLI access to marker fill opacity (the existing StripPlot::with_marker_opacity). Semi-transparent markers reveal density where dense points overlap, which matters for real-data strip/beeswarm plots at large N.

  • Histogram distribution modes — five matplotlib/seaborn-parity options on Histogram, all sharing one binning path (so bounds() and the renderer always agree): .with_step(bool) draws outline-only staircases for clean overlays (matplotlib histtype='step'); .with_cumulative(bool) accumulates counts left-to-right (cumulative=True); .with_bin_method(BinMethod::{Sturges,Scott,FreedmanDiaconis}) picks the bin count from the data, overriding .with_bins; .with_weights(Vec<f64>) weights each sample into its bin (weights=); and .with_group(data, color, label) + .with_stacked(bool) stack multiple series per bin (stacked=True, seaborn multiple='stack'). CLI: --step, --cumulative, --bin-method <sturges|scott|fd>, --weight-col <COL>, and --stacked (stacks the --y columns instead of overlaying them). BinMethod/HistGroup are re-exported from kuva::plot.

  • LTC colour palettes (#112) — 15 curated qualitative palettes from loukesio/ltc-color-palettes (MIT), added as Palette constructors (Palette::paloma() … Palette::casa_natal()). CLI --palette accepts each by name with an optional ltc- prefix (e.g. ltc-maya). The Reference → Color Palettes page gains a visual swatch section with copyable hex values.

Changed

  • BrickPlot STRIGAR input format updated to match bladerunner — the motif-run encoding now uses bijective base-26 multi-character letters (so a row may reference more than 26 distinct motifs), and the | and @ separators were dropped. This is a breaking input change, and it now fails loudly. The tokeniser skips characters it cannot use, so an old-format string would otherwise lose its separators and still render: a row with the wrong bricks in the wrong order, indistinguishable from a good one. with_strigars now validates each row first and rejects any carrying | or @, reporting it as StrigarIssue::LegacyFormat via the new BrickPlot::strigar_issues() (mirroring cigar_issues()); characters that are neither a count nor a base-26 letter are reported as StrigarIssue::Unparseable. A rejected row keeps its slot but draws no bricks, and validation runs before the motif-frequency passes so a bad row cannot steer the global canonical and colour assignment shared by every other row. Regenerate STRIGAR strings from a current bladerunner, or construct rows through the builder API instead. Motif colour assignment was also made deterministic (it previously depended on hash iteration order, so the same input could come back with different colours between runs) and the inter-brick gap calculation was fixed.
  • kuva funnel: the color-mode flag is renamed --color-by → --color-mode to remove a naming collision. Everywhere else --color-by selects a data column (ColSpec), but on funnel it chose a bar color mode (uniform/stage/gradient). It is now --color-mode, with --color-by kept as a hidden backward-compatible alias so existing scripts keep working.

Fixed

  • Mosaic cell labels showing both a percentage and a value rendered as one mangled line. MosaicPlot built the label as a single "46.7%\n70" string, but a \n inside one text primitive is not a line break for any backend: SVG collapses it to a space under XML whitespace handling, and the terminal backend rewrites it to U+FFFD along with every other control character (the GHSA-3c48 escape-injection guard), so kuva mosaic --show-values --terminal printed 46.7%<?>70. It also defeated the fit test, which measures the widest line rather than the joined string, so a label that passed could still overflow its cell. The two lines are now separate primitives, stacked and centred as a block. --show-values turns this on by default, since show_percents is already true. Single-line labels are positioned exactly as before.

  • LollipopPlot point labels were clipped when the labelled point was the tallest one. A per-point label is drawn clear of its dot (dot_radius + 4 px, then the glyphs) entirely in pixel space, but bounds() reported only the dot's data value, so the axis maximum landed on the dot itself and the plot-area clip rectangle sliced the top off the text. It bit hardest when the data maximum fell exactly on a tick boundary: auto_nice_range_capped then caps the axis at raw_max + 5 % of the span, which is 11 px of headroom against a label needing about 17 px. (Same shape as the Pareto cumulative-axis clipping fixed in v0.5.0.) bounds() now reserves headroom above the tallest labelled point, and below the lowest labelled point for the below-baseline labels, so the whole label always falls inside the plot area. The canonical case is a mutation landscape whose hotspot is also the most-mutated residue: docs/src/assets/lollipop/mutation_landscape.svg had R248W cut in half.

  • BW mode drew the lightest continuous cells pure white, i.e. invisible. BW mode maps continuous colormaps onto ColorBrewer Greys, whose ramp starts at #ffffff — the canvas colour. For a dense grid (heatmap, clustermap) that was merely faint, but for plots that omit empty cells (HexbinPlot, CalendarPlot) a minimum-value cell was indistinguishable from no cell at all: in a 3,000-point hexbin, 96 of 204 hexagons rendered at or above #f0f0f0 and 30 were pure white. The BW ramp is now floored at #efefef and still reaches full black, so the lightest cells stay visible with essentially the whole tonal range intact. This matches the floor the categorical BW_GREYS sequence already applied for the same reason. Fills and colorbars share one bw_colormap() helper, so a bar always describes the ramp its cells were drawn with. Affects every BW continuous plot: hexbin, heatmap, calendar, contour, clustermap, 2D histogram, dot plot, quiver, 3D surface, and continuous dice plots.

  • BrickPlot CIGAR legend labels were drawn outside the legend box. Legend entries are collected in collect_legend_entries while the legend width is reserved separately in Layout::auto_from_plots, and only the former knew about the CIGAR operation swatches. The box was sized to the widest motif label ("CAG", 25.89 px) while "insertion" needed roughly 54 px, so the op labels ran past the box border and off the canvas edge. Both sites now share a cigar_legend_ops() helper, so the entry list and the width reservation cannot drift apart again.

  • All-numeric column names could not be selected on the CLI (#109) — --x 2024 parses to ColSpec::Index(2024), so a header column literally named 2024 (a year, etc.) was unreachable and failed with an out-of-range error. DataTable::resolve now falls back to a header column named by that number when the index is out of range; in-range indices are untouched, so --x 0 still always means the first column (fully backward compatible). Applied to the TSV/CSV and parquet-projection paths, and the auto axis/series labels (scatter/line/twin-y/parallel/radar) route through the shared resolver so the label shows the real column name.

    • Added explicit disambiguation prefixes on any column argument, parsed centrally in ColSpec::from_str so they work across every subcommand: name:X / col:X force a column name (even an all-digit one, e.g. select a column literally named 1 via --x name:1), and idx:N / index:N / #N force a pure 0-based index that never resolves to a name (an out-of-range idx: errors instead of falling back). A bare token keeps the index-first-with-fallback heuristic.
    • Added column ranges and globs on multi-column arguments (--y, --value-cols, --samples, --axes): index ranges a..b / a..=b / a.. / ..b / .., and header-name globs sum_* / *_A / *mid* (* matches any run). They expand against the parsed table (deduped, order-preserving) via DataTable::expand_columns; a lone range/glob still routes into the plot's multi-column path. Wired through scatter/line/bar/histogram/box/violin/strip/density/ridgeline/parallel/radar/coverage.
  • Histogram legends never rendered — Layout::auto_from_plots had no has_legend wiring for Plot::Histogram, so .with_legend(...) (and the CLI --legend) silently produced no legend. Now wired for both the primary series and stacked/overlaid groups, matching collect_legend_entries.

  • Single-slice pie chart rendered as a line (#117) — a PiePlot whose slices sum to one full turn drew nothing but a line. An SVG arc is defined by its endpoints, so a 360° sweep starts and ends at the same point and degenerates to zero length; the slice is now drawn as two 180° arcs. Donuts (with_inner_radius) are covered too: the ring is emitted as a single subpath (outer arcs, L inwards, inner arcs reversed) matching the existing partial-sweep donut, rather than as two subpaths whose hole would depend on the nonzero fill rule being applied across subpaths — which the SVG spec provides but kuva's raster and terminal backends do not.

  • Axis ticks: linear major and minor ticks retain small values and stop repeating positions at large offsets. Automatic labels no longer display tiny nonzero values as zero, and histogram tick selection searches requested intervals instead of scanning every bin multiplier. A tick step far below the axis's own resolution (for example an explicit with_x_tick_step near one ULP) is now rejected outright instead of generating positions until the process runs out of memory.

  • ROC thresholds and DeLong AUC now use exact score equality for ties. Close but distinct scores remain ordered, preventing a pair from counting as both a win and a tie and keeping the reported AUC consistent with the curve.

  • Precision-recall thresholds now also use exact score equality for ties, so close but distinct scores stay ordered and infinite scores no longer stop the curve from finishing.

  • ROC and precision-recall groups with a NaN score now produce no curve instead of running out of memory. The legend no longer shows an AUC of 0.000 for a group without a curve.

  • Scatter LOESS fits: scaled local coordinates and weighted squared deviations avoid distorted curves with large coordinate offsets, sparse neighborhoods, or large y values. Small x units no longer trigger a fixed-cutoff constant fit. Non-finite observations produce no smoother, and unrepresentable predictions are omitted.

  • Linear regression and Pearson correlation keep their results across large coordinate offsets and small or large units. Scatter trend lines and bounds use centered predictions, and undefined or unrepresentable statistics no longer return non-finite coefficients.

  • 2D histogram correlation annotations are omitted when Pearson correlation is undefined, instead of panicking on too few points or constant coordinates.

  • Scatter trend lines on constant y now draw the flat least-squares line and omit only the r annotation, which is undefined there rather than zero. Previously no line was drawn at all.

  • Invalid volcano p-values are no longer drawn as the most significant points. A NaN or negative p-value used to be placed at the top of the plot and could receive a top label, and a p-value above 1 was placed below the axis; such points are now left out, and kuva volcano rejects them with the row number. Data whose p-values are all exactly zero also keeps a finite p-value floor, instead of placing every point at -inf.

  • Invalid Manhattan p-values are no longer drawn as the most significant points. A NaN or negative p-value used to be placed at the top of the plot and could receive a top label, and a p-value above 1 was placed below the axis; such points are now left out, and kuva manhattan rejects them with the row number. Data whose p-values are all exactly zero also keeps a finite p-value floor, instead of placing every point at -inf.

  • Pie charts with negative values or a zero total no longer write NaN coordinates or draw overlapping arcs. No slices are drawn, the legend omits percentages, and kuva pie reports the negative value, the zero total, or input with no values to draw at all (including --count-by over a table with no rows).

  • Manhattan plots with no plottable point no longer write an unmappable chromosome label position into the output; with no x range to map onto, the labels are omitted along with the points.

  • UpSet plots silently merged sets past the 64th. An intersection is a u64 bitmask, but nothing enforced that: a 65th set shifted past the top bit, which panicked in a debug build and in release wrapped around to alias set 0, giving a wrong plot with no warning. kuva upset now rejects more than 64 columns, the builders drop sets past the limit instead of aliasing them, and UpSetPlot::MAX_SETS names it.

  • kuva upset output varied between runs. Intersections with equal counts, and all intersections with --sort natural, appeared in a different order each time. They now follow the order of their set membership, as UpSetPlot::with_sets already did.

Pull Requests/Contributions

New Contributors

Full Changelog: v0.5.0...v0.6.0

Edit: I re-ran the release actions that build the binaries with some changes. There are now -typst binaries that include the full typst backend, and then those without. Those without will have a much smaller binary size.