.md →
HeatmapSelect API#
HeatmapSelect renders a 2D array as a dense grid — one image pixel per cell, in the
spirit of the parameter spaces in Bret Victor's
Up and Down the Ladder of Abstraction.
Hover or click a cell in the body to pick one point of the sweep, or grab the left or
bottom gutter to pin a whole row or column. The three pins are independent and coexist, so
you can hold a cell, a row and a column at once. The values behind the picture never cross
the wire: the widget reports indices and you do the slicing, which is what lets a 14 641-cell
field stay a single PNG. Reach for it when a sweep has two knobs and you want to see the runs
behind any slice of it.
See also: ChartSelect for box and lasso selection over a matplotlib
figure, ChartPuck for dragging a single point across a chart, and
Slider2D for picking a continuous (x, y) pair rather than a grid cell.
Bases: AnyWidget
A dense parameter-space grid where you pick one cell or a whole row/column.
Modelled on the grid in Bret Victor's Up and Down the Ladder of Abstraction. The body picks a single cell; the left gutter picks a whole row (a horizontal band); the bottom gutter picks a whole column (a vertical band).
Hovering previews, clicking pins. The three pins are independent and coexist — you can hold a cell, a row and a column at once, and clicking one region only replaces that region's pin. Double-clicking a region drops just that pin. What to make of a combination is the caller's business.
One pixel of image is one grid cell, and the values behind the picture
never cross the wire — the widget hands back indices, and you do the slicing
yourself with values[widget.pinned_row]. Use x_at/y_at to turn an
index back into a data coordinate.
Examples:
import numpy as np
import marimo as mo
from wigglystuff import HeatmapSelect
steps = np.random.rand(100, 91)
widget = mo.ui.anywidget(
HeatmapSelect(
steps,
x_range=(0, 90),
y_range=(0.1, 10.0),
x_label="bend angle",
y_label="turning rate",
x_suffix="°",
y_suffix="°",
)
)
widget
Then react to the pins in another cell. They are independent, so this is
three ifs rather than a branch:
if widget.pinned_row is not None:
row_sweep = steps[widget.pinned_row, :]
if widget.pinned_col is not None:
col_sweep = steps[:, widget.pinned_col]
if widget.pinned_cell is not None:
row, col = widget.pinned_cell
value = steps[row, col]
x, y = widget.x_at(col), widget.y_at(row)
Coloring follows matplotlib, so a Bret-Victor-style crash region is just a masked array plus a "bad" color — the widget has no concept of one:
import matplotlib
HeatmapSelect(
np.ma.masked_where(crashed, distance),
cmap=matplotlib.colormaps["gray"].with_extremes(bad="red"),
)
Create a HeatmapSelect widget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Any
|
The grid bitmap — one pixel per cell. Accepts a base64/data-URI
PNG string, a path, a PIL |
required |
x_range
|
Tuple[float, float]
|
Data coordinates of the first and last column centers. |
(0.0, 1.0)
|
y_range
|
Tuple[float, float]
|
Data coordinates of the first and last row centers. |
(0.0, 1.0)
|
x_label
|
str
|
Label drawn under the bottom gutter. |
''
|
y_label
|
str
|
Label drawn beside the left gutter. |
''
|
x_suffix
|
str
|
Suffix appended to x tick labels, e.g. |
''
|
y_suffix
|
str
|
Suffix appended to y tick labels. |
''
|
origin
|
str
|
|
'lower'
|
cell_width
|
int
|
Screen pixels per cell horizontally. |
4
|
cell_height
|
int
|
Screen pixels per cell vertically. |
4
|
row_color
|
str
|
Tint for the row band grabbed from the left (y) axis, e.g.
|
''
|
col_color
|
str
|
Tint for the column band grabbed from the bottom (x) axis.
Empty uses the |
''
|
cmap
|
Any
|
Colormap name or |
'gray'
|
norm
|
Any
|
Optional |
None
|
vmin
|
Optional[float]
|
Optional lower end of the color scale. |
None
|
vmax
|
Optional[float]
|
Optional upper end of the color scale. |
None
|
throttle
|
Union[int, str]
|
How often hover updates reach Python — |
50
|
**kwargs
|
Any
|
Forwarded to |
{}
|
Source code in wigglystuff/heatmap_select.py
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | |
selection
property
#
All six selection traits in one dict, handy for one-shot reads.
clear #
Drop all three pins and the current hover.
Source code in wigglystuff/heatmap_select.py
set_image #
Swap the grid bitmap, re-deriving the grid shape from its pixels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Any
|
A new image, in any of the forms the constructor accepts. |
required |
**color_kwargs
|
Any
|
Optional |
{}
|
Source code in wigglystuff/heatmap_select.py
x_at #
Data coordinate at the center of a column, or None for None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
col
|
Optional[int]
|
Column index, or |
required |
Returns:
| Type | Description |
|---|---|
Optional[float]
|
float | None: The x coordinate, interpolated across |
Source code in wigglystuff/heatmap_select.py
y_at #
Data coordinate at the center of a row, or None for None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
Optional[int]
|
Row index, or |
required |
Returns:
| Type | Description |
|---|---|
Optional[float]
|
float | None: The y coordinate, interpolated across |
Source code in wigglystuff/heatmap_select.py
Synced traitlets#
| Traitlet | Type | Notes |
|---|---|---|
image_base64 |
str |
The grid bitmap as a PNG data URI. One image pixel is one cell. |
n_rows |
int |
Grid rows, derived from the image height. |
n_cols |
int |
Grid columns, derived from the image width. |
x_range |
tuple[float, float] |
Data coordinates of the first and last column centers. |
y_range |
tuple[float, float] |
Data coordinates of the first and last row centers. |
x_label |
str |
Label under the bottom gutter. May contain \n to stack lines. |
y_label |
str |
Label beside the left gutter. May contain \n to stack lines. |
x_suffix |
str |
Appended to x tick labels, e.g. "°". |
y_suffix |
str |
Appended to y tick labels. |
origin |
str |
"lower" puts image row 0 at the bottom (like imshow), "upper" at the top. |
cell_width |
int |
Screen pixels per cell horizontally. |
cell_height |
int |
Screen pixels per cell vertically. |
row_color |
str |
Tint for the row band from the left (y) axis. Empty uses --hs-row-color. |
col_color |
str |
Tint for the column band from the bottom (x) axis. Empty uses --hs-col-color. |
pinned_cell |
tuple[int, int] \| None |
(row, col) of the pinned cell. |
pinned_row |
int \| None |
Row index pinned from the left axis. |
pinned_col |
int \| None |
Column index pinned from the bottom axis. |
hover_cell |
tuple[int, int] \| None |
(row, col) under the cursor, when it is over the grid body. |
hover_row |
int \| None |
Row under the cursor, when it is over the left gutter. |
hover_col |
int \| None |
Column under the cursor, when it is over the bottom gutter. |
throttle |
int \| str |
Hover sync rate. 0 = every move, int = ms, "dragend" = on release. Pin changes always sync immediately. |
Interaction#
| Gesture | Result |
|---|---|
| Hover the body | Sets hover_cell. |
| Hover the left gutter | Sets hover_row — a horizontal band. |
| Hover the bottom gutter | Sets hover_col — a vertical band. |
| Click (anywhere, including a gutter) | Pins that region. Only that region's pin is replaced. |
| Drag | Keeps moving that region's pin. |
| Double-click a region | Drops only that region's pin. |
| Mouse out | Clears the hover traits; pins are untouched. |
The three pins are independent, so a cell, a row and a column can all be held at once. Hovering never disturbs a pin — it draws a faint ghost instead.
Coloring#
Pass a 2D numeric array and the widget colormaps it with matplotlib's own
conventions: cmap (a name or a Colormap), norm, vmin, vmax. The default
is grayscale. Cells that are masked or non-finite take the colormap's "bad"
color, which is all you need for a crash region:
import matplotlib
import numpy as np
from wigglystuff import HeatmapSelect
HeatmapSelect(
np.ma.masked_where(crashed, distance),
cmap=matplotlib.colormaps["gray"].with_extremes(bad="red"),
)
Autoscaling is relative, exactly as with imshow: uniformly rescaling the data
produces an identical picture. Pin vmin/vmax if you need an absolute scale
across successive set_image calls.
You can also skip colormapping entirely and hand over a finished picture — a PIL
image, an (rows, cols, 3|4) uint8 array, a path, or a base64 PNG.
Sizing#
The plot size is derived: n_cols * cell_width by n_rows * cell_height. Cells
are therefore always whole pixel blocks and never shimmer, which is why there is
no width/height argument — both are read-only properties.