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])