diff --git a/CHANGES.rst b/CHANGES.rst
index 8ecc95cec6..311c642020 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -6,6 +6,8 @@ New Features
- Line list plugin now supports exact-text filtering on line names. [#1298]
- Added a Subset Tools plugin for viewing information about defined subsets. [#1292]
+- Data menus in the viewers are filtered to applicable entries only and support removing generated data from
+ the app. [#1313]
- Offscreen indication for spectral lines and slice indicator. [#1312]
diff --git a/jdaviz/app.py b/jdaviz/app.py
index ba12d44691..5954a624c5 100644
--- a/jdaviz/app.py
+++ b/jdaviz/app.py
@@ -25,6 +25,7 @@
from glue.core.state_objects import State
from glue.core.subset import Subset, RangeSubsetState, RoiSubsetState
from glue_jupyter.app import JupyterApplication
+from glue_jupyter.common.toolbar_vuetify import read_icon
from glue_jupyter.state_traitlets_helpers import GlueState
from glue_jupyter.bqplot.profile import BqplotProfileView
from ipyvuetify import VuetifyTemplate
@@ -37,6 +38,7 @@
ViewerAddedMessage, ViewerRemovedMessage)
from jdaviz.core.registries import (tool_registry, tray_registry, viewer_registry,
data_parser_registry)
+from jdaviz.core.tools import ICON_DIR
from jdaviz.utils import SnackbarQueue
__all__ = ['Application']
@@ -54,57 +56,29 @@
# some glue-core versions
glue_settings.DATA_ALPHA = 1
-ipyvue.register_component_from_file(None, 'j-tooltip',
- os.path.join(os.path.dirname(__file__),
- 'components/tooltip.vue'))
+custom_components = {'j-tooltip': 'components/tooltip.vue',
+ 'j-external-link': 'components/external_link.vue',
+ 'j-docs-link': 'components/docs_link.vue',
+ 'j-viewer-data-select': 'components/viewer_data_select.vue',
+ 'j-viewer-data-select-item': 'components/viewer_data_select_item.vue',
+ 'j-tray-plugin': 'components/tray_plugin.vue',
+ 'j-play-pause-widget': 'components/play_pause_widget.vue',
+ 'j-plugin-section-header': 'components/plugin_section_header.vue',
+ 'j-number-uncertainty': 'components/number_uncertainty.vue',
+ 'plugin-dataset-select': 'components/plugin_dataset_select.vue',
+ 'plugin-subset-select': 'components/plugin_subset_select.vue',
+ 'plugin-viewer-select': 'components/plugin_viewer_select.vue',
+ 'plugin-add-results': 'components/plugin_add_results.vue',
+ 'plugin-auto-label': 'components/plugin_auto_label.vue'}
-ipyvue.register_component_from_file(None, 'j-external-link',
- os.path.join(os.path.dirname(__file__),
- 'components/external_link.vue'))
-
-ipyvue.register_component_from_file(None, 'j-docs-link',
- os.path.join(os.path.dirname(__file__),
- 'components/docs_link.vue'))
-
-ipyvue.register_component_from_file(None, 'j-tray-plugin',
- os.path.join(os.path.dirname(__file__),
- 'components/tray_plugin.vue'))
-
-ipyvue.register_component_from_file(None, 'j-plugin-section-header',
- os.path.join(os.path.dirname(__file__),
- 'components/plugin_section_header.vue'))
-
-ipyvue.register_component_from_file(None, 'j-number-uncertainty',
- os.path.join(os.path.dirname(__file__),
- 'components/number_uncertainty.vue'))
-
-ipyvue.register_component_from_file(None, 'plugin-dataset-select',
- os.path.join(os.path.dirname(__file__),
- 'components/plugin_dataset_select.vue'))
-
-ipyvue.register_component_from_file(None, 'plugin-subset-select',
- os.path.join(os.path.dirname(__file__),
- 'components/plugin_subset_select.vue'))
-
-ipyvue.register_component_from_file(None, 'plugin-viewer-select',
- os.path.join(os.path.dirname(__file__),
- 'components/plugin_viewer_select.vue'))
-
-ipyvue.register_component_from_file(None, 'plugin-add-results',
- os.path.join(os.path.dirname(__file__),
- 'components/plugin_add_results.vue'))
-
-ipyvue.register_component_from_file(None, 'plugin-auto-label',
- os.path.join(os.path.dirname(__file__),
- 'components/plugin_auto_label.vue'))
# Register pure vue component. This allows us to do recursive component instantiation only in the
# vue component file
-ipyvue.register_component_from_file('g-viewer-tab', "container.vue", __file__)
+for name, path in custom_components.items():
+ ipyvue.register_component_from_file(None, name,
+ os.path.join(os.path.dirname(__file__), path))
-ipyvue.register_component_from_file(None, 'j-play-pause-widget',
- os.path.join(os.path.dirname(__file__),
- 'components/play_pause_widget.vue'))
+ipyvue.register_component_from_file('g-viewer-tab', "container.vue", __file__)
class ApplicationState(State):
@@ -151,6 +125,11 @@ class ApplicationState(State):
}
}, docstring="Top-level application settings.")
+ icons = DictCallbackProperty({
+ 'radialtocheck': read_icon(os.path.join(ICON_DIR, 'radialtocheck.svg'), 'svg+xml'),
+ 'checktoradial': read_icon(os.path.join(ICON_DIR, 'checktoradial.svg'), 'svg+xml')
+ }, docstring="Custom application icons")
+
data_items = ListCallbackProperty(
docstring="List of data items parsed from the Glue data collection.")
@@ -1124,11 +1103,14 @@ def vue_data_item_selected(self, event):
"""
viewer_id, item_id, checked = event['id'], event['item_id'], event['checked']
viewer_item = self._viewer_item_by_id(viewer_id)
+ replace = event.get('replace', False)
if viewer_item is None:
raise ValueError(f'viewer {viewer_id} not found')
- if checked:
+ if replace:
+ selected_items = [item_id]
+ elif checked:
selected_items = [*viewer_item['selected_data_items'], item_id]
else:
selected_items = list(filter(
@@ -1136,6 +1118,9 @@ def vue_data_item_selected(self, event):
self._update_selected_data_items(viewer_id, selected_items)
+ def vue_data_item_remove(self, event):
+ self.data_collection.remove(self.data_collection[event['item_name']])
+
def vue_close_snackbar_message(self, event):
"""
Callback to close a message in the snackbar when the "close"
@@ -1225,7 +1210,7 @@ def _on_data_added(self, msg):
the new data.
"""
self._link_new_data()
- data_item = self._create_data_item(msg.data.label)
+ data_item = self._create_data_item(msg.data)
self.state.data_items.append(data_item)
def _on_data_deleted(self, msg):
@@ -1244,11 +1229,39 @@ def _on_data_deleted(self, msg):
self.state.data_items.remove(data_item)
@staticmethod
- def _create_data_item(label):
+ def _create_data_item(data):
+ ndims = len(data.shape)
+ wcsaxes = data.meta.get('WCSAXES', None)
+ if wcsaxes is None:
+ # then we'll need to determine type another way, we want to avoid
+ # this when we can though since its not as cheap
+ component_ids = [str(c) for c in data.component_ids()]
+ if data.label == 'MOS Table':
+ typ = 'table'
+ elif ndims == 1:
+ typ = '1d spectrum'
+ elif ndims == 2 and wcsaxes is not None:
+ if wcsaxes == 3:
+ typ = '2d spectrum'
+ elif wcsaxes == 2:
+ typ = 'image'
+ else:
+ typ = 'unknown'
+ elif ndims == 2 and wcsaxes is None:
+ typ = '2d spectrum' if 'Wavelength' in component_ids else 'image'
+ elif ndims == 3:
+ typ = 'cube'
+ else:
+ typ = 'unknown'
+ # we'll expose any information we need here. For "meta", not all entries are guaranteed
+ # to be serializable, so we'll just send those that we need.
return {
'id': str(uuid.uuid4()),
- 'name': label,
+ 'name': data.label,
'locked': False,
+ 'ndims': data.ndim,
+ 'type': typ,
+ 'meta': {k: v for k, v in data.meta.items() if k in ['Plugin', 'mosviz_row']},
'children': []}
@staticmethod
diff --git a/jdaviz/app.vue b/jdaviz/app.vue
index fb45dec8a8..60f58c6ad4 100644
--- a/jdaviz/app.vue
+++ b/jdaviz/app.vue
@@ -38,10 +38,13 @@
v-for="(stack, index) in state.stack_items"
:stack="stack"
:key="stack.viewers.map(v => v.id).join('-')"
- :data-items="state.data_items"
+ :data_items="state.data_items"
+ :app_settings="state.settings"
+ :icons="state.icons"
@resize="relayout"
:closefn="destroy_viewer_item"
@data-item-selected="data_item_selected($event)"
+ @data-item-remove="data_item_remove($event)"
@call-viewer-method="call_viewer_method($event)"
>
@@ -134,6 +137,7 @@ export default {
secondary: "#007DA4",
accent: "#C75109",
turquoise: "#007BA1",
+ lightblue: "#E3F2FD", // matches highlighted row in MOS table
spinner: "#163C4C",
error: '#FF5252',
info: '#2196F3',
@@ -147,6 +151,7 @@ export default {
secondary: "#007DA4",
accent: "#C75109",
turquoise: "#007BA1",
+ lightblue: "#E3F2FD",
spinner: "#ACE1FF",
error: '#FF5252',
info: '#2196F3',
diff --git a/jdaviz/components/tooltip.vue b/jdaviz/components/tooltip.vue
index 2579040a88..2b80f9d27e 100644
--- a/jdaviz/components/tooltip.vue
+++ b/jdaviz/components/tooltip.vue
@@ -46,6 +46,11 @@ const tooltips = {
'viewer-toolbar-figure-save': 'Save figure',
'viewer-toolbar-menu': 'Adjust display: contrast, bias, stretch',
'viewer-toolbar-more': 'More options...',
+ 'viewer-data-select-enabled': 'Allow multiple entries (click to enable replace)',
+ 'viewer-data-radio-enabled': 'Replace current entry (click to enable multi-select)',
+ 'viewer-data-select': 'Toggle whether data entry is loaded in the viewer',
+ 'viewer-data-radio': 'Change viewer to this data entry',
+ 'viewer-data-delete': 'Remove data entry across entire app',
'table-prev': 'Select previous row in table',
'table-next': 'Select next row in table',
diff --git a/jdaviz/components/viewer_data_select.vue b/jdaviz/components/viewer_data_select.vue
new file mode 100644
index 0000000000..5dee8ca977
--- /dev/null
+++ b/jdaviz/components/viewer_data_select.vue
@@ -0,0 +1,192 @@
+
+
+
+
+
+ mdi-format-list-bulleted-square
+
+
+
+
+
+
+ {{viewerTitleCase}}
+
+
+
+
+ {multi_select = !multi_select}"
+ style="opacity: 0.7"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{mosvizShowExtraItems ? 'mdi-chevron-double-up' : 'mdi-chevron-double-down'}}
+ {{mosvizShowExtraItems ? 'hide from other MOS rows' : 'show from other MOS rows'}}
+
+
+
+
+
+
+