Skip to content

Commit 2591c5f

Browse files
authored
Merge pull request #305 from ianhi/gcf
Use _Backend from matplotlib to simplify the code
2 parents 7301e67 + 55336aa commit 2591c5f

File tree

5 files changed

+146
-66
lines changed

5 files changed

+146
-66
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ js/*.tgz
1313

1414
# OS X
1515
.DS_Store
16+
17+
# vscode
18+
.vscode/

ipympl/backend_nbagg.py

Lines changed: 39 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313
)
1414

1515
from matplotlib import rcParams
16-
from matplotlib.figure import Figure
1716
from matplotlib import is_interactive
1817
from matplotlib.backends.backend_webagg_core import (FigureManagerWebAgg,
1918
FigureCanvasWebAggCore,
2019
NavigationToolbar2WebAgg,
2120
TimerTornado)
22-
from matplotlib.backend_bases import ShowBase, NavigationToolbar2, cursors
21+
from matplotlib.backend_bases import NavigationToolbar2, cursors, _Backend
22+
from matplotlib._pylab_helpers import Gcf
2323

2424
from ._version import js_semver
2525

@@ -32,43 +32,6 @@
3232
}
3333

3434

35-
class Show(ShowBase):
36-
37-
def __call__(self, block=None):
38-
from matplotlib._pylab_helpers import Gcf
39-
40-
managers = Gcf.get_all_fig_managers()
41-
if not managers:
42-
return
43-
44-
interactive = is_interactive()
45-
46-
for manager in managers:
47-
manager.show()
48-
49-
# plt.figure adds an event which puts the figure in focus
50-
# in the activeQue. Disable this behaviour, as it results in
51-
# figures being put as the active figure after they have been
52-
# shown, even in non-interactive mode.
53-
if hasattr(manager, '_cidgcf'):
54-
manager.canvas.mpl_disconnect(manager._cidgcf)
55-
56-
if not interactive and manager in Gcf._activeQue:
57-
Gcf._activeQue.remove(manager)
58-
59-
60-
show = Show()
61-
62-
63-
def draw_if_interactive():
64-
import matplotlib._pylab_helpers as pylab_helpers
65-
66-
if is_interactive():
67-
manager = pylab_helpers.Gcf.get_active()
68-
if manager is not None:
69-
manager.show()
70-
71-
7235
def connection_info():
7336
"""
7437
Return a string showing the figure and connection status for
@@ -260,33 +223,47 @@ def destroy(self):
260223
self.canvas.close()
261224

262225

263-
def new_figure_manager(num, *args, **kwargs):
264-
"""
265-
Create a new figure manager instance
266-
"""
267-
figure_class = kwargs.pop('FigureClass', Figure)
268-
this_fig = figure_class(*args, **kwargs)
269-
return new_figure_manager_given_figure(num, this_fig)
226+
@_Backend.export
227+
class _Backend_ipympl(_Backend):
228+
FigureCanvas = Canvas
229+
FigureManager = FigureManager
270230

231+
@staticmethod
232+
def new_figure_manager_given_figure(num, figure):
233+
canvas = Canvas(figure)
234+
if 'nbagg.transparent' in rcParams and rcParams['nbagg.transparent']:
235+
figure.patch.set_alpha(0)
236+
manager = FigureManager(canvas, num)
237+
if is_interactive():
238+
manager.show()
239+
figure.canvas.draw_idle()
271240

272-
def new_figure_manager_given_figure(num, figure):
273-
"""
274-
Create a new figure manager instance for the given figure.
275-
"""
276-
from matplotlib._pylab_helpers import Gcf
241+
def destroy(event):
242+
canvas.mpl_disconnect(cid)
243+
Gcf.destroy(manager)
277244

278-
def closer(event):
279-
Gcf.destroy(num)
245+
cid = canvas.mpl_connect('close_event', destroy)
246+
return manager
280247

281-
canvas = Canvas(figure)
282-
if 'nbagg.transparent' in rcParams and rcParams['nbagg.transparent']:
283-
figure.patch.set_alpha(0)
284-
manager = FigureManager(canvas, num)
248+
@staticmethod
249+
def show(block=None):
250+
# TODO: something to do when keyword block==False ?
285251

286-
if is_interactive():
287-
manager.show()
288-
figure.canvas.draw_idle()
252+
managers = Gcf.get_all_fig_managers()
253+
if not managers:
254+
return
255+
256+
interactive = is_interactive()
257+
258+
for manager in managers:
259+
manager.show()
289260

290-
canvas.mpl_connect('close_event', closer)
261+
# plt.figure adds an event which makes the figure in focus the
262+
# active one. Disable this behaviour, as it results in
263+
# figures being put as the active figure after they have been
264+
# shown, even in non-interactive mode.
265+
if hasattr(manager, '_cidgcf'):
266+
manager.canvas.mpl_disconnect(manager._cidgcf)
291267

292-
return manager
268+
if not interactive:
269+
Gcf.figs.pop(manager.num, None)

pyproject.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
11
[build-system]
22
requires = ["jupyter_packaging~=0.7.0", "jupyterlab>=3.0.0,==3.*", "setuptools>=40.8.0", "wheel"]
33
build-backend = "setuptools.build_meta"
4+
5+
6+
[tool.pytest.ini_options]
7+
testpaths = [
8+
"test-notebooks",
9+
"examples",
10+
]
11+
norecursedirs = [
12+
"node_modules",
13+
".ipynb_checkpoints",
14+
]
15+
addopts = "--nbval --current-env"

pytest.ini

Lines changed: 0 additions & 4 deletions
This file was deleted.

test-notebooks/UAT.ipynb

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "antique-treasure",
6+
"metadata": {},
7+
"source": [
8+
"# User Automated Testing\n",
9+
"\n",
10+
"A notebook to run manually to catch issues with the backend."
11+
]
12+
},
13+
{
14+
"cell_type": "markdown",
15+
"id": "binary-speaker",
16+
"metadata": {},
17+
"source": [
18+
"# 1. Checking plt.show()\n",
19+
"\n",
20+
"The implementation of plt.show relies on the private `matplotlib._pylab_helpers.Gcf` which can break (https://github.com/matplotlib/ipympl/issues/303). The cell should run without any errors."
21+
]
22+
},
23+
{
24+
"cell_type": "code",
25+
"execution_count": null,
26+
"id": "agricultural-whole",
27+
"metadata": {},
28+
"outputs": [],
29+
"source": [
30+
"%matplotlib ipympl\n",
31+
"import matplotlib.pyplot as plt\n",
32+
"plt.ioff()\n",
33+
"fig = plt.figure()\n",
34+
"plt.plot([0,1],[0,1])\n",
35+
"plt.show()"
36+
]
37+
},
38+
{
39+
"cell_type": "markdown",
40+
"id": "secure-consistency",
41+
"metadata": {},
42+
"source": [
43+
"# 2 plt.close()\n",
44+
"\n",
45+
"Calling `plt.close()` should prevent further interactions with the figure - so panning and resizing should no longer work after the second cell in this test."
46+
]
47+
},
48+
{
49+
"cell_type": "code",
50+
"execution_count": null,
51+
"id": "hollow-front",
52+
"metadata": {},
53+
"outputs": [],
54+
"source": [
55+
"plt.ion()\n",
56+
"plt.figure()\n",
57+
"plt.plot([0,1],[0,1])\n"
58+
]
59+
},
60+
{
61+
"cell_type": "code",
62+
"execution_count": null,
63+
"id": "classical-pavilion",
64+
"metadata": {},
65+
"outputs": [],
66+
"source": [
67+
"plt.close()"
68+
]
69+
}
70+
],
71+
"metadata": {
72+
"kernelspec": {
73+
"display_name": "Python 3",
74+
"language": "python",
75+
"name": "python3"
76+
},
77+
"language_info": {
78+
"codemirror_mode": {
79+
"name": "ipython",
80+
"version": 3
81+
},
82+
"file_extension": ".py",
83+
"mimetype": "text/x-python",
84+
"name": "python",
85+
"nbconvert_exporter": "python",
86+
"pygments_lexer": "ipython3",
87+
"version": "3.9.2"
88+
}
89+
},
90+
"nbformat": 4,
91+
"nbformat_minor": 5
92+
}

0 commit comments

Comments
 (0)