Skip to content

NiceWidgets widget API notes

Purpose

NiceWidgets provides reusable NiceGUI widgets for scientific and desktop applications. The widgets should be framework-level building blocks, not application-specific orchestration.

CloudScope is one known consumer. This document records the public widget contracts those consumers rely on.

Design rule:

nicewidgets owns reusable UI behavior.
applications own orchestration.
domain packages own domain interpretation.

PlotlyRasterViewer

Used by:

PrimaryImageView
ReferenceImageView

Responsibilities:

- display 2D raster/image data
- handle Plotly/NiceGUI integration
- provide efficient image updates
- expose public ROI overlay APIs
- expose future axis range callbacks for linked views

CloudScope should call public methods only. It should not mutate internal Plotly dictionaries directly except through the widget API.

Plotly update policy

Full plotly.update() is expensive because it can push the full plot data/image payload. Use it only for full figure rebuilds.

For ROI-only changes, use browser-side incremental updates such as:

Plotly.relayout(..., {shapes: [...]})

The widget should own this implementation detail.

CloudScope should simply call:

viewer.set_rois(...)
viewer.select_roi(...)
viewer.add_roi(...)
viewer.delete_roi(...)

Plotly raster viewer context menu

The Plotly raster viewer owns its right-click menu and exposes display toggles through public widget APIs. Callers may provide initial display defaults with:

PlotlyRasterViewer(
    display_options=PlotlyRasterViewerDisplayOptions(
        show_plotly_toolbar=False,
        show_rois=True,
        show_trace_overlays=True,
        show_axis_labels=False,
        theme='light',
    )
)

The context menu currently provides:

- ROIs
- Traces
- Axis Labels
- Plotly Toolbar
- Copy To Clipboard

Checked items are prefixed with . Visibility toggles must not delete source widget state. ROI visibility is applied through Plotly shape visible; trace overlay visibility is applied through Plotly trace visible; axis display is updated through Plotly.relayout and includes title text, tick labels, tick marks, axis line, zero line, and grid lines; toolbar visibility is updated through Plotly config. Plotly light/dark theme changes are also applied through Plotly.relayout without rebuilding the raster image.

Public display API:

set_roi_overlays_visible(visible: bool) -> None
set_trace_overlays_visible(visible: bool) -> None
set_axis_labels_visible(visible: bool) -> None
set_plotly_toolbar_visible(visible: bool) -> None
set_theme(theme: Literal['light', 'dark']) -> None
set_dark_mode(enabled: bool) -> None
copy_plot_to_clipboard() -> None

ROI overlay API

Current/desired public API:

set_rois(rois: Sequence[RectRoiOverlay]) -> None
select_roi(roi_id: int | None) -> None
add_roi(roi: RectRoiOverlay) -> None
delete_roi(roi_id: int) -> None

ROI shapes should be stored in Plotly layout shapes and identified with:

name = "roi:<roi_id>"

ROI style should be configured centrally, not scattered:

RectRoiStyleConfig(
    line_width=..., line_color=..., fill_color=...,
    selected_line_width=..., selected_line_color=..., selected_fill_color=...,
)

ROI overlay support currently targets rectangular ROIs only. Line-segment ROI display can be added later with separate shape/model APIs.

ROI overlay performance rule

ROI-only updates should:

- mutate layout.shapes in the widget's local plotly dict
- call Plotly.relayout for shapes only
- avoid NiceGUI plotly.update()

This rule prevents expensive redraws and avoids resending large image payloads.

Future ROI editing API

Planned editing flow:

CloudScope enters ROI edit mode
PlotlyRasterViewer enables selected shape editing
user drags/resizes shape
widget emits preview bounds
CloudScope controller stores pending bounds
toolbar OK commits
cancel restores model shapes

nicewidgets should expose clean callbacks/events such as:

on_roi_bounds_preview(roi_id, bounds)
set_roi_editing(enabled, roi_id)
get_current_roi_bounds(roi_id)

CloudScope should not parse raw Plotly relayout payloads if nicewidgets can hide that complexity.

EChartWidget

Used by:

AcqAnalysisPlotView

Responsibilities:

- display one or more 1D/2D chart series
- provide simple line plot API
- update chart options cleanly
- future axis range/datazoom events

Current/desired public API:

set_line_data(x, y, x_label, y_label, title=None) -> None
clear() -> None
set_x_axis_limits(x_min, x_max) -> None
reset_x_axis_limits() -> None

CloudScope should pass AnalysisPlotData from acqstore directly into EChartWidget. It should not construct low-level ECharts options except through widget APIs.

Axis range linking

CloudScope needs the primary image x-axis and analysis plot x-axis linked.

nicewidgets responsibilities:

PlotlyRasterViewer:
  emit x-axis range changes from Plotly relayout
  accept programmatic x-axis range updates

EChartWidget:
  emit x-axis range changes from datazoom/axis interactions
  accept programmatic x-axis range updates

CloudScope responsibility:

translate widget callbacks into app-level axis range events
avoid feedback loops by tagging event source view_id

Suggested CloudScope event:

XAxisRangeChanged(source_view_id, x_min, x_max)

This should be implemented after the widgets expose stable callback APIs.

ImageToolbarWidget

Used by:

ImageToolbarView

Responsibilities:

- channel selection UI
- ROI selection UI
- ROI CRUD/edit lifecycle buttons
- emit widget-level toolbar intents

CloudScope translates nicewidgets toolbar intents into CloudScope application intents.

Example:

ImageToolbarRoiAddRequestIntent -> AddRoiIntent
ImageToolbarSelectRoiIntent -> SelectRoiIntent

The widget should not mutate acqstore or CloudScope state directly.

Widget state APIs

Reusable widgets should provide external setter methods that update internal/UI state without emitting user-intent callbacks. This prevents feedback loops.

Examples:

set_file_ext(...)
set_roi_options_and_selection_ext(...)
set_enabled_ext(...)

Pattern:

user action -> emits intent callback
programmatic state sync -> no intent callback

Stress points and improvement areas

Plotly DOM id handling is fragile

Any browser-side JS must use the established widget DOM-id pattern. Do not guess new selectors. Tests should validate generated JS strings where practical, but real browser behavior still needs manual testing.

Avoid async public APIs unless required

Simple widget state updates should be synchronous where possible. Async widget APIs caused test/runtime friction in ROI overlay work.

Axis linking is not finished

Plotly and ECharts need stable range callback APIs before CloudScope should implement full axis linking.

Keep widgets CloudScope-agnostic

If a widget imports CloudScope events/controllers/state, that is an architecture smell.

Test widgets at option/model boundary

Most widget tests should assert generated option dictionaries, styles, and JS payloads rather than requiring a browser.