Selkie--Plot--Scaler

NAME

Selkie::Plot::Scaler - Map a numeric domain onto a discrete cell range

SYNOPSIS

use Selkie::Plot::Scaler;

# A linear scaler that maps the domain [0, 100] onto a 20-cell axis.
my $s = Selkie::Plot::Scaler.linear(min => 0, max => 100, cells => 20);

$s.value-to-cell(0);     # β†’ 0
$s.value-to-cell(50);    # β†’ 10
$s.value-to-cell(100);   # β†’ 19
$s.value-to-cell(150);   # β†’ 19  (clamped)
$s.value-to-cell(-10);   # β†’ 0   (clamped)
$s.cell-to-value(10);    # β†’ 52.6315... (midpoint of cell 10)

# Inverted axis β€” cell 0 holds the maximum value, useful for y-axes
# where the top of the screen is row 0 but the largest value should
# render highest on the chart.
my $y = Selkie::Plot::Scaler.linear(
    min => 0, max => 100, cells => 20, :invert,
);
$y.value-to-cell(100);   # β†’ 0    (top row)
$y.value-to-cell(0);     # β†’ 19   (bottom row)

DESCRIPTION

A Selkie::Plot::Scaler maps a numeric value in the domain [min, max] to an integer cell index in [0, cells-1]. It's the shared coordinate-mapping primitive used by every chart widget β€” Sparkline, BarChart, LineChart, ScatterPlot, Heatmap, and the axes that label them.

The scaler is pure data: no notcurses, no widget, no I/O. It's deterministic and exhaustively unit-testable, which matters because a miscomputed mapping silently corrupts every chart that uses it.

The linear formula

For cells E<gt> 1:

cell = round( (value - min) / (max - min) * (cells - 1) )

then clamped to [0, cells-1]. With :invert the result is flipped: cell = (cells - 1) - cell.

The inverse cell-to-value returns the value at the midpoint of the target cell:

value = (cell / (cells - 1)) * (max - min) + min

A round-trip cell-to-value(value-to-cell(v)) recovers v within Β± (max - min) / (2 * (cells - 1)) β€” the half-cell precision floor imposed by the integer cell grid.

Edge cases

  • cells must be > 0 β€” zero-cell scalers are nonsensical and throw.

  • min == max degenerate range β€” every value maps to the middle cell. cell-to-value returns min (which equals max).

  • min E<gt> max β€” throws. Inverted axes are expressed via :invert, not via reversed bounds.

  • NaN input β€” value-to-cell returns UInt (the typed undef). NaN is propagated, not clamped, so callers can detect missing samples.

  • +Inf / -Inf input β€” clamped to the corresponding edge cell (cells - 1 for +Inf, 0 for -Inf; flipped under :invert).

  • Out-of-domain value β€” clamped to the nearest edge cell. No exception.

EXAMPLES

Composing scalers for a 2D plot

A scatter plot needs two scalers β€” one per axis. The y-scaler is typically inverted because terminal row 0 is the top of the screen but charts conventionally place the maximum value at the top.

my $x-scaler = Selkie::Plot::Scaler.linear(
    min => 0, max => $duration, cells => $width,
);
my $y-scaler = Selkie::Plot::Scaler.linear(
    min => $min-y, max => $max-y, cells => $height, :invert,
);

for @samples -> %point {
    my $col = $x-scaler.value-to-cell(%point<t>);
    my $row = $y-scaler.value-to-cell(%point<v>);
    plot-dot($row, $col);
}

Recovering tick values for axis labels

When generating axis tick labels (see Selkie::Plot::Ticks) you want the value at a given cell. cell-to-value gives the cell midpoint:

my $axis-scaler = Selkie::Plot::Scaler.linear(
    min => 0, max => 1000, cells => 80,
);
say $axis-scaler.cell-to-value(0);    # β†’ 0
say $axis-scaler.cell-to-value(40);   # β†’ 506.32...
say $axis-scaler.cell-to-value(79);   # β†’ 1000

SEE ALSO

has Real $.min

Domain lower bound (inclusive).

has Real $.max

Domain upper bound (inclusive).

has UInt $.cells

Number of discrete cells in the target range. Must be > 0.

