spacr.qt.widgets.graph_builder¶
Graph Builder — drag a column onto a channel and the chart appears.
A drag-and-drop alternative to choosing among the forty plot_* functions
and their arguments. Six drop zones — x, y, colour, size, facet-row,
facet-column — a well of the columns worth plotting, and a canvas that
re-renders the moment a zone changes.
What is in here and what is not¶
Everything about what to draw lives in spacr.qt.widgets.graph_spec:
the spec object, the plot-type inference, the facet grid, the shared scales and
the large-data policy. This module is the chrome and the matplotlib calls. The
split exists because small multiples, the gate editor, the feature explorer and
the campaign control charts are all “the graph builder with one more rule”, and
they need the engine without inheriting a drag-and-drop panel.
Linked, and asymmetric on purpose¶
GraphCanvas mixes in
spacr.qt.linked_selection.LinkedView, so it is one of the views that
talk to each other:
a brush (drag a rectangle across a panel) publishes the rows it swept as the shared selection;
an incoming selection rings those rows and dims the rest. It never removes a point — a selection highlights, it does not hide;
an incoming filter does remove rows, and the axes re-scale to what is left, because a filter genuinely narrows the population.
The brush is evaluated as a predicate over the frame, not as a hit test against drawn marks, which is what keeps it exact when a panel was drawn as a density raster or from a sample.
Colour¶
Categorical series take a fixed eight-hue order — never cycled, never re-assigned when a filter changes the series count, so a gene keeps its colour between two charts. The order is the validated reference palette (light and dark steps kept separately rather than flipped), and a continuous colour column gets a single-hue light-to-dark ramp. Beyond eight levels the extras fold into one “other” grey rather than adding hues that are hard to tell apart.
Classes¶
The list of plottable columns, filtered by a search box, draggable out. |
|
One channel's drop target. |
|
The well, the six zones, the plot-type override and the canvas. |
|
The matplotlib canvas every built graph is drawn on. |
Functions¶
|
The fixed eight-hue series order for the active theme. |
|
The page-opacity preference as a plain float, for matplotlib. |
|
The single-hue magnitude ramp for the active theme, light → dark. |
Module Contents¶
- class spacr.qt.widgets.graph_builder.ColumnWell(parent=None)[source]¶
Bases:
PySide6.QtWidgets.QWidgetThe list of plottable columns, filtered by a search box, draggable out.
Only the columns
spacr.qt.widgets.graph_spec.plottable_columns()offers — the same rule the Local Data Filter uses to decide what is worth a control. A measurement table has hundreds of columns, too many to list usefully.- Parameters:
parent – parent widget.
Build the well of draggable columns.
- Parameters:
parent – parent widget.
- set_frame(frame: pandas.DataFrame | None) None[source]¶
Re-list the plottable columns for a new table.
Noneempties the well rather than leaving the previous table’s columns on screen, which would offer drags that cannot land.- Parameters:
frame – the table to read columns from, or None.
- class spacr.qt.widgets.graph_builder.DropZone(channel: str, parent=None)[source]¶
Bases:
PySide6.QtWidgets.QFrameOne channel’s drop target.
Emits
column_changedwith(channel, column_or_empty). The empty string rather thanNoneso the signal can be typedstr, strand connected across a queued connection without a custom metatype.- Parameters:
channel – which channel this zone accepts. It is carried in every
column_changed, so the host does not have to remember which zone it connected.parent – parent widget.
Build one channel’s drop target.
- Parameters:
channel – the channel this zone binds.
parent – parent widget.
- dragEnterEvent(event)[source]¶
Light up when a droppable column arrives over the zone.
- Parameters:
event – the Qt drag event.
- dragLeaveEvent(event)[source]¶
Drop the highlight when the pointer leaves.
- Parameters:
event – the Qt drag event.
- dragMoveEvent(event)[source]¶
Keep accepting while a droppable column stays over the zone.
- Parameters:
event – the Qt drag event.
- dropEvent(event)[source]¶
Bind the dropped column to this channel.
- Parameters:
event – the Qt drop event.
- set_column(column: str | None) None[source]¶
Put
columnon this channel (Noneempties it) and announce it.Silent when nothing changes: the panel rebuilds the chart on every emission, and a re-drop of the same column would otherwise cost a full re-render for no visible difference.
- Parameters:
column – column name for this channel, converted with
str(); None or an empty string empties the zone.
- class spacr.qt.widgets.graph_builder.GraphBuilderPanel(parent=None, *, link=None, source: str = 'graph_builder', fold_key: str = '')[source]¶
Bases:
PySide6.QtWidgets.QWidgetThe well, the six zones, the plot-type override and the canvas.
- Parameters:
parent – parent widget.
link – the
LinkedSelectionthis view joins, so selecting here selects in every other view on it.Nonejoins the shared one; pass a private one in a test so the selection does not reach the rest of the application.source – this view’s name on that link, stamped onto everything it publishes – which is how a view knows not to answer its own selection.
Build the well, the drop zones and the canvas.
- Parameters:
parent – parent widget.
fold_key – the host module’s key. Given, the width the user drags the shelf | graph split to is remembered under
"<fold_key>::graph"and a fold of the Columns shelf or the Graph under"<fold_key>/Columns"and"<fold_key>/Graph"; empty remembers nothing, which is what a test or a second host that has not chosen a key wants.
- closeEvent(event)[source]¶
Stop background work and unlink before going away.
- Parameters:
event – the Qt close event.
- set_frame(frame: pandas.DataFrame | None) None[source]¶
Point the whole panel at a new table: the well and the canvas both.
- Parameters:
frame – the table to plot, or None to clear.
- set_spec(spec: spacr.qt.widgets.graph_spec.GraphSpec) None[source]¶
Push a whole spec in — restoring a saved chart, or a preset.
- Parameters:
spec – the complete chart specification; the canvas redraws and the drop zones are updated to match it.
- zone(channel: str) DropZone[source]¶
One channel’s drop zone, for a caller that needs to drive it.
- Parameters:
channel – the channel’s name.
- Returns:
the zone widget, or None when there is no such channel.
- property spec: spacr.qt.widgets.graph_spec.GraphSpec[source]¶
The graph the canvas is drawing.
- Returns:
the spec.
- class spacr.qt.widgets.graph_builder.GraphCanvas(parent=None, *, link=None, source: str = 'graph_builder')[source]¶
Bases:
spacr.qt.linked_selection.LinkedView,PySide6.QtWidgets.QWidgetThe matplotlib canvas every built graph is drawn on.
A
LinkedView, so a selection made here propagates to the other views sharing its model, and the base class forGateCanvas.- Parameters:
parent – parent widget.
link – the
LinkedSelectionthis view joins, so selecting here selects in every other view on it.Nonejoins the shared one; pass a private one in a test so the selection does not reach the rest of the application.source – this view’s name on that link, stamped onto everything it publishes – which is how a view knows not to answer its own selection.
Build the canvas and link it to the shared selection.
- Parameters:
parent – parent widget.
- axes_at(row: int = 0, col: int = 0)[source]¶
The matplotlib axes at one facet position.
- Parameters:
row – the grid row, from 0.
col – the grid column, from 0.
- Returns:
the axes, or None when that position was not drawn.
- brush(x0: float, y0: float, x1: float, y1: float, *, row: int = 0, col: int = 0, publish: bool = True) spacr.selection.Selection | None[source]¶
Select every row of one panel inside the rectangle, and publish it.
Evaluated against the panel’s unsampled rows, so a brush over a density raster or a sampled scatter still names every row in the rectangle rather than only the ones that got drawn.
- Parameters:
x0 – horizontal start of the rectangle in the panel’s data coordinates; on a categorical axis these are tick positions, one per level, and the two ends may come in either order.
y0 – vertical start of the rectangle, in the same coordinates.
x1 – horizontal end of the rectangle.
y1 – vertical end of the rectangle; ignored on a histogram or bar chart, whose vertical axis is a count.
- Returns:
the published
Selection, orNonewhen this table carries no object keys to name rows with.
- closeEvent(event)[source]¶
Unlink from the shared selection before going away.
A linked view that outlives its window would receive selection broadcasts after its C++ half is gone, which crashes.
- Parameters:
event – the Qt close event.
- decorate_axes(ax) None[source]¶
Called after each panel is drawn. Nothing by default.
Where a subclass puts grid lines and log scales – after the data, so it cannot change what was plotted, only how it is read.
- Parameters:
ax – the Matplotlib axes of the panel that was just drawn.
- figure()[source]¶
The matplotlib Figure this canvas draws on.
Public so a caller can export the graph without reaching into a private attribute. Do not draw on it from outside –
set_specand the redraw path own its contents.
- on_linked_filter_changed(data_filter) None[source]¶
A filter genuinely narrows the population: redraw and re-scale.
- Parameters:
data_filter – the linked filter that changed; it is not read here, only a debounced redraw is started.
- on_linked_selection_changed(selection: spacr.selection.Selection) None[source]¶
A selection only highlights — never a row fewer on screen.
- Parameters:
selection – the linked selection that changed; it is not read directly, and the highlight is recomputed from the current linked selection.
- panel_axes() Dict[Tuple[int, int], object][source]¶
{(row, col): Axes}for every panel, empty ones included.
- point_colormap()[source]¶
The colour map for a continuous colour axis.
A hook: the Gate Editor lets the user choose one, and the choice has to reach the drawing rather than only the settings dict.
- set_channel(channel: str, column: str | None) None[source]¶
Rebind one channel and redraw.
- Parameters:
channel – the channel’s name, such as
xorcolour.column – the column to bind, or None to clear it.
- set_frame(frame: pandas.DataFrame | None) None[source]¶
Point the canvas at a table.
Channels naming a column the new table does not have are emptied rather than carried over: a spec that half-resolves would draw a chart of fewer variables than the zones claim.
- Parameters:
frame – the table to plot, or None for no table; channels naming a column it lacks are emptied.
- set_spec(spec: spacr.qt.widgets.graph_spec.GraphSpec, *, immediate: bool = True) None[source]¶
Replace the spec and redraw.
- Parameters:
spec – the chart specification to draw; column kinds are recomputed from it for the current table.
- property grid[source]¶
The facet grid the last draw laid out, or None when unfaceted.
- Returns:
the grid.
- property kinds: Dict[str, str][source]¶
The loaded table’s column kinds, with the spec’s role overrides.
Public because “is this column continuous here?” is the question every caller of
spec()asks next, and re-deriving it would risk two answers.
- property render_data: spacr.qt.widgets.graph_spec.RenderData | None[source]¶
What the last draw plotted, or None before the first.
The rendered data rather than the source table: a large frame is sampled or binned before it is drawn, and this is what is on screen.
- Returns:
the render data, or None.
- property spec: spacr.qt.widgets.graph_spec.GraphSpec[source]¶
The graph this canvas is drawing.
- Returns:
the spec.
- spacr.qt.widgets.graph_builder.categorical_colours() Tuple[str, ...][source]¶
The fixed eight-hue series order for the active theme.
- spacr.qt.widgets.graph_builder.page_alpha() float[source]¶
The page-opacity preference as a plain float, for matplotlib.
Matplotlib takes alpha as a number, not as a QSS colour, so
pane_surface()is no help to an axes patch. Degrades to the theme’s designed scrim when preferences cannot be read, which is what a first run mid-generation gets.
Nested helpers¶
- GraphCanvas._draw_points.update(new_mask) None¶
Redraw the points for a new selection mask.
spacr/qt/widgets/graph_builder.py:1197
- GraphCanvas._xy.axis(column, levels)¶
One axis’s values and its level order, or empty when unset.
spacr/qt/widgets/graph_builder.py:1130
- _canvas_class.OwnedTimerFigureCanvas.__init__(self, figure, *, panel: bool = True)¶
Wrap a figure in a canvas that owns its own redraw timer.
- Parameters:
figure – the Matplotlib
Figureto draw. Held by the canvas, which is what “owned” means here – the timer is a child of the canvas, so the figure and the redraw it schedules are destroyed together and a queued redraw cannot outlive the widget it would paint.panel – draw the page surface under the figure.
Falsefor a canvas that is already sitting ON a panel — the scree plot inside the PCA shelf, say. Two surfaces stacked read 0.49 at a requested 30 %, a shade no position of the slider can reach, so the inner one shows the outer panel through instead.spacr/qt/widgets/graph_builder.py:569
- _canvas_class.OwnedTimerFigureCanvas._spacr_draw(self)¶
Draw once, if a draw is still pending.
The flag is cleared first so a draw that schedules another does not lose it.
spacr/qt/widgets/graph_builder.py:622
- _canvas_class.OwnedTimerFigureCanvas.cancel_pending_draw(self)¶
Drop any queued redraw. Safe on a canvas Qt has already deleted.
spacr/qt/widgets/graph_builder.py:636
- _canvas_class.OwnedTimerFigureCanvas.draw_idle(self)¶
Ask for a redraw on the owned timer rather than a static one.
Matplotlib’s Qt canvas uses static
QTimer.singleShot, whose callback is not owned by the canvas and can run after Qt has deleted it. The timer here is a child of the canvas, so it dies with what it would draw.spacr/qt/widgets/graph_builder.py:608
- _canvas_class.OwnedTimerFigureCanvas.paintEvent(self, event)¶
Draw the page panel, then let matplotlib draw over it.
spacr/qt/widgets/graph_builder.py:600