+
Viewer Options
-
+
@@ -31,7 +28,7 @@
Layer Options
-
+
diff --git a/jdaviz/configs/imviz/plugins/compass/compass.py b/jdaviz/configs/imviz/plugins/compass/compass.py
index 1e874a1872..00fe427fb9 100644
--- a/jdaviz/configs/imviz/plugins/compass/compass.py
+++ b/jdaviz/configs/imviz/plugins/compass/compass.py
@@ -1,48 +1,38 @@
-from traitlets import Unicode, List, observe
+from traitlets import Unicode, observe
-from jdaviz.core.events import (ViewerAddedMessage, ViewerRemovedMessage,
- AddDataMessage, RemoveDataMessage)
+from jdaviz.core.events import AddDataMessage, RemoveDataMessage
from jdaviz.core.registries import tray_registry
-from jdaviz.core.template_mixin import PluginTemplateMixin
+from jdaviz.core.template_mixin import PluginTemplateMixin, ViewerSelectMixin
__all__ = ['Compass']
@tray_registry('imviz-compass', label="Imviz Compass")
-class Compass(PluginTemplateMixin):
+class Compass(PluginTemplateMixin, ViewerSelectMixin):
template_file = __file__, "compass.vue"
- viewer_items = List([]).tag(sync=True)
- selected_viewer = Unicode("").tag(sync=True)
data_label = Unicode("").tag(sync=True)
img_data = Unicode("").tag(sync=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.hub.subscribe(self, ViewerAddedMessage, handler=self._on_viewers_changed)
- self.hub.subscribe(self, ViewerRemovedMessage, handler=self._on_viewers_changed)
self.hub.subscribe(self, AddDataMessage, handler=self._on_viewer_data_changed)
self.hub.subscribe(self, RemoveDataMessage, handler=self._on_viewer_data_changed)
- self._on_viewers_changed() # Populate it on start-up
-
- def _on_viewers_changed(self, msg=None):
- self.viewer_items = self.app.get_viewer_ids()
-
- # Selected viewer was removed but Imviz always has a default viewer to fall back on.
- if self.selected_viewer not in self.viewer_items:
- self.selected_viewer = f'{self.app.config}-0'
-
def _on_viewer_data_changed(self, msg=None):
- if self.selected_viewer:
- viewer = self.app.get_viewer_by_id(self.selected_viewer)
+ if self.viewer_selected:
+ viewer = self.viewer.selected_obj
viewer.on_limits_change() # Force redraw
- @observe("selected_viewer", "plugin_opened")
+ @observe("viewer_selected", "plugin_opened")
def _compass_with_new_viewer(self, *args, **kwargs):
+ if not hasattr(self, 'viewer'):
+ # mixin object not yet initialized
+ return
+
# There can be only one!
for vid, viewer in self.app._viewer_store.items():
- if vid == self.selected_viewer and self.plugin_opened:
+ if vid == self.viewer.selected_id and self.plugin_opened:
viewer.compass = self
viewer.on_limits_change() # Force redraw
else:
diff --git a/jdaviz/configs/imviz/plugins/compass/compass.vue b/jdaviz/configs/imviz/plugins/compass/compass.vue
index 5f20538504..2d7b92167d 100644
--- a/jdaviz/configs/imviz/plugins/compass/compass.vue
+++ b/jdaviz/configs/imviz/plugins/compass/compass.vue
@@ -6,15 +6,12 @@
-
-
-
+
+
+ """
+ def __init__(self, plugin, items, selected):
+ super().__init__(plugin, items=items, selected=selected)
+
+ self.hub.subscribe(self, ViewerAddedMessage, handler=self._on_viewers_changed)
+ self.hub.subscribe(self, ViewerRemovedMessage, handler=self._on_viewers_changed)
+ self.add_observe(selected, self._selected_changed)
+
+ # initialize viewer_items from original viewers
+ self._on_viewers_changed()
+
+ @property
+ def ids(self):
+ return [item['id'] for item in self.items]
+
+ @property
+ def references(self):
+ return [item['reference'] for item in self.items]
+
+ @property
+ def ref_or_ids(self):
+ return [item['ref_or_id'] for item in self.items]
+
+ @property
+ def selected_item(self):
+ for item in self.items:
+ if item['ref_or_id'] == self.selected:
+ return item
+ # try again but this time allow match to id alone. Note that _selected_changed
+ # will handle resetting the trait to the reference since it exists, but this
+ # will allow access to the underlying item/object for any observes in the meantime.
+ for item in self.items:
+ if item['id'] == self.selected:
+ return item
+
+ @property
+ def selected_id(self):
+ return self.selected_item['id']
+
+ @property
+ def selected_obj(self):
+ return self.app.get_viewer_by_id(self.selected_id)
+
+ def _selected_changed(self, event):
+ if event['new'] not in self.ref_or_ids:
+ if self.selected in self.ids:
+ # provided id in place of ref
+ self.selected = self.ref_or_ids[self.ids.index(self.selected)]
+ else:
+ self._handle_default()
+ raise ValueError(f"{event['new']} not one of {self.ref_or_ids}")
+
+ def _handle_default(self):
+ if self.selected not in self.ref_or_ids:
+ # default to first entry, will trigger any observer on selected
+ self.selected = self.ref_or_ids[0] if len(self.items) else ""
+
+ def _on_viewers_changed(self, msg=None):
+ # NOTE: _on_viewers_changed is passed without a msg object during init
+ # list of dictionaries with id, ref, ref_or_id
+ def _dict_from_viewer(viewer_item):
+ d = {'id': viewer_item['id']}
+ if viewer_item.get('reference') is not None:
+ d['reference'] = viewer_item['reference']
+ d['ref_or_id'] = viewer_item['reference']
+ else:
+ d['reference'] = None
+ d['ref_or_id'] = viewer_item['id']
+ return d
+
+ self.items = [_dict_from_viewer(self.app._viewer_item_by_id(vid))
+ for vid, viewer in self.app._viewer_store.items()
+ if viewer.__class__.__name__ != 'MosvizTableViewer']
+ self._handle_default()
+
+
+class ViewerSelectMixin(VuetifyTemplate, HubListener):
+ """
+ Applies the ViewerSelect component as a mixin in the base plugin. This
+ automatically adds traitlets as well as new properties to the plugin with minimal
+ extra code. For multiple instances or custom traitlet names/defaults, use the
+ SpectralSubsetSelect component instead.
+
+ Traitlets (available from the plugin):
+
+ * ``viewer_items``
+ * ``viewer_selected``
+
+ Properties (available from the plugin):
+
+ * ``viewer.ids``
+ * ``viewer.references``
+ * ``viewer.ref_or_ids``
+ * ``viewer.selected_item``
+ * ``viewer.selected_id``
+ * ``viewer.selected_obj``
+
+ To use in a plugin:
+
+ * add ``ViewerSelectMixin`` as a mixin to the class
+ * use the traitlets and properties above as needed (note the prefix for properties)
+
+ Example template (label and hint are optional)::
+
+
+
+
+ """
+ viewer_items = List().tag(sync=True)
+ viewer_selected = Unicode().tag(sync=True)
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.viewer = ViewerSelect(self, 'viewer_items', 'viewer_selected')
diff --git a/jdaviz/core/tests/test_template_mixin.py b/jdaviz/core/tests/test_template_mixin.py
index e4a6672514..ad8d742c8c 100644
--- a/jdaviz/core/tests/test_template_mixin.py
+++ b/jdaviz/core/tests/test_template_mixin.py
@@ -1,3 +1,5 @@
+import pytest
+
from glue.core.roi import XRangeROI
@@ -33,3 +35,28 @@ def test_spectralsubsetselect(specviz_helper, spectrum1d):
assert p.spectral_subset.selected_obj is None
p.spectral_subset_selected = 'Subset 1'
assert p.spectral_subset.selected_obj is not None
+
+
+@pytest.mark.filterwarnings('ignore:No observer defined on WCS')
+def test_viewer_select(cubeviz_helper, spectrum1d_cube):
+ app = cubeviz_helper.app
+ app.add_data(spectrum1d_cube, 'test')
+ app.add_data_to_viewer("spectrum-viewer", "test")
+ app.add_data_to_viewer("flux-viewer", "test")
+ fv = app.get_viewer("flux-viewer")
+ sv = app.get_viewer("spectrum-viewer")
+
+ # export plot uses the mixin
+ p = app.get_tray_item_from_name('g-export-plot')
+ assert len(p.viewer.ids) == 4
+ assert len(p.viewer.references) == 4
+ assert len(p.viewer.ref_or_ids) == 4
+ assert p.viewer.selected_obj == fv
+
+ # set by reference
+ p.viewer_selected = 'spectrum-viewer'
+ assert p.viewer.selected_obj == sv
+
+ # try setting based on id instead of reference
+ p.viewer_selected = p.viewer.ids[0]
+ assert p.viewer_selected == p.viewer.ref_or_ids[0]
diff --git a/jdaviz/core/tools.py b/jdaviz/core/tools.py
index c5dd370562..a1d69e1dc7 100644
--- a/jdaviz/core/tools.py
+++ b/jdaviz/core/tools.py
@@ -146,7 +146,7 @@ def on_mouse_event(self, data):
class _BaseSidebarShortcut(Tool):
plugin_label = None # define in subclass
- viewer_select_traitlet = 'selected_viewer'
+ viewer_select_traitlet = 'viewer_selected'
def activate(self):
jdaviz_state = self.viewer.jdaviz_app.state
@@ -162,7 +162,7 @@ def activate(self):
@viewer_tool
class SidebarShortcutPlotOptions(_BaseSidebarShortcut):
plugin_name = 'g-plot-options'
- viewer_select_traitlet = 'selected_viewer'
+ viewer_select_traitlet = 'viewer_selected'
icon = os.path.join(ICON_DIR, 'tune.svg')
tool_id = 'jdaviz:sidebar_plot'
@@ -173,7 +173,7 @@ class SidebarShortcutPlotOptions(_BaseSidebarShortcut):
@viewer_tool
class SidebarShortcutExportPlot(_BaseSidebarShortcut):
plugin_name = 'g-export-plot'
- viewer_select_traitlet = 'selected_viewer'
+ viewer_select_traitlet = 'viewer_selected'
icon = os.path.join(ICON_DIR, 'image.svg')
tool_id = 'jdaviz:sidebar_export'
@@ -184,7 +184,7 @@ class SidebarShortcutExportPlot(_BaseSidebarShortcut):
@viewer_tool
class SidebarShortcutCompass(_BaseSidebarShortcut):
plugin_name = 'imviz-compass'
- viewer_select_traitlet = 'selected_viewer'
+ viewer_select_traitlet = 'viewer_selected'
icon = os.path.join(ICON_DIR, 'compass.svg')
tool_id = 'jdaviz:sidebar_compass'