diff --git a/CHANGES.rst b/CHANGES.rst
index 2b1a782015..a1c278dc7a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,7 +4,7 @@
New Features
------------
-* New design for viewer legend. [#3220, #3254, #3263]
+* New design for viewer legend. [#3220, #3254, #3263, #3264]
Cubeviz
^^^^^^^
diff --git a/jdaviz/app.py b/jdaviz/app.py
index bcc0c405b2..007f907d37 100644
--- a/jdaviz/app.py
+++ b/jdaviz/app.py
@@ -133,6 +133,8 @@ def to_unit(self, data, cid, values, original_units, target_units):
'j-plugin-popout': 'components/plugin_popout.vue',
'j-multiselect-toggle': 'components/multiselect_toggle.vue',
'j-subset-icon': 'components/subset_icon.vue',
+ 'j-plugin-live-results-icon': 'components/plugin_live_results_icon.vue',
+ 'j-child-layer-icon': 'components/child_layer_icon.vue',
'plugin-previews-temp-disabled': 'components/plugin_previews_temp_disabled.vue', # noqa
'plugin-table': 'components/plugin_table.vue',
'plugin-dataset-select': 'components/plugin_dataset_select.vue',
@@ -152,7 +154,9 @@ def to_unit(self, data, cid, values, original_units, target_units):
'plugin-color-picker': 'components/plugin_color_picker.vue',
'plugin-input-header': 'components/plugin_input_header.vue',
'glue-state-sync-wrapper': 'components/glue_state_sync_wrapper.vue',
- 'data-menu-add-data': 'components/data_menu_add_data.vue'}
+ 'data-menu-add-data': 'components/data_menu_add_data.vue',
+ 'data-menu-remove': 'components/data_menu_remove.vue',
+ 'data-menu-subset-edit': 'components/data_menu_subset_edit.vue'}
_verbosity_levels = ('debug', 'info', 'warning', 'error')
@@ -2287,7 +2291,7 @@ def vue_data_item_remove(self, event):
data_label = event['item_name']
data = self.data_collection[data_label]
orientation_plugin = self._jdaviz_helper.plugins.get("Orientation")
- if orientation_plugin is not None:
+ if orientation_plugin is not None and orientation_plugin.align_by == "WCS":
from jdaviz.configs.imviz.plugins.orientation.orientation import base_wcs_layer_label
orient = orientation_plugin.orientation.selected
if orient == data_label:
diff --git a/jdaviz/components/child_layer_icon.vue b/jdaviz/components/child_layer_icon.vue
new file mode 100644
index 0000000000..4d00657341
--- /dev/null
+++ b/jdaviz/components/child_layer_icon.vue
@@ -0,0 +1,14 @@
+
+
+ mdi-layers-outline
+
+
+
+
\ No newline at end of file
diff --git a/jdaviz/components/data_menu_remove.vue b/jdaviz/components/data_menu_remove.vue
new file mode 100644
index 0000000000..8a8e4d24b6
--- /dev/null
+++ b/jdaviz/components/data_menu_remove.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+ mdi-delete
+
+
+
+
+
+
+
+ {$emit('remove-from-viewer')}"
+ >
+ Remove from viewer
+
+
+
+
+
+
+
+ {if (delete_app_enabled) {$emit('remove-from-app')}}"
+ >
+ Remove from app
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jdaviz/components/data_menu_subset_edit.vue b/jdaviz/components/data_menu_subset_edit.vue
new file mode 100644
index 0000000000..f93d05aa25
--- /dev/null
+++ b/jdaviz/components/data_menu_subset_edit.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
+ Edit Subset
+
+
+
+
+
+
+
+ {$emit('view-info')}"
+ >
+ Edit in plugin
+
+
+
+
+
+
+
+ {hover_mode=mode_item.glue_name}"
+ @mouseleave="() => {if (hover_mode == mode_item.glue_name) {hover_mode=''}}"
+ >
+
+
+
+
+
+ {{ mode_item.glue_name }}
+
+
+
+ {$emit('modify-subset', mode_item.glue_name, tool.name)}"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jdaviz/components/plugin_live_results_icon.vue b/jdaviz/components/plugin_live_results_icon.vue
new file mode 100644
index 0000000000..4b9e628beb
--- /dev/null
+++ b/jdaviz/components/plugin_live_results_icon.vue
@@ -0,0 +1,14 @@
+
+
+ mdi-reload-alert
+
+
+
+
\ No newline at end of file
diff --git a/jdaviz/configs/default/plugins/data_menu/data_menu.py b/jdaviz/configs/default/plugins/data_menu/data_menu.py
index 5c4acb930b..be6c68fd92 100644
--- a/jdaviz/configs/default/plugins/data_menu/data_menu.py
+++ b/jdaviz/configs/default/plugins/data_menu/data_menu.py
@@ -1,11 +1,18 @@
+import os
+
from contextlib import contextmanager
-from traitlets import Bool, Dict, Unicode, List, observe
+from traitlets import Bool, Dict, Unicode, Integer, List, observe
from jdaviz.core.template_mixin import (TemplateMixin, LayerSelectMixin, DatasetSelectMixin)
from jdaviz.core.user_api import UserApiWrapper
from jdaviz.core.events import IconsUpdatedMessage, AddDataMessage
from jdaviz.utils import cmap_samples, is_not_wcs_only
+from glue.core.edit_subset_mode import (AndMode, AndNotMode, OrMode,
+ ReplaceMode, XorMode, NewMode)
+from glue.icons import icon_path
+from glue_jupyter.common.toolbar_vuetify import read_icon
+
__all__ = ['DataMenu']
@@ -18,6 +25,15 @@
SUBSET_NAMES = {v: k for k, v in SUBSET_TOOL_IDS.items()}
+SUBSET_MODES = {
+ 'new': NewMode,
+ 'replace': ReplaceMode,
+ 'or': OrMode,
+ 'and': AndMode,
+ 'xor': XorMode,
+ 'andnot': AndNotMode,
+}
+
class DataMenu(TemplateMixin, LayerSelectMixin, DatasetSelectMixin):
"""Viewer Data Menu
@@ -30,7 +46,11 @@ class DataMenu(TemplateMixin, LayerSelectMixin, DatasetSelectMixin):
* :meth:`set_layer_visibility`
* :meth:`toggle_layer_visibility`
* :meth:`create_subset`
+ * :meth:`modify_subset`
* :meth:`add_data`
+ * :meth:`view_info`
+ * :meth:`remove_from_viewer`
+ * :meth:`remove_from_app`
"""
template_file = __file__, "data_menu.vue"
@@ -47,6 +67,24 @@ class DataMenu(TemplateMixin, LayerSelectMixin, DatasetSelectMixin):
dm_layer_selected = List().tag(sync=True)
+ selected_n_layers = Integer(0).tag(sync=True)
+ selected_n_data = Integer(0).tag(sync=True)
+ selected_n_subsets = Integer(0).tag(sync=True)
+
+ info_enabled = Bool(False).tag(sync=True)
+ info_tooltip = Unicode().tag(sync=True)
+
+ delete_enabled = Bool(False).tag(sync=True)
+ delete_tooltip = Unicode().tag(sync=True)
+ delete_viewer_tooltip = Unicode().tag(sync=True)
+ delete_app_enabled = Bool(False).tag(sync=True)
+ delete_app_tooltip = Unicode().tag(sync=True)
+
+ subset_edit_enabled = Bool(False).tag(sync=True)
+ subset_edit_tooltip = Unicode().tag(sync=True)
+
+ subset_edit_modes = List().tag(sync=True)
+
dev_data_menu = Bool(False).tag(sync=True)
def __init__(self, viewer, *args, **kwargs):
@@ -74,6 +112,12 @@ def data_not_in_viewer(data):
self.viewer_icons = dict(self.app.state.viewer_icons)
self.layer_icons = dict(self.app.state.layer_icons)
+ self.subset_edit_modes = [{'glue_name': 'replace', 'icon': read_icon(os.path.join(icon_path("glue_replace", icon_format="svg")), 'svg+xml')}, # noqa
+ {'glue_name': 'or', 'icon': read_icon(os.path.join(icon_path("glue_or", icon_format="svg")), 'svg+xml')}, # noqa
+ {'glue_name': 'and', 'icon': read_icon(os.path.join(icon_path("glue_and", icon_format="svg")), 'svg+xml')}, # noqa
+ {'glue_name': 'xor', 'icon': read_icon(os.path.join(icon_path("glue_xor", icon_format="svg")), 'svg+xml')}, # noqa
+ {'glue_name': 'andnot', 'icon': read_icon(os.path.join(icon_path("glue_andnot", icon_format="svg")), 'svg+xml')}] # noqa
+
# this currently assumes that toolbar.tools_data is set at init and does not change
# if we ever support dynamic tool registration, this will need to be updated
self.subset_tools = [{'id': k, 'img': v['img'], 'name': SUBSET_NAMES.get(k, k)}
@@ -83,9 +127,14 @@ def data_not_in_viewer(data):
@property
def user_api(self):
expose = ['layer', 'set_layer_visibility', 'toggle_layer_visibility',
- 'create_subset', 'add_data']
+ 'create_subset', 'modify_subset', 'add_data', 'view_info',
+ 'remove_from_viewer', 'remove_from_app']
return UserApiWrapper(self, expose=expose)
+ @property
+ def existing_subset_labels(self):
+ return [sg.label for sg in self.app.data_collection.subset_groups]
+
@observe('layer_items')
def _update_data_not_in_viewer(self, msg):
# changing the layers in the viewer needs to trigger an update to dataset_items
@@ -144,13 +193,102 @@ def _dm_layer_selected_changed(self, event={}):
def _update_dm_layer_selected(self, event={}):
if not hasattr(self, 'layer') or not self.layer.multiselect: # pragma: no cover
return
- if self._during_select_sync:
+ if not self._during_select_sync:
+ with self.during_select_sync():
+ # map list of strings in self.layer.selected to indices in dm_layer_selected
+ layer_labels = [layer['label'] for layer in self.layer_items][::-1]
+ self.dm_layer_selected = [layer_labels.index(label) for label in self.layer.selected
+ if label in layer_labels]
+
+ if event.get('name') == 'layer_items':
+ # don't need to make the updates below unless the selection has been changed
return
- with self.during_select_sync():
- # map list of strings in self.layer.selected to indices in dm_layer_selected
- layer_labels = [layer['label'] for layer in self.layer_items][::-1]
- self.dm_layer_selected = [layer_labels.index(label) for label in self.layer.selected
- if label in layer_labels]
+
+ # update internal counts and tooltips
+ self.selected_n_layers = len(self.layer.selected)
+ subset_labels = self.existing_subset_labels
+ self.selected_n_subsets = len([lyr for lyr in self.layer.selected if lyr in subset_labels])
+ self.selected_n_data = self.selected_n_layers - self.selected_n_subsets
+
+ # user-friendly representation of selection
+ selected_repr = ""
+ if self.selected_n_data:
+ selected_repr += f"data ({self.selected_n_data})"
+ if self.selected_n_subsets:
+ if self.selected_n_data:
+ selected_repr += " and"
+ if self.selected_n_subsets == 1:
+ selected_repr += f" subset ({self.selected_n_subsets})"
+ else:
+ selected_repr += f" subsets ({self.selected_n_subsets})"
+
+ # layer info rules
+ if self.selected_n_layers == 1:
+ if max(self.dm_layer_selected) >= len(self.layer_items): # pragma: no cover
+ # can happen during state transition but should immediately be followed up
+ # with an update
+ self.info_enabled = False
+ self.info_tooltip = ''
+ if self.layer_items[self.dm_layer_selected[0]].get('from_plugin', False):
+ self.info_enabled = False
+ self.info_tooltip = 'Selected data layer is a plugin product and does not have metadata' # noqa
+ else:
+ self.info_enabled = True
+ if self.selected_n_data == 1:
+ self.info_tooltip = 'View metadata for selected data'
+ else:
+ self.info_tooltip = 'View subset info for selected subset'
+ else:
+ self.info_enabled = False
+ if self.selected_n_layers == 0:
+ self.info_tooltip = 'Select a layer to view info'
+ else:
+ self.info_tooltip = 'Select a single layer to view info'
+
+ # delete layer rules
+ if self.selected_n_layers == 0:
+ self.delete_tooltip = "Select layer(s) to delete"
+ self.delete_enabled = False
+ else:
+ self.delete_tooltip = f"Remove selected {selected_repr}..."
+ self.delete_enabled = True
+
+ # delete from entire app rules
+ subset_str = "subset" if self.selected_n_subsets == 1 else "subsets"
+ if self.selected_n_subsets and self.selected_n_data:
+ self.delete_viewer_tooltip = f"Remove selected data and hide selected {subset_str} in this viewer" # noqa
+ elif self.selected_n_data:
+ self.delete_viewer_tooltip = "Remove selected data from this viewer"
+ elif self.selected_n_subsets:
+ self.delete_viewer_tooltip = f"Hide selected {subset_str} in this viewer"
+
+ delete_app_tooltip = "Remove from all viewers and application (permanent, might affect existing subsets)" # noqa
+ if self.app.config == 'cubeviz':
+ # forbid deleting non-plugin generated data
+ selected_items = self.layer.selected_item
+ for i, layer in enumerate(self.layer.selected):
+ if (layer not in self.existing_subset_labels
+ and selected_items['from_plugin'][i] is None):
+ self.delete_app_enabled = False
+ self.delete_app_tooltip = f"Cannot delete imported data from {self.app.config}"
+ break
+ else:
+ self.delete_app_enabled = True
+ self.delete_app_tooltip = delete_app_tooltip
+ else:
+ self.delete_app_enabled = True
+ self.delete_app_tooltip = delete_app_tooltip
+
+ # subset edit rules
+ if self.selected_n_subsets == 1 and self.selected_n_layers == 1:
+ self.subset_edit_enabled = True
+ self.subset_edit_tooltip = f"Edit {self.layer_selected[0]}..."
+ else:
+ self.subset_edit_enabled = False
+ if self.selected_n_subsets == 0:
+ self.subset_edit_tooltip = "Select a subset to edit"
+ else:
+ self.subset_edit_tooltip = "Select a single subset to edit"
def set_layer_visibility(self, layer_label, visible=True):
"""
@@ -196,32 +334,35 @@ def toggle_layer_visibility(self, layer_label):
def vue_set_layer_visibility(self, info, *args):
return self.set_layer_visibility(info.get('layer'), info.get('value')) # pragma: no cover
- def add_data(self, data_label):
+ def add_data(self, *data_labels):
"""
Add a dataset to the viewer.
Parameters
----------
- data_label : str
- The label of the dataset to add to the viewer.
+ *data_labels : str
+ The label(s) of the dataset to add to the viewer.
"""
- if data_label not in self.dataset.choices:
- raise ValueError(f"Data label '{data_label}' not able to be loaded into '{self.viewer_id}'. Must be one of: {self.dataset.choices}") # noqa
- return self.app.add_data_to_viewer(self.viewer_id, data_label)
+ unavailable = [data_label for data_label in data_labels
+ if data_label not in self.dataset.choices]
+ if len(unavailable):
+ raise ValueError(f"Data labels {unavailable} not able to be loaded into '{self.viewer_id}'. Must be one of: {self.dataset.choices}") # noqa
+ for data_label in data_labels:
+ self.app.add_data_to_viewer(self.viewer_id, data_label)
def vue_add_data_to_viewer(self, info, *args):
self.add_data(info.get('data_label')) # pragma: no cover
def create_subset(self, subset_type):
"""
- Create a new subset in the viewer. This sets the app-wide subset selection to 'Create New'
- and selects the appropriate tool in this viewer's toolbar.
+ Interactively create a new subset in the viewer. This sets the app-wide subset
+ selection to 'Create New' and selects the appropriate tool in this viewer's toolbar.
Parameters
----------
subset_type : str
The type of subset to create. Must be one of 'circle', 'rectangle', 'ellipse',
- 'annulus', 'xrange', or 'yrange'.
+ 'annulus', 'xrange', or 'yrange', and must be an available tool in this viewer.
"""
# clear previous selection, finalize subsets, temporarily sets default tool
self._viewer.toolbar.active_tool_id = None
@@ -231,3 +372,94 @@ def create_subset(self, subset_type):
def vue_create_subset(self, info, *args):
self.create_subset(info.get('subset_type')) # pragma: no cover
+
+ def modify_subset(self, combination_mode, subset_type):
+ """
+ Interactively modify an existing subset in the viewer. This sets the app-wide subset
+ selection to the currently selected subset, mode to the selected combination_mode,
+ and selects the appropriate tool in this viewer's toolbar.
+
+ Parameters
+ ----------
+ combination_mode : str
+ The combination mode to apply to the subset. Must be one of 'replace', 'or', 'and',
+ 'xor', or 'andnot'.
+ subset_type : str
+ The type of subset to modify. Must be one of 'circle', 'rectangle', 'ellipse',
+ 'annulus', 'xrange', or 'yrange', and must be an available tool in this viewer.
+ """
+ # future improvement: allow overriding layer.selected, with pre-validation
+ if len(self.layer.selected) != 1:
+ raise ValueError("Only one layer can be selected to modify subset.")
+ if self.layer.selected[0] not in self.existing_subset_labels:
+ raise ValueError("Selected layer is not a subset.")
+ subset = self.layer.selected[0]
+
+ # set tool first since that might default to "Create New"
+ self._viewer.toolbar.select_tool(SUBSET_TOOL_IDS.get(subset_type, subset_type))
+ # set subset selection to the subset to modify
+ subset_grp = [sg for sg in self.app.data_collection.subset_groups if sg.label == subset]
+ self.session.edit_subset_mode.edit_subset = subset_grp
+ # set combination mode
+ self.session.edit_subset_mode.mode = SUBSET_MODES.get(combination_mode)
+
+ def vue_modify_subset(self, info, *args):
+ self.modify_subset(info.get('combination_mode'),
+ info.get('subset_type')) # pragma: no cover
+
+ def view_info(self):
+ """
+ View info for the selected layer by opening either the metadata or subset plugin to the
+ selected entry.
+ """
+ # future improvement: allow overriding layer.selected, with pre-validation
+ if len(self.layer.selected) != 1:
+ raise ValueError("Only one layer can be selected to view info.")
+ if self.layer.selected[0] in self.existing_subset_labels:
+ sp = self._viewer.jdaviz_helper.plugins.get('Subset Tools', None)
+ if sp is None: # pragma: no cover
+ raise ValueError("subset tools plugin not available")
+ sp._obj.subset_select.selected = self.layer.selected[0]
+ sp.open_in_tray()
+ else:
+ mp = self._viewer.jdaviz_helper.plugins.get('Metadata', None)
+ if mp is None: # pragma: no cover
+ raise ValueError("metadata plugin not available")
+ mp.dataset.selected = self.layer.selected[0]
+ mp.open_in_tray()
+
+ def vue_view_info(self, *args):
+ self.view_info() # pragma: no cover
+
+ def remove_from_viewer(self):
+ """
+ Remove the selected layers from the viewer. For subset layers, this
+ sets the visibility of the subset layer. For data layers,
+ this unloads the data from the viewer, but keeps it in the application or other viewers.
+ """
+ # future improvement: allow overriding layer.selected via *args, with pre-validation
+ for layer in self.layer.selected:
+ if layer in self.existing_subset_labels:
+ self.set_layer_visibility(layer, visible=False)
+ else:
+ self.app.remove_data_from_viewer(self.viewer_id, layer)
+
+ def vue_remove_from_viewer(self, *args):
+ self.remove_from_viewer() # pragma: no cover
+
+ def remove_from_app(self):
+ """
+ Remove the selected layers from the entire app and all viewers.
+ """
+ # future improvement: allow overriding layer.selected via *args, with pre-validation
+ for layer in self.layer.selected:
+ if layer in self.existing_subset_labels:
+ for sg in self.app.data_collection.subset_groups:
+ if sg.label == layer:
+ self.app.data_collection.remove_subset_group(sg)
+ break
+ else:
+ self.app.vue_data_item_remove({'item_name': layer})
+
+ def vue_remove_from_app(self, *args):
+ self.remove_from_app() # pragma: no cover
diff --git a/jdaviz/configs/default/plugins/data_menu/data_menu.vue b/jdaviz/configs/default/plugins/data_menu/data_menu.vue
index 8de84bc32a..c1627a09c3 100644
--- a/jdaviz/configs/default/plugins/data_menu/data_menu.vue
+++ b/jdaviz/configs/default/plugins/data_menu/data_menu.vue
@@ -46,8 +46,10 @@
/>
-
- {{item.label}}
+
+
+
+ {{ item.label }}
@@ -109,14 +111,9 @@
-
- mdi-layers-outline
-
- {{ item.label }}
+
+
+ {{ item.label }}
@@ -135,41 +132,39 @@
diff --git a/jdaviz/configs/default/tests/test_data_menu.py b/jdaviz/configs/default/tests/test_data_menu.py
index af3105b42a..9316ac0cc7 100644
--- a/jdaviz/configs/default/tests/test_data_menu.py
+++ b/jdaviz/configs/default/tests/test_data_menu.py
@@ -1,3 +1,4 @@
+import pytest
import numpy as np
from specutils import SpectralRegion
@@ -68,7 +69,7 @@ def test_data_menu_selection(specviz_helper, spectrum1d):
assert dm.layer.selected == ['test']
-def test_data_menu_add_data(imviz_helper):
+def test_data_menu_add_remove_data(imviz_helper):
for i in range(3):
imviz_helper.load_data(np.zeros((2, 2)) + i, data_label=f'image_{i}', show_in_viewer=False)
@@ -81,6 +82,24 @@ def test_data_menu_add_data(imviz_helper):
assert dm.layer.choices == ['image_0']
assert len(dm._obj.dataset.choices) == 2
+ with pytest.raises(ValueError,
+ match="Data labels \\['dne1', 'dne2'\\] not able to be loaded into 'imviz-0'. Must be one of: \\['image_1', 'image_2'\\]"): # noqa
+ dm.add_data('dne1', 'dne2')
+
+ dm.add_data('image_1', 'image_2')
+ assert len(dm.layer.choices) == 3
+ assert len(dm._obj.dataset.choices) == 0
+
+ dm.layer.selected = ['image_0']
+ dm.remove_from_viewer()
+ assert len(dm.layer.choices) == 2
+ assert len(dm._obj.dataset.choices) == 1
+
+ dm.layer.selected = ['image_1']
+ dm.remove_from_app()
+ assert len(dm.layer.choices) == 1
+ assert len(dm._obj.dataset.choices) == 1
+
def test_data_menu_create_subset(imviz_helper):
imviz_helper.load_data(np.zeros((2, 2)), data_label='image', show_in_viewer=True)
@@ -91,3 +110,84 @@ def test_data_menu_create_subset(imviz_helper):
dm.create_subset('circle')
assert imviz_helper.app.session.edit_subset_mode.edit_subset == []
assert imviz_helper.viewers['imviz-0']._obj.toolbar.active_tool_id == 'bqplot:truecircle'
+
+
+def test_data_menu_remove_subset(specviz_helper, spectrum1d):
+ # load 2 data entries
+ specviz_helper.load_data(spectrum1d, data_label="test")
+ new_spec = specviz_helper.get_spectra(apply_slider_redshift=True)["test"]*0.9
+ specviz_helper.load_data(new_spec, data_label="test2")
+
+ dm = specviz_helper.viewers['spectrum-viewer']._obj.data_menu
+ sp = specviz_helper.plugins['Subset Tools']
+
+ sp._obj.import_region(SpectralRegion(6000 * spectrum1d.spectral_axis.unit,
+ 6100 * spectrum1d.spectral_axis.unit),
+ combination_mode='new')
+ sp._obj.import_region(SpectralRegion(6000 * spectrum1d.spectral_axis.unit,
+ 6100 * spectrum1d.spectral_axis.unit),
+ combination_mode='new')
+
+ assert dm.layer.choices == ['test', 'test2', 'Subset 1', 'Subset 2']
+ dm.layer.selected = ['Subset 1']
+ dm.remove_from_viewer()
+
+ # subset visibility is set to false, but still appears in menu (unlike removing data)
+ assert dm.layer.choices == ['test', 'test2', 'Subset 1', 'Subset 2']
+ assert dm._obj.layer_items[2]['label'] == 'Subset 1'
+ # TODO: sometimes appearing as mixed right now, known bug
+ assert dm._obj.layer_items[2]['visible'] is not True
+
+ # selection should not have changed by removing subset from viewer
+ assert dm.layer.selected == ['Subset 1']
+ dm.remove_from_app()
+ # TODO: not quite sure why this isn't passing, seems to
+ # work on local tests, so may just be async?
+ # assert dm.layer.choices == ['test', 'test2', 'Subset 2']
+
+
+@pytest.mark.skip(reason="known issue")
+def test_data_menu_subset_appearance(specviz_helper, spectrum1d):
+ # NOTE: this test is similar to above - the subset is appearing in time IF there
+ # are two data entries, but not in this case with just one
+ specviz_helper.load_data(spectrum1d, data_label="test")
+
+ dm = specviz_helper.viewers['spectrum-viewer']._obj.data_menu
+ sp = specviz_helper.plugins['Subset Tools']
+
+ sp._obj.import_region(SpectralRegion(6000 * spectrum1d.spectral_axis.unit,
+ 6100 * spectrum1d.spectral_axis.unit))
+
+ assert dm.layer.choices == ['test', 'Subset 1']
+
+
+def test_data_menu_view_info(specviz_helper, spectrum1d):
+ # load 2 data entries
+ specviz_helper.load_data(spectrum1d, data_label="test")
+ new_spec = specviz_helper.get_spectra(apply_slider_redshift=True)["test"]*0.9
+ specviz_helper.load_data(new_spec, data_label="test2")
+
+ dm = specviz_helper.viewers['spectrum-viewer']._obj.data_menu
+ mp = specviz_helper.plugins['Metadata']
+ sp = specviz_helper.plugins['Subset Tools']
+
+ sp._obj.import_region(SpectralRegion(6000 * spectrum1d.spectral_axis.unit,
+ 6100 * spectrum1d.spectral_axis.unit),
+ combination_mode='new')
+ sp._obj.import_region(SpectralRegion(6200 * spectrum1d.spectral_axis.unit,
+ 6300 * spectrum1d.spectral_axis.unit),
+ combination_mode='new')
+
+ assert dm.layer.choices == ['test', 'test2', 'Subset 1', 'Subset 2']
+
+ dm.layer.selected = ["test2"]
+ dm.view_info()
+ assert mp.dataset.selected == "test2"
+
+ dm.layer.selected = ["Subset 2"]
+ dm.view_info()
+ assert sp._obj.subset_select.selected == "Subset 2"
+
+ dm.layer.selected = ["test", "test2"]
+ with pytest.raises(ValueError, match="Only one layer can be selected to view info"):
+ dm.view_info()
diff --git a/jdaviz/core/template_mixin.py b/jdaviz/core/template_mixin.py
index 6e24465e06..db5466aab6 100644
--- a/jdaviz/core/template_mixin.py
+++ b/jdaviz/core/template_mixin.py
@@ -1538,6 +1538,8 @@ def not_trace(lyr):
def _layer_to_dict(self, layer_label):
is_subset = None
subset_type = None
+ from_plugin = None
+ live_plugin_results = None
colors = []
visibilities = []
linewidths = []
@@ -1549,6 +1551,10 @@ def _layer_to_dict(self, layer_label):
(hasattr(layer, 'layer') and hasattr(layer.layer, 'subset_state'))) # noqa
if is_subset:
subset_type = get_subset_type(layer.layer)
+ if from_plugin is None:
+ from_plugin = layer.layer.data.meta.get('Plugin', None)
+ if live_plugin_results is None:
+ live_plugin_results = layer.layer.data.meta.get('_update_live_plugin_results', None) is not None # noqa
if (getattr(viewer.state, 'color_mode', None) == 'Colormaps'
and hasattr(layer.state, 'cmap')):
@@ -1563,6 +1569,8 @@ def _layer_to_dict(self, layer_label):
return {"label": layer_label,
"is_subset": is_subset,
"subset_type": subset_type,
+ "from_plugin": from_plugin,
+ "live_plugin_results": live_plugin_results,
"icon": self.app.state.layer_icons.get(layer_label),
"visible": visibilities[0] if len(list(set(visibilities))) == 1 else 'mixed',
"linewidth": linewidths[0] if len(list(set(linewidths))) == 1 else 'mixed',
@@ -1730,7 +1738,7 @@ def selected_obj(self):
layers = [[layer for layer in viewer.layers
if layer.layer.label in selected and self._is_valid_item(layer.layer)]
- for viewer in viewers]
+ for viewer in viewers if viewer is not None]
if not self.is_multiselect and len(layers) == 1:
return layers[0]