From 5192aa5b6bee0ba454cfcbf15600149d1cd68262 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Sun, 24 Nov 2024 11:56:43 -0500 Subject: [PATCH 01/11] Add save hover dialog for Jupyter viewers. --- glue_plotly/html_exporters/base_save_hover.py | 72 ++++++++ .../html_exporters/jupyter/save_hover.py | 67 ++++++++ .../html_exporters/jupyter/save_hover.vue | 51 ++++++ glue_plotly/html_exporters/qt/save_hover.py | 98 +++++++++++ .../{ => html_exporters/qt}/save_hover.ui | 0 glue_plotly/save_hover.py | 162 ------------------ 6 files changed, 288 insertions(+), 162 deletions(-) create mode 100644 glue_plotly/html_exporters/base_save_hover.py create mode 100644 glue_plotly/html_exporters/jupyter/save_hover.py create mode 100644 glue_plotly/html_exporters/jupyter/save_hover.vue create mode 100644 glue_plotly/html_exporters/qt/save_hover.py rename glue_plotly/{ => html_exporters/qt}/save_hover.ui (100%) delete mode 100644 glue_plotly/save_hover.py diff --git a/glue_plotly/html_exporters/base_save_hover.py b/glue_plotly/html_exporters/base_save_hover.py new file mode 100644 index 0000000..2143d7a --- /dev/null +++ b/glue_plotly/html_exporters/base_save_hover.py @@ -0,0 +1,72 @@ +from echo import SelectionCallbackProperty +from glue import config +from glue.core.data_combo_helper import ComponentIDComboHelper, DataCollectionComboHelper +from glue.core.state_objects import State + + +class SaveHoverState(State): + + data = SelectionCallbackProperty() + subset = SelectionCallbackProperty() + component = SelectionCallbackProperty() + exporter = SelectionCallbackProperty() + + def __init__(self, data_collection=None): + + super(SaveHoverState, self).__init__() + + self.data_helper = DataCollectionComboHelper(self, 'data', data_collection) + self.component_helper = ComponentIDComboHelper(self, 'component', + data_collection=data_collection) + + self.add_callback('data', self._on_data_change) + self._on_data_change() + + self._sync_data_exporters() + + def _sync_data_exporters(self): + + exporters = list(config.data_exporter) + + def display_func(exporter): + if exporter.extension == '': + return "{0} (*)".format(exporter.label) + else: + return "{0} ({1})".format(exporter.label, ' '.join('*.' + ext for ext in exporter.extension)) + + SaveHoverState.exporter.set_choices(self, exporters) + SaveHoverState.exporter.set_display_func(self, display_func) + + def _on_data_change(self, event=None): + self.component_helper.set_multiple_data([self.data]) + self._sync_subsets() + + def _sync_subsets(self): + + def display_func(subset): + if subset is None: + return "All data (no subsets applied)" + else: + return subset.label + + subsets = [None] + list(self.data.subsets) + + SaveHoverState.subset.set_choices(self, subsets) + SaveHoverState.subset.set_display_func(self, display_func) + + +class BaseSaveHoverDialog: + + def __init__(self, data_collection=None, checked_dictionary=None): + + self.checked_dictionary = checked_dictionary + self.state = SaveHoverState(data_collection=data_collection) + + self.state.add_callback('component', self._on_component_change) + self.state.add_callback('data', self._on_data_change) + + def _on_component_change(self, *args): + pass + + def _on_data_change(self, *args): + pass diff --git a/glue_plotly/html_exporters/jupyter/save_hover.py b/glue_plotly/html_exporters/jupyter/save_hover.py new file mode 100644 index 0000000..65271cc --- /dev/null +++ b/glue_plotly/html_exporters/jupyter/save_hover.py @@ -0,0 +1,67 @@ +from ipyvuetify.VuetifyTemplate import VuetifyTemplate +from traitlets import Bool, Int, List, observe + +from glue_jupyter.vuetify_helpers import link_glue_choices + +from ..base_save_hover import BaseSaveHoverDialog + + +class JupyterSaveHoverDialog(BaseSaveHoverDialog, VuetifyTemplate): + + template_file = (__file__, "save_hover.vue") + dialog_open = Bool().tag(sync=True) + + data_items = List().tag(sync=True) + data_selected = Int().tag(sync=True) + + component_items = List().tag(sync=True) + component_selected = List().tag(sync=True) + + def __init__(self, + data_collection=None, + checked_dictionary=None, + on_cancel=None, + on_export=None): + + BaseSaveHoverDialog.__init__(self, data_collection=data_collection, checked_dictionary=checked_dictionary) + VuetifyTemplate.__init__(self) + + self.on_cancel = on_cancel + self.on_export = on_export + + link_glue_choices(self, self.state, 'data') + + self._on_data_change() + + def _on_data_change(self, *args): + super()._on_data_change(*args) + data_components = self.state.data.main_components + self.component_items = [ + {"text": component.label, "value": index} + for index, component in enumerate(data_components) + ] + current_selections = self.checked_dictionary[self.state.data.label] + self.component_selected = [i for i in range(len(data_components)) if current_selections[i]] + + @observe('component_selected') + def _on_component_selected_changed(self, change): + selections = change["new"] + current_layer = self.state.data.label + self.checked_dictionary[current_layer] = [i in selections for i in range(len(self.state.data.main_components))] + + def vue_select_none(self, *args): + self.component_selected = [] + + def vue_select_all(self, *args): + self.component_selected = list(range(len(self.component_items))) + + def vue_cancel_dialog(self, *args): + # self.checked_dictionary = {} + self.dialog_open = False + if self.on_cancel is not None: + self.on_cancel() + + def vue_export_viewer(self, *args): + self.dialog_open = False + if self.on_export is not None: + self.on_export() diff --git a/glue_plotly/html_exporters/jupyter/save_hover.vue b/glue_plotly/html_exporters/jupyter/save_hover.vue new file mode 100644 index 0000000..d208579 --- /dev/null +++ b/glue_plotly/html_exporters/jupyter/save_hover.vue @@ -0,0 +1,51 @@ + diff --git a/glue_plotly/html_exporters/qt/save_hover.py b/glue_plotly/html_exporters/qt/save_hover.py new file mode 100644 index 0000000..6c83cd1 --- /dev/null +++ b/glue_plotly/html_exporters/qt/save_hover.py @@ -0,0 +1,98 @@ +import os + +from qtpy.QtWidgets import QDialog, QListWidgetItem +from qtpy.QtCore import Qt + +from echo import ChoiceSeparator +from echo.qt import autoconnect_callbacks_to_qt + +from glue_qt.utils import load_ui + +import numpy as np + +from ..base_save_hover import BaseSaveHoverDialog + +__all__ = ['SaveHoverDialog'] + + +class SaveHoverDialog(BaseSaveHoverDialog, QDialog): + + def __init__(self, data_collection=None, parent=None, checked_dictionary=None): + + BaseSaveHoverDialog.__init__(self, data_collection=data_collection, checked_dictionary=checked_dictionary) + QDialog.__init__(self, parent=parent) + + self.ui = load_ui('save_hover.ui', self, + directory=os.path.dirname(__file__)) + + self._connections = autoconnect_callbacks_to_qt(self.state, self.ui) + + self.ui.button_cancel.clicked.connect(self.reject) + self.ui.button_ok.clicked.connect(self.accept) + self.ui.button_select_none.clicked.connect(self.select_none) + self.ui.button_select_all.clicked.connect(self.select_all) + + self.ui.list_component.itemChanged.connect(self._on_check_change) + + self._on_component_change() + + def _on_component_change(self, *event): + super()._on_component_change(*event) + + components = getattr(type(self.state), 'component').get_choices(self.state) + self.ui.list_component.clear() + + for (component, k) in zip(components, np.arange(0, len(components))): + + if isinstance(component, ChoiceSeparator): + item = QListWidgetItem(str(component)) + item.setFlags(item.flags() & ~Qt.ItemIsSelectable) + item.setForeground(Qt.gray) + else: + item = QListWidgetItem(component.label) + if self.checked_dictionary[self.state.data.label][k]: + item.setCheckState(Qt.Checked) + else: + item.setCheckState(Qt.Unchecked) + + self.ui.list_component.addItem(item) + + def _on_check_change(self, *event): + + current_layer = self.state.data.label + any_checked = False + for idx in range(self.ui.list_component.count()): + item = self.ui.list_component.item(idx) + checked = item.checkState() == Qt.Checked + any_checked = any_checked or checked + self.checked_dictionary[current_layer][idx] = checked + + self.button_ok.setEnabled(any_checked) + + def select_none(self, *event): + self._set_all_checked(False) + + def select_all(self, *event): + self._set_all_checked(True) + + def _set_all_checked(self, checked): + state = Qt.Checked if checked else Qt.Unchecked + for idx in range(self.ui.list_component.count()): + item = self.ui.list_component.item(idx) + item.setCheckState(state) + + def accept(self): + components = [] + for idx in range(self.ui.list_component.count()): + item = self.ui.list_component.item(idx) + if item.checkState() == Qt.Checked: + components.append(self.state.data.id[item.text()]) + + # if self.state.subset is None: + # data = self.state.data + # else: + # data = self.state.subset + + # return checked_dictionary + # export_data(data, components=components, exporter=self.state.exporter.function) + QDialog.accept(self) diff --git a/glue_plotly/save_hover.ui b/glue_plotly/html_exporters/qt/save_hover.ui similarity index 100% rename from glue_plotly/save_hover.ui rename to glue_plotly/html_exporters/qt/save_hover.ui diff --git a/glue_plotly/save_hover.py b/glue_plotly/save_hover.py deleted file mode 100644 index b251892..0000000 --- a/glue_plotly/save_hover.py +++ /dev/null @@ -1,162 +0,0 @@ -import os - -from qtpy.QtWidgets import QDialog, QListWidgetItem -from qtpy.QtCore import Qt - -from echo import ChoiceSeparator, SelectionCallbackProperty -from echo.qt import autoconnect_callbacks_to_qt - -from glue import config -from glue.core.data_combo_helper import ComponentIDComboHelper, DataCollectionComboHelper -from glue.core.state_objects import State -from glue_qt.utils import load_ui - -import numpy as np - - -__all__ = ['SaveHoverDialog'] - - -class SaveHoverState(State): - - data = SelectionCallbackProperty() - subset = SelectionCallbackProperty() - component = SelectionCallbackProperty() - exporter = SelectionCallbackProperty() - - def __init__(self, data_collection=None): - - super(SaveHoverState, self).__init__() - - self.data_helper = DataCollectionComboHelper(self, 'data', data_collection) - self.component_helper = ComponentIDComboHelper(self, 'component', - data_collection=data_collection) - - self.add_callback('data', self._on_data_change) - self._on_data_change() - - self._sync_data_exporters() - - def _sync_data_exporters(self): - - exporters = list(config.data_exporter) - - def display_func(exporter): - if exporter.extension == '': - return "{0} (*)".format(exporter.label) - else: - return "{0} ({1})".format(exporter.label, ' '.join('*.' + ext for ext in exporter.extension)) - - SaveHoverState.exporter.set_choices(self, exporters) - SaveHoverState.exporter.set_display_func(self, display_func) - - def _on_data_change(self, event=None): - self.component_helper.set_multiple_data([self.data]) - self._sync_subsets() - - def _sync_subsets(self): - - def display_func(subset): - if subset is None: - return "All data (no subsets applied)" - else: - return subset.label - - subsets = [None] + list(self.data.subsets) - - SaveHoverState.subset.set_choices(self, subsets) - SaveHoverState.subset.set_display_func(self, display_func) - - -class SaveHoverDialog(QDialog): - - def __init__(self, data_collection=None, parent=None, checked_dictionary=None): - - super(SaveHoverDialog, self).__init__(parent=parent) - - self.checked_dictionary = checked_dictionary - - self.state = SaveHoverState(data_collection=data_collection) - - self.ui = load_ui('save_hover.ui', self, - directory=os.path.dirname(__file__)) - - self._connections = autoconnect_callbacks_to_qt(self.state, self.ui) - - self.ui.button_cancel.clicked.connect(self.reject) - self.ui.button_ok.clicked.connect(self.accept) - self.ui.button_select_none.clicked.connect(self.select_none) - self.ui.button_select_all.clicked.connect(self.select_all) - - self.ui.list_component.itemChanged.connect(self._on_check_change) - - self.state.add_callback('component', self._on_data_change) - - self._on_data_change() - - def _on_data_change(self, *event): - - components = getattr(type(self.state), 'component').get_choices(self.state) - self.ui.list_component.clear() - - for (component, k) in zip(components, np.arange(0, len(components))): - - if isinstance(component, ChoiceSeparator): - item = QListWidgetItem(str(component)) - item.setFlags(item.flags() & ~Qt.ItemIsSelectable) - item.setForeground(Qt.gray) - else: - item = QListWidgetItem(component.label) - if self.checked_dictionary[self.state.data.label][k]: - item.setCheckState(Qt.Checked) - else: - item.setCheckState(Qt.Unchecked) - - self.ui.list_component.addItem(item) - - def _on_check_change(self, *event): - - current_layer = self.state.data.label - for idx in range(self.ui.list_component.count()): - item = self.ui.list_component.item(idx) - if item.checkState() == Qt.Checked: - self.checked_dictionary[current_layer][idx] = True - else: - self.checked_dictionary[current_layer][idx] = False - - any_checked = False - - for idx in range(self.ui.list_component.count()): - item = self.ui.list_component.item(idx) - if item.checkState() == Qt.Checked: - any_checked = True - break - - self.button_ok.setEnabled(any_checked) - - def select_none(self, *event): - self._set_all_checked(False) - - def select_all(self, *event): - self._set_all_checked(True) - - def _set_all_checked(self, check_state): - for idx in range(self.ui.list_component.count()): - item = self.ui.list_component.item(idx) - item.setCheckState(Qt.Checked if check_state else Qt.Unchecked) - - def accept(self): - components = [] - for idx in range(self.ui.list_component.count()): - item = self.ui.list_component.item(idx) - if item.checkState() == Qt.Checked: - components.append(self.state.data.id[item.text()]) - - # if self.state.subset is None: - # data = self.state.data - # else: - # data = self.state.subset - - # return checked_dictionary - # export_data(data, components=components, exporter=self.state.exporter.function) - super(SaveHoverDialog, self).accept() From 6332e97760dcfa089d5d2e40e317569304881e3e Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Sun, 24 Nov 2024 12:15:56 -0500 Subject: [PATCH 02/11] Add some fallback behavior to the Jupyter dialog. --- glue_plotly/html_exporters/jupyter/save_hover.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/glue_plotly/html_exporters/jupyter/save_hover.py b/glue_plotly/html_exporters/jupyter/save_hover.py index 65271cc..75cf2aa 100644 --- a/glue_plotly/html_exporters/jupyter/save_hover.py +++ b/glue_plotly/html_exporters/jupyter/save_hover.py @@ -26,6 +26,9 @@ def __init__(self, BaseSaveHoverDialog.__init__(self, data_collection=data_collection, checked_dictionary=checked_dictionary) VuetifyTemplate.__init__(self) + if self.checked_dictionary is None: + self.checked_dictionary = {} + self.on_cancel = on_cancel self.on_export = on_export @@ -40,7 +43,7 @@ def _on_data_change(self, *args): {"text": component.label, "value": index} for index, component in enumerate(data_components) ] - current_selections = self.checked_dictionary[self.state.data.label] + current_selections = self.checked_dictionary.get(self.state.data.label, [False for _ in data_components]) self.component_selected = [i for i in range(len(data_components)) if current_selections[i]] @observe('component_selected') From 947565da2663986f15cfa25246867c099340e1ea Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Sun, 24 Nov 2024 16:17:53 -0500 Subject: [PATCH 03/11] Add hover dialog to 2D scatter. --- .../html_exporters/jupyter/save_hover.py | 19 ++++++++---- .../html_exporters/jupyter/scatter2d.py | 29 ++++++++++++++++--- glue_plotly/jupyter_base_export_tool.py | 8 ++++- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/glue_plotly/html_exporters/jupyter/save_hover.py b/glue_plotly/html_exporters/jupyter/save_hover.py index 75cf2aa..c82a061 100644 --- a/glue_plotly/html_exporters/jupyter/save_hover.py +++ b/glue_plotly/html_exporters/jupyter/save_hover.py @@ -18,27 +18,34 @@ class JupyterSaveHoverDialog(BaseSaveHoverDialog, VuetifyTemplate): component_selected = List().tag(sync=True) def __init__(self, - data_collection=None, + data_collection, checked_dictionary=None, on_cancel=None, - on_export=None): + on_export=None, + display=False): BaseSaveHoverDialog.__init__(self, data_collection=data_collection, checked_dictionary=checked_dictionary) VuetifyTemplate.__init__(self) if self.checked_dictionary is None: - self.checked_dictionary = {} + self.checked_dictionary = { + data.label: [False for _ in data.components] + for data in data_collection + } self.on_cancel = on_cancel self.on_export = on_export + if display: + self.dialog_open = True + link_glue_choices(self, self.state, 'data') self._on_data_change() def _on_data_change(self, *args): super()._on_data_change(*args) - data_components = self.state.data.main_components + data_components = self.state.data.components self.component_items = [ {"text": component.label, "value": index} for index, component in enumerate(data_components) @@ -50,7 +57,7 @@ def _on_data_change(self, *args): def _on_component_selected_changed(self, change): selections = change["new"] current_layer = self.state.data.label - self.checked_dictionary[current_layer] = [i in selections for i in range(len(self.state.data.main_components))] + self.checked_dictionary[current_layer] = [i in selections for i in range(len(self.state.data.components))] def vue_select_none(self, *args): self.component_selected = [] @@ -59,7 +66,7 @@ def vue_select_all(self, *args): self.component_selected = list(range(len(self.component_items))) def vue_cancel_dialog(self, *args): - # self.checked_dictionary = {} + self.checked_dictionary = {} self.dialog_open = False if self.on_cancel is not None: self.on_cancel() diff --git a/glue_plotly/html_exporters/jupyter/scatter2d.py b/glue_plotly/html_exporters/jupyter/scatter2d.py index 9374f34..d9d6231 100644 --- a/glue_plotly/html_exporters/jupyter/scatter2d.py +++ b/glue_plotly/html_exporters/jupyter/scatter2d.py @@ -1,11 +1,13 @@ from glue.config import viewer_tool - -from glue_plotly.common.common import data_count, layers_to_export -from glue_plotly.common.scatter2d import rectilinear_layout_config, traces_for_layer +from IPython.display import display from plotly.offline import plot import plotly.graph_objs as go +from glue_plotly.common.common import data_count, layers_to_export +from glue_plotly.common.scatter2d import rectilinear_layout_config, traces_for_layer +from glue_plotly.html_exporters.jupyter.save_hover import JupyterSaveHoverDialog + from ...jupyter_base_export_tool import JupyterBaseExportTool @@ -13,6 +15,22 @@ class PlotlyScatter2DBqplotExport(JupyterBaseExportTool): tool_id = 'save:bqplot_plotly2d' + def activate(self): + done = False + + def on_cancel(): + nonlocal done + done = True + + self.save_hover_dialog = \ + JupyterSaveHoverDialog(data_collection=self.viewer.state.data_collection, + checked_dictionary=None, + display=True, + on_cancel=on_cancel, + on_export=self.open_file_dialog) + with self.viewer.output_widget: + display(self.save_hover_dialog) + def save_figure(self, filepath): if not filepath: @@ -26,7 +44,10 @@ def save_figure(self, filepath): layers = layers_to_export(self.viewer) add_data_label = data_count(layers) > 1 for layer in layers: - traces = traces_for_layer(self.viewer, layer.state, add_data_label=add_data_label) + traces = traces_for_layer(self.viewer, + layer.state, + hover_data=self.save_hover_dialog.checked_dictionary[layer.state.layer.label], + add_data_label=add_data_label) fig.add_traces(traces) plot(fig, filename=filepath, auto_open=False) diff --git a/glue_plotly/jupyter_base_export_tool.py b/glue_plotly/jupyter_base_export_tool.py index 1082be6..126b6d8 100644 --- a/glue_plotly/jupyter_base_export_tool.py +++ b/glue_plotly/jupyter_base_export_tool.py @@ -18,7 +18,7 @@ class JupyterBaseExportTool(Tool): action_text = "Save Plotly HTML page" tool_tip = "Save Plotly HTML page" - def activate(self): + def open_file_dialog(self): file_chooser = FileChooser(getcwd()) ok_btn = v.Btn(color='success', disabled=True, children=['Ok']) close_btn = v.Btn(color='error', children=['Close']) @@ -70,6 +70,7 @@ def maybe_save_figure(self, filepath): def on_yes_click(button, event, data): self.save_figure(filepath) + check_dialog.v_model = False self.viewer.output_widget.clear_output() def on_no_click(button, event, data): @@ -86,3 +87,8 @@ def on_no_click(button, event, data): def save_figure(self, filepath): raise NotImplementedError() + + # Subclasses should override this if they have another dialog + # to display before the file chooser dialog + def activate(self): + self.open_file_dialog() From 3f32e30d78e7e41ea2327adf999acf104b82c470 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Mon, 25 Nov 2024 02:23:51 -0500 Subject: [PATCH 04/11] Refactor construction of hover data collection into utility functions. --- glue_plotly/html_exporters/base_save_hover.py | 10 ++++- glue_plotly/html_exporters/hover_utils.py | 26 ++++++++++++ glue_plotly/html_exporters/jupyter/image.py | 42 +++++++++++++++++-- .../html_exporters/jupyter/scatter2d.py | 7 ++-- .../html_exporters/jupyter/scatter3d.py | 24 ++++++++++- glue_plotly/html_exporters/qt/image.py | 31 ++++++-------- glue_plotly/html_exporters/qt/scatter2d.py | 16 ++----- glue_plotly/html_exporters/qt/scatter3d.py | 17 ++------ 8 files changed, 120 insertions(+), 53 deletions(-) create mode 100644 glue_plotly/html_exporters/hover_utils.py diff --git a/glue_plotly/html_exporters/base_save_hover.py b/glue_plotly/html_exporters/base_save_hover.py index 2143d7a..94f02ad 100644 --- a/glue_plotly/html_exporters/base_save_hover.py +++ b/glue_plotly/html_exporters/base_save_hover.py @@ -57,9 +57,15 @@ def display_func(subset): class BaseSaveHoverDialog: - def __init__(self, data_collection=None, checked_dictionary=None): + def __init__(self, data_collection, checked_dictionary=None): - self.checked_dictionary = checked_dictionary + if checked_dictionary is not None: + self.checked_dictionary = checked_dictionary + else: + self.checked_dictionary = { + data.label: [False for _ in data.components] + for data in data_collection + } self.state = SaveHoverState(data_collection=data_collection) self.state.add_callback('component', self._on_component_change) diff --git a/glue_plotly/html_exporters/hover_utils.py b/glue_plotly/html_exporters/hover_utils.py new file mode 100644 index 0000000..fb29c96 --- /dev/null +++ b/glue_plotly/html_exporters/hover_utils.py @@ -0,0 +1,26 @@ +from glue.core import Data, DataCollection +from numpy import ones + + +def hover_dummy_data(data_or_subset): + components = {} + for component in data_or_subset.components: + components[component.label] = ones(10) + + dummy_data = Data(**components, label=data_or_subset.label) + + seen = set() + for component in dummy_data.components: + if component.label in seen: + dummy_data.remove_component(component) + else: + seen.add(component.label) + return dummy_data + + +def hover_data_collection_for_viewer(viewer, + layer_condition=None): + if layer_condition is None: + layer_condition = lambda layer: layer.enabled and layer.state.visible + + return DataCollection([hover_dummy_data(layer.layer) for layer in viewer.layers if layer_condition(layer)]) diff --git a/glue_plotly/html_exporters/jupyter/image.py b/glue_plotly/html_exporters/jupyter/image.py index 54af25d..8997103 100644 --- a/glue_plotly/html_exporters/jupyter/image.py +++ b/glue_plotly/html_exporters/jupyter/image.py @@ -1,19 +1,52 @@ from glue.config import viewer_tool - -from glue_plotly.common import data_count, layers_to_export -from glue_plotly.common.image import axes_data_from_bqplot, layout_config, traces +from IPython.display import display from plotly.offline import plot import plotly.graph_objs as go from plotly.subplots import make_subplots -from ...jupyter_base_export_tool import JupyterBaseExportTool +from glue_plotly.common import data_count, layers_to_export +from glue_plotly.common.image import axes_data_from_bqplot, layers_by_type, layout_config, traces +from glue_plotly.html_exporters.hover_utils import hover_data_collection_for_viewer +from glue_plotly.html_exporters.jupyter.save_hover import JupyterSaveHoverDialog +from glue_plotly.jupyter_base_export_tool import JupyterBaseExportTool @viewer_tool class PlotlyImageBqplotExport(JupyterBaseExportTool): tool_id = 'save:bqplot_plotlyimage2d' + def activate(self): + done = False + + def on_cancel(): + nonlocal done + done = True + + layers = layers_by_type(self.viewer) + scatter_layers = layers["scatter"] + + if len(scatter_layers) > 0: + dc_hover = hover_data_collection_for_viewer( + self.viewer, + layer_condition=lambda layer: layer.state.visible \ + and layer.enabled + and layer in scatter_layers) + + dialog = \ + JupyterSaveHoverDialog(data_collection=dc_hover, + checked_dictionary=None, + display=True, + on_cancel=on_cancel, + on_export=self.open_file_dialog) + self.checked_dictionary = dialog.checked_dictionary + + with self.viewer.output_widget: + display(dialog) + else: + self.checked_dictionary = None + + def save_figure(self, filepath): if not filepath: @@ -40,6 +73,7 @@ def save_figure(self, filepath): traces_to_add = traces(self.viewer, secondary_x=secondary_x, secondary_y=secondary_y, + hover_selections=self.checked_dictionary, add_data_label=add_data_label) fig.add_traces(traces_to_add) diff --git a/glue_plotly/html_exporters/jupyter/scatter2d.py b/glue_plotly/html_exporters/jupyter/scatter2d.py index d9d6231..933dede 100644 --- a/glue_plotly/html_exporters/jupyter/scatter2d.py +++ b/glue_plotly/html_exporters/jupyter/scatter2d.py @@ -6,9 +6,9 @@ from glue_plotly.common.common import data_count, layers_to_export from glue_plotly.common.scatter2d import rectilinear_layout_config, traces_for_layer +from glue_plotly.html_exporters.hover_utils import hover_data_collection_for_viewer from glue_plotly.html_exporters.jupyter.save_hover import JupyterSaveHoverDialog - -from ...jupyter_base_export_tool import JupyterBaseExportTool +from glue_plotly.jupyter_base_export_tool import JupyterBaseExportTool @viewer_tool @@ -22,8 +22,9 @@ def on_cancel(): nonlocal done done = True + dc_hover = hover_data_collection_for_viewer(self.viewer) self.save_hover_dialog = \ - JupyterSaveHoverDialog(data_collection=self.viewer.state.data_collection, + JupyterSaveHoverDialog(data_collection=dc_hover, checked_dictionary=None, display=True, on_cancel=on_cancel, diff --git a/glue_plotly/html_exporters/jupyter/scatter3d.py b/glue_plotly/html_exporters/jupyter/scatter3d.py index 5cb922b..fb4b1d4 100644 --- a/glue_plotly/html_exporters/jupyter/scatter3d.py +++ b/glue_plotly/html_exporters/jupyter/scatter3d.py @@ -1,8 +1,11 @@ from glue.config import viewer_tool +from IPython.display import display from glue_plotly.common.base_3d import layout_config from glue_plotly.common.common import data_count, layers_to_export from glue_plotly.common.scatter3d import traces_for_layer +from glue_plotly.html_exporters.hover_utils import hover_data_collection_for_viewer +from glue_plotly.html_exporters.jupyter.save_hover import JupyterSaveHoverDialog from glue_plotly.jupyter_base_export_tool import JupyterBaseExportTool import plotly.graph_objs as go @@ -13,6 +16,23 @@ class PlotlyScatter3DStaticExport(JupyterBaseExportTool): tool_id = 'save:jupyter_plotly3dscatter' + def activate(self): + done = False + + def on_cancel(): + nonlocal done + done = True + + dc_hover = hover_data_collection_for_viewer(self.viewer) + self.save_hover_dialog = \ + JupyterSaveHoverDialog(data_collection=dc_hover, + checked_dictionary=None, + display=True, + on_cancel=on_cancel, + on_export=self.open_file_dialog) + with self.viewer.output_widget: + display(self.save_hover_dialog) + def save_figure(self, filepath): if not filepath: @@ -25,7 +45,9 @@ def save_figure(self, filepath): layers = layers_to_export(self.viewer) add_data_label = data_count(layers) > 1 for layer in layers: - traces = traces_for_layer(self.viewer.state, layer.state, + traces = traces_for_layer(self.viewer.state, + layer.state, + hover_data=self.save_hover_dialog.checked_dictionary[layer.state.layer.label], add_data_label=add_data_label) for trace in traces: fig.add_trace(trace) diff --git a/glue_plotly/html_exporters/qt/image.py b/glue_plotly/html_exporters/qt/image.py index d4129b1..26756c9 100644 --- a/glue_plotly/html_exporters/qt/image.py +++ b/glue_plotly/html_exporters/qt/image.py @@ -1,21 +1,19 @@ from __future__ import absolute_import, division, print_function -import numpy as np - from qtpy import compat from qtpy.QtWidgets import QDialog from glue.config import viewer_tool -from glue.core import DataCollection, Data from glue_qt.utils import messagebox_on_error from glue_qt.utils.threading import Worker from glue_qt.viewers.common.tool import Tool -from ... import save_hover, export_dialog - from glue_plotly import PLOTLY_ERROR_MESSAGE, PLOTLY_LOGO from glue_plotly.common import data_count, layers_to_export from glue_plotly.common.image import axes_data_from_mpl, layers_by_type, layout_config, traces +from glue_plotly import export_dialog +from glue_plotly.html_exporters.hover_utils import hover_data_collection_for_viewer +from glue_plotly.html_exporters.qt.save_hover import SaveHoverDialog import plotly.graph_objects as go from plotly.offline import plot @@ -59,23 +57,20 @@ def activate(self): layers = layers_by_type(self.viewer) scatter_layers = layers["scatter"] - checked_dictionary = {} if len(scatter_layers) > 0: - dc_hover = DataCollection() - for layer in scatter_layers: - layer_state = layer.state - if layer_state.visible and layer.enabled: - data = Data(label=layer_state.layer.label) - for component in layer_state.layer.components: - data[component.label] = np.ones(10) - dc_hover.append(data) - checked_dictionary[layer_state.layer.label] = np.zeros((len(layer_state.layer.components))).astype( - bool) - - dialog = save_hover.SaveHoverDialog(data_collection=dc_hover, checked_dictionary=checked_dictionary) + dc_hover = hover_data_collection_for_viewer( + self.viewer, + layer_condition=lambda layer: layer.state.visible \ + and layer.enabled + and layer in scatter_layers) + + dialog = SaveHoverDialog(data_collection=dc_hover) result = dialog.exec_() if result == QDialog.Rejected: return + checked_dictionary = dialog.checked_dictionary + else: + checked_dictionary = None filename, _ = compat.getsavefilename(parent=self.viewer, basedir="plot.html") if not filename: diff --git a/glue_plotly/html_exporters/qt/scatter2d.py b/glue_plotly/html_exporters/qt/scatter2d.py index 93a2c12..e7c3578 100644 --- a/glue_plotly/html_exporters/qt/scatter2d.py +++ b/glue_plotly/html_exporters/qt/scatter2d.py @@ -11,12 +11,13 @@ from glue_qt.core.dialogs import warn from glue_qt.utils import messagebox_on_error -from ... import save_hover from glue_plotly import PLOTLY_ERROR_MESSAGE, PLOTLY_LOGO from glue_plotly.common import data_count, layers_to_export from glue_plotly.common.scatter2d import polar_layout_config_from_mpl, rectilinear_layout_config, \ traces_for_layer +from glue_plotly.html_exporters.hover_utils import hover_data_collection_for_viewer +from glue_plotly.html_exporters.qt.save_hover import SaveHoverDialog from plotly.offline import plot import plotly.graph_objs as go @@ -37,16 +38,7 @@ class PlotlyScatter2DStaticExport(Tool): @messagebox_on_error(PLOTLY_ERROR_MESSAGE) def activate(self): - # grab hover info - dc_hover = DataCollection() - for layer in self.viewer.layers: - layer_state = layer.state - if layer_state.visible and layer.enabled: - data = Data(label=layer_state.layer.label) - for component in layer_state.layer.components: - data[component.label] = np.ones(10) - dc_hover.append(data) - + dc_hover = hover_data_collection_for_viewer(self.viewer) checked_dictionary = {} # figure out which hover info user wants to display @@ -55,7 +47,7 @@ def activate(self): if layer_state.visible and layer.enabled: checked_dictionary[layer_state.layer.label] = np.zeros((len(layer_state.layer.components))).astype(bool) - dialog = save_hover.SaveHoverDialog(data_collection=dc_hover, checked_dictionary=checked_dictionary) + dialog = SaveHoverDialog(data_collection=dc_hover, checked_dictionary=checked_dictionary) result = dialog.exec_() if result == QDialog.Rejected: return diff --git a/glue_plotly/html_exporters/qt/scatter3d.py b/glue_plotly/html_exporters/qt/scatter3d.py index facf678..fe8ecb4 100644 --- a/glue_plotly/html_exporters/qt/scatter3d.py +++ b/glue_plotly/html_exporters/qt/scatter3d.py @@ -16,7 +16,8 @@ from glue_plotly.common import data_count, layers_to_export from glue_plotly.common.base_3d import layout_config from glue_plotly.common.scatter3d import traces_for_layer -from ... import save_hover, export_dialog +from glue_plotly.html_exporters.hover_utils import hover_data_collection_for_viewer +from glue_plotly.html_exporters.qt.save_hover import SaveHoverDialog from plotly.offline import plot import plotly.graph_objs as go @@ -52,17 +53,7 @@ def _export_to_plotly(self, filename, checked_dictionary): def activate(self): - # grab hover info - dc_hover = DataCollection() - - for layer in self.viewer.layers: - layer_state = layer.state - if layer_state.visible and layer.enabled: - data = Data(label=layer_state.layer.label) - for component in layer_state.layer.components: - data[component.label] = np.ones(10) - dc_hover.append(data) - + dc_hover = hover_data_collection_for_viewer(self.viewer) checked_dictionary = {} # figure out which hover info user wants to display @@ -78,7 +69,7 @@ def activate(self): if not proceed: return - dialog = save_hover.SaveHoverDialog(data_collection=dc_hover, checked_dictionary=checked_dictionary) + dialog = SaveHoverDialog(data_collection=dc_hover, checked_dictionary=checked_dictionary) result = dialog.exec_() if result == QDialog.Rejected: return From 506d4d1f9cf89f499db8ab097f503b4b8dccc0e3 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Mon, 25 Nov 2024 02:30:12 -0500 Subject: [PATCH 05/11] Update styling of hover dialog. --- glue_plotly/html_exporters/jupyter/save_hover.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/glue_plotly/html_exporters/jupyter/save_hover.vue b/glue_plotly/html_exporters/jupyter/save_hover.vue index d208579..eeab1ff 100644 --- a/glue_plotly/html_exporters/jupyter/save_hover.vue +++ b/glue_plotly/html_exporters/jupyter/save_hover.vue @@ -30,16 +30,17 @@ - + Select None - Select All -

