Skip to content

Commit

Permalink
Merge pull request #107 from Carifio24/jupyter-export-dialogs
Browse files Browse the repository at this point in the history
Add save hover dialog for Jupyter exporters
  • Loading branch information
Carifio24 authored Nov 27, 2024
2 parents 3cefb1e + b411788 commit 8960dda
Show file tree
Hide file tree
Showing 19 changed files with 454 additions and 245 deletions.
14 changes: 7 additions & 7 deletions glue_plotly/common/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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] + '{}: {} <br>'
.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):
Expand Down Expand Up @@ -418,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:
Expand Down
12 changes: 5 additions & 7 deletions glue_plotly/common/scatter2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -299,15 +297,15 @@ 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.shape[0]))]
for i in range(len(layer_state.layer.components)):
if hover_data[i]:
label = layer_state.layer.components[i].label
hovertext = ["" for _ in range(mask.size)]
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] + "{}: {} <br>"
Expand Down
8 changes: 4 additions & 4 deletions glue_plotly/common/scatter3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,10 @@ 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]))]
for i in range(len(layer_state.layer.components)):
if hover_data[i]:
label = layer_state.layer.components[i].label
hovertext = ["" for _ in range(mask.size)]
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] + "{}: {} <br>"
Expand Down
2 changes: 1 addition & 1 deletion glue_plotly/common/tests/test_scatter2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
78 changes: 78 additions & 0 deletions glue_plotly/html_exporters/base_save_hover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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, checked_dictionary=None):

if checked_dictionary is not None:
self.checked_dictionary = checked_dictionary
else:
self.checked_dictionary = {
data.label: {component.label: False for component in data.components}
for data in data_collection
}
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
30 changes: 30 additions & 0 deletions glue_plotly/html_exporters/hover_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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 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 = default_layer_condition

return DataCollection([hover_dummy_data(layer.layer) for layer in viewer.layers if layer_condition(layer)])
38 changes: 34 additions & 4 deletions glue_plotly/html_exporters/jupyter/image.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
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
)

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:
Expand All @@ -37,9 +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=checked_dictionary,
add_data_label=add_data_label)
fig.add_traces(traces_to_add)

Expand Down
74 changes: 74 additions & 0 deletions glue_plotly/html_exporters/jupyter/save_hover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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,
checked_dictionary=None,
on_cancel=None,
on_export=None,
display=False):

BaseSaveHoverDialog.__init__(self, data_collection=data_collection, checked_dictionary=checked_dictionary)
VuetifyTemplate.__init__(self)

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.components
self.component_items = [
{"text": component.label, "value": index}
for index, component in enumerate(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, component in enumerate(data_components)
if current_selections[component.label]]

@observe('component_selected')
def _on_component_selected_changed(self, change):
selections = change["new"]
current_layer = self.state.data.label
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 = []

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()
52 changes: 52 additions & 0 deletions glue_plotly/html_exporters/jupyter/save_hover.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<v-dialog
v-model="dialog_open"
width="600px"
>
<v-card class="pa-3">
<v-card-title>
Save Hover
</v-card-title>

<p>Choose the layer you want to add hover info to:</p>
<v-select
v-model="data_selected"
:items="data_items"
>
</v-select>

<p>Choose component of the layer you want to appear in hover over:</p>
<v-list>
<v-list-item-group
v-model="component_selected"
multiple
>
<v-list-item
v-for="(component, index) in component_items"
:key="index"
:value="index"
>
{{ component.text }}
</v-list-item>
</v-list-item-group>
</v-list>
<v-row class="d-flex justify-space-around">
<v-btn @click="select_none">
Select None
</v-btn>
<v-btn @click="select_all">
Select All
</v-btn>
</v-row>
<v-row class="py-3">
<p>Once you click on "Export" you will be prompted to choose the filename.
</v-row>
<v-row>
<v-spacer></v-spacer>
<v-btn @click="cancel_dialog">Cancel</v-btn>
<v-btn @click="export_viewer">Export</v-btn>
</v-row>

</v-card>
<v-dialog>
</template>
Loading

0 comments on commit 8960dda

Please sign in to comment.