Documentation
¶
Overview ¶
Package boxenplot is the imzero2 widget for letter-value (Hofmann, Wickham & Kafadar 2017) plots. It composes:
- boxer/public/analytics/stats/letterval — LV math + oracle
- boxer/public/analytics/stats/tdigest — streaming quantile source
- egui2 plotBoxes / plotScatter / plotText primitives — rendering
The widget is stateless: construct one Renderer, then call Render any number of times per frame. Configure-once / render-many is the canonical pattern (see fieldview / errorview).
Caller must wrap Render calls inside a c.Plot(id) block. Each Render emits one BoxPlot series (with N nested BoxElems) plus optional outlier scatter/text annotations.
Index ¶
- Constants
- Variables
- func WriteStatusLine(ch Crosshair)
- func WriteStatusLineVerbose(ch Crosshair)
- type Crosshair
- type OutlierModeE
- type Renderer
- func (inst Renderer) AnnotationColor(col color.Color) (out Renderer)
- func (inst Renderer) At(plotID c.AbsoluteWidgetId, argument float64, name string, ...) (out Crosshair)
- func (inst Renderer) BoxWidth(base, shrink float64) (out Renderer)
- func (inst Renderer) FillAlpha(a uint8) (out Renderer)
- func (inst Renderer) OutlierAutoThreshold(n int64) (out Renderer)
- func (inst Renderer) OutlierMode(m OutlierModeE) (out Renderer)
- func (inst Renderer) PaintCrosshair(ch Crosshair)
- func (inst Renderer) Palette(p styletokens.SequentialE) (out Renderer)
- func (inst Renderer) PaletteRange(start, end float32) (out Renderer)
- func (inst Renderer) Render(argument float64, levels []letterval.LVLevel, extremes []float64, ...)
- func (inst Renderer) SeriesName(name string) (out Renderer)
- func (inst Renderer) SnapWindow(w float64) (out Renderer)
- func (inst Renderer) Stroke(col color.Color, widthPx float32) (out Renderer)
Constants ¶
const VerboseReadoutLineCount = 3
VerboseReadoutLineCount is the fixed number of rows WriteStatusLineVerbose emits, so a host panel's status area keeps a constant height whether or not the cursor is over a distribution (mirrors ecdf.ReadoutLineCount). Hosts budget vertical space from this.
Variables ¶
var PackageProps = packageprops.Props{ WASMWASI: packageprops.WASMCompiles, WASMJS: packageprops.WASMCompiles, WASMFreestanding: packageprops.WASMCompiles, }
PackageProps records this package's curated properties (ADR-0080). Seeded by `wasmsurvey props generate`; curate by hand, then `wasmsurvey props verify`.
Functions ¶
func WriteStatusLine ¶
func WriteStatusLine(ch Crosshair)
WriteStatusLine emits a single weak-styled LabelAtoms row that fully summarises the hovered letter-value box for the reader, suitable for placement immediately below c.Plot(...).Send(). The content names the distribution, anchors the cursor on the value axis, and describes the hovered ring by the quantiles its edges represent (the directly-meaningful concept) rather than by the Hofmann/Wickham/Kafadar "letter-value depth" (an academic index for the same thing).
- Inside a box (ch.Depth ≥ 2): `<name> │ x=…, y=… │ n=…, median=… │ quantiles [lo%, hi%] = [v_lo, v_hi] │ ≈… obs/tail beyond`
- Above the deepest box (ch.Depth == 0, ch.PlotY > ch.MaxDepthHigh): `<name> │ x=…, y=… │ n=…, median=… │ above hi-th percentile (deepest box [v_lo, v_hi]) │ ≈… obs in this tail`
- Below the deepest box (ch.Depth == 0, ch.PlotY < ch.MaxDepthLow): `<name> │ x=…, y=… │ n=…, median=… │ below lo-th percentile (deepest box [v_lo, v_hi]) │ ≈… obs in this tail`
Quantile percentages render via `%g` so a depth-3 box reads `[12.5%, 87.5%]` and depth-2 reads `[25%, 75%]`. Coverage (the fraction of the distribution inside the box) is derivable as `hi% − lo%` and intentionally not duplicated on the line to keep it scannable. The tail count is the analytical estimate from the matched LV (inside-box) or the deepest LV (outside-box) — the same number OutlierModeCount draws as `+N`.
No-op when ch.Valid is false; callers that want a placeholder message ("hover a distribution to inspect cursor values") should emit it themselves on the !ch.Valid branch. For the explaining, multi-line counterpart use WriteStatusLineVerbose.
func WriteStatusLineVerbose ¶ added in v0.0.5
func WriteStatusLineVerbose(ch Crosshair)
WriteStatusLineVerbose emits the explaining, multi-line counterpart to WriteStatusLine: it describes the hovered letter-value box in plain language — what quantiles its edges represent, the central fraction it covers, and the tail beyond it — rather than packing the same facts into one dense line. It always emits VerboseReadoutLineCount rows: the description when ch.Valid, a one-line hover hint otherwise, padded with blank rows so hovering on / off never reflows the host. Text via the pure [formatVerbose].
Types ¶
type Crosshair ¶
type Crosshair struct {
Valid bool
// Raw hover position in plot-data coordinates.
PlotX float64
PlotY float64
// Distribution context. TotalN is recovered from the levels slice
// via the shallowest non-median LV (TailCount = floor(n · 2⁻ᵈ));
// the recovery is exact for samples whose n is divisible by 2ᵈ and
// otherwise off by < 2ᵈ — fine for a human-readable readout.
Argument float64
Name string
Median float64
TotalN int64
// Innermost LV box containing PlotY. Depth==0 signals "outside every
// drawn box" — PlotY is in the tail beyond the deepest ring; the
// remaining Depth* fields are zero/NaN in that case.
Depth uint8
DepthLowerQ float64
DepthUpperQ float64
DepthLow float64
DepthHigh float64
DepthTailCount int64
// Deepest LV in the distribution. Always populated when Valid so
// the status line can describe where PlotY sits relative to the
// outermost ring even when no box contains it.
MaxDepth uint8
MaxDepthLow float64
MaxDepthHigh float64
MaxDepthTailCount int64
}
Crosshair captures the cursor position over a boxenplot's Plot block and every derived statistic needed to interpret a hovered letter- value box: the matched distribution's series name and argument, the recovered sample size and median, the innermost LV depth whose box contains the hover Y (plus that depth's quantile range, value bounds, and analytical per-tail count), and the deepest LV's bounds and tail count so a cursor outside every drawn box is still placed relative to the outermost ring.
Valid is false when no hover information is currently available — the cursor is outside the plot, the cached hover refers to a different plot id, or no distribution claimed the hover via At() this frame.
Crosshair is intentionally analogous to ecdf.Crosshair so callers already familiar with the ECDF pattern (At → Render → PaintCrosshair → c.Plot(...).Send → WriteStatusLine) can lift the same scaffold across the two widgets without re-learning the contract.
type OutlierModeE ¶
type OutlierModeE uint8
OutlierModeE controls how observations beyond the deepest rendered LV level are visualised.
const ( // OutlierModeAuto picks Points or Count based on the analytical // budget (per-tail expected count) against OutlierAutoThreshold. // Below threshold → Points; at/above → Count. OutlierModeAuto OutlierModeE = iota // OutlierModeNone draws nothing beyond the deepest box. OutlierModeNone // OutlierModePoints draws each value in the extremes slice as a // scatter point at (argument, value). Caller must supply the // extremes (e.g. K-smallest and K-largest tracked alongside the // digest). OutlierModePoints // OutlierModeCount draws a "+N" annotation at the lower and upper // edges of the deepest box, where N is the per-tail expected count. OutlierModeCount )
type Renderer ¶
type Renderer struct {
// contains filtered or unexported fields
}
Renderer is the configured boxenplot. Values are immutable after construction; fluent setters return modified copies.
func New ¶
New constructs a Renderer with IDS-aligned defaults. Palette and fill alpha are resolved from the styletokens Tier-1 / Tier-2 env surface (IDS_PALETTE_SEQUENTIAL / IDS_ACCESSIBILITY), so a typical caller never touches palette plumbing:
r := boxenplot.New("p99-mem") // honours user's IDS env
r.Render(arg, levels, nil, -1)
Static defaults:
- palette: styletokens.SequentialDefault()
- paletteTStart/End: [0.20, 0.85] (default) or [0.10, 0.95] under AccessibilityHighContrast for boosted discriminability
- fillAlpha: 0xC0 (default) or 0xFF under AccessibilityHighContrast
- stroke: NeutralBorderDefault at 1.0 px
- box width: 0.6 (argument-axis units), shrink 0.85 per depth
- outlier mode: Auto with threshold 20
idPrefix scopes any widget-id-bearing primitive emitted by Render — pass a stable short string (e.g. "lat-cluster", "p99-mem").
func (Renderer) AnnotationColor ¶
AnnotationColor sets the colour used for the "+N" outlier-count labels and for any outlier scatter points. Default NeutralTextSecondary.
func (Renderer) At ¶
func (inst Renderer) At(plotID c.AbsoluteWidgetId, argument float64, name string, levels []letterval.LVLevel) (out Crosshair)
At returns the Crosshair for the (argument, name, levels) tuple describing one distribution rendered into the given plot. The caller passes the same plotID it will hand to c.Plot — as an AbsoluteWidgetId (not ids.PrepareStr), so the id is stable across frames and matches the r15 hover register's stored value.
Crosshair.Valid is true when:
- the r15 hover register names plotID as the hovered plot,
- HoverX is finite,
- and |HoverX - argument| ≤ snapWindow (default 0.5).
The typical caller loops over its distributions and keeps the last Valid Crosshair the loop produced — the snap window is half the argument-axis spacing, so at most one distribution claims any given hover. levels may be empty (the depth-1-only median-marker case); the returned Crosshair still reports the median and Argument while Depth stays at 0.
func (Renderer) BoxWidth ¶
BoxWidth sets the depth-2 (innermost LV) box width in argument-axis units, plus a per-depth shrink multiplier. Each successive box is width × shrink^(depth-2) wide. Defaults: base 0.6, shrink 0.85. shrink = 1.0 gives Hofmann's constant-width convention; shrink < 1 produces the seaborn-style taper. Values outside (0, 1] are clamped.
func (Renderer) FillAlpha ¶
FillAlpha sets the alpha channel applied to every per-depth fill, overriding the palette's opaque output. Default 0xC0.
func (Renderer) OutlierAutoThreshold ¶
OutlierAutoThreshold sets the per-tail observation count at which Auto mode switches from Points (small counts) to Count (large). Default 20.
func (Renderer) OutlierMode ¶
func (inst Renderer) OutlierMode(m OutlierModeE) (out Renderer)
OutlierMode selects the outlier-rendering strategy. See OutlierModeE for the enumerated semantics.
func (Renderer) PaintCrosshair ¶
PaintCrosshair emits a vertical PlotVLine at ch.Argument (snapped to the matched distribution's centre, not the raw hover X) using the renderer's annotation colour at half alpha. No-op when ch.Valid is false. Must be invoked inside the same c.Plot block as Render — the egui_plot drain renders vlines after box series so the crosshair sits visually on top of every box.
The vline anchors to the argument rather than HoverX because the boxenplot's argument axis is categorical (one column per distribution); a vline at the raw cursor X would slide between columns and read as a "no-man's-land" cursor instead of a "selected distribution" affordance.
func (Renderer) Palette ¶
func (inst Renderer) Palette(p styletokens.SequentialE) (out Renderer)
Palette selects the IDS sequential data-encoding palette used for per-depth fills. Default SequentialBatlow.
func (Renderer) PaletteRange ¶
PaletteRange clamps the t ∈ [0, 1] range sampled from the palette. Defaults (0.20, 0.85) avoid the extreme dark/light ends which lose shape against typical dark IDS backgrounds.
Note: when the resolved palette ramps in the opposite direction to batlow (white→black; currently only SequentialGrayC), fillForDepth silently swaps the supplied (start, end) so "deep=light, shallow=dark" reads consistently across presets. Callers needing the supplied range verbatim must avoid SequentialGrayC or override fillForDepth directly — there is no per-call opt-out today.
func (Renderer) Render ¶
func (inst Renderer) Render(argument float64, levels []letterval.LVLevel, extremes []float64, perTailCountOverride int64)
Render emits the boxenplot primitives for one distribution at the given x position. Caller must already be inside a Plot block.
- levels: from letterval.RecommendedLevels(oracle) or letterval.Levels(oracle, maxDepth). May be empty (no-op) or contain only depth 1 (single median marker is drawn).
- extremes: raw extreme values for OutlierModePoints. Ignored by other modes; can be nil there. Provide both lower and upper extremes in one slice (the renderer treats the slice as opaque points, no sign convention).
- perTailCountOverride: optional explicit per-tail outlier count for Count / Auto modes. Pass -1 to use the analytical estimate (deepest LV's TailCount). Use the override when the caller tracks the true count separately (a top-K + counter).
func (Renderer) SeriesName ¶
SeriesName sets the legend label for the BoxPlot series. Empty disables the legend entry for this series. Default "boxen".
func (Renderer) SnapWindow ¶
SnapWindow sets the half-width (in argument-axis units) within which At() claims a hover for this distribution. The default 0.5 matches unit-spaced arguments (1, 2, 3, …) and selects the nearest distribution by construction: hovering at x=1.4 lands inside argument 1's window but outside argument 2's. Callers with denser or sparser argument layouts should override accordingly. Non-positive values are clamped to a small positive epsilon so At() never matches everywhere.