diff --git a/python/neuroglancer/viewer_config_state.py b/python/neuroglancer/viewer_config_state.py index b58dff562f..f0141cc598 100644 --- a/python/neuroglancer/viewer_config_state.py +++ b/python/neuroglancer/viewer_config_state.py @@ -379,6 +379,9 @@ class ConfigState(JsonObjectWrapper): show_panel_borders = showPanelBorders = wrapped_property( "showPanelBorders", optional(bool, True) ) + show_all_dimension_plot_bounds = showAllDimensionPlotBounds = wrapped_property( + "showAllDimensionPlotBounds", optional(bool, True) + ) scale_bar_options = scaleBarOptions = wrapped_property( "scaleBarOptions", ScaleBarOptions ) diff --git a/src/layer_group_viewer.ts b/src/layer_group_viewer.ts index 007661a16f..ec6266f1e3 100644 --- a/src/layer_group_viewer.ts +++ b/src/layer_group_viewer.ts @@ -109,6 +109,7 @@ export interface LayerGroupViewerOptions { showLayerPanel: WatchableValueInterface; showViewerMenu: boolean; showLayerHoverValues: WatchableValueInterface; + showAllPlotBounds?: WatchableValueInterface; } export const viewerDragType = "neuroglancer-layer-group-viewer"; @@ -536,6 +537,7 @@ export class LayerGroupViewer extends RefCounted { this, () => this.layout.toJSON(), this.options.showLayerHoverValues, + this.options.showAllPlotBounds, )); if (options.showViewerMenu) { layerPanel.registerDisposer(makeViewerMenu(layerPanel.element, this)); diff --git a/src/layer_groups_layout.ts b/src/layer_groups_layout.ts index 4944ef1c3a..ba108c3c1b 100644 --- a/src/layer_groups_layout.ts +++ b/src/layer_groups_layout.ts @@ -765,6 +765,7 @@ function makeComponent(container: LayoutComponentContainer, spec: any) { showLayerPanel: viewer.uiControlVisibility.showLayerPanel, showViewerMenu: true, showLayerHoverValues: viewer.uiControlVisibility.showLayerHoverValues, + showAllPlotBounds: viewer.uiConfiguration.showAllDimensionPlotBounds, }, ); try { diff --git a/src/ui/layer_bar.ts b/src/ui/layer_bar.ts index 711a77e400..68413179f9 100644 --- a/src/ui/layer_bar.ts +++ b/src/ui/layer_bar.ts @@ -216,6 +216,7 @@ export class LayerBar extends RefCounted { public layerGroupViewer: LayerGroupViewer, public getLayoutSpecForDrag: () => any, public showLayerHoverValues: WatchableValueInterface, + public showAllPlotBounds?: WatchableValueInterface, ) { super(); this.positionWidget = this.registerDisposer( @@ -225,6 +226,7 @@ export class LayerBar extends RefCounted { { velocity: this.viewerNavigationState.velocity.velocity, getToolBinder: () => this.layerGroupViewer.toolBinder, + showAllPlotBounds: showAllPlotBounds, }, ), ); diff --git a/src/viewer.ts b/src/viewer.ts index be885a5fae..e7372be3c7 100644 --- a/src/viewer.ts +++ b/src/viewer.ts @@ -183,6 +183,7 @@ export const VIEWER_UI_CONFIG_OPTIONS = [ "showTopBar", "showUIControls", "showPanelBorders", + "showAllDimensionPlotBounds", ] as const; export type ViewerUIOptions = { @@ -699,6 +700,7 @@ export class Viewer extends RefCounted implements ViewerState { { velocity: this.velocity, getToolBinder: () => this.toolBinder, + showAllPlotBounds: this.uiConfiguration.showAllDimensionPlotBounds, }, ), ); diff --git a/src/widget/position_plot.ts b/src/widget/position_plot.ts index 960c05c80c..f9146d0d43 100644 --- a/src/widget/position_plot.ts +++ b/src/widget/position_plot.ts @@ -25,6 +25,7 @@ import { getDisplayLowerUpperBounds, } from "#src/coordinate_transform.js"; import type { Position } from "#src/navigation_state.js"; +import type { WatchableValueInterface } from "#src/trackable_value.js"; import { WatchableValue } from "#src/trackable_value.js"; import { animationFrameDebounce } from "#src/util/animation_frame_debounce.js"; import { filterArrayInplace } from "#src/util/array.js"; @@ -106,6 +107,7 @@ export class PositionPlot extends RefCounted { constructor( public position: Position, public dimensionId: DimensionId, + private showAllBounds?: WatchableValueInterface, public orientation: "row" | "column" = "column", ) { super(); @@ -177,6 +179,25 @@ export class PositionPlot extends RefCounted { this.visible = false; return; } + if (this.showAllBounds?.value === false) { + let minLowerBound = Infinity; + let maxUpperBound = -Infinity; + + for (const { + lower, + upper, + } of normalizedDimensionBounds.normalizedBounds) { + if (lower < minLowerBound) minLowerBound = lower; + if (upper > maxUpperBound) maxUpperBound = upper; + } + + normalizedDimensionBounds.normalizedBounds = [ + { + lower: isFinite(minLowerBound) ? minLowerBound : 0, + upper: isFinite(maxUpperBound) ? maxUpperBound : 1, + }, + ]; + } this.element.style.display = ""; this.visible = true; @@ -282,6 +303,9 @@ export class PositionPlot extends RefCounted { animationFrameDebounce(updateView), ); this.registerDisposer(this.position.changed.add(scheduleUpdateView)); + if (this.showAllBounds !== undefined) { + this.registerDisposer(this.showAllBounds.changed.add(scheduleUpdateView)); + } const getPositionFromMouseEvent = ( event: MouseEvent, ): number | undefined => { diff --git a/src/widget/position_widget.ts b/src/widget/position_widget.ts index e985b8fdaa..e156229d2a 100644 --- a/src/widget/position_widget.ts +++ b/src/widget/position_widget.ts @@ -303,6 +303,7 @@ export class PositionWidget extends RefCounted { private allowFocus: boolean; private showPlayback: boolean; private showDropdown: boolean; + private showAllPlotBounds: WatchableValueInterface | undefined; private dimensionWidgets = new Map(); private dimensionWidgetList: DimensionWidget[] = []; @@ -332,7 +333,7 @@ export class PositionWidget extends RefCounted { } const plot = dropdownOwner.registerDisposer( - new PositionPlot(this.position, widget.id), + new PositionPlot(this.position, widget.id, this.showAllPlotBounds), ); dropdown.appendChild(plot.element); @@ -1040,6 +1041,7 @@ export class PositionWidget extends RefCounted { allowFocus = true, showPlayback = true, showDropdown = true, + showAllPlotBounds = undefined, }: { copyButton?: boolean; velocity?: CoordinateSpacePlaybackVelocity; @@ -1048,6 +1050,7 @@ export class PositionWidget extends RefCounted { allowFocus?: boolean; showPlayback?: boolean; showDropdown?: boolean; + showAllPlotBounds?: WatchableValueInterface; } = {}, ) { super(); @@ -1058,6 +1061,7 @@ export class PositionWidget extends RefCounted { this.allowFocus = allowFocus; this.showPlayback = showPlayback; this.showDropdown = showDropdown; + this.showAllPlotBounds = showAllPlotBounds; this.registerDisposer( position.coordinateSpace.changed.add( this.registerCancellable( @@ -1360,6 +1364,7 @@ interface SupportsDimensionTool { velocity: CoordinateSpacePlaybackVelocity; coordinateSpaceCombiner: CoordinateSpaceCombiner; toolBinder: LocalToolBinder; + showAllDimensionPlotBounds: WatchableValueInterface; } const TOOL_INPUT_EVENT_MAP = EventActionMap.fromObject({ @@ -1452,12 +1457,18 @@ class DimensionTool extends Tool { allowFocus: inPalette, showPlayback: false, showDropdown: false, + showAllPlotBounds: viewer.showAllDimensionPlotBounds, }, ); positionWidget.element.style.userSelect = "none"; content.appendChild(activation.registerDisposer(positionWidget).element); const plot = activation.registerDisposer( - new PositionPlot(viewer.position, this.dimensionId, "row"), + new PositionPlot( + viewer.position, + this.dimensionId, + viewer.showAllDimensionPlotBounds, + "row", + ), ); plot.element.style.flex = "1"; content.appendChild(plot.element); @@ -1659,6 +1670,8 @@ export function registerDimensionToolForViewer(contextType: typeof Viewer) { coordinateSpaceCombiner: viewer.layerSpecification.coordinateSpaceCombiner, toolBinder: viewer.toolBinder, + showAllDimensionPlotBounds: + viewer.uiConfiguration.showAllDimensionPlotBounds, }, obj, ), @@ -1679,6 +1692,7 @@ export function registerDimensionToolForUserLayer( velocity: layer.localVelocity, coordinateSpaceCombiner: layer.localCoordinateSpaceCombiner, toolBinder: layer.toolBinder, + showAllDimensionPlotBounds: new WatchableValue(true), }, obj, ), @@ -1698,6 +1712,7 @@ export function registerDimensionToolForLayerGroupViewer( coordinateSpaceCombiner: layerGroupViewer.layerSpecification.root.coordinateSpaceCombiner, toolBinder: layerGroupViewer.toolBinder, + showAllDimensionPlotBounds: new WatchableValue(true), }, obj, ),