Selkie.git | docs/api/ | Selkie--Widget--ScatterPlot.md
NAME
====
Selkie::Widget::ScatterPlot - 2D point plot using braille sub-cell dots
SYNOPSIS
========
```raku
use Selkie::Widget::ScatterPlot;
use Selkie::Sizing;
# Single-series scatter — auto-derives axis ranges from the data.
# Points are Pairs (x => y) so Raku doesn't flatten the list.
my @points = (1..50).map: { (rand * 100) => (rand * 100) };
my $sp = Selkie::Widget::ScatterPlot.new(
series => [
{ label => 'samples', points => @points },
],
sizing => Sizing.flex,
);
# Multi-series with explicit colours
my $sp2 = Selkie::Widget::ScatterPlot.new(
series => [
{ label => 'group A', points => @group-a, color => 0xE69F00 },
{ label => 'group B', points => @group-b, color => 0x56B4E9 },
],
sizing => Sizing.flex,
);
# Tip: use Pair (x => y), [x, y] arrays, or hash {x => , y => } per
# point. Don't use bare lists `(x, y)` — Raku flattens them in
# array context and your single-point scatter becomes two
# independent values.
# Reactive
my $live = Selkie::Widget::ScatterPlot.new(
store-path => <viz scatter-data>,
sizing => Sizing.flex,
);
```
DESCRIPTION
===========
A scatter plot of 2D points. Uses Unicode braille (U+2800-U+28FF) for **sub-cell** resolution: each terminal cell holds a 2×4 grid of dot positions (8 dots per cell). A 50-cell-wide plot can resolve 100 distinct x-positions, and a 20-cell-tall plot can resolve 80 distinct y-positions.
The braille dot grid
--------------------
Each braille codepoint encodes which of 8 sub-cell dots are filled:
0 3
1 4
2 5
6 7
The codepoint is `U+2800 + bit-pattern`, where bit N controls dot N. A cell with all 8 dots filled is `⣿` (U+28FF). A cell with no dots is `⠀` (U+2800).
Multi-series colour collision
-----------------------------
Each braille cell renders with a single foreground colour. When two series have dots in the same 2×4 sub-cell window, the cell's colour is determined by the `:overlap` setting:
* `z-order` (default) — the last-drawn series wins the cell's colour. The earlier series' dots are still drawn but they take the later series' colour.
This is a documented limitation of single-foreground terminal rendering. For non-overlapping multi-series, the colour assignment is always correct. For overlapping data, prefer faceted layouts (separate scatter plots per series) over single-plot overlay.
Range
-----
Each axis range auto-derives from the data extent. Pass explicit `:x-min`, `:x-max`, `:y-min`, `:y-max` to fix any of them. Useful when streaming so the axes don't jitter as new points expand the range.
EXAMPLES
========
Single cluster
--------------
```raku
my @cluster = (1..50).map: {
(50 + rand * 20 - 10, 50 + rand * 20 - 10);
};
my $sp = Selkie::Widget::ScatterPlot.new(
series => [{ label => 'cluster', points => @cluster }],
x-min => 0, x-max => 100,
y-min => 0, y-max => 100,
sizing => Sizing.flex,
);
```
Two clusters with distinct colours
----------------------------------
```raku
my $sp = Selkie::Widget::ScatterPlot.new(
series => [
{ label => 'cluster A', points => @a, color => 0xE69F00 },
{ label => 'cluster B', points => @b, color => 0x009E73 },
],
sizing => Sizing.flex,
);
```
SEE ALSO
========
* [Selkie::Widget::LineChart](Selkie--Widget--LineChart.md) — connects points with lines (also braille)
* [Selkie::Widget::Heatmap](Selkie--Widget--Heatmap.md) — for 2D data on a regular grid
* [Selkie::Plot::Palette](Selkie--Plot--Palette.md) — colourblind-safe series palettes
### has Positional @.series
List of series. Each series is a hash with `label` (Str), `points` (list of (x, y) pairs), and optional `color` (UInt RGB).
### has Positional[Str] @.store-path
Reactive store path. Mutually exclusive with `series`.
### has Str $.palette
Series-color palette. Used when individual series don't specify `color`.
### has Real $.x-min
Optional explicit X axis bounds. Auto-derived when unset.
### has Real $.y-min
Optional explicit Y axis bounds. Auto-derived when unset.
### has Str $.overlap
How to handle cells where multiple series have dots. Currently only `z-order` is supported (last-drawn wins the colour).
### has Str $.empty-message
Message rendered when there are no points. The default is the expected startup state for monitoring dashboards. Set to the empty string to suppress.
### method on-store-attached
```raku
method on-store-attached(
$store
) returns Mu
```
Hook called when the widget is attached to a store. Wires up a subscription against `:store-path` so the chart re-renders on state changes. No-op in `:series` mode.
### method set-series
```raku
method set-series(
@new
) returns Mu
```
Replace the chart's series array. Each series' points are realised into an Array up-front because callers often pass Seqs from `.map` chains, and a TUI re-renders every frame — an exhausted Seq would produce an empty plot on subsequent renders. Throws when constructed in `:store-path` mode.
### method braille-glyph
```raku
method braille-glyph(
Int $bits where { ... }
) returns Str
```
Compute the braille codepoint for a given bit pattern (0..255). Pure function, exhaustively unit-testable.
### method braille-bit
```raku
method braille-bit(
Int $sub-col where { ... },
Int $sub-row where { ... }
) returns UInt
```
Compute the bit position within a braille cell for a sub-cell coordinate. `$sub-col` is 0 or 1; `$sub-row` is 0..3. Returns a bit index 0..7 suitable for use with `braille-glyph`.