Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add hover and horizontal/vertical zoom tools #56

Merged
merged 8 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions glue_plotly/common/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def base_rectilinear_axis(viewer_state, axis):
ticks='outside',
showline=True,
showgrid=False,
fixedrange=True,
showticklabels=True,
tickfont=dict(
family=DEFAULT_FONT,
Expand Down
Empty file.
104 changes: 104 additions & 0 deletions glue_plotly/viewers/common/tests/test_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from glue_jupyter import jglue

from glue_plotly.viewers.common.tests.utils import ExampleViewer


class TestTools(object):

def setup_method(self, method):
self.app = jglue()
self.viewer = self.app.new_data_viewer(ExampleViewer)

def teardown_method(self, method):
pass

def get_tool(self, id):
return self.viewer.toolbar.tools[id]

def test_rectzoom(self):
tool = self.get_tool('plotly:zoom')
tool.activate()
assert self.viewer.figure.layout['selectdirection'] == 'any'
assert self.viewer.figure.layout['dragmode'] == 'select'
tool.deactivate()
assert self.viewer.figure.layout['dragmode'] is False

def test_hzoom(self):
tool = self.get_tool('plotly:hzoom')
tool.activate()
assert self.viewer.figure.layout['selectdirection'] == 'h'
assert self.viewer.figure.layout['dragmode'] == 'select'
tool.deactivate()
assert self.viewer.figure.layout['dragmode'] is False

def test_vzoom(self):
tool = self.get_tool('plotly:vzoom')
tool.activate()
assert self.viewer.figure.layout['selectdirection'] == 'v'
assert self.viewer.figure.layout['dragmode'] == 'select'
tool.deactivate()
assert self.viewer.figure.layout['dragmode'] is False

def test_pan(self):
tool = self.get_tool('plotly:pan')
tool.activate()
assert self.viewer.figure.layout['dragmode'] == 'pan'
tool.deactivate()
assert self.viewer.figure.layout['dragmode'] is False

def test_hrange_select(self):
tool = self.get_tool('plotly:xrange')
tool.activate()
assert self.viewer.figure.layout['selectdirection'] == 'h'
assert self.viewer.figure.layout['dragmode'] == 'select'
tool.deactivate()
assert self.viewer.figure.layout['dragmode'] is False

def test_vrange_select(self):
tool = self.get_tool('plotly:yrange')
tool.activate()
assert self.viewer.figure.layout['selectdirection'] == 'v'
assert self.viewer.figure.layout['dragmode'] == 'select'
tool.deactivate()
assert self.viewer.figure.layout['dragmode'] is False

def test_rect_select(self):
tool = self.get_tool('plotly:rectangle')
tool.activate()
assert self.viewer.figure.layout['selectdirection'] == 'any'
assert self.viewer.figure.layout['dragmode'] == 'select'
tool.deactivate()
assert self.viewer.figure.layout['dragmode'] is False

def test_lasso_select(self):
tool = self.get_tool('plotly:lasso')
tool.activate()
assert self.viewer.figure.layout['dragmode'] == 'lasso'
tool.deactivate()
assert self.viewer.figure.layout['dragmode'] is False

def test_home(self):
xmin, xmax = self.viewer.state.x_min, self.viewer.state.x_max
ymin, ymax = self.viewer.state.y_min, self.viewer.state.y_max
self.viewer.state.x_min = 10
self.viewer.state.x_max = 27
self.viewer.state.y_min = -5
self.viewer.state.y_max = 13
tool = self.get_tool('plotly:home')
tool.activate()
print(self.viewer.state)
assert self.viewer.state.x_min == xmin
assert self.viewer.state.x_max == xmax
assert self.viewer.state.y_min == ymin
assert self.viewer.state.y_max == ymax
xaxis = self.viewer.figure.layout.xaxis
assert xaxis.range == (xmin, xmax)
yaxis = self.viewer.figure.layout.yaxis
assert yaxis.range == (ymin, ymax)

def test_hover(self):
tool = self.get_tool('plotly:hover')
tool.activate()
assert self.viewer.figure.layout['hovermode'] == 'closest'
tool.deactivate()
assert self.viewer.figure.layout['hovermode'] is False
43 changes: 43 additions & 0 deletions glue_plotly/viewers/common/tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from echo import CallbackProperty
from glue.viewers.common.state import ViewerState
from ipywidgets import VBox

from glue_plotly.viewers.common import PlotlyBaseView


class ExampleState(ViewerState):
x_axislabel = CallbackProperty("")
y_axislabel = CallbackProperty("")
x_min = CallbackProperty(0)
x_max = CallbackProperty(1)
y_min = CallbackProperty(0)
y_max = CallbackProperty(1)
show_axes = CallbackProperty(True)

def reset_limits(self):
self.x_min = 0
self.x_max = 1
self.y_min = 0
self.y_max = 1


class ExampleOptions(VBox):

def __init__(self, viewer_state):

self.state = viewer_state
super().__init__([])


class ExampleViewer(PlotlyBaseView):

_state_cls = ExampleState
_options_cls = ExampleOptions

tools = ['plotly:zoom', 'plotly:hzoom', 'plotly:vzoom',
'plotly:pan', 'plotly:xrange', 'plotly:yrange',
'plotly:rectangle', 'plotly:lasso', 'plotly:home',
'plotly:hover']

def __init__(self, session, state=None):
super().__init__(session, state)
72 changes: 72 additions & 0 deletions glue_plotly/viewers/common/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
self.viewer.set_selection_callback(None)
self.viewer.set_selection_active(False)
self.viewer.figure.on_edits_completed(self._clear_selection)
super().deactivate()

def _clear_selection(self):
self.viewer.figure.plotly_relayout({'selections': [], 'dragmode': False})
Expand Down Expand Up @@ -73,6 +74,52 @@
viewer_state.y_max = ymax


@viewer_tool
class PlotlyHZoomMode(PlotlySelectionMode):

icon = 'glue_zoom_to_rect'
tool_id = 'plotly:hzoom'
action_text = 'Horizontal zoom'
tool_tip = 'Horizontal zoom'

def __init__(self, viewer):
super().__init__(viewer, 'select')

def activate(self):
super().activate()
self.viewer.figure.update_layout(selectdirection="h")

def _on_selection(self, _trace, _points, selector):
xmin, xmax = selector.xrange
viewer_state = self.viewer.state
with self.viewer.figure.batch_update(), delay_callback(viewer_state, 'x_min', 'x_max'):
viewer_state.x_min = xmin
viewer_state.x_max = xmax

Check warning on line 97 in glue_plotly/viewers/common/tools.py

View check run for this annotation

Codecov / codecov/patch

glue_plotly/viewers/common/tools.py#L93-L97

Added lines #L93 - L97 were not covered by tests


@viewer_tool
class PlotlyVZoomMode(PlotlySelectionMode):

icon = 'glue_zoom_to_rect'
tool_id = 'plotly:vzoom'
action_text = 'Vertical zoom'
tool_tip = 'Vertical zoom'

def __init__(self, viewer):
super().__init__(viewer, 'select')

def activate(self):
super().activate()
self.viewer.figure.update_layout(selectdirection="v")

def _on_selection(self, _trace, _points, selector):
ymin, ymax = selector.yrange
viewer_state = self.viewer.state
with self.viewer.figure.batch_update(), delay_callback(viewer_state, 'y_min', 'y_max'):
viewer_state.y_min = ymin
viewer_state.y_max = ymax

Check warning on line 120 in glue_plotly/viewers/common/tools.py

View check run for this annotation

Codecov / codecov/patch

glue_plotly/viewers/common/tools.py#L116-L120

Added lines #L116 - L120 were not covered by tests


@viewer_tool
class PlotlyPanMode(PlotlyDragMode):

Expand All @@ -84,6 +131,16 @@
def __init__(self, viewer):
super().__init__(viewer, 'pan')

def activate(self):
super().activate()
self.viewer.figure.layout['xaxis']['fixedrange'] = False
self.viewer.figure.layout['yaxis']['fixedrange'] = False

def deactivate(self):
self.viewer.figure.layout['xaxis']['fixedrange'] = True
self.viewer.figure.layout['yaxis']['fixedrange'] = True
super().deactivate()


@viewer_tool
class PlotlyHRangeSelectionMode(PlotlySelectionMode):
Expand Down Expand Up @@ -180,3 +237,18 @@
def activate(self):
with self.viewer.figure.batch_update():
self.viewer.state.reset_limits()


@viewer_tool
class PlotlyHoverTool(CheckableTool):

icon = 'glue_point'
tool_id = 'plotly:hover'
action_text = 'Hover'
tool_tip = 'Show hover info'

def activate(self):
self.viewer.figure.update_layout(hovermode="closest")

def deactivate(self):
self.viewer.figure.update_layout(hovermode=False)
2 changes: 1 addition & 1 deletion glue_plotly/viewers/common/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class PlotlyBaseView(IPyWidgetView):

LAYOUT_SETTINGS = dict(
include_dimensions=False,
hovermode=False, hoverdistance=1,
dragmode=False, showlegend=False, grid=None,
newselection=dict(line=dict(color=INTERACT_COLOR), mode='immediate'),
modebar=dict(remove=['toimage', 'zoom', 'pan', 'lasso', 'zoomIn2d',
Expand Down Expand Up @@ -84,7 +85,6 @@ def _remove_traces(self, traces):
self.figure.data = [t for t in self.figure.data if t not in traces]

def _clear_traces(self):
print("In _clear_traces")
self.figure.data = [self.selection_layer]

@property
Expand Down
2 changes: 1 addition & 1 deletion glue_plotly/viewers/histogram/layer_artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def _update_data(self):

bars = traces_for_layer(self.view.state, self.state, add_data_label=True)
for bar in bars:
bar.update(unselected=dict(marker=dict(opacity=self.state.alpha)))
bar.update(hoverinfo='all', unselected=dict(marker=dict(opacity=self.state.alpha)))
self._bars_id = bars[0].meta if bars else None
self.view.figure.add_traces(bars)

Expand Down
2 changes: 1 addition & 1 deletion glue_plotly/viewers/histogram/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
@viewer_registry("plotly_histogram")
class PlotlyHistogramView(PlotlyBaseView):

tools = ['plotly:home', 'plotly:zoom', 'plotly:pan', 'plotly:xrange']
tools = ['plotly:home', 'plotly:zoom', 'plotly:pan', 'plotly:xrange', 'plotly:hover']

allow_duplicate_data = False
allow_duplicate_subset = False
Expand Down
2 changes: 1 addition & 1 deletion glue_plotly/viewers/scatter/layer_artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def _create_scatter(self):

scatter_info = dict(mode=scatter_mode(self.state),
name=name,
hoverinfo='skip',
hoverinfo='all',
unselected=dict(marker=dict(opacity=self.state.alpha)),
meta=self._scatter_id)
if self._viewer_state.using_rectilinear:
Expand Down
2 changes: 1 addition & 1 deletion glue_plotly/viewers/scatter/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
class PlotlyScatterView(PlotlyBaseView):

tools = ['plotly:home', 'plotly:zoom', 'plotly:pan', 'plotly:xrange',
'plotly:yrange', 'plotly:rectangle', 'plotly:lasso']
'plotly:yrange', 'plotly:rectangle', 'plotly:lasso', 'plotly:hover']

allow_duplicate_data = False
allow_duplicate_subset = False
Expand Down
Loading