Source code for plopp.widgets.tools
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
from collections.abc import Callable
from functools import partial
import ipywidgets as ipw
from .style import BUTTON_LAYOUT
[docs]
class ButtonTool(ipw.Button):
"""
Create a button with a callback that is called when the button is clicked.
Parameters
----------
callback:
The function that will be called when the button is clicked.
**kwargs:
All other kwargs are forwarded to ipywidgets.Button.
"""
[docs]
def __init__(self, callback: Callable | None = None, **kwargs):
super().__init__(**{**BUTTON_LAYOUT, **kwargs})
self.callback = callback
self.on_click(self)
def __call__(self, *ignored):
self.callback()
[docs]
class ToggleTool(ipw.ToggleButton):
"""
Create a toggle button with a callback that is called when the button is
toggled.
Parameters
----------
callback:
The function that will be called when the button is toggled.
**kwargs:
All other kwargs are forwarded to ipywidgets.ToggleButton.
"""
[docs]
def __init__(self, callback: Callable, **kwargs):
super().__init__(**{**BUTTON_LAYOUT, **kwargs})
self.callback = callback
self.observe(self, names='value')
def __call__(self, *ignored):
self.callback()
class PlusMinusTool(ipw.HBox):
def __init__(self, plus, minus):
layout = {'width': '16px', 'padding': '0px'}
self.callback = {'plus': plus.pop('callback'), 'minus': minus.pop('callback')}
self._plus = ipw.Button(icon='plus', **{**{'layout': layout}, **plus})
self._minus = ipw.Button(icon='minus', **{**{'layout': layout}, **minus})
self._plus.on_click(self.plus)
self._minus.on_click(self.minus)
super().__init__([self._minus, self._plus])
def plus(self, *ignored):
self.callback['plus']()
def minus(self, *ignored):
self.callback['minus']()
@property
def disabled(self):
return self._plus.disabled
@disabled.setter
def disabled(self, value):
self._plus.disabled = value
self._minus.disabled = value
[docs]
class MultiToggleTool(ipw.VBox):
"""
Create toggle buttons with a callback that is called when one of the buttons is
toggled. In addition to ipywidgets ToggleButtons, when you click the button
which is already selected, it resets the value to `None` (no button selected).
Parameters
----------
callback:
The function that will be called when the one of the buttons is toggled.
options:
A list of values for the different buttons (one button per value will be
created).
icons:
A list of icons (one icon per button).
tooltips:
A list of tooltips (one tooltip per button).
descriptions:
A list of descriptions (one description per button).
value:
If given, the value of the button with the corresponding option will be set to
True.
**kwargs:
All other kwargs are forwarded to ipywidgets.ToggleButton.
"""
[docs]
def __init__(
self,
callback: Callable,
options: list[str],
icons: list[str] | None = None,
tooltips: list[str] | None = None,
descriptions: list[str] | None = None,
value: str | None = None,
**kwargs,
):
self.callback = callback
self._options = options
self._buttons = {}
for i, key in enumerate(self._options):
tb = ipw.ToggleButton(
icon=icons[i] if icons is not None else None,
tooltip=tooltips[i] if tooltips is not None else None,
description=descriptions[i] if descriptions is not None else '',
value=key == value,
**{**BUTTON_LAYOUT, **kwargs},
)
tb._option = key
tb.observe(self, names='value')
self._buttons[key] = tb
self._lock = False
super().__init__(list(self._buttons.values()))
def __call__(self, change: dict):
if self._lock:
return
key = change['owner']._option
self._lock = True
for name in set(self._buttons.keys()) - {key}:
if self._buttons[name].value:
self._buttons[name].value = False
self._lock = False
self.callback()
@property
def value(self) -> str | None:
for b in self._buttons.values():
if b.value:
return b._option
HomeTool = partial(ButtonTool, icon='home')
"""Return home tool."""
AutoscaleTool = partial(
ButtonTool, icon='arrows-alt-v', tooltip='Autoscale colorbar range'
)
"""Autoscale the colorbar to fit the data."""
LogxTool = partial(ToggleTool, description='logx', tooltip='Toggle X axis scale')
"""Toggle horizontal axis scale tool."""
LogyTool = partial(ToggleTool, description='logy', tooltip='Toggle Y axis scale')
"""Toggle vertical axis scale tool."""
LogNormTool = partial(
ToggleTool, description='log', tooltip='Toggle colorscale normalization'
)
"""Toggle normalization scale tool."""
SaveTool = partial(ButtonTool, icon='save', tooltip='Save figure')
"""Save figure to png tool."""
CameraTool = partial(ButtonTool, icon='camera')
"""Tool for changing the position of the camera in a 3d scene."""
OutlineTool = partial(
ToggleTool, value=True, icon='cube', tooltip='Toggle outline visibility'
)
"""Toggle outline visibility tool"""
AxesTool = partial(
ToggleTool,
value=True,
icon='ruler-combined',
style={'font_weight': 'bold'},
tooltip='Toggle visibility of XYZ axes',
)
"""Toggle RGB axes helper visibility tool"""
[docs]
class PanZoomTool(MultiToggleTool):
"""
Tool to control the panning and zooming actions on a Matplotlib figure.
Parameters
----------
canvas:
The canvas to act upon.
value:
Set the initially selected button. No button selected if ``None``.
"""
[docs]
def __init__(self, callback: Callable, value: bool | None = None, **kwargs):
self._callback = callback
super().__init__(
callback=self._panzoom,
options=['pan', 'zoom'],
icons=['arrows', 'search-plus'],
tooltips=['Pan', 'Zoom'],
value=value,
**kwargs,
)
def _panzoom(self):
self._callback(self.value)
[docs]
class ColorTool(ipw.HBox):
"""
Tool for saving lines and controlling their color.
It also comes with a close button to remove a line.
Parameters
----------
text:
Text label for the tool.
color:
Initial color.
"""
[docs]
def __init__(self, text: str, color: str):
layout = ipw.Layout(display="flex", justify_content="flex-end", width='150px')
self.text = ipw.Label(value=text, layout=layout)
self.color = ipw.ColorPicker(
concise=True, value=color, description='', layout={'width': "30px"}
)
self.button = ipw.Button(icon='times', **BUTTON_LAYOUT)
super().__init__([self.text, self.color, self.button])