Skip to content

Commit

Permalink
Merge pull request #20 from scipp/inherit_from_widget
Browse files Browse the repository at this point in the history
Inherit from widget to make toolbar, figures and widgets embedabble inside other widgets
  • Loading branch information
nvaytet authored Sep 20, 2022
2 parents c65017a + 9a161ab commit c554938
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 211 deletions.
4 changes: 1 addition & 3 deletions src/plopp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
plt.ioff()

from .graph import show_graph
from .plot import Plot
from .model import Node, node, input_node
from .model import Node, node, input_node, widget_node
from .wrappers import plot, figure, slicer

from . import data
from . import widgets


def patch_scipp():
Expand Down
25 changes: 0 additions & 25 deletions src/plopp/displayable.py

This file was deleted.

41 changes: 17 additions & 24 deletions src/plopp/interactive.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2022 Scipp contributors (https://github.com/scipp)

from .displayable import Displayable
from .figure import Figure
from .toolbar import Toolbar

import ipywidgets as ipw
from ipywidgets import VBox, HBox


def _running_in_jupyter() -> bool:
Expand Down Expand Up @@ -40,23 +39,29 @@ def _is_sphinx_build():
return meta.get("scipp_sphinx_build", False)


class SideBar(list, Displayable):
class InteractiveFig(Figure, VBox):

def to_widget(self):
return ipw.VBox([child.to_widget() for child in self])
def __init__(self, *args, **kwargs):


class InteractiveFig(Figure, Displayable):
Figure.__init__(self, *args, **kwargs)
VBox.__init__(self, [
self.top_bar,
HBox([
self.left_bar,
self._to_image() if _is_sphinx_build() else self._fig.canvas,
self.right_bar
]), self.bottom_bar
])

def _post_init(self):

self._fig.canvas.toolbar_visible = False
self._fig.canvas.header_visible = False

self.left_bar = SideBar()
self.right_bar = SideBar()
self.bottom_bar = SideBar()
self.top_bar = SideBar()
self.left_bar = VBox()
self.right_bar = VBox()
self.bottom_bar = HBox()
self.top_bar = HBox()

self.toolbar = Toolbar(
tools={
Expand All @@ -69,19 +74,7 @@ def _post_init(self):
})
self._fig.canvas.toolbar_visible = False
self._fig.canvas.header_visible = False
self.left_bar.append(self.toolbar)

def to_widget(self) -> ipw.Widget:
"""
Convert the Matplotlib figure to a widget.
"""
canvas = self._to_image() if _is_sphinx_build() else self._fig.canvas
return ipw.VBox([
self.top_bar.to_widget(),
ipw.HBox([self.left_bar.to_widget(), canvas,
self.right_bar.to_widget()]),
self.bottom_bar.to_widget()
])
self.left_bar.children = tuple([self.toolbar])

def home(self):
self._autoscale()
Expand Down
11 changes: 11 additions & 0 deletions src/plopp/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,14 @@ def make_node(*args, **kwargs):

def input_node(obj):
return Node(lambda: obj)


def widget_node(widget):
n = Node(func=lambda: widget.value)
# TODO: Our custom widgets have a '_plopp_observe' method instead of 'observe'
# because inheriting from VBox causes errors when overriding the 'observe' method
# (see https://bit.ly/3SggPVS).
func = widget._plopp_observe_ if hasattr(widget,
'_plopp_observe_') else widget.observe
func(n.notify_children, names="value")
return n
26 changes: 0 additions & 26 deletions src/plopp/plot.py

This file was deleted.

15 changes: 3 additions & 12 deletions src/plopp/toolbar.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2022 Scipp contributors (https://github.com/scipp)

from ipywidgets import Button, VBox
from functools import partial
from typing import Callable

from .displayable import Displayable

LAYOUT_STYLE = {"layout": {"width": "34px", "padding": "0px 0px 0px 0px"}}


Expand All @@ -15,7 +14,6 @@ def __init__(self, callback: Callable = None, **kwargs):
"""
Create a new button with a callback that is called when the button is clicked.
"""
from ipywidgets import Button
self.widget = Button(**{**LAYOUT_STYLE, **kwargs})
self._callback = callback
self.widget.on_click(self)
Expand All @@ -33,7 +31,6 @@ def __init__(self, callback: Callable, value: bool = False, **kwargs):
cases, we need to toggle the button color without triggering the callback
function.
"""
from ipywidgets import Button
self.widget = Button(**{**LAYOUT_STYLE, **kwargs})
self._callback = callback
self.widget.on_click(self)
Expand Down Expand Up @@ -70,7 +67,7 @@ def _toggle(self):
}


class Toolbar(Displayable):
class Toolbar(VBox):
"""
Custom toolbar with additional buttons for controlling log scales and
normalization, and with back/forward buttons removed.
Expand All @@ -82,10 +79,4 @@ def __init__(self, tools=None):
tool = TOOL_LIBRARY[key](callback=callback)
setattr(self, key, tool)
self._widgets[key] = tool.widget

def to_widget(self):
"""
Return the VBox container
"""
from ipywidgets import VBox
return VBox(tuple(self._widgets.values()))
super().__init__(tuple(self._widgets.values()))
8 changes: 2 additions & 6 deletions src/plopp/view.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2022 Scipp contributors (https://github.com/scipp)

from abc import ABC, abstractmethod
from abc import abstractmethod
import uuid


class View(ABC):
class View:

def __init__(self, *nodes):
self.id = str(uuid.uuid1())
Expand All @@ -17,7 +17,3 @@ def __init__(self, *nodes):
@abstractmethod
def notify_view(self, _):
return

@abstractmethod
def to_widget(self):
return
2 changes: 1 addition & 1 deletion src/plopp/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
# flake8: noqa: F401

# from .masks import MaskWidget, hide_masks
from .box import Box
from .checkboxes import Checkboxes
from .slice import SliceWidget, slice_dims
from .widgetnode import widget_node
20 changes: 20 additions & 0 deletions src/plopp/widgets/box.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2022 Scipp contributors (https://github.com/scipp)

from ipywidgets import VBox, HBox


class Box(VBox):
"""
Container widget that accepts a list of items. For each item in the list, if the
item is itself a list, it will be bade into a horizontal row of the underlying
items, if not, the item will span then entire row.
Finally, all the rows will be placed inside a vertical box container.
"""

def __init__(self, widgets):
self.widgets = widgets
children = []
for view in self.widgets:
children.append(HBox(view) if isinstance(view, (list, tuple)) else view)
return super().__init__(children)
57 changes: 15 additions & 42 deletions src/plopp/widgets/checkboxes.py
Original file line number Diff line number Diff line change
@@ -1,79 +1,52 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2022 Scipp contributors (https://github.com/scipp)

from functools import partial
from html import escape
import ipywidgets as ipw
from typing import Callable
from ..displayable import Displayable


class Checkboxes(Displayable):
class Checkboxes(ipw.HBox):
"""
Widget providing a list of checkboxes, along with a button to toggle them all.
"""

def __init__(self, entries: list, description=""):

self._callback = None

self.checkboxes = {}
self._lock = False
self._description = ipw.Label(value=description)

for key in entries:
self.checkboxes[key] = ipw.Checkbox(value=True,
description=f"{escape(key)}",
indent=False,
layout={"width": "initial"})

if len(self.checkboxes):
self._description = ipw.Label(value=description)
to_hbox = [
self._description,
ipw.HBox(list(self.checkboxes.values()),
layout=ipw.Layout(flex_flow='row wrap'))
]

if len(self.checkboxes):
# Add a master button to control all masks in one go
self.toggle_all_button = ipw.ToggleButton(value=True,
description="De-select all",
disabled=False,
button_style="",
layout={"width": "100px"})
layout={"width": "initial"})
for cbox in self.checkboxes.values():
ipw.jsdlink((self.toggle_all_button, 'value'), (cbox, 'value'))
to_hbox.insert(1, self.toggle_all_button)

def to_widget(self) -> ipw.Widget:
"""
Gather all widgets in a single container box.
"""
out = ipw.HBox()
if len(self.checkboxes):
out.children = [
self._description, self.toggle_all_button,
ipw.HBox(list(self.checkboxes.values()))
]
return out
super().__init__(to_hbox)

def observe(self, callback: Callable, **kwargs):
def _plopp_observe_(self, callback: Callable, **kwargs):
for chbx in self.checkboxes.values():
chbx.observe(self._toggle, **kwargs)
if len(self.checkboxes):
self.toggle_all_button.observe(self._toggle_all, **kwargs)
self._callback = partial(callback, None)
chbx.observe(callback, **kwargs)

@property
def value(self) -> dict:
"""
"""
return {key: chbx.value for key, chbx in self.checkboxes.items()}

def _toggle(self, _):
if self._lock:
return
self._callback()

def _toggle_all(self, change: dict):
"""
A main button to hide or show all masks at once.
"""
self._lock = True
for key in self.checkboxes:
self.checkboxes[key].value = change["new"]
change["owner"].description = ("De-select all"
if change["new"] else "Select all")
self._lock = False
self._callback()
Loading

0 comments on commit c554938

Please sign in to comment.