has Bool $.invert

Whether to flip the mapping (cell 0 holds the maximum value).

method linear

method linear(
    Real :$min!,
    Real :$max!,
    Int :$cells! where { ... },
    Bool :$invert = Bool::False
) returns Selkie::Plot::Scaler

Linear scaler constructor. Throws if cells is 0 or if C<min E max>. min == max is permitted (degenerate range β€” every value maps to the middle cell). Use :invert for axes where cell 0 should hold the maximum value (typically y-axes β€” terminal row 0 is the top of the screen, and charts conventionally render the largest value highest).

method value-to-cell

method value-to-cell(
    Real $value
) returns UInt

Map a value in the domain to a cell index in [0, cells-1]. Out-of-domain values are clamped to the nearest edge. NaN propagates as UInt (the typed undef). Β±Inf clamps to the corresponding edge.

method cell-to-value

method cell-to-value(
    Int $cell-in
) returns Real

Map a cell index back to its midpoint value in the domain. Out-of-range cell indices are clamped.

Selkie v0.10.0

High-level TUI framework built on Notcurses

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

Notcurses::Native:ver<0.4.0+>:auth<zef:apogee>

Test Dependencies

Provides

  • Selkie
  • Selkie::App
  • Selkie::App::Internal::Dispatch
  • Selkie::App::Internal::ErrorLog
  • Selkie::App::Internal::FocusTree
  • Selkie::App::Internal::HitTest
  • Selkie::App::Internal::IdleBudget
  • Selkie::App::Internal::OverlayTree
  • Selkie::App::Internal::RenderLoop
  • Selkie::App::Internal::ScreenModalLifecycle
  • Selkie::App::Internal::Terminal
  • Selkie::App::Internal::TerminalSequences
  • Selkie::Container
  • Selkie::EffectiveBounds
  • Selkie::Event
  • Selkie::Layout::Allocate
  • Selkie::Layout::HBox
  • Selkie::Layout::Split
  • Selkie::Layout::VBox
  • Selkie::Plot::Palette
  • Selkie::Plot::Scaler
  • Selkie::Plot::Ticks
  • Selkie::ScreenManager
  • Selkie::Sizing
  • Selkie::Store
  • Selkie::Style
  • Selkie::Test::Focus
  • Selkie::Test::Keys
  • Selkie::Test::Snapshot
  • Selkie::Test::Snapshot::Harness
  • Selkie::Test::Store
  • Selkie::Test::Supply
  • Selkie::Test::Tree
  • Selkie::Theme
  • Selkie::Trace
  • Selkie::Tree
  • Selkie::Widget
  • Selkie::Widget::Axis
  • Selkie::Widget::BarChart
  • Selkie::Widget::Border
  • Selkie::Widget::Button
  • Selkie::Widget::CardList
  • Selkie::Widget::Checkbox
  • Selkie::Widget::CommandPalette
  • Selkie::Widget::ConfirmModal
  • Selkie::Widget::FileBrowser
  • Selkie::Widget::FocusableByDefault
  • Selkie::Widget::Heatmap
  • Selkie::Widget::HelpOverlay
  • Selkie::Widget::Histogram
  • Selkie::Widget::Image
  • Selkie::Widget::Legend
  • Selkie::Widget::LineChart
  • Selkie::Widget::ListView
  • Selkie::Widget::Modal
  • Selkie::Widget::MultiLineInput
  • Selkie::Widget::PasswordStrength
  • Selkie::Widget::Plot
  • Selkie::Widget::ProgressBar
  • Selkie::Widget::RadioGroup
  • Selkie::Widget::RichText
  • Selkie::Widget::RichText::Span
  • Selkie::Widget::ScatterPlot
  • Selkie::Widget::ScrollView
  • Selkie::Widget::Select
  • Selkie::Widget::Sparkline
  • Selkie::Widget::Spinner
  • Selkie::Widget::TabBar
  • Selkie::Widget::Table
  • Selkie::Widget::Text
  • Selkie::Widget::TextInput
  • Selkie::Widget::TextInput::HighlightSpan
  • Selkie::Widget::TextStream
  • Selkie::Widget::Toast
  • Selkie::Widget::ViewportedCardList

Documentation

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.