spacr.qt.widgets.figure_grid

A grid of search figures that fills as the search runs.

FigureQueue is a one-at-a-time gallery: it shows the figure you navigated to. That is the right shape for a pipeline that produces a dozen plots over an hour, and the wrong one for a hyperparameter search, where the whole point is to compare fifty embeddings against each other and stop early when the range is obviously wrong.

This module is the comparison view. Figures land as they are produced, the grid reflows to whatever space the container has, and where a cell SITS says which parameter values produced it.

Two halves, deliberately separated:

  • reflow_shape() and axis_layout() are pure. They decide how many columns fit and which cell a trial belongs in, and they are tested without a display. Nearly all of the behaviour worth defending is here.

  • SearchFigureGrid is the Qt shell around them.

Rendering never happens on the GUI thread. The figure arrives as a pre-rendered PNG from the worker that made it, exactly as FigureQueue receives one; a grid of fifty PDFs rasterised in the paint path would freeze the application, which is the one thing it must never do.

Classes

GridCell

One figure in the grid, with where it came from.

SearchFigureGrid

Figures on a reflowing grid, filling as a search produces them.

Functions

axis_layout(→ Tuple[List[str], List[Any], ...)

Place each trial on a grid whose axes are the searched parameters.

cell_caption(→ str)

A one-line label naming the parameter values behind one figure.

reflow_shape(→ Tuple[int, int, int])

How many columns and rows fit count figures in width x height.

Module Contents

class spacr.qt.widgets.figure_grid.GridCell[source]

One figure in the grid, with where it came from.

class spacr.qt.widgets.figure_grid.SearchFigureGrid(parameters: Sequence[str] | None = None, parent: PySide6.QtWidgets.QWidget | None = None)[source]

Bases: PySide6.QtWidgets.QWidget

Figures on a reflowing grid, filling as a search produces them.

Parameters:
  • parameters – the searched parameter names, in the order that decides the axes. May be set later with set_parameters().

  • parent – optional Qt parent.

Variables:

cell_clicked – emitted with the index of a clicked figure.

Build the grid that lays a search’s figures out by parameter.

Parameters:
  • parameters – the parameters the search varied, which become the grid’s captions.

  • parent – parent widget, or None.

add_figure(png_path: str, coordinate: Mapping[str, Any] | None = None) → int[source]

Add one already-rendered figure.

The PNG is produced by whoever ran the trial, on the worker thread that ran it. This method only loads and places it – a search that rendered its figures here would render them on the GUI thread.

Parameters:
  • png_path – path to the rendered image.

  • coordinate – the parameter values behind this figure.

Returns:

the index of the new cell.

clear() → None[source]

Drop every figure.

columns() → int[source]

The current column count. 0 while empty.

coordinates() → List[Dict[str, Any]][source]

The parameter values behind each figure, in arrival order.

count() → int[source]

How many figures are in the grid.

eventFilter(obj, event)[source]

Debounce reflow while the container is being resized.

Parameters:
  • obj – the watched object; only the scroll area’s viewport is acted on.

  • event – the filtered event; a Resize restarts the reflow timer, and every event is passed on to the base class.

static figure_dpi() → int[source]

The PNG resolution from Preferences.

static figure_format() → str[source]

The figure format from Preferences, not from a second setting.

The design is explicit that this must not grow its own control: a user who sets PDF once should get PDF everywhere.

figure_path(index: int) → str[source]

The best file for cell index – the PDF when one exists.

render_figure_to_png writes a sibling pdf when the figure format preference is PDF, and the grid necessarily DISPLAYS the PNG, because a PDF cannot be painted into a label. So the file a user should be handed is not always the one on screen.

Parameters:

index – zero-based cell index in arrival order; out of range gives "".

parameters() → List[str][source]

The parameters currently deciding the axes.

relayout() → None[source]

Rebuild the grid for the current size and figure count.

set_parameters(parameters: Sequence[str]) → None[source]

Set the parameters whose values place a figure, then re-lay out.

Parameters:

parameters – the searched parameter names, in the order that decides the axes; empty lays the figures out in arrival order.

spacr.qt.widgets.figure_grid.axis_layout(coordinates: Sequence[Mapping[str, Any]], parameters: Sequence[str]) → Tuple[List[str], List[Any], List[Tuple[int, int]]][source]

Place each trial on a grid whose axes are the searched parameters.

With ONE searched parameter the grid is a single row ordered by it. With two it is the familiar table: one parameter across, the other down. With more than two there is no honest two-dimensional picture, so the widest parameter goes across and every distinct combination of the rest gets its own row – a small multiple of small multiples. That is the arrangement that keeps the promise the position makes: two cells in the same row differ in exactly one parameter.

Trials whose coordinates repeat (a resumed search, a duplicate) share a cell; the caller decides which figure wins.

Parameters:
  • coordinates – one mapping of parameter values per figure, in arrival order.

  • parameters – the searched parameter names. Order decides which axis is which; the caller passes them in the order the user chose.

Returns:

(row_parameters, column_values, cells) where cells[i] is (row, column) for coordinates[i]. row_parameters is empty when there is only one axis.

spacr.qt.widgets.figure_grid.cell_caption(coordinate: Mapping[str, Any], parameters: Sequence[str]) → str[source]

A one-line label naming the parameter values behind one figure.

Parameters:
  • coordinate – parameter name to value for the figure; floats are formatted with :g.

  • parameters – the names to include, in caption order; names missing from coordinate are skipped.

spacr.qt.widgets.figure_grid.reflow_shape(count: int, width: int, height: int, *, min_cell: int = MIN_CELL_PX, aspect: float = DEFAULT_CELL_ASPECT, spacing: int = 6) → Tuple[int, int, int][source]

How many columns and rows fit count figures in width x height.

The grid prefers to fill the container in BOTH directions rather than to make one long row: with nine figures in a square panel the useful answer is 3x3, not 9x1, because comparing nine embeddings side by side is the entire reason the view exists.

Columns are capped by min_cell and by count. When the result does not fit vertically the grid scrolls – it does not shrink cells below the size at which a figure stops being readable, because an unreadable grid is not a cheaper version of a readable one.

Parameters:
  • count – how many figures are in the grid.

  • width – usable container width in pixels.

  • height – usable container height in pixels.

  • min_cell – narrowest cell worth drawing.

  • aspect – cell width divided by cell height.

  • spacing – gap between cells in pixels.

Returns:

(columns, rows, cell_width). (0, 0, 0) for no figures.

Nested helpers

axis_layout.distinct(name: str) → List[Any]

The distinct values of a column, in first-seen order.

spacr/qt/widgets/figure_grid.py:150