Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Framework for legend data menu #3254

Merged
merged 24 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
New Features
------------

* New design for viewer legend. [#3220]
* New design for viewer legend. [#3220, #3254]

Cubeviz
^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion jdaviz/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ def to_unit(self, data, cid, values, original_units, target_units):
'j-number-uncertainty': 'components/number_uncertainty.vue',
'j-plugin-popout': 'components/plugin_popout.vue',
'j-multiselect-toggle': 'components/multiselect_toggle.vue',
'j-subset-icon': 'components/subset_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',
Expand Down Expand Up @@ -206,7 +207,6 @@ class ApplicationState(State):
'tray': True,
'tab_headers': True,
},
'viewer_labels': True,
'dense_toolbar': True,
'server_is_remote': False, # sets some defaults, should be set before loading the config
'context': {
Expand Down
7 changes: 5 additions & 2 deletions jdaviz/components/layer_viewer_icon_stylized.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<j-tooltip :tooltipcontent="tooltipContent(tooltip, label, visible, colormode, colors, linewidth, is_subset)">
<j-tooltip :tooltipcontent="tooltipContent(tooltip, label, visible, colormode, colors, linewidth, is_subset)" :disabled="disabled">
<v-btn
:rounded="is_subset"
@click="(e) => $emit('click', e)"
Expand All @@ -9,7 +9,10 @@
height="30px"
:disabled="disabled"
>
<span :style="'color: white; text-shadow: 0px 0px 3px black; '+borderStyle(linewidth)">
<v-icon v-if="String(icon).startsWith('mdi-')" style="color: white">
{{ icon }}
</v-icon>"
<span v-else :style="'color: white; text-shadow: 0px 0px 3px black; '+borderStyle(linewidth)">
{{ icon }}
</span>
</v-btn>
Expand Down
4 changes: 2 additions & 2 deletions jdaviz/components/plugin_switch.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<span v-if="use_eye_icon">
<v-btn icon @click.stop="$emit('update:value', !value)">
<v-btn icon @click.stop="$emit('update:value', !value); $emit('click', !value)">
<v-icon>mdi-eye{{ value ? '' : '-off' }}</v-icon>
</v-btn>
<span v-if="api_hints_enabled && api_hint" class="api-hint">
Expand All @@ -16,7 +16,7 @@
:class="api_hints_enabled && api_hint ? 'api-hint' : null"
:hint="hint"
v-model="value"
@change="$emit('update:value', $event)"
@change="$emit('update:value', $event); $emit('click', $event)"
persistent-hint>
</v-switch>
</template>
Expand Down
23 changes: 23 additions & 0 deletions jdaviz/components/subset_icon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<template>
<j-tooltip v-if="subset_type == 'spatial'" span_style="display: inline-block" tooltipcontent="Spatial subset">
<v-icon dense>
mdi-chart-scatter-plot
</v-icon>
</j-tooltip>
<j-tooltip v-else-if="subset_type == 'spectral'" span_style="display: inline-block" tooltipcontent="Spectral subset">
<v-icon dense>
mdi-chart-bell-curve
</v-icon>
</j-tooltip>
<j-tooltip v-else-if="subset_type == 'temporal'" span_style="display: inline-block" tooltipcontent="Temporal subset">
<v-icon dense>
mdi-chart-line
</v-icon>
</j-tooltip>
</template>

<script>
module.exports = {
props: ['subset_type']
};
</script>
2 changes: 1 addition & 1 deletion jdaviz/components/tooltip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ module.exports = {
return tooltips[this.$props.tipid];
},
getSpanStyle() {
return this.$props.span_style || "height: inherit; display: inherit";
return this.$props.span_style || "height: inherit; display: inherit; cursor: default";
},
getOpenDelay() {
return this.$props.delay || "0";
Expand Down
106 changes: 101 additions & 5 deletions jdaviz/configs/default/plugins/data_menu/data_menu.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from traitlets import Dict, Unicode
from contextlib import contextmanager
from traitlets import Bool, Dict, Unicode, List, observe

from jdaviz.core.template_mixin import TemplateMixin, LayerSelectMixin
from jdaviz.core.user_api import UserApiWrapper
Expand All @@ -15,6 +16,9 @@ class DataMenu(TemplateMixin, LayerSelectMixin):
:ref:`public API <plugin-apis>`:

* ``layer`` (:class:`~jdaviz.core.template_mixin.LayerSelect`):
actively selected layer(s)
* :meth:`set_layer_visibility`
* :meth:`toggle_layer_visibility`
"""
template_file = __file__, "data_menu.vue"

Expand All @@ -28,27 +32,34 @@ class DataMenu(TemplateMixin, LayerSelectMixin):

cmap_samples = Dict(cmap_samples).tag(sync=True)

dm_layer_selected = List().tag(sync=True)

dev_data_menu = Bool(False).tag(sync=True)

def __init__(self, viewer, *args, **kwargs):
super().__init__(*args, **kwargs)
self._viewer = viewer
self._during_select_sync = False

# TODO: refactor how this is applied by default to go through filters directly
self.layer.remove_filter('filter_is_root')
self.layer.add_filter(is_not_wcs_only)
self.layer.multiselect = True
self.layer._default_mode = 'empty'

# first attach callback to catch any updates to viewer/layer icons and then
# set their initial state
self.hub.subscribe(self, IconsUpdatedMessage, self._on_app_icons_updated)
self.hub.subscribe(self, AddDataMessage, handler=lambda _: self.set_viewer_id())
self.hub.subscribe(self, AddDataMessage, handler=lambda _: self._set_viewer_id())
self.viewer_icons = dict(self.app.state.viewer_icons)
self.layer_icons = dict(self.app.state.layer_icons)

@property
def user_api(self):
expose = ['layer']
expose = ['layer', 'set_layer_visibility', 'toggle_layer_visibility']
return UserApiWrapper(self, expose=expose)

def set_viewer_id(self):
def _set_viewer_id(self):
# viewer_ids are not populated on the viewer at init, so we'll keep checking and set
# these the first time that they are available
if len(self.viewer_id) and len(self.viewer_reference):
Expand All @@ -65,4 +76,89 @@ def _on_app_icons_updated(self, msg):
self.viewer_icons = msg.icons
elif msg.icon_type == 'layer':
self.layer_icons = msg.icons
self.set_viewer_id()
self._set_viewer_id()

@contextmanager
def during_select_sync(self):
self._during_select_sync = True
try:
yield
except Exception: # pragma: no cover
self._during_select_sync = False
raise
self._during_select_sync = False

@observe('dm_layer_selected')
def _dm_layer_selected_changed(self, event={}):
if not hasattr(self, 'layer') or not self.layer.multiselect: # pragma: no cover
return
if self._during_select_sync:
return
if len(event.get('new')) == len(event.get('old')):
# not possible from UI interaction, but instead caused by a selected
# layer being removed (deleting a selected subset, etc). We want
# to update dm_layer_selected in order to preserve layer.selected
self._update_dm_layer_selected(event)
return
with self.during_select_sync():
# map index in dm_layer_selected (inverse order of layer_items)
# to set self.layer.selected
length = len(self.layer_items)
self.layer.selected = [self.layer_items[length-1-i]['label']
for i in self.dm_layer_selected]

@observe('layer_selected', 'layer_items')
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:
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]

def set_layer_visibility(self, layer_label, visible=True):
"""
Set the visibility of a layer in the viewer.

Parameters
----------
layer_label : str
The label of the layer to set the visibility of.
visible : bool
Whether the layer should be visible or not.

Returns
-------
dict
A dictionary of the current visible layers.
"""
for layer in self._viewer.layers:
if layer.layer.label == layer_label:
layer.visible = visible
elif hasattr(layer.layer, 'data') and layer.layer.data.label == layer_label:
layer.visible = layer.layer.label in self.visible_layers
return self.visible_layers

def toggle_layer_visibility(self, layer_label):
"""
Toggle the visibility of a layer in the viewer.

Parameters
----------
layer_label : str
The label of the layer to toggle the visibility of.

Returns
-------
bool
The new visibility state of the layer.
"""
visible = layer_label not in self.visible_layers
self.set_layer_visibility(layer_label, visible=visible)
return visible

def vue_set_layer_visibility(self, info, *args):
return self.set_layer_visibility(info.get('layer'), info.get('value')) # pragma: no cover
Loading
Loading