Once you click on "Export" you will be prompted to choose the filename. + +

Once you click on "Export" you will be prompted to choose the filename. + Cancel From 8b2a5054b42d368ecdd849e67a8b44f48a1bf079 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Mon, 25 Nov 2024 15:20:43 -0500 Subject: [PATCH 06/11] Use size for exporting data with ndim > 1. --- glue_plotly/common/scatter2d.py | 2 +- glue_plotly/common/scatter3d.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/glue_plotly/common/scatter2d.py b/glue_plotly/common/scatter2d.py index fcb740d..123bf42 100644 --- a/glue_plotly/common/scatter2d.py +++ b/glue_plotly/common/scatter2d.py @@ -304,7 +304,7 @@ def trace_data_for_layer(viewer, layer_state, hover_data=None, add_data_label=Tr hovertext = None else: hoverinfo = 'text' - hovertext = ["" for _ in range((mask.shape[0]))] + hovertext = ["" for _ in range(mask.size)] for i in range(len(layer_state.layer.components)): if hover_data[i]: label = layer_state.layer.components[i].label diff --git a/glue_plotly/common/scatter3d.py b/glue_plotly/common/scatter3d.py index d51cc16..873f4d5 100644 --- a/glue_plotly/common/scatter3d.py +++ b/glue_plotly/common/scatter3d.py @@ -127,7 +127,7 @@ def traces_for_layer(viewer_state, layer_state, hover_data=None, add_data_label= hovertext = None else: hoverinfo = 'text' - hovertext = ["" for _ in range((mask.shape[0]))] + hovertext = ["" for _ in range(mask.size)] for i in range(len(layer_state.layer.components)): if hover_data[i]: label = layer_state.layer.components[i].label From fb08f01328ddda0ba788230ef4d1f725e2785d69 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Tue, 26 Nov 2024 00:49:52 -0500 Subject: [PATCH 07/11] Make hover checked dictionary independent of component order. Codestyle fixes. --- glue_plotly/common/scatter2d.py | 9 +++---- glue_plotly/common/scatter3d.py | 5 ++-- glue_plotly/common/tests/test_scatter2d.py | 2 +- glue_plotly/html_exporters/base_save_hover.py | 4 +-- glue_plotly/html_exporters/hover_utils.py | 6 ++++- glue_plotly/html_exporters/jupyter/image.py | 8 +++--- .../html_exporters/jupyter/save_hover.py | 12 +++------ glue_plotly/html_exporters/qt/image.py | 7 +++-- glue_plotly/html_exporters/qt/save_hover.py | 27 ++++--------------- glue_plotly/html_exporters/qt/scatter2d.py | 6 ++--- glue_plotly/html_exporters/qt/scatter3d.py | 11 ++++---- 11 files changed, 35 insertions(+), 62 deletions(-) diff --git a/glue_plotly/common/scatter2d.py b/glue_plotly/common/scatter2d.py index 123bf42..f2d8f61 100644 --- a/glue_plotly/common/scatter2d.py +++ b/glue_plotly/common/scatter2d.py @@ -262,8 +262,6 @@ def base_marker(layer_state, mask=None): def trace_data_for_layer(viewer, layer_state, hover_data=None, add_data_label=True): traces = {} - if hover_data is None: - hover_data = [] x = layer_state.layer[viewer.state.x_att].copy() y = layer_state.layer[viewer.state.y_att].copy() @@ -299,15 +297,14 @@ def trace_data_for_layer(viewer, layer_state, hover_data=None, add_data_label=Tr if yerr_traces: traces['yerr'] = yerr_traces - if np.sum(hover_data) == 0: + if hover_data is None or np.sum(hover_data) == 0: hoverinfo = 'skip' hovertext = None else: hoverinfo = 'text' hovertext = ["" for _ in range(mask.size)] - for i in range(len(layer_state.layer.components)): - if hover_data[i]: - label = layer_state.layer.components[i].label + for label in layer_state.layer.components: + if hover_data[label]: hover_values = layer_state.layer[label][mask] for k in range(len(hover_values)): hovertext[k] = (hovertext[k] + "{}: {}
" diff --git a/glue_plotly/common/scatter3d.py b/glue_plotly/common/scatter3d.py index 873f4d5..efa1c6b 100644 --- a/glue_plotly/common/scatter3d.py +++ b/glue_plotly/common/scatter3d.py @@ -128,9 +128,8 @@ def traces_for_layer(viewer_state, layer_state, hover_data=None, add_data_label= else: hoverinfo = 'text' hovertext = ["" for _ in range(mask.size)] - for i in range(len(layer_state.layer.components)): - if hover_data[i]: - label = layer_state.layer.components[i].label + for label in layer_state.layer.components: + if hover_data[label]: hover_values = layer_state.layer[label][mask] for k in range(len(hover_values)): hovertext[k] = (hovertext[k] + "{}: {}
" diff --git a/glue_plotly/common/tests/test_scatter2d.py b/glue_plotly/common/tests/test_scatter2d.py index 95daf60..f8909a1 100644 --- a/glue_plotly/common/tests/test_scatter2d.py +++ b/glue_plotly/common/tests/test_scatter2d.py @@ -230,7 +230,7 @@ def test_rectilinear_traces(self): self.layer.state.yerr_visible = True hover_components = [self.data.id['x'], self.data.id['z']] - hover_data = [cid in hover_components for cid in self.layer.layer.components] + hover_data = {cid.label: cid in hover_components for cid in self.layer.layer.components} traces = trace_data_for_layer(self.viewer, self.layer.state, hover_data=hover_data, add_data_label=True) assert set(traces.keys()) == {'scatter', 'vector'} scatter = traces['scatter'][0] diff --git a/glue_plotly/html_exporters/base_save_hover.py b/glue_plotly/html_exporters/base_save_hover.py index 94f02ad..4710e33 100644 --- a/glue_plotly/html_exporters/base_save_hover.py +++ b/glue_plotly/html_exporters/base_save_hover.py @@ -58,12 +58,12 @@ def display_func(subset): class BaseSaveHoverDialog: def __init__(self, data_collection, checked_dictionary=None): - + if checked_dictionary is not None: self.checked_dictionary = checked_dictionary else: self.checked_dictionary = { - data.label: [False for _ in data.components] + data.label: {component.label: False for component in data.components} for data in data_collection } self.state = SaveHoverState(data_collection=data_collection) diff --git a/glue_plotly/html_exporters/hover_utils.py b/glue_plotly/html_exporters/hover_utils.py index fb29c96..ba04dba 100644 --- a/glue_plotly/html_exporters/hover_utils.py +++ b/glue_plotly/html_exporters/hover_utils.py @@ -18,9 +18,13 @@ def hover_dummy_data(data_or_subset): return dummy_data +def default_layer_condition(layer): + return layer.enabled and layer.state.visible + + def hover_data_collection_for_viewer(viewer, layer_condition=None): if layer_condition is None: - layer_condition = lambda layer: layer.enabled and layer.state.visible + layer_condition = default_layer_condition return DataCollection([hover_dummy_data(layer.layer) for layer in viewer.layers if layer_condition(layer)]) diff --git a/glue_plotly/html_exporters/jupyter/image.py b/glue_plotly/html_exporters/jupyter/image.py index 8997103..56e991f 100644 --- a/glue_plotly/html_exporters/jupyter/image.py +++ b/glue_plotly/html_exporters/jupyter/image.py @@ -28,10 +28,9 @@ def on_cancel(): if len(scatter_layers) > 0: dc_hover = hover_data_collection_for_viewer( - self.viewer, - layer_condition=lambda layer: layer.state.visible \ - and layer.enabled - and layer in scatter_layers) + self.viewer, + layer_condition=lambda layer: layer.state.visible and layer.enabled and layer in scatter_layers + ) dialog = \ JupyterSaveHoverDialog(data_collection=dc_hover, @@ -46,7 +45,6 @@ def on_cancel(): else: self.checked_dictionary = None - def save_figure(self, filepath): if not filepath: diff --git a/glue_plotly/html_exporters/jupyter/save_hover.py b/glue_plotly/html_exporters/jupyter/save_hover.py index c82a061..19eac3a 100644 --- a/glue_plotly/html_exporters/jupyter/save_hover.py +++ b/glue_plotly/html_exporters/jupyter/save_hover.py @@ -27,12 +27,6 @@ def __init__(self, BaseSaveHoverDialog.__init__(self, data_collection=data_collection, checked_dictionary=checked_dictionary) VuetifyTemplate.__init__(self) - if self.checked_dictionary is None: - self.checked_dictionary = { - data.label: [False for _ in data.components] - for data in data_collection - } - self.on_cancel = on_cancel self.on_export = on_export @@ -50,14 +44,16 @@ def _on_data_change(self, *args): {"text": component.label, "value": index} for index, component in enumerate(data_components) ] - current_selections = self.checked_dictionary.get(self.state.data.label, [False for _ in data_components]) + current_selections = self.checked_dictionary.get(self.state.data.label, + {component.label: False for component in data_components}) self.component_selected = [i for i in range(len(data_components)) if current_selections[i]] @observe('component_selected') def _on_component_selected_changed(self, change): selections = change["new"] current_layer = self.state.data.label - self.checked_dictionary[current_layer] = [i in selections for i in range(len(self.state.data.components))] + self.checked_dictionary[current_layer] = {component.label: i in selections + for i, component in enumerate(self.state.data.components)} def vue_select_none(self, *args): self.component_selected = [] diff --git a/glue_plotly/html_exporters/qt/image.py b/glue_plotly/html_exporters/qt/image.py index 26756c9..c5fe9d1 100644 --- a/glue_plotly/html_exporters/qt/image.py +++ b/glue_plotly/html_exporters/qt/image.py @@ -59,10 +59,9 @@ def activate(self): if len(scatter_layers) > 0: dc_hover = hover_data_collection_for_viewer( - self.viewer, - layer_condition=lambda layer: layer.state.visible \ - and layer.enabled - and layer in scatter_layers) + self.viewer, + layer_condition=lambda layer: layer.state.visible and layer.enabled and layer in scatter_layers + ) dialog = SaveHoverDialog(data_collection=dc_hover) result = dialog.exec_() diff --git a/glue_plotly/html_exporters/qt/save_hover.py b/glue_plotly/html_exporters/qt/save_hover.py index 6c83cd1..5e5d65a 100644 --- a/glue_plotly/html_exporters/qt/save_hover.py +++ b/glue_plotly/html_exporters/qt/save_hover.py @@ -8,8 +8,6 @@ from glue_qt.utils import load_ui -import numpy as np - from ..base_save_hover import BaseSaveHoverDialog __all__ = ['SaveHoverDialog'] @@ -42,7 +40,7 @@ def _on_component_change(self, *event): components = getattr(type(self.state), 'component').get_choices(self.state) self.ui.list_component.clear() - for (component, k) in zip(components, np.arange(0, len(components))): + for component in components: if isinstance(component, ChoiceSeparator): item = QListWidgetItem(str(component)) @@ -50,7 +48,7 @@ def _on_component_change(self, *event): item.setForeground(Qt.gray) else: item = QListWidgetItem(component.label) - if self.checked_dictionary[self.state.data.label][k]: + if self.checked_dictionary[self.state.data.label][component.label]: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) @@ -63,9 +61,10 @@ def _on_check_change(self, *event): any_checked = False for idx in range(self.ui.list_component.count()): item = self.ui.list_component.item(idx) + label = item.text() checked = item.checkState() == Qt.Checked any_checked = any_checked or checked - self.checked_dictionary[current_layer][idx] = checked + self.checked_dictionary[current_layer][label] = checked self.button_ok.setEnabled(any_checked) @@ -76,23 +75,7 @@ def select_all(self, *event): self._set_all_checked(True) def _set_all_checked(self, checked): - state = Qt.Checked if checked else Qt.Unchecked + state = Qt.Checked if checked else Qt.Unchecked for idx in range(self.ui.list_component.count()): item = self.ui.list_component.item(idx) item.setCheckState(state) - - def accept(self): - components = [] - for idx in range(self.ui.list_component.count()): - item = self.ui.list_component.item(idx) - if item.checkState() == Qt.Checked: - components.append(self.state.data.id[item.text()]) - - # if self.state.subset is None: - # data = self.state.data - # else: - # data = self.state.subset - - # return checked_dictionary - # export_data(data, components=components, exporter=self.state.exporter.function) - QDialog.accept(self) diff --git a/glue_plotly/html_exporters/qt/scatter2d.py b/glue_plotly/html_exporters/qt/scatter2d.py index e7c3578..b03b267 100644 --- a/glue_plotly/html_exporters/qt/scatter2d.py +++ b/glue_plotly/html_exporters/qt/scatter2d.py @@ -1,12 +1,9 @@ from __future__ import absolute_import, division, print_function -import numpy as np - from qtpy import compat from qtpy.QtWidgets import QDialog from glue.config import viewer_tool, settings -from glue.core import DataCollection, Data from glue.viewers.common.tool import Tool from glue_qt.core.dialogs import warn from glue_qt.utils import messagebox_on_error @@ -45,7 +42,8 @@ def activate(self): for layer in self.viewer.layers: layer_state = layer.state if layer_state.visible and layer.enabled: - checked_dictionary[layer_state.layer.label] = np.zeros((len(layer_state.layer.components))).astype(bool) + checked_dictionary[layer_state.layer.label] = {component.label: False + for component in layer_state.layer.components} dialog = SaveHoverDialog(data_collection=dc_hover, checked_dictionary=checked_dictionary) result = dialog.exec_() diff --git a/glue_plotly/html_exporters/qt/scatter3d.py b/glue_plotly/html_exporters/qt/scatter3d.py index fe8ecb4..7068d6d 100644 --- a/glue_plotly/html_exporters/qt/scatter3d.py +++ b/glue_plotly/html_exporters/qt/scatter3d.py @@ -1,12 +1,9 @@ from __future__ import absolute_import, division, print_function -import numpy as np - from qtpy import compat from qtpy.QtWidgets import QDialog from glue.config import viewer_tool, settings -from glue.core import DataCollection, Data from glue.viewers.common.tool import Tool from glue_qt.core.dialogs import warn from glue_qt.utils import messagebox_on_error @@ -17,6 +14,7 @@ from glue_plotly.common.base_3d import layout_config from glue_plotly.common.scatter3d import traces_for_layer from glue_plotly.html_exporters.hover_utils import hover_data_collection_for_viewer +from glue_plotly.export_dialog import ExportDialog from glue_plotly.html_exporters.qt.save_hover import SaveHoverDialog from plotly.offline import plot @@ -53,14 +51,15 @@ def _export_to_plotly(self, filename, checked_dictionary): def activate(self): - dc_hover = hover_data_collection_for_viewer(self.viewer) + dc_hover = hover_data_collection_for_viewer(self.viewer) checked_dictionary = {} # figure out which hover info user wants to display for layer in self.viewer.layers: layer_state = layer.state if layer_state.visible and layer.enabled: - checked_dictionary[layer_state.layer.label] = np.zeros((len(layer_state.layer.components))).astype(bool) + checked_dictionary[layer_state.layer.label] = {component.label: False + for component in layer_state.layer.components} proceed = warn('Scatter 3d plotly may look different', 'Plotly and Matlotlib graphics differ and your graph may look different when exported. Do you ' @@ -81,7 +80,7 @@ def activate(self): return worker = Worker(self._export_to_plotly, filename, checked_dictionary) - exp_dialog = export_dialog.ExportDialog(parent=self.viewer) + exp_dialog = ExportDialog(parent=self.viewer) worker.result.connect(exp_dialog.close) worker.error.connect(exp_dialog.close) worker.start() From ee70a59b9b05d0f9d97be25c91bd11b9425c9182 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Tue, 26 Nov 2024 01:38:44 -0500 Subject: [PATCH 08/11] Fix up a few issues with hover data. --- glue_plotly/common/image.py | 11 +++++------ glue_plotly/common/scatter2d.py | 5 +++-- glue_plotly/common/scatter3d.py | 5 +++-- glue_plotly/html_exporters/jupyter/save_hover.py | 3 ++- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/glue_plotly/common/image.py b/glue_plotly/common/image.py index 75f3412..9cd1aab 100644 --- a/glue_plotly/common/image.py +++ b/glue_plotly/common/image.py @@ -317,20 +317,19 @@ def traces_for_scatter_layer(viewer_state, layer_state, hover_data=None, add_dat size=scatter_size_info(layer_state, mask), sizemin=1) - if np.sum(hover_data) == 0: + if hover_data is None or np.sum(hover_data) == 0: hoverinfo = 'skip' hovertext = None else: hoverinfo = 'text' hovertext = ['' for _ in range(layer_state.layer.shape[0])] - for i in range(len(layer_state.layer.components)): - if hover_data[i]: - label = layer_state.layer.components[i].label + for component in layer_state.layer.components: + label = component.label + if hover_data.get(label, False): hover_values = layer_state.layer[label][mask] for k in range(len(hover_values)): hovertext[k] = (hovertext[k] + '{}: {}
' - .format(layer_state.layer.components[i].label, - hover_values[k])) + .format(label, hover_values[k])) name = layer_state.layer.label if add_data_label and not isinstance(layer_state.layer, BaseData): diff --git a/glue_plotly/common/scatter2d.py b/glue_plotly/common/scatter2d.py index f2d8f61..865cc7e 100644 --- a/glue_plotly/common/scatter2d.py +++ b/glue_plotly/common/scatter2d.py @@ -303,8 +303,9 @@ def trace_data_for_layer(viewer, layer_state, hover_data=None, add_data_label=Tr else: hoverinfo = 'text' hovertext = ["" for _ in range(mask.size)] - for label in layer_state.layer.components: - if hover_data[label]: + for component in layer_state.layer.components: + label = component.label + if hover_data.get(label, False): hover_values = layer_state.layer[label][mask] for k in range(len(hover_values)): hovertext[k] = (hovertext[k] + "{}: {}
" diff --git a/glue_plotly/common/scatter3d.py b/glue_plotly/common/scatter3d.py index efa1c6b..7bbe6c0 100644 --- a/glue_plotly/common/scatter3d.py +++ b/glue_plotly/common/scatter3d.py @@ -128,8 +128,9 @@ def traces_for_layer(viewer_state, layer_state, hover_data=None, add_data_label= else: hoverinfo = 'text' hovertext = ["" for _ in range(mask.size)] - for label in layer_state.layer.components: - if hover_data[label]: + for component in layer_state.layer.components: + label = component.label + if hover_data.get(label, False): hover_values = layer_state.layer[label][mask] for k in range(len(hover_values)): hovertext[k] = (hovertext[k] + "{}: {}
" diff --git a/glue_plotly/html_exporters/jupyter/save_hover.py b/glue_plotly/html_exporters/jupyter/save_hover.py index 19eac3a..3d30a2c 100644 --- a/glue_plotly/html_exporters/jupyter/save_hover.py +++ b/glue_plotly/html_exporters/jupyter/save_hover.py @@ -46,7 +46,8 @@ def _on_data_change(self, *args): ] current_selections = self.checked_dictionary.get(self.state.data.label, {component.label: False for component in data_components}) - self.component_selected = [i for i in range(len(data_components)) if current_selections[i]] + self.component_selected = [i for i, component in enumerate(data_components) + if current_selections[component.label]] @observe('component_selected') def _on_component_selected_changed(self, change): From 8ca9e7e809421ab302b342e1620e072217d0cfdd Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Wed, 27 Nov 2024 09:30:05 -0500 Subject: [PATCH 09/11] Update import location in Jupyter base exporter test. --- glue_plotly/html_exporters/qt/tests/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glue_plotly/html_exporters/qt/tests/test_base.py b/glue_plotly/html_exporters/qt/tests/test_base.py index 734ce4c..f721b9b 100644 --- a/glue_plotly/html_exporters/qt/tests/test_base.py +++ b/glue_plotly/html_exporters/qt/tests/test_base.py @@ -1,7 +1,7 @@ from mock import patch from glue_qt.app import GlueApplication -from glue_plotly.save_hover import SaveHoverDialog +from glue_plotly.html_exporters.qt.save_hover import SaveHoverDialog from glue_plotly.sort_components import SortComponentsDialog from qtpy.QtWidgets import QMessageBox From cff540c0c56d332d2db0b1b48bdabea1aa181d5b Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Wed, 27 Nov 2024 09:30:25 -0500 Subject: [PATCH 10/11] Fix small issue with Jupyter image exporter. --- glue_plotly/html_exporters/jupyter/image.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/glue_plotly/html_exporters/jupyter/image.py b/glue_plotly/html_exporters/jupyter/image.py index 56e991f..c76fbd2 100644 --- a/glue_plotly/html_exporters/jupyter/image.py +++ b/glue_plotly/html_exporters/jupyter/image.py @@ -32,16 +32,15 @@ def on_cancel(): layer_condition=lambda layer: layer.state.visible and layer.enabled and layer in scatter_layers ) - dialog = \ + self.save_hover_dialog = \ JupyterSaveHoverDialog(data_collection=dc_hover, checked_dictionary=None, display=True, on_cancel=on_cancel, on_export=self.open_file_dialog) - self.checked_dictionary = dialog.checked_dictionary with self.viewer.output_widget: - display(dialog) + display(self.save_hover_dialog) else: self.checked_dictionary = None @@ -71,7 +70,7 @@ def save_figure(self, filepath): traces_to_add = traces(self.viewer, secondary_x=secondary_x, secondary_y=secondary_y, - hover_selections=self.checked_dictionary, + hover_selections=self.save_hover_dialog.checked_dictionary, add_data_label=add_data_label) fig.add_traces(traces_to_add) From b411788d1f727c179e572c87422a80a02374e388 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Wed, 27 Nov 2024 09:48:54 -0500 Subject: [PATCH 11/11] Update logic for fetching layer hover data. --- glue_plotly/common/image.py | 3 ++- glue_plotly/html_exporters/jupyter/image.py | 5 ++--- glue_plotly/html_exporters/jupyter/scatter2d.py | 4 +++- glue_plotly/html_exporters/jupyter/scatter3d.py | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/glue_plotly/common/image.py b/glue_plotly/common/image.py index 9cd1aab..01e9fa3 100644 --- a/glue_plotly/common/image.py +++ b/glue_plotly/common/image.py @@ -417,8 +417,9 @@ def traces(viewer, secondary_x=False, secondary_y=False, hover_selections=None, traces += traces_for_nonpixel_subset_layer(viewer.state, layer.state, full_view, transpose) for layer in layers['scatter']: + hover_data = hover_selections[layer.state.layer.label] if hover_selections else None traces += traces_for_scatter_layer(viewer.state, layer.state, - hover_data=hover_selections[layer.state.layer.label], + hover_data=hover_data, add_data_label=add_data_label) if secondary_x or secondary_y: diff --git a/glue_plotly/html_exporters/jupyter/image.py b/glue_plotly/html_exporters/jupyter/image.py index c76fbd2..96bd23e 100644 --- a/glue_plotly/html_exporters/jupyter/image.py +++ b/glue_plotly/html_exporters/jupyter/image.py @@ -41,8 +41,6 @@ def on_cancel(): with self.viewer.output_widget: display(self.save_hover_dialog) - else: - self.checked_dictionary = None def save_figure(self, filepath): @@ -67,10 +65,11 @@ def save_figure(self, filepath): layout = go.Layout(**config) fig = go.Figure(layout=layout) + checked_dictionary = self.save_hover_dialog.checked_dictionary if hasattr(self, 'save_hover_dialog') else None traces_to_add = traces(self.viewer, secondary_x=secondary_x, secondary_y=secondary_y, - hover_selections=self.save_hover_dialog.checked_dictionary, + hover_selections=checked_dictionary, add_data_label=add_data_label) fig.add_traces(traces_to_add) diff --git a/glue_plotly/html_exporters/jupyter/scatter2d.py b/glue_plotly/html_exporters/jupyter/scatter2d.py index 933dede..236b8bb 100644 --- a/glue_plotly/html_exporters/jupyter/scatter2d.py +++ b/glue_plotly/html_exporters/jupyter/scatter2d.py @@ -44,10 +44,12 @@ def save_figure(self, filepath): layers = layers_to_export(self.viewer) add_data_label = data_count(layers) > 1 + checked_dictionary = self.save_hover_dialog.checked_dictionary if hasattr(self, 'save_hover_dialog') else None for layer in layers: + hover_data = checked_dictionary[layer.layer.label] if checked_dictionary is not None else None traces = traces_for_layer(self.viewer, layer.state, - hover_data=self.save_hover_dialog.checked_dictionary[layer.state.layer.label], + hover_data=hover_data, add_data_label=add_data_label) fig.add_traces(traces) diff --git a/glue_plotly/html_exporters/jupyter/scatter3d.py b/glue_plotly/html_exporters/jupyter/scatter3d.py index fb4b1d4..581eb40 100644 --- a/glue_plotly/html_exporters/jupyter/scatter3d.py +++ b/glue_plotly/html_exporters/jupyter/scatter3d.py @@ -44,10 +44,12 @@ def save_figure(self, filepath): layers = layers_to_export(self.viewer) add_data_label = data_count(layers) > 1 + checked_dictionary = self.save_hover_dialog.checked_dictionary if hasattr(self, 'save_hover_dialog') else None for layer in layers: + hover_data = checked_dictionary[layer.layer.label] if checked_dictionary is not None else None traces = traces_for_layer(self.viewer.state, layer.state, - hover_data=self.save_hover_dialog.checked_dictionary[layer.state.layer.label], + hover_data=hover_data, add_data_label=add_data_label) for trace in traces: fig.add_trace(trace)