Temporal Projection View Widget
Temporal Projection View Widget Overview
The Temporal Projection View Widget renders a 2D spatial overlay of PointData and LineData collapsed across all time frames into a single view. Unlike trial-aligned plots (LinePlotWidget, EventPlotWidget, PSTHWidget, HeatmapWidget), this widget does not use PlotAlignmentState or GatherResult. Instead, it flattens all temporal samples via SpatialMapper_AllTimes and displays them in a single spatial coordinate frame.
Users can add multiple PointData and LineData keys, configure rendering appearance (point glyph shape, size, color, alpha, and line width), and interact via zoom, pan, and selection. Points and lines carry EntityID associations, enabling click-to-navigate, selection-to-group workflows, and group-based coloring.
Architecture
The widget follows the standard plot widget directory layout:
Plots/TemporalProjectionViewWidget/
├── CMakeLists.txt
├── TemporalProjectionViewWidgetRegistration.hpp # EditorRegistry registration module
├── TemporalProjectionViewWidgetRegistration.cpp
├── Core/
│ ├── TemporalProjectionViewState.hpp # QObject state class (EditorState subclass)
│ └── TemporalProjectionViewState.cpp
├── Rendering/
│ ├── TemporalProjectionOpenGLWidget.hpp # OpenGL rendering + mouse interaction
│ └── TemporalProjectionOpenGLWidget.cpp
└── UI/
├── TemporalProjectionViewWidget.hpp # Main composite widget (axes + OpenGL + layout)
├── TemporalProjectionViewWidget.cpp
├── TemporalProjectionViewWidget.ui
├── TemporalProjectionViewPropertiesWidget.hpp # Properties/settings panel
├── TemporalProjectionViewPropertiesWidget.cpp
└── TemporalProjectionViewPropertiesWidget.ui
Component Overview
| Component | Responsibility |
|---|---|
TemporalProjectionViewWidget (UI) |
Top-level Qt widget — composes OpenGL canvas, horizontal axis, vertical axis |
TemporalProjectionViewPropertiesWidget (UI) |
Properties panel — data key management (point + line), rendering controls, selection mode, axis range controls |
TemporalProjectionOpenGLWidget (Rendering) |
OpenGL canvas — point rendering (via SceneRenderer), line rendering (via BatchLineRenderer), mouse interaction, selection |
TemporalProjectionViewState (Core) |
Serializable state — view state, axis ranges, data keys, rendering parameters, selection mode |
TemporalProjectionViewWidgetRegistration |
Registers type with EditorRegistry (state, view, properties factories) |
EditorState Pattern
The Temporal Projection View Widget follows the project’s EditorState architecture:
TemporalProjectionViewStateinherits fromEditorState- Registered with
EditorRegistryviaTemporalProjectionViewWidgetModule::registerTypes() TemporalProjectionViewWidgetis the view factory,TemporalProjectionViewPropertiesWidgetis the properties factory- Both share the same
TemporalProjectionViewStateinstance - Properties placed in Right zone, view placed in Center zone (default)
- The
create_editor_customfactory inTemporalProjectionViewWidgetRegistrationhandles coupling the view and properties widgets, connecting range controls and forwardingtimePositionSelectedtoEditorRegistry::setCurrentTime()
State Management
TemporalProjectionViewStateData
The serializable state struct contains all persistent configuration:
struct TemporalProjectionViewStateData {
std::string instance_id;
std::string display_name = "Spatial Overlay";
CorePlotting::ViewStateData view_state;
HorizontalAxisStateData horizontal_axis;
VerticalAxisStateData vertical_axis;
// Data keys
std::vector<std::string> point_data_keys;
std::vector<std::string> line_data_keys;
// Rendering
CorePlotting::GlyphStyleData point_glyph_style; // shape, size, color, alpha
float line_width = 2.0f;
// Selection
std::string selection_mode = "none"; // "none", "point", "line", "polygon"
// Group integration
bool color_by_group = true; // Toggle for group-based coloring
};Signal Architecture
TemporalProjectionViewState emits category-level signals to minimize coupling complexity:
| Signal | Trigger |
|---|---|
viewStateChanged |
Zoom, pan, or bounds changed (view transform only — no scene rebuild) |
pointDataKeyAdded / pointDataKeyRemoved / pointDataKeysCleared |
Point data key changes |
lineDataKeyAdded / lineDataKeyRemoved / lineDataKeysCleared |
Line data key changes |
glyphStyleChanged |
Any point glyph style property (shape, size, color, alpha) changed |
lineWidthChanged |
Line rendering width changed |
selectionModeChanged |
Selection mode changed |
selectionChanged |
Selection set changed (entities added/removed) |
colorByGroupChanged |
Group-based coloring toggled on/off |
stateChanged (inherited) |
Any change requiring scene rebuild |
Performance-critical separation: Zoom and pan operations emit only viewStateChanged(), which updates the projection matrix without triggering an expensive scene rebuild via stateChanged().
Axis States
TemporalProjectionViewState owns both axis states:
HorizontalAxisState— X-axis (generic value axis). See Plot Widget Guide for full axis documentation.VerticalAxisState— Y-axis (generic value axis). Supports the “silent update” pattern to prevent feedback loops during zoom/pan.
Both axis states synchronize bidirectionally with the view state:
- User changes axis range via spinboxes → view state zoom/pan updated → projection refreshed
- User zoom/pans in OpenGL → axis ranges updated silently via
setRangeSilent()(no feedback loop)
This is the same axis pattern used by ScatterPlotWidget and ACFWidget.
Data Sources
Overview
The widget supports two spatial data types, each added as multiple keys from the DataManager:
| Data Type | Rendering Backend | Selection Method | EntityID Source |
|---|---|---|---|
PointData |
SceneRenderer via SceneBuilder::addGlyphs() |
Click-to-select (point mode) | Per-point EntityID from PointData |
LineData |
BatchLineStore / BatchLineRenderer |
Ctrl+Drag stroke intersection (line mode) | Per-line EntityID from LineData |
PointData
Each PointData key in point_data_keys is loaded via DataManager::getData<PointData>(key). All time frames are flattened into a single set of (x, y) positions using CorePlotting::SpatialMapper::mapAllPoints(), which returns MappedElement objects containing world-space coordinates and associated EntityIDs.
Points are rendered as circle glyphs via SceneBuilder::addGlyphs() → SceneRenderer. This is the same glyph rendering infrastructure used by EventPlotWidget for raster tick marks.
LineData
Each LineData key in line_data_keys is loaded via DataManager::getData<LineData>(key). Lines are iterated across all time frames and built into LineBatchData via CorePlotting::buildLineBatchFromLineData().
Lines are rendered via BatchLineStore / BatchLineRenderer, the same SSBO-based batch line rendering pipeline shared with LinePlotWidget. This infrastructure provides:
- Efficient rendering of large numbers of line segments
- Per-line selection state (normal / selected visual styles)
- GPU compute shader intersection for selection strokes (with CPU fallback)
DataManager Observer
The properties widget registers a DataManager observer to keep combo boxes current when data is added/removed:
// Constructor
_dm_observer_id = _data_manager->addObserver([this]() {
_populatePointComboBox();
_populateLineComboBox();
});
// Destructor — critical to avoid dangling references
if (_data_manager && _dm_observer_id != -1) {
_data_manager->removeObserver(_dm_observer_id);
}See Plot Widget Guide — DataManager Integration for the full observer contract.
Rendering Pipeline
Data Flow
TemporalProjectionViewState (data keys + rendering params)
↓
DataManager::getData<PointData>(key) / getData<LineData>(key)
↓
SpatialMapper::mapAllPoints() → MappedElement[] (points)
LineData::elementsView() → line vertices (lines)
↓
Bounding box computation (auto-fit from data, 2% margin)
↓
setXBounds() / setYBounds() → view state + axis sync
↓
SceneBuilder::addGlyphs() → RenderableScene (points)
buildLineBatchFromLineData() → LineBatchData (lines)
↓
SceneRenderer::uploadScene() + BatchLineStore::upload()
↓
SceneRenderer::render() + BatchLineRenderer::render()
Dual Rendering Backend
The widget uses two separate rendering pipelines to handle points and lines with their distinct requirements:
Points — Rendered via SceneRenderer / SceneBuilder, which uses the GlyphRenderer for instanced glyph drawing. Selected points are highlighted by overwriting their color in the RenderableGlyphBatch::colors array (yellow highlight for selected entities).
Lines — Rendered via BatchLineStore / BatchLineRenderer, which uses SSBO-based segment storage with per-line selection masks. This enables cheap selection updates (mask-only GPU upload, no full scene rebuild). Selected lines are rendered in a distinct color (bright yellow) versus normal lines (semi-transparent red).
This dual-backend approach avoids double-rendering: lines are not added to the SceneBuilder scene.
Auto-Fit Bounds
After gathering all point and line data, the widget computes a tight bounding box with 2% padding:
- Scan all mapped point positions for min/max X and Y
- Scan all line vertices for min/max X and Y
- Fall back to
ImageSizefrom data if no actual data points exist - Fall back to 100×100 default if no data and no ImageSize
- Apply 2% margin to prevent edge clipping
- Reset zoom and pan to fit when bounds change
Line Rendering Colors
The BatchLineRenderer supports visual states per line:
| State | Color | Purpose |
|---|---|---|
| Normal | Semi-transparent red (0.8, 0.2, 0.2, 0.6) | Unselected lines |
| Selected | Bright yellow (1.0, 0.8, 0.0, 1.0) | Lines selected by stroke intersection |
Visual Customization
The widget uses the project-wide CorePlotting::GlyphStyleData / GlyphStyleState / GlyphStyleControls stack to give users control over point glyph appearance. The same stack is used by ScatterPlotWidget and EventPlotWidget.
Points
Serializable Style Data
CorePlotting::GlyphStyleData (in CorePlotting/DataTypes/GlyphStyleData.hpp) is a plain struct with no Qt dependencies, suitable for embedding directly in state data structs and serializing with rfl::json:
struct GlyphStyleData {
GlyphType glyph_type = GlyphType::Circle;
float size = 5.0f;
std::string hex_color = "#FF4444";
float alpha = 0.8f;
};TemporalProjectionViewStateData embeds this as:
CorePlotting::GlyphStyleData point_glyph_style; // default: red circle, size 5, alpha 0.8Available GlyphType values: Circle, Square, Tick, Cross, Diamond, Triangle. (Diamond and Triangle fall back to Circle in the current renderer.)
QObject State Wrapper
TemporalProjectionViewState owns a std::unique_ptr<GlyphStyleState> (Plots/Common/GlyphStyleWidget/Core/GlyphStyleState.hpp). GlyphStyleState is a QObject that wraps GlyphStyleData and emits:
| Signal | When Emitted | Purpose |
|---|---|---|
styleChanged() |
User edits a property | Triggers scene rebuild via stateChanged() |
styleUpdated() |
setStyleSilent() (e.g., on fromJson) |
Refreshes UI without rebuild |
The state class wires these up in its constructor:
// Initialize GlyphStyleState from serialized data
_glyph_style_state->setStyleSilent(_data.point_glyph_style);
// Propagate user changes back to serializable struct and notify renderer
connect(_glyph_style_state.get(), &GlyphStyleState::styleChanged, this, [this]() {
_data.point_glyph_style = _glyph_style_state->data();
emit glyphStyleChanged();
emit stateChanged();
});The glyphStyleState() accessor returns the raw pointer for binding to GlyphStyleControls.
Properties Panel Controls
A “Point Appearance” collapsible Section in TemporalProjectionViewPropertiesWidget embeds GlyphStyleControls, which provides four controls laid out as a QFormLayout:
| Control | Type | What It Sets |
|---|---|---|
| Shape | QComboBox |
GlyphType (Circle / Square / Tick / Cross / Diamond / Triangle) |
| Size | QDoubleSpinBox |
Radius in pixels (0.5 – 50 px, step 0.5) |
| Color | QPushButton |
Opens QColorDialog; stores as hex string |
| Alpha | QDoubleSpinBox |
Transparency (0.0 – 1.0, step 0.05) |
The controls use the anti-recursion _updating_ui guard pattern to avoid feedback loops when the state updates the UI.
Rendering Conversion
TemporalProjectionOpenGLWidget::rebuildScene() reads the glyph style at scene build time via _state->glyphStyleState()->data() and converts it to rendering types using two free functions from CorePlotting/DataTypes/GlyphStyleConversion.hpp:
auto const & glyph_data = _state->glyphStyleState()->data();
CorePlotting::GlyphStyle point_style;
point_style.glyph_type = CorePlotting::toRenderableGlyphType(glyph_data.glyph_type);
point_style.size = glyph_data.size;
point_style.color = CorePlotting::hexColorToVec4(glyph_data.hex_color, glyph_data.alpha);
builder.addGlyphs("spatial_points", elements, point_style);toRenderableGlyphType() maps the serializable GlyphType enum to the renderer-level RenderableGlyphBatch::GlyphType. hexColorToVec4() parses a "#RRGGBB" string and applies the separate alpha value, returning a glm::vec4 for the shader.
The glyphStyleChanged signal from TemporalProjectionViewState is connected in TemporalProjectionOpenGLWidget::setState() to set _scene_dirty = true and call update(), ensuring the next paint picks up the new style without any extra bookkeeping.
Shared infrastructure used (see Plots/Common/GlyphStyleWidget/):
| Component | Location |
|---|---|
CorePlotting::GlyphType enum |
CorePlotting/DataTypes/GlyphStyleData.hpp |
CorePlotting::GlyphStyleData struct |
CorePlotting/DataTypes/GlyphStyleData.hpp |
CorePlotting::toRenderableGlyphType() |
CorePlotting/DataTypes/GlyphStyleConversion.hpp |
CorePlotting::hexColorToVec4() |
CorePlotting/DataTypes/GlyphStyleConversion.hpp |
GlyphStyleState |
Plots/Common/GlyphStyleWidget/Core/GlyphStyleState.hpp |
GlyphStyleControls |
Plots/Common/GlyphStyleWidget/GlyphStyleControls.hpp |
Interaction
Zoom and Pan
The widget supports separated X/Y zoom for independent exploration of spatial dimensions, using the shared PlotInteractionHelpers:
| Input | Action |
|---|---|
| Mouse wheel | X-zoom only |
| Shift + wheel | Y-zoom only |
| Ctrl + wheel | Uniform zoom (both axes) |
| Click + drag | Pan in both axes |
These are the same shared interaction helpers used across all plot widgets — see Plot Widget Guide.
Selection Modes
The widget supports four selection modes, controlled by a combo box in the properties panel:
Point Selection (mode: “point”)
- Ctrl + Click on a point: Toggle selection (add or remove entity)
- Ctrl + Shift + Click: Remove mode (deselect only)
- Uses
SceneHitTester::queryQuadTree()for spatial point lookup with 10-pixel tolerance - Selected points are highlighted orange in the glyph batch
This shares the SceneHitTester infrastructure from CorePlotting/Interaction/ also used by EventPlotWidget for raster click hit testing.
Line Selection (mode: “line”)
- Ctrl + Click + Drag: Draw intersection stroke across lines
- Ctrl + Shift + Click + Drag: Remove mode (deselect intersected lines)
- Escape: Cancel in-progress selection
- Uses the shared
LineSelectionHelpersfromPlots/Common/, which delegates to either GPU compute shader or CPU intersector - Selection updates are mask-only (no full scene rebuild)
This shares the line selection infrastructure with LinePlotWidget via LineSelectionHelpers:
| Component | Location | Purpose |
|---|---|---|
LineSelectionHelpers |
Plots/Common/ |
Shared stroke intersection + preview utilities |
ILineBatchIntersector |
CorePlotting/LineBatch/ |
Intersector interface |
CpuLineBatchIntersector |
CorePlotting/LineBatch/ |
CPU fallback intersector |
ComputeShaderIntersector |
PlottingOpenGL/LineBatch/ |
GPU compute shader (GL 4.3+) |
Polygon Selection (mode: “polygon”)
- Ctrl + Click: Begin polygon / add vertex
- Mouse move (while active): Update preview line from last vertex to cursor
- Enter: Close polygon and select all points inside
- Escape: Cancel polygon drawing
- Backspace: Undo last vertex
- Uses the shared
PolygonInteractionControllerfromCorePlotting/Interaction/ - Point containment test via
selectPointsInPolygon()fromCorePlotting/Selection/(ray-casting algorithm) - Polygon preview rendered via
PreviewRenderer - Additive selection: Polygon selection adds to existing selection rather than replacing
This shares the polygon interaction infrastructure with ScatterPlotWidget:
| Component | Location | Purpose |
|---|---|---|
PolygonInteractionController |
CorePlotting/Interaction/ |
Click-based polygon creation, cursor preview, auto-close |
selectPointsInPolygon |
CorePlotting/Selection/ |
Ray-casting point-in-polygon test |
PreviewRenderer |
CorePlotting/Rendering/ |
Renders polygon preview during construction |
The selection mode instructions are provided via the shared SelectionInstructions helpers from Plots/Common/SelectionInstructions.hpp, which provide context-sensitive help text for each mode.
Selection State
Selected entity IDs are tracked in an std::unordered_set<EntityId>. When the selection changes, the entitiesSelected signal is emitted, allowing other components (e.g., group management) to react.
Selection is preserved across scene rebuilds: after rebuilding, both point glyph colors and line selection masks are restored from the cached _selected_entity_ids set.
Group Integration
The widget integrates with the project’s Entity Group system to enable organizing selected points and lines into groups for downstream analysis. A GroupManager is passed during widget registration and wired to the OpenGL renderer.
Group-Based Coloring
Points can be colored by their group membership, allowing visual identification of subgroups. A “Color by Group” checkbox in the properties panel toggles the feature (stored in TemporalProjectionViewStateData::color_by_group).
When enabled, applyGroupColorsToScene() applies group colors with the following priority:
- Selected points — orange (
1.0, 0.6, 0.0, 0.9) for visual feedback - Group colors — each group has an assigned color from
GroupManager::getEntityColor() - Default style — falls back to the user-configured glyph style
The GroupManager emits signals (groupCreated, groupRemoved, groupModified) that trigger scene rebuilds to reflect group changes made in other widgets.
Note: Line group coloring is deferred due to the separate BatchLineRenderer pipeline. Only point glyph coloring is currently implemented.
Properties Panel
The TemporalProjectionViewPropertiesWidget provides the following controls:
Data Key Management
- Point data key combo box + Add/Remove buttons — lists all
PointDatakeys from DataManager - Point data table — shows currently added point data keys
- Line data key combo box + Add/Remove buttons — lists all
LineDatakeys from DataManager - Line data table — shows currently added line data keys
Rendering Controls
- Point Appearance section —
GlyphStyleControlswith shape, size, color, and alpha controls. See Visual Customization — Points for full details. - Line width spinbox — adjusts line thickness (default: 2.0)
Selection and Grouping Controls
- Selection mode combo — None / Point / Line / Polygon
- Selection instructions label — context-sensitive help text (via shared
SelectionInstructionshelpers) - Color by Group checkbox — toggles group-based coloring for points
- Clear selection button — clears all selected entities
Axis Range Controls
When setPlotWidget() is called (during registration wiring), the properties panel creates:
- X-Axis Range Controls —
HorizontalAxisRangeControlsin a collapsibleSection - Y-Axis Range Controls —
VerticalAxisRangeControlsin a collapsibleSection
See Also
- Plot Widget Guide — Shared architecture, axis types, DataManager integration
- Temporal Projection View Roadmap — Development roadmap for upcoming features
- ScatterPlotWidget — Another point-rendering widget (paired numeric axes)
- LinePlotWidget — Shares
BatchLineRendererandLineSelectionHelpersinfrastructure - EventPlotWidget — Shares
SceneRendererglyph rendering andSceneHitTester