-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add cued containers for pyobjects matplotlib figures
* add CueOutput -> (CueObject, CueFigure) with css * add regression tests with reference screenshots for the construction of a figure
- Loading branch information
1 parent
76112f9
commit bb2aeb0
Showing
13 changed files
with
686 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
# postpones evaluation of annotations | ||
# see https://stackoverflow.com/a/33533514 | ||
from __future__ import annotations | ||
|
||
from typing import List, Optional, Union | ||
|
||
import matplotlib | ||
import matplotlib.pyplot as plt | ||
from IPython.display import display | ||
from ipywidgets import Widget | ||
from matplotlib.figure import Figure | ||
from traitlets.utils.sentinel import Sentinel | ||
|
||
from ._widget_cue_output import CueOutput | ||
|
||
|
||
class CueFigure(CueOutput): | ||
""" | ||
A cued displayable ipywidget.Output for a matplotlib figure. Provides utilities to | ||
clear and draw the updated figure. For the matplotlib inline backend it closes the | ||
active figure to prevent any display outside of the container, that happens on the | ||
creation of the figure because pyplot does magic behind the curtain that is hard to | ||
suppress. For the matplot interactive widget backend, named "nbagg", it wraps te | ||
figure within. | ||
:param figure: | ||
The matplotlib figure | ||
:param widgets_to_observe: | ||
The widget to observe if the :param traits_to_observe: has changed. | ||
:param traits_to_observe: | ||
The trait from the :param widgets_to_observe: to observe if changed. | ||
Specify `traitlets.All` to observe all traits. | ||
:param cued: | ||
Specifies if it is cued on initialization | ||
:param css_syle: | ||
- **base**: the css style of the box during initialization | ||
- **cue**: the css style that is added when :param | ||
traits_to_observe: in widget :param widgets_to_observe: changes. | ||
It is supposed to change the style of the box such that the user has a visual | ||
cue that :param widget_to_cue: has changed. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
figure: Figure, | ||
widgets_to_observe: Union[None, List[Widget], Widget] = None, | ||
traits_to_observe: Union[ | ||
None, str, List[str], List[List[str]], Sentinel | ||
] = None, | ||
cued: bool = True, | ||
css_style: Optional[dict] = None, | ||
**kwargs, | ||
): | ||
CueOutput.__init__( | ||
self, | ||
widgets_to_observe, | ||
traits_to_observe, | ||
cued, | ||
css_style, | ||
**kwargs, | ||
) | ||
self.figure = figure | ||
|
||
if matplotlib.backends.backend == "module://matplotlib_inline.backend_inline": | ||
# we close the figure so the figure is only contained in this widget | ||
# and not shown using plt.show() | ||
plt.close(self.figure) | ||
elif matplotlib.backends.backend == "module://ipympl.backend_nbagg": | ||
with self: | ||
self.figure.canvas.show() | ||
else: | ||
raise NotImplementedError( | ||
f"matplotlib backend " f"{matplotlib.backends.backend!r} not supported." | ||
) | ||
self.draw_display() | ||
|
||
def clear_display(self, wait=False): | ||
""" | ||
:param wait: | ||
same meaning as for the `wait` parameter in the ipywidgets.clear_output | ||
function | ||
""" | ||
if matplotlib.backends.backend == "module://matplotlib_inline.backend_inline": | ||
self.clear_figure() | ||
self.clear_output(wait=wait) | ||
elif matplotlib.backends.backend == "module://ipympl.backend_nbagg": | ||
self.clear_figure() | ||
if not (wait): | ||
self.figure.canvas.draw_idle() | ||
self.figure.canvas.flush_events() | ||
else: | ||
raise NotImplementedError( | ||
f"matplotlib backend " f"{matplotlib.backends.backend!r} not supported." | ||
) | ||
|
||
def draw_display(self): | ||
""" | ||
Enforces redrawing the figure | ||
""" | ||
if matplotlib.backends.backend == "module://matplotlib_inline.backend_inline": | ||
with self: | ||
display(self.figure) | ||
elif matplotlib.backends.backend == "module://ipympl.backend_nbagg": | ||
self.figure.canvas.draw_idle() | ||
self.figure.canvas.flush_events() | ||
else: | ||
raise NotImplementedError( | ||
f"matplotlib backend " f"{matplotlib.backends.backend!r} not supported." | ||
) | ||
|
||
def clear_figure(self): | ||
""" | ||
Clears the figure while retainin axes. figure.clear() removes the axes | ||
sometimes. | ||
""" | ||
for ax in self.figure.get_axes(): | ||
if ax.has_data() or len(ax.artists) > 0: | ||
ax.clear() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# postpones evaluation of annotations | ||
# see https://stackoverflow.com/a/33533514 | ||
from __future__ import annotations | ||
|
||
from typing import Any, List, Optional, Union | ||
|
||
from IPython.display import display | ||
from ipywidgets import Widget | ||
from traitlets.utils.sentinel import Sentinel | ||
|
||
from ._widget_cue_output import CueOutput | ||
|
||
|
||
class CueObject(CueOutput): | ||
""" | ||
A cued displayable ipywidget.Output for any Python object. Provides utilities to | ||
clear and redraw the object, for example after an update. | ||
:param display_object: | ||
The object to display | ||
:param widgets_to_observe: | ||
The widget to observe if the :param traits_to_observe: has changed. | ||
:param traits_to_observe: | ||
The trait from the :param widgets_to_observe: to observe if changed. | ||
Specify `traitlets.All` to observe all traits. | ||
:param cued: | ||
Specifies if it is cued on initialization | ||
:param css_syle: | ||
- **base**: the css style of the box during initialization | ||
- **cue**: the css style that is added when :param | ||
traits_to_observe: in widget :param widgets_to_observe: changes. | ||
It is supposed to change the style of the box such that the user has a visual | ||
cue that :param widget_to_cue: has changed. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
display_object: Any, | ||
widgets_to_observe: Union[None, List[Widget], Widget] = None, | ||
traits_to_observe: Union[ | ||
None, str, List[str], List[List[str]], Sentinel | ||
] = None, | ||
cued: bool = True, | ||
css_style: Optional[dict] = None, | ||
*args, | ||
**kwargs, | ||
): | ||
CueOutput.__init__( | ||
self, | ||
widgets_to_observe, | ||
traits_to_observe, | ||
cued, | ||
css_style, | ||
**kwargs, | ||
) | ||
|
||
self._display_object = display_object | ||
self.draw_display() | ||
|
||
@property | ||
def display_object(self): | ||
return self._display_object | ||
|
||
@display_object.setter | ||
def display_object(self, display_object: Any): | ||
self._display_object = display_object | ||
|
||
def clear_display(self, wait=False): | ||
self.clear_output(wait=wait) | ||
|
||
def draw_display(self): | ||
with self: | ||
display(self._display_object) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# postpones evaluation of annotations | ||
# see https://stackoverflow.com/a/33533514 | ||
from __future__ import annotations | ||
|
||
from typing import List, Optional, Union | ||
|
||
from ipywidgets import Output, Widget | ||
from traitlets.utils.sentinel import Sentinel | ||
|
||
from ._widget_cue import CueWidget | ||
|
||
|
||
class CueOutput(Output, CueWidget): | ||
""" | ||
A cued displayable ipywidget.Output for any Python object. | ||
:param widgets_to_observe: | ||
The widget to observe if the :param traits_to_observe: has changed. | ||
:param traits_to_observe: | ||
The trait from the :param widgets_to_observe: to observe if changed. | ||
Specify `traitlets.All` to observe all traits. | ||
:param cued: | ||
Specifies if it is cued on initialization | ||
:param css_syle: | ||
- **base**: the css style of the box during initialization | ||
- **cue**: the css style that is added when :param | ||
traits_to_observe: in widget :param widgets_to_observe: changes. | ||
It is supposed to change the style of the box such that the user has a visual | ||
cue that :param widget_to_cue: has changed. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
widgets_to_observe: Union[None, List[Widget], Widget] = None, | ||
traits_to_observe: Union[ | ||
None, str, List[str], List[List[str]], Sentinel | ||
] = None, | ||
cued: bool = True, | ||
css_style: Optional[dict] = None, | ||
*args, | ||
**kwargs, | ||
): | ||
if css_style is None: | ||
css_style = { | ||
"base": "scwidget-cue-output", | ||
"cue": "scwidget-cue-output--cue", | ||
} | ||
if "base" not in css_style.keys(): | ||
raise ValueError('css_style is missing key "base".') | ||
if "cue" not in css_style.keys(): | ||
raise ValueError('css_style is missing key "cue".') | ||
|
||
self._css_style = css_style | ||
|
||
# TODO make disabling of cued transparent | ||
if widgets_to_observe is None and traits_to_observe is None: | ||
cued = False | ||
if widgets_to_observe is None: | ||
widgets_to_observe = [] | ||
if traits_to_observe is None: | ||
traits_to_observe = [] | ||
|
||
Output.__init__(self, **kwargs) | ||
CueWidget.__init__(self, widgets_to_observe, traits_to_observe, cued) | ||
|
||
self.add_class(self._css_style["base"]) | ||
|
||
@property | ||
def cued(self): | ||
return self._cued | ||
|
||
@cued.setter | ||
def cued(self, cued: bool): | ||
if cued: | ||
self.add_class(self._css_style["cue"]) | ||
else: | ||
self.remove_class(self._css_style["cue"]) | ||
self._cued = cued |
Oops, something went wrong.