Orchestrator Flow: Plot Creation Lifecycle#
This document describes the interaction between PlotOrchestrator, JobOrchestrator, PlottingController, and PlotDataService when a plot layer is created.
Overview#
Plot creation is static: the dashboard data plane is keyed by stable DataKey(workflow_id, source_name, output_name), so a layer’s keys are fully determined by its PlotConfig. add_layer creates the plotter and sets up the data pipeline immediately and unconditionally — there is no waiting for a workflow job.
Workflow run-state reaches layers by polling, not callbacks: the orchestrator update loop calls PlotOrchestrator.sync_job_states() each pass, which compares every layer’s workflows’ active job numbers against the last observed values:
Changed, all running — a new generation was committed. The commit cleared the workflow’s buffers (
ActiveJobRegistry.begin_generation), so the layer’s presentation is reset: a fresh plotter replaces the old one, blanking the plot and resetting plotter-internal accumulation (e.g. autoscale ranges). The data pipeline is untouched; keys are generation-independent.Changed, not all running — the workflow stopped. The layer freezes in STOPPED with its last frame retained; the retained buffers stay displayed until the next commit clears them.
Sequence Diagram#
sequenceDiagram
participant User as User/PlotGridTabs
participant PO as PlotOrchestrator
participant JO as JobOrchestrator
participant PC as PlottingController
participant PDS as PlotDataService
Note over User,PDS: Layer setup (static, at add-layer time)
User->>PO: add_layer(cell_id, PlotConfig)
PO->>PC: create_plotter(plot_name, params)
PC-->>PO: Plotter instance
PO->>PDS: job_started(layer_id, plotter)<br/>[state: WAITING_FOR_DATA]
PO->>PC: setup_pipeline(keys_by_role, plot_name,<br/>params, on_update callback)
PC-->>PO: DataServiceSubscriber
opt Workflow not running
PO->>PDS: job_stopped(layer_id)<br/>[state: STOPPED; retained data still renders]
end
PO-->>User: on_cell_updated(cell)
Note over User,PDS: Data arrival (ingestion thread)
Note over PO: on_update marks layer dirty;<br/>flush_frames pulls and rebuilds
PO->>PO: plotter.compute(data)
PO->>PDS: data_arrived(layer_id)<br/>[state: READY]
Note over User,PDS: Run-state poll (orchestrator update loop)
PO->>JO: get_active_job_number(workflow_id) per workflow
alt New generation (commit observed)
PO->>PC: create_plotter(plot_name, params)
PO->>PDS: job_started(layer_id, fresh plotter)<br/>[state: WAITING_FOR_DATA, blank]
else Workflow stopped
PO->>PDS: job_stopped(layer_id)<br/>[state: STOPPED, frame retained]
end
Key Components#
Component |
Responsibility |
|---|---|
PlotGridTabs |
UI widget; subscribes to PlotOrchestrator lifecycle events, polls PlotDataService for layer state |
PlotOrchestrator |
Manages plot lifecycle: plotter creation, data pipeline setup, run-state polling ( |
JobOrchestrator |
Manages workflow jobs; exposes run-state via |
PlottingController |
|
PlotDataService |
Layer state machine (WAITING_FOR_DATA → READY / STOPPED / ERROR). Version-based polling for UI. See |
DataService |
Holds job result data under stable DataKeys; manages subscriber lifecycle and buffer retention |
Layer States#
Each layer transitions through explicit states managed by PlotDataService. States are derived from workflow run-state plus data arrival:
stateDiagram-v2
[*] --> WAITING_FOR_DATA: layer setup
WAITING_FOR_DATA --> READY: data_arrived()
WAITING_FOR_DATA --> STOPPED: job_stopped()
WAITING_FOR_DATA --> ERROR: error_occurred()
READY --> STOPPED: job_stopped()
READY --> WAITING_FOR_DATA: job_started(plotter) [new generation]
READY --> ERROR: error_occurred()
STOPPED --> WAITING_FOR_DATA: job_started(plotter)
STOPPED --> ERROR: error_occurred()
Each transition increments a version counter. UI components poll for version changes to detect when rebuilds are needed. A layer in STOPPED may still display retained data: data_arrived while STOPPED is a no-op and has_displayable_plot reports True once the plotter holds a computed frame.
Multi-Source Layers (Correlation Plots)#
PlotConfig.data_sources maps role names to DataSourceConfig:
“primary”: Main data source (required for all layers)
“x_axis”, “y_axis”: Correlation axes (optional, for correlation histograms)
Data readiness is gated at the data layer: the DataSubscriber only assembles when every role has data. Run-state gating in sync_job_states mirrors this: the presentation resets only when all of the layer’s workflows are running; a stop of any freezes the layer (a correlation plot cannot function with partial data).
The keys_by_role dict flows through to setup_pipeline(), preserving role structure so plotters receive data grouped by role.
Lifecycle Callbacks#
PlotGridTabs subscribes to PlotOrchestrator lifecycle events (grid created/removed/updated, cell updated/removed). The on_cell_updated callback signals that a cell’s layer configuration changed. Actual layer state (waiting, ready, error) is read from PlotDataService by the UI during periodic polling.