Histogram

A histogram bins a 1-D dataset into equal-width intervals and renders each bin as a vertical bar. It supports explicit ranges, normalization, and overlapping distributions.

Import path: kuva::plot::Histogram


Basic usage

.with_range((min, max)) is required — without it Layout::auto_from_plots cannot determine the axis extent and will produce an empty chart. Compute the range from your data before building the histogram:

#![allow(unused)]
fn main() {
use kuva::plot::Histogram;
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;

let data: Vec<f64> = vec![/* your samples */];

// Compute range from data first.
let min = data.iter().cloned().fold(f64::INFINITY, f64::min);
let max = data.iter().cloned().fold(f64::NEG_INFINITY, f64::max);

let hist = Histogram::new()
    .with_data(data)
    .with_bins(20)
    .with_range((min, max))   // required for auto_from_plots
    .with_color("steelblue");

let plots = vec![Plot::Histogram(hist)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Histogram")
    .with_x_label("Value")
    .with_y_label("Count");

let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write("histogram.svg", svg).unwrap();
}
Basic histogram

Bin count

.with_bins(n) sets the number of equal-width bins (default 10). Fewer bins smooth out noise; more bins reveal finer structure at the cost of per-bin counts. The same range is used in both cases so the x-axis stays comparable.

#![allow(unused)]
fn main() {
use kuva::plot::Histogram;
// Coarse — few bins, clear shape
let hist = Histogram::new().with_data(data.clone()).with_bins(5).with_range(range);

// Fine — many bins, more detail
let hist = Histogram::new().with_data(data).with_bins(40).with_range(range);
}
5 bins 40 bins

Fixed range

Pass an explicit (min, max) to .with_range() to fix the bin edges regardless of the data. Values outside the range are silently ignored. This is useful when you want to focus on a sub-range, exclude outliers, or ensure two independent histograms cover the same x-axis scale.

#![allow(unused)]
fn main() {
use kuva::plot::Histogram;
let hist = Histogram::new()
    .with_data(data)
    .with_bins(20)
    .with_range((-3.0, 3.0))   // bins fixed to [-3, 3]; outliers ignored
    .with_color("steelblue");
}

Normalized histogram

.with_normalize() rescales bar heights so the tallest bar equals 1.0. This is peak-normalization — useful for comparing the shape of distributions with different sample sizes. The y-axis shows relative frequency, not counts or probability density.

#![allow(unused)]
fn main() {
use kuva::plot::Histogram;
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;

let min = data.iter().cloned().fold(f64::INFINITY, f64::min);
let max = data.iter().cloned().fold(f64::NEG_INFINITY, f64::max);

let hist = Histogram::new()
    .with_data(data)
    .with_bins(20)
    .with_range((min, max))
    .with_color("steelblue")
    .with_normalize();

let plots = vec![Plot::Histogram(hist)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Normalized Histogram")
    .with_x_label("Value")
    .with_y_label("Relative frequency");
}
Normalized histogram

Overlapping distributions

Place multiple Histogram structs in the same Vec<Plot>. Since bars have no built-in opacity setting, use 8-digit hex colors (#RRGGBBAA) to make each series semi-transparent so the overlap is visible.

When overlapping, compute a shared range from the combined data so both histograms use the same bin edges and x-axis scale:

#![allow(unused)]
fn main() {
use kuva::plot::Histogram;
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;

// Shared range across both groups so x-axes align.
let combined_min = group_a.iter().chain(group_b.iter())
    .cloned().fold(f64::INFINITY, f64::min);
let combined_max = group_a.iter().chain(group_b.iter())
    .cloned().fold(f64::NEG_INFINITY, f64::max);
let range = (combined_min, combined_max);

// #4682b480 = steelblue at ~50% opacity
// #dc143c80 = crimson  at ~50% opacity
let hist_a = Histogram::new()
    .with_data(group_a)
    .with_bins(20)
    .with_range(range)
    .with_color("#4682b480")
    .with_legend("Group A");

let hist_b = Histogram::new()
    .with_data(group_b)
    .with_bins(20)
    .with_range(range)
    .with_color("#dc143c80")
    .with_legend("Group B");

let plots = vec![Plot::Histogram(hist_a), Plot::Histogram(hist_b)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Overlapping Distributions")
    .with_x_label("Value")
    .with_y_label("Count");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Overlapping histograms

The AA byte in the hex color controls opacity: ff = fully opaque, 80 ≈ 50%, 40 ≈ 25%.


Step (outline) mode

.with_step(true) draws each histogram as an outline-only staircase instead of filled bars. This is the cleanest way to overlay several distributions: the outlines never occlude each other, so you can skip the semi-transparent fills.

#![allow(unused)]
fn main() {
use kuva::plot::Histogram;
let hist_a = Histogram::new()
    .with_data(group_a).with_bins(24).with_range(range)
    .with_color("#4682b4").with_step(true).with_legend("Group A");
let hist_b = Histogram::new()
    .with_data(group_b).with_bins(24).with_range(range)
    .with_color("#dc143c").with_step(true).with_legend("Group B");
}
Step histograms

Cumulative mode

.with_cumulative(true) accumulates counts left-to-right, so each bar includes everything to its left and the final bar equals the total sample count. Combine with .with_normalize() for an empirical CDF-style view (peak scaled to 1.0).

#![allow(unused)]
fn main() {
use kuva::plot::Histogram;
let hist = Histogram::new()
    .with_data(data).with_bins(30).with_range((-3.0, 3.0))
    .with_color("steelblue").with_cumulative(true);
}
Cumulative histogram

Stacked groups

Add extra series with .with_group(data, color, label) and turn on .with_stacked(true) to sum them per bin, each group drawn on top of the one below. The primary series (from .with_data) is the bottom layer. Without .with_stacked(true), groups draw from a shared baseline (overlaid) instead.

#![allow(unused)]
fn main() {
use kuva::plot::Histogram;
let hist = Histogram::new()
    .with_data(group_a).with_bins(24).with_range(range)
    .with_color("#4e79a7").with_stacked(true).with_legend("Group A")
    .with_group(group_b, "#f28e2b", Some("Group B".to_string()))
    .with_group(group_c, "#59a14f", Some("Group C".to_string()));
}
Stacked histogram

Automatic bin count

.with_bin_method(BinMethod::…) picks the bin count from the data, overriding .with_bins. Three rules are available:

RuleBin width / countBest for
BinMethod::Sturgesceil(log2(n)) + 1 binsSmall, roughly Gaussian samples
BinMethod::Scottwidth 3.49·sd·n^(-1/3)Smooth data
BinMethod::FreedmanDiaconiswidth 2·IQR·n^(-1/3)Data with outliers (robust)

Scott and Freedman-Diaconis fall back to Sturges on degenerate data (zero variance or IQR).

#![allow(unused)]
fn main() {
use kuva::plot::{Histogram, BinMethod};
let hist = Histogram::new()
    .with_data(data).with_range(range)
    .with_bin_method(BinMethod::FreedmanDiaconis);
}

Weighted samples

.with_weights(weights) gives each sample a weight, so it contributes its weight to its bin instead of 1. The vector length must match the data (a mismatch falls back to plain counts).

#![allow(unused)]
fn main() {
use kuva::plot::Histogram;
let hist = Histogram::new()
    .with_data(values).with_bins(20).with_range(range)
    .with_weights(weights);   // one weight per sample
}

API reference

MethodDescription
Histogram::new()Create a histogram with defaults (10 bins, color "black")
.with_data(iter)Set input values; accepts any Into<f64> numeric type
.with_bins(n)Number of equal-width bins (default 10)
.with_range((min, max))Required. Sets bin edges and the layout axis extent
.with_color(s)Bar fill color; use 8-digit hex (#RRGGBBAA) for alpha transparency
.with_normalize()Scale heights so peak bar = 1.0 (relative frequency)
.with_legend(s)Attach a legend label to this series
.with_step(bool)Draw an outline-only staircase instead of filled bars
.with_cumulative(bool)Accumulate counts left-to-right (final bar = total)
.with_bin_method(m)Auto bin count (Sturges / Scott / FreedmanDiaconis); overrides .with_bins
.with_weights(v)Per-sample weights (length must match the data)
.with_group(data, color, label)Add another series, stacked or overlaid
.with_stacked(bool)Stack groups on the primary series instead of overlaying

See also: Density Plot for a smoothed alternative, Ridgeline Plot for comparing many groups, Q-Q Plot for checking normality.


CLI

Frequency histogram from one or more numeric columns.

Input: one or more numeric columns per row.

FlagDefaultDescription
--value-col <COL>0Value column (single-column mode)
--y <COL>[,<COL>…]—Comma-separated columns; overlays one histogram per column over a shared x-range (overrides --value-col)
--color <CSS>steelblueBar fill color (single-column mode)
--bins <N>10Number of bins (ignored when --bin-method is set)
--bin-method <RULE>—Auto bin count: sturges, scott, or fd; overrides --bins
--normalizeoffNormalize to probability density (area = 1)
--stepoffOutline-only staircases instead of filled bars
--cumulativeoffAccumulate counts left-to-right
--stackedoffStack the --y columns instead of overlaying them
--weight-col <COL>—Per-sample weight column (single-column mode)
--legendoffShow a legend entry for each series
kuva histogram histogram.tsv --value-col value --bins 30

kuva histogram histogram.tsv --bins 20 --normalize \
    --title "Expression distribution" --y-label "Density"

# automatic bin count (Freedman-Diaconis) and a cumulative view
kuva histogram histogram.tsv --value-col value --bin-method fd --cumulative

# overlay two columns as clean step outlines
kuva histogram data.tsv --y col_a,col_b --step --legend

# stack three columns into one histogram
kuva histogram data.tsv --y col_a,col_b,col_c --stacked --legend

See also: Shared flags — output, appearance, axes, log scale.