Edit on GitHub

starplast.star_map

The star map: one gene in the middle, the genes linked to it around it, and who says so.

The 3D map answers "what is near what" in one embedding. This view answers a different question -- "what links THIS gene to others, and through which evidence or inference" -- by drawing the chosen gene (the "star") at the centre and its linked genes on rings around it. Every edge is a link from :mod:starplast.star_edges and carries its provenance:

  • colour = its source: each measured layer, each strategy, and one colour for your own runs;
  • line = solid for measured, dashed for inferred;
  • width = its strength (the score's percentile within its run);
  • hover = the gene's id and product, or the link's layer / strategy / run, setting and score.

Click a neighbour to re-centre on it (Back returns), double-click it to select it on the 3D map. Depth is one or two hops. Each source keeps only its strongest links around each gene (per source), so a dense layer cannot hide a sparse one. The list on the right is the legend, the per-source toggle and the per-source link count at once.

The larger network. The mode box chooses how much is drawn: one gene (the star above), the neighbourhood of that gene (breadth-first, strongest links first, up to E.MAX_HOPS hops and E.NEIGHBOURHOOD_MAX genes), or the whole network (up to E.OVERVIEW_MAX genes and E.LARGE_EDGE_MAX links, with the largest clusters coloured). Both larger views degrade by keeping the strongest links and the best-linked genes, and print the sentence that says what they left out. They are drawn by :class:_NetworkCanvas, which builds one path per source and strength bucket once and paints it into a cached pixmap, so panning, zooming and hovering rebuild nothing.

What counts as a link is the user's to decide: which sources may contribute, a minimum strength, a cap on how many links one gene may bring, and "at least N different sources must agree" (:class:star_edges.Definition). The resulting network is counted live, and a definition can be saved and reloaded by name (:class:star_edges.DefinitionStore, under paths.user_cache_dir()).

Nothing in this module moves the graph when the pointer does. A hover changes a colour, a highlight and the text in a fixed-height box, and nothing else: the layout is cached per centre, mode and definition, the view is framed by the rectangle that layout fixed, and hover text is rebuilt on a timer. The history of why each of those is necessary is in instructions/open/58_star_map_steady_larger_defined.md.

Runs made in the Strategies tab arrive through :meth:StarMapPanel.add_result and appear at once, in the "your runs" colour; they are also kept on disk (star_edges.UserEdgeStore) and come back next session. :func:install puts the panel in the main window as a dock beside Strategies.

DEFAULT_PER_SOURCE = 6
MAX_NODES = 160
HOVER_MARGIN = 3.0
HOVER_DEBOUNCE_MS = 40
RULE_DEBOUNCE_MS = 180
MODE_LABELS = (('star', 'one gene (star)'), ('neighbourhood', 'neighbourhood'), ('network', 'whole network'))
DEFAULT_HOPS = 2
DEFAULT_SIZE_CAP = 600
MINIMAP = 132
PICK_RADIUS = 9.0
WIDTH_BUCKETS = 3
CLUSTER_COLOURS = 12
MIN_CLUSTER = 4
INFO_HEIGHT = 58
HEADLINE_HEIGHT = 22
TIPS = {'gene': 'Type a gene id (or pick one from the list) and press Enter to put it at the centre. Its linked genes are drawn around it.', 'centre': 'Put the typed gene at the centre of the star map.', 'back': 'Return to the gene that was at the centre before this one.', 'depth': 'How far out to draw: 1 hop shows the genes linked to the centre; 2 hops also shows the genes linked to those (each keeping a third as many links, so the map stays readable).', 'per_source': 'How many links each source (a measured layer, a strategy, your runs) keeps around each gene -- the strongest ones. A dense layer such as shared compartment joins hundreds of genes; this keeps it from hiding a sparse one such as crosslinks.', 'follow': 'When on, selecting a gene anywhere else in the application (the 3D map, search, a results table) also centres it here.', 'map': 'Select the centre gene on the 3D map and fly to it, with its evidence in the side panel. Double-clicking a gene here does the same for that gene.', 'all': 'Show links from every source.', 'none': 'Hide links from every source; then tick the ones to compare.', 'measured': 'Show only the measured layers (the data), hiding every inferred source.', 'sources': 'The legend and the switches: one row per source with its colour, and how many links the centre gene has in it. Untick a row to hide that source. Measured layers are drawn solid, strategy inferences dashed, and your own runs in gold.', 'view': 'The star map. The centre gene is the star; linked genes sit on the rings. Hover a gene or a link to read where it comes from; click a gene to re-centre on it; double-click to select it on the 3D map; scroll to zoom; drag to pan.', 'info': "What the pointer is over: a gene's id and product, or a link's source, run, setting and score. This box keeps its height whatever it says, so reading it never moves the graph.", 'mode': "How much of the network to draw. 'one gene (star)' puts the chosen gene in the middle with its links on rings. 'neighbourhood' grows outwards from it for as many hops as you ask, up to the gene cap. 'whole network' lays out every gene the connection rules below leave, with its clusters visible.", 'hops': 'How far the neighbourhood reaches from the centre gene: 1 is its direct links, 2 also their links, and so on. The walk takes the strongest links first, so the gene cap keeps the best-supported part of the neighbourhood.', 'size_cap': 'The most genes the larger views will draw. Raising it shows more and costs more time to lay out; lowering it keeps the picture readable. Whatever is left out is stated on screen.', 'min_strength': "A link is drawn only when its strength -- its score's percentile within its own run -- is at least this. 0 keeps every link; raise it to keep only each source's best-supported ones.", 'max_per_gene': "The most links any one gene may contribute, its strongest first. This is what stops a hub (a gene in hundreds of shared-compartment links) from filling the picture by itself. 'no cap' lets every link through.", 'min_sources': 'Draw a pair of genes only when at least this many DIFFERENT sources say they are linked -- two independent sources agreeing is much better evidence than one. 1 asks for no agreement at all.', 'counts': 'The network these rules define, right now: how many links survive them, between how many genes, from how many sources -- and what each rule removed.', 'definition': 'A saved set of connection rules, by name. Pick one to load it, or type a new name and press Save to keep the rules now set (the sources ticked on the right, the minimum strength, the per-gene cap and the agreement rule). Saved definitions are kept with your other state and come back next session.', 'save_def': 'Save the connection rules now set under the name in the box beside this, so you can come back to exactly this definition of the network.', 'delete_def': 'Forget the saved definition named in the box beside this.', 'canvas': 'The larger network. Drag to pan, scroll to zoom, hover a gene to read it (the graph does not move), click a gene to make it the centre, double-click to select it on the 3D map. The small picture in the corner is the whole layout with your view marked; click it to jump.', 'fit_btn': 'Frame the whole network in the view again, at the zoom that shows all of it.', 'note': 'What the caps left out of this picture, if anything.'}
class StarMapPanel(PyQt6.QtWidgets.QWidget):

A navigable network centred on one gene, drawing measured and inferred links by source.

StarMapPanel( nodes: pandas.DataFrame, organism: str, ctx=None, graph=None, store: starplast.star_edges.UserEdgeStore | None = None, theme='dark', shipped_path: str | None = None, definitions: starplast.star_edges.DefinitionStore | None = None, parent=None)

Build the panel over one organism's table. Links are loaded on first use.

:param ctx: a strategies.Context over the same table; needed to turn a user run into links (and its graph is reused for the measured layers). :param graph: measured layers (layer__a/b/w arrays); default: ctx.graph, else the space's own graph file. :param store: where the user's runs are kept; None keeps them for this session only. :param theme: a theme name, or a callable returning the current one. :param definitions: where the user's named connection definitions are kept; None keeps them for this session only.

def centre_changed(unknown):

pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL

types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.

def select_on_map(unknown):

pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL

types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.

def status(unknown):

pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL

types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.

nodes
organism
gene_ids
products
colors: dict
labels: dict
kinds: dict
enabled: set | None
centre: int | None
history: list
rings: tuple
last_nodes
last_edges
last_counts: dict
definitions
gene_edit
centre_btn
back_btn
mode_box
def depth(unknown):
per_source
hops
size_cap
fit_btn
follow_box
map_btn
min_strength
strength_label
max_per_gene
min_sources
definition_box
save_def_btn
delete_def_btn
counts_label
note_label
all_btn
none_btn
measured_btn
sources
legend
headline
info
scene
view
canvas
stack
def palette_colors(self) -> dict:

The current theme's palette.

def theme_name(self) -> str:

The current theme's name.

Every link of this table (measured, shipped and the user's), built on first use.

def add_result(self, result) -> int:

Add the links of a strategy run made in the application; returns how many.

They are kept by the store (on disk when it has a root) and drawn at once under "your runs". A run that relates no genes to each other adds nothing and says so.

def position(self, gene) -> int | None:

A gene id (or position) as a position in this table, or None.

def centre_on(self, gene, remember: bool = True) -> bool:

Put a gene at the centre and redraw. False if the gene is not in this table.

def back(self) -> bool:

Return to the previous centre.

def follow(self, gene) -> None:

Centre a gene selected elsewhere, if following is on (deferred while hidden).

def showEvent(self, ev):

Draw a gene selected while the panel was hidden.

def select_gene_on_map(self, gene) -> None:

Ask the main window to select this gene on the 3D map.

def groups_shown(self) -> list:

The groups whose links are drawn.

def set_enabled(self, groups) -> None:

Show exactly these groups (None: all of them, including ones added later).

def only_measured(self) -> None:

Show the measured layers only.

def mode(self) -> str:

Which view is showing: MODE_STAR, MODE_NEIGHBOURHOOD or MODE_NETWORK.

def set_mode(self, mode: str) -> None:

Show one of the three views.

def current_definition(self) -> starplast.star_edges.Definition:

The connection definition the controls now describe.

def set_definition(self, definition: starplast.star_edges.Definition) -> None:

Put a definition into the controls and redraw once.

def defined(self) -> tuple:

The links this definition calls edges: (EdgeIndex, frame, counts).

Cached on the definition's signature, so moving the pointer, changing the view or re-centring never re-applies the rules.

def save_definition(self) -> str:

Keep the rules now set under the name in the box; returns the name, or "".

def forget_definition(self) -> bool:

Forget the saved definition named in the box.

def layout_positions(self, nodes: pandas.DataFrame, edges: pandas.DataFrame) -> dict:

gene -> (x, y): the centre at the origin, hop 1 on the inner ring grouped by source, hop 2 on the outer ring beside the gene that reached it.

def redraw(self) -> None:

Rebuild the view for the centre, the mode and the connection rules -- and only for those.

Nothing here runs on a hover. The positions it computes are cached under :meth:_layout_key, so asking for the same picture twice gives the same coordinates, and fit frames the rectangle they define rather than whatever the scene currently measures.

def fit(self) -> None:

Frame the whole graph in the view, from the rectangle the LAYOUT fixed.

The rectangle comes from the frozen positions, not from scene.itemsBoundingRect(): that one answers differently while an item is hovered, and framing the view by it made the graph jump under the pointer.

def edge_html(self, row) -> str:

A link's provenance as the hover text shows it.

def hover_gene(self, g: int) -> None:

The pointer is over a gene: queue its text (nothing is drawn or laid out).

def hover_edge(self, row) -> None:

The pointer is over a link: queue its provenance.

def install(window) -> StarMapPanel | None:

Add the star map to the main window as a dock beside Strategies, and wire it up.

Strategy runs feed it (result_ready); "Select on 3D map" goes through the window's own search; selecting a gene in the window centres it here when following is on (the window calls :meth:StarMapPanel.follow from its pick handler).