Heatmap Widget Roadmap
HeatmapWidget Development Roadmap
This document tracks remaining development tasks for the Heatmap Widget. For documentation of completed features and current architecture, see the main developer documentation.
Completed Work Summary
The following features are complete and documented in index.qmd:
- Widget skeleton: EditorState pattern, registration, state/view/properties factories
- State management:
HeatmapStatewithPlotAlignmentState,RelativeTimeAxisState,VerticalAxisStatecomposition - Axis system:
RelativeTimeAxisWidget(X) andVerticalAxisWidget(Y) with bidirectional sync - Interaction: Separated X/Y zoom, pan, double-click time navigation via shared
PlotInteractionHelpers - Properties panel: Alignment controls (
PlotAlignmentWidget), axis range controls in collapsible sections - OpenGL lifecycle:
SceneRendererinitialization, background color, projection/view matrix management - State serialization: Full round-trip JSON via
rfl::json - Unit selection state:
HeatmapStateData::unit_keyswith full add/remove/batch API andunitsChanged()signal - Unit selection UI:
Feature_Tree_Widgetembedded in properties panel, filtered toDigitalEventSeries, with prefix-based group toggling - Rate estimation infrastructure:
EventRateEstimationlibrary inPlots/Common/EventRateEstimation/providingcreateUnitGatherContext()/createUnitGatherContexts()for batched aligned gathering andestimateRate()/estimateRates()returningRateEstimate(pairedtimes[]+values[]) with astd::variant<BinningParams, GaussianKernelParams, CausalExponentialParams>-based variation point. See Rate Estimation Redesign for the full API design. - Colormap infrastructure:
CorePlotting::Colormapslibrary inCorePlotting/Colormaps/providingColormapFunction,ColormapPreset(Inferno, Viridis, Magma, Plasma, Coolwarm, Grayscale, Hot), LUT-based O(1) evaluation with linear interpolation,mapValue()/mapValues()/mapMatrix()utilities. Shared with SpectrogramWidget and any future external-feature coloring use cases. - Heatmap scene building:
HeatmapMapperinCorePlotting/Mappers/convertingHeatmapRowDatarows toRenderableRectangleBatchwith Auto / Manual / Symmetric color range modes.HeatmapOpenGLWidget::rebuildScene()now gathers units viacreateUnitGatherContexts(), estimates rates viaestimateRates(), applies scaling viaapplyScaling(), convertsRateEstimateobjects toHeatmapRowData, and uploads the scene. - Y-axis auto-fit:
HeatmapWidgetauto-fits the vertical axis when the unit count changes. Y bounds are set to[0, num_units], Y zoom is reset to 1.0, and Y pan is reset to 0.0 so all units are visible. TheVerticalAxisWidgetusesidentityAxis("Unit", 0)for integer unit-index labels. Signal renamed fromtrialCountChangedtounitCountChangedfor semantic clarity. - Scaling and normalization: Five scaling modes (Firing Rate Hz, Z-Score, Normalized [0,1], Raw Count, Count/Trial). Uses shared
ScalingMode/applyScaling()fromRateEstimate.hpp.HeatmapStateDataextended withscalingandcolor_range(Auto/Manual/Symmetric). Auto-switches to Coolwarm colormap for z-score and Symmetric color range. Properties panel has “Scaling & Color Range” collapsible section withScalingModeControlswidget and manual vmin/vmax spinboxes. - Rate estimation options :
EstimationParamsvariant (std::variant<BinningParams, GaussianKernelParams, CausalExponentialParams>) stored directly inHeatmapStateData::estimation_params. LightweightEstimationParams.hppheader (no heavy transitive deps) safe to include from state headers.RateEstimationControlslibrary provides composable Qt widgets:EstimationMethodControls(method combo + stacked per-method parameter pages) andScalingModeControls(scaling mode combo).HeatmapPropertiesWidgetuses both widgets. Renderer passesestimationParams()directly toestimateRates()— no intermediate conversion. - Row sorting: Five sorting modes (Manual, TimeToPeak, PeakRate, MeanRate, Alphabetical) with ascending/descending toggle.
HeatmapSortModeenum andsortModeLabel()/allSortModes()utilities defined inHeatmapState.hpp.HeatmapStateDataextended withsort_modeandsort_ascending. Sorting logic inHeatmapDataPipeline.hpp/cppviacomputeSortOrder()(returns index permutation) andapplySortOrder()(reorders rows, rate estimates, and unit keys in-place). Applied afterrunHeatmapPipeline()in renderer before colormap mapping. Properties panel has “Sorting” collapsible section with sort mode combo and ascending checkbox. Full serialization round-trip. - Colormap selection UI:
ColormapControlscomposable widget inPlots/Common/ColormapControls/with colormap preset combo (gradient icon previews from LUT), color range mode combo (Auto/Manual/Symmetric), and vmin/vmax spinboxes.HeatmapStateData::colormapfield withcolormapChanged()signal. Auto-switches to Coolwarm for z-score scaling. Renderer reads colormap from state.
Not yet implemented: colorbar display (Phase 4) and all cross-widget linking described below.
Phase 4: Colorbar Display
Status: Not started. Phases 2 and 3 are complete; this phase can proceed.
Goal: Render a color legend alongside the heatmap showing the value-to-color mapping.
4.1 ColorbarWidget
Create a lightweight QWidget that renders a vertical color gradient with tick labels:
CorePlotting/Widgets/
├── ColorbarWidget.hpp
└── ColorbarWidget.cpp
The ColorbarWidget needs:
- A
ColormapFunctionto render the gradient - A value range (
vmin,vmax) for tick label computation - An axis label (e.g., “Firing Rate (Hz)”, “Z-Score”)
- Optional tick formatting (integer, decimal, scientific notation)
4.2 Integration with HeatmapWidget
Place the colorbar to the right of the OpenGL canvas in the HeatmapWidget layout:
┌──────────────────────────────────┬────┐
│ │ │
│ HeatmapOpenGLWidget │ CB │
│ │ │
├──────────────────────────────────┴────┤
│ RelativeTimeAxisWidget │
└───────────────────────────────────────┘
The colorbar updates when the colormap, scaling mode, or value range changes.
Phase 7: Cross-Widget Linking
Status: Design complete. Related to EventPlot Roadmap Phase 6 and PSTH Roadmap Phase 4.
Goal: Clicking a heatmap row updates PSTHWidget and EventPlotWidget to show the selected unit’s data.
7.1 SelectionContext Integration
Following the existing SelectionContext pattern used by DataInspector widgets:
// When user clicks a heatmap row:
void HeatmapOpenGLWidget::mouseReleaseEvent(QMouseEvent * event) {
if (!_is_panning) {
int unit_index = worldToUnitIndex(screenToWorld(event->pos()));
auto const & unit_config = _state->units().at(unit_index);
// Emit via SelectionContext:
SelectionContext::instance().setSelectedData(
unit_config.event_key, SelectionSource::HeatmapWidget);
}
}7.2 Receiving Widgets
In PSTHWidget and EventPlotWidget (if not pinned):
void PSTHPropertiesWidget::_onSelectionChanged(SelectionSource const & source) {
if (source.type != SelectionSourceType::HeatmapWidget) return;
if (_state->isPinned()) return;
auto key = source.data_key;
auto type = _data_manager->getDataType(key);
if (type == DataType::DigitalEventSeries) {
_state->clearPlotEvents();
_state->addPlotEvent(key, PSTHEventOptions{.event_key = key});
// Scene rebuilds automatically via stateChanged()
}
}7.3 Row Click → Unit Selection Flow
The primary cross-widget workflow:
HeatmapWidget row click
↓
SelectionContext::setSelectedData("unit_42", source)
↓
PSTHWidget (if not pinned) receives selectionChanged
↓
Clears existing events, adds "unit_42" → rebuilds histogram
↓
EventPlotWidget (if not pinned) also receives selectionChanged
↓
Updates raster plot for "unit_42"
This enables rapid exploration of neural data: click through units in the heatmap while the PSTH and raster plot update in sync. See EventPlot Roadmap Phase 6 and PSTH Roadmap Phase 4 for the complementary receiver implementations.
7.4 Visual Row Highlighting
When a row is selected (either by click or via incoming SelectionContext), highlight the selected row with a border or background overlay to provide visual feedback.
Phase 8: Tooltip with Stacked PSTH + Raster
Status: In progress. Basic tooltip wiring is complete — hovering over a heatmap row shows the unit key name via PlotTooltipManager. The tooltip correctly reflects the current sort order. PSTH and raster glyph rendering in the tooltip is not yet implemented. Depends on Phase 1 and Phase 7.
Goal: When hovering over a heatmap row, display a tooltip containing a miniature stacked PSTH histogram and raster plot for that unit.
8.1 Concept
┌─────────────────────────────────────────────────────┐
│ Heatmap │
│ unit_A ██████████████████████████████████████████ │
│ unit_B ██████████████████████████████████████████ │
│ unit_C ████████▓▓▓▓████████████████████████████ ← hover
│ unit_D ██████████████████████████████████████████ │
│ │
│ ┌─────────────────────┐ │
│ │ unit_C PSTH │ │
│ │ ▄▄ ██ ▄▄ .. │ │
│ │─────────────────────│ │
│ │ unit_C Raster │ │
│ │ | || | || | │ │
│ │ || | | | || │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────┘
8.2 Lightweight Rendering
The tooltip must render quickly without creating full widget instances. Two approaches:
Option A: Pre-rendered QPixmap Cache
When the scene rebuilds, pre-render PSTH and raster thumbnails for each unit into QPixmap objects. The tooltip simply displays the cached pixmap. This has memory cost proportional to the number of units but provides instant tooltips.
Option B: On-Demand QPainter Rendering
When the hover event fires, compute the PSTH bins and raster tick positions for the hovered unit using the cached GatherResult data, and render directly with QPainter into a tooltip widget. This avoids the memory cost but requires fast rendering.
Recommendation: Option B with the cached GatherResult data from the scene rebuild. The rate computation uses the shared estimateRate() from the EventRateEstimation library (the same function used by the heatmap itself), and QPainter rendering of a simple histogram + tick marks is lightweight.
8.3 Tooltip Content
The tooltip should show:
- Header: Unit label / event key
- PSTH subplot: Bar or line histogram of the unit’s rate profile (same data as the heatmap row)
- Raster subplot: Tick marks for each trial’s events (using the unit’s
GatherResult) - Optional statistics: Peak rate, time to peak, mean rate
8.4 Implementation Approach
The tooltip lifecycle (dwell timer, suppression during pan/zoom, show/hide) is handled by the shared PlotTooltipManager (Plots/Common/TooltipManager/). The HeatmapWidget provides two callbacks:
- Hit test provider: Converts the screen position to a unit index (row lookup from Y coordinate). Returns a
PlotTooltipHitwith the unit index inuser_data. - Content provider: Receives the hit, renders the PSTH + raster thumbnail into a
QPixmapviaQPainter, and returns it asPlotTooltipContent. ThePlotTooltipManagerdisplays the pixmap in its built-inPixmapPopupwidget.
_tooltip_mgr = std::make_unique<PlotTooltipManager>(this);
_tooltip_mgr->setHitTestProvider([this](QPoint pos) -> std::optional<PlotTooltipHit> {
QPointF world = screenToWorld(pos);
int unit_index = worldToUnitIndex(world);
if (unit_index < 0) return std::nullopt;
PlotTooltipHit hit;
hit.world_x = static_cast<float>(world.x());
hit.world_y = static_cast<float>(world.y());
hit.user_data = unit_index;
return hit;
});
_tooltip_mgr->setContentProvider([this](PlotTooltipHit const & hit) -> PlotTooltipContent {
int unit_index = std::any_cast<int>(hit.user_data);
PlotTooltipContent content;
content.pixmap = renderUnitTooltip(unit_index); // QPainter PSTH + raster
return content;
});The renderUnitTooltip() method:
- Reuses the shared
estimateRate()from theEventRateEstimationlibrary for the PSTH portion - Reuses
RasterMapperlogic for the raster portion (a simplified version without full scene building) - Renders both subplots into a single
QPixmapviaQPainter
This is related to PSTH Roadmap Phase 6 (Embeddable PSTH for Tooltips), which proposes decoupling the PSTH computation and rendering from the full widget machinery.
Phase 9: Colormap Selection UI
Status: Complete. Implemented as a shared ColormapControls library in Plots/Common/ColormapControls/. See ColormapControls documentation.
Completed work:
ColormapControlswidget: Composable widget with colormap preset combo box (gradient icon previews rendered from LUT) and color range controls (Auto / Manual / Symmetric mode with vmin/vmax spinboxes). Embedded inHeatmapPropertiesWidgetwithin the “Rate Estimation & Scaling” collapsible section.- State extension:
HeatmapStateData::colormapfield (ColormapPreset, defaultInferno).HeatmapStateprovidescolormapPreset()/setColormapPreset()withcolormapChanged()signal. Full JSON serialization round-trip viarfl::json. - Auto-suggestion: When switching to z-score scaling, the colormap automatically changes to
Coolwarmand the color range switches toSymmetric. Reverting from z-score restoresInfernoandAuto. - Renderer integration:
HeatmapOpenGLWidget::rebuildScene()reads the colormap from_state->colormapPreset()instead of hard-coding based on scaling mode.
Phase 10: PNG Export
Status: Not started. Qt provides the underlying infrastructure via QOpenGLWidget::grabFramebuffer().
Goal: Export the current heatmap view as a publication-quality PNG image.
10.1 Basic Grab
The simplest approach uses Qt’s built-in framebuffer grab:
void HeatmapWidget::exportPNG(QString const & path) {
QImage image = _opengl_widget->grabFramebuffer();
image.save(path, "PNG");
}This captures exactly what is displayed, including the current zoom/pan state.
10.2 High-Resolution Export
For publication quality, render at a higher resolution than screen:
void HeatmapWidget::exportPNG(QString const & path, int scale_factor) {
// Resize OpenGL widget to scaled dimensions
// Render at high resolution
// Grab framebuffer
// Restore original size
}10.3 Composite Export with Axes and Colorbar
For a complete figure, composite the OpenGL framebuffer with axis labels and colorbar:
- Render the heatmap at target resolution
- Render axes using
QPainterat matching resolution - Render colorbar using
QPainter - Composite into a single
QImage - Save as PNG
10.4 UI Integration
Add an “Export PNG…” action to:
- The properties panel (button or menu)
- A right-click context menu on the plot
The dialog should offer:
- File path selection
- Resolution scale factor (1x, 2x, 4x)
- Include axes checkbox
- Include colorbar checkbox
10.5 SVG Export (Future)
The PSTH Roadmap Phase 5 proposes shared SVG export infrastructure for all plot widgets using SceneRenderer. The HeatmapWidget should adopt this when available, as SVG provides resolution-independent output for publications.
Phase 11: AnalogTimeSeries Heatmap Variant (Future)
Status: Early conceptual design. Separate from the DigitalEventSeries heatmap.
Goal: A sibling widget that creates heatmaps from AnalogTimeSeries data rather than discrete events. This enables visualization of continuous signals like ΔF/F calcium imaging traces across multiple cells, aligned to events.
11.1 Concept
Where the current HeatmapWidget computes rate from discrete events (analogous to stacking PSTHWidget rows), the AnalogTimeSeries variant would display raw continuous signals (analogous to stacking LinePlotWidget rows).
| Feature | Event Heatmap (current) | Analog Heatmap (future) |
|---|---|---|
| Data source | DigitalEventSeries |
AnalogTimeSeries |
| Per-unit computation | Binning + rate estimation | Direct value sampling / interpolation |
| Analogous single-unit view | PSTHWidget | LinePlotWidget |
| Typical use case | Population spike rate | ΔF/F, LFP, EMG across units |
| Normalization | Firing rate, z-score | Z-score, ΔF/F₀, % change |
11.3 Different Infrastructure
Key differences that may warrant a separate widget rather than a mode switch:
- Data gathering:
GatherResult<AnalogTimeSeries>instead ofGatherResult<DigitalEventSeries> - Value computation: Direct sampling/interpolation instead of binning + rate estimation
- Alignment: May align to
DigitalIntervalSeriesonly (baseline period for ΔF/F₀ normalization) - Time resolution: Native sampling rate rather than binned; may need downsampling for display
- Tooltip: Would show a line trace rather than a PSTH histogram
11.4 Recommendation
Implement as a separate widget (AnalogHeatmapWidget) that reuses the colormap, colorbar, sorting, linking, and export infrastructure. The rendering and data gathering paths are sufficiently different that forcing them into a single widget would add unwanted complexity. The two widgets can coexist in the Plot menu, clearly labeled:
- Plot → Heatmap Plot (Events) — current widget
- Plot → Heatmap Plot (Analog) — future widget
Open Questions
Row label rendering: Should unit labels be rendered as axis tick labels via
VerticalAxisWidget, or as a separate label column? The axis approach reuses existing infrastructure but may not handle long labels well.Performance at scale: What is the target unit count? 100 units × 200 bins = 20K rectangles is trivial for OpenGL. 1000 units × 1000 bins = 1M rectangles may need texture-based rendering instead of rectangle batches.
AnalogTimeSeries variant timing: Should the analog heatmap be a mode of this widget or a completely separate widget? The shared infrastructure is substantial, but the data pipelines are fundamentally different.
See Also
- HeatmapWidget Documentation — Full architecture and feature documentation
- Plot Widget Guide — Shared architecture, axis types, DataManager integration
- PSTHWidget — Single-unit PSTH; shares rate estimation pipeline
- PSTHWidget Roadmap — Rate estimation factoring, embeddable PSTH for tooltips
- EventPlotWidget — Trial-level raster plot; shares alignment and linking
- EventPlotWidget Roadmap — Cross-widget linking, external feature coloring
- SpectrogramWidget — Shares colormap infrastructure