Skip to content

Commit bf03f1e

Browse files
committed
Backend class to factor out common code.
1 parent 0194395 commit bf03f1e

26 files changed

+469
-840
lines changed

lib/matplotlib/backend_bases.py

+104-46
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,11 @@
5252
import matplotlib.colors as colors
5353
import matplotlib.transforms as transforms
5454
import matplotlib.widgets as widgets
55-
#import matplotlib.path as path
5655
from matplotlib import rcParams
5756
from matplotlib import is_interactive
5857
from matplotlib import get_backend
59-
from matplotlib._pylab_helpers import Gcf
6058
from matplotlib import lines
59+
from matplotlib._pylab_helpers import Gcf
6160

6261
from matplotlib.transforms import Bbox, TransformedBbox, Affine2D
6362

@@ -142,60 +141,119 @@ def get_registered_canvas_class(format):
142141
return backend_class
143142

144143

145-
class ShowBase(object):
146-
"""
147-
Simple base class to generate a show() callable in backends.
144+
class _Backend(object):
145+
# A backend can be defined by using the following pattern:
146+
#
147+
# @_Backend.export
148+
# class FooBackend(_Backend):
149+
# # override the attributes and methods documented below.
148150

149-
Subclass must override mainloop() method.
150-
"""
151-
def __call__(self, block=None):
151+
# The following attributes and methods must be overridden by subclasses.
152+
153+
# The `FigureCanvas` and `FigureManager` classes must be defined.
154+
FigureCanvas = None
155+
FigureManager = None
156+
157+
# The following methods must be left as None for non-interactive backends.
158+
# For interactive backends, `trigger_manager_draw` should be a function
159+
# taking a manager as argument and triggering a canvas draw, and `mainloop`
160+
# should be a function taking no argument and starting the backend main
161+
# loop.
162+
trigger_manager_draw = None
163+
mainloop = None
164+
165+
# The following methods will be automatically defined and exported, but
166+
# can be overridden.
167+
168+
@classmethod
169+
def new_figure_manager(cls, num, *args, **kwargs):
170+
"""Create a new figure manager instance.
171+
"""
172+
# This import needs to happen here due to circular imports.
173+
from matplotlib.figure import Figure
174+
fig_cls = kwargs.pop('FigureClass', Figure)
175+
fig = fig_cls(*args, **kwargs)
176+
return cls.new_figure_manager_given_figure(num, fig)
177+
178+
@classmethod
179+
def new_figure_manager_given_figure(cls, num, figure):
180+
"""Create a new figure manager instance for the given figure.
152181
"""
153-
Show all figures. If *block* is not None, then
154-
it is a boolean that overrides all other factors
155-
determining whether show blocks by calling mainloop().
156-
The other factors are:
157-
it does not block if run inside ipython's "%pylab" mode
158-
it does not block in interactive mode.
182+
canvas = cls.FigureCanvas(figure)
183+
manager = cls.FigureManager(canvas, num)
184+
return manager
185+
186+
@classmethod
187+
def draw_if_interactive(cls):
188+
if cls.trigger_manager_draw is not None and is_interactive():
189+
manager = Gcf.get_active()
190+
if manager:
191+
cls.trigger_manager_draw()
192+
193+
@classmethod
194+
def show(cls, block=None):
195+
"""Show all figures.
196+
197+
`show` blocks by calling `mainloop` if *block* is ``True``, or if it
198+
is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
199+
`interactive` mode.
159200
"""
201+
if cls.mainloop is None:
202+
return
160203
managers = Gcf.get_all_fig_managers()
161204
if not managers:
162205
return
163-
164206
for manager in managers:
165207
manager.show()
208+
if block is None:
209+
# Hack: Are we in IPython's pylab mode?
210+
from matplotlib import pyplot
211+
try:
212+
# IPython versions >= 0.10 tack the _needmain attribute onto
213+
# pyplot.show, and always set it to False, when in %pylab mode.
214+
ipython_pylab = not pyplot.show._needmain
215+
except AttributeError:
216+
ipython_pylab = False
217+
block = not ipython_pylab and not is_interactive()
218+
# TODO: The above is a hack to get the WebAgg backend working
219+
# with ipython's `%pylab` mode until proper integration is
220+
# implemented.
221+
if get_backend() == "WebAgg":
222+
block = True
223+
if block:
224+
cls.mainloop()
225+
226+
# This method is the one actually exporting the required methods.
227+
228+
@staticmethod
229+
def export(cls):
230+
for name in ["FigureCanvas",
231+
"FigureManager",
232+
"new_figure_manager",
233+
"new_figure_manager_given_figure",
234+
"draw_if_interactive",
235+
"show"]:
236+
setattr(sys.modules[cls.__module__], name, getattr(cls, name))
237+
238+
# For back-compatibility, generate a shim `Show` class.
239+
240+
class Show(ShowBase):
241+
def mainloop(self):
242+
return cls.mainloop()
243+
244+
setattr(sys.modules[cls.__module__], "Show", Show)
245+
return cls
246+
247+
248+
class ShowBase(_Backend):
249+
"""
250+
Simple base class to generate a show() callable in backends.
166251
167-
if block is not None:
168-
if block:
169-
self.mainloop()
170-
return
171-
else:
172-
return
173-
174-
# Hack: determine at runtime whether we are
175-
# inside ipython in pylab mode.
176-
from matplotlib import pyplot
177-
try:
178-
ipython_pylab = not pyplot.show._needmain
179-
# IPython versions >= 0.10 tack the _needmain
180-
# attribute onto pyplot.show, and always set
181-
# it to False, when in %pylab mode.
182-
ipython_pylab = ipython_pylab and get_backend() != 'WebAgg'
183-
# TODO: The above is a hack to get the WebAgg backend
184-
# working with ipython's `%pylab` mode until proper
185-
# integration is implemented.
186-
except AttributeError:
187-
ipython_pylab = False
188-
189-
# Leave the following as a separate step in case we
190-
# want to control this behavior with an rcParam.
191-
if ipython_pylab:
192-
return
193-
194-
if not is_interactive() or get_backend() == 'WebAgg':
195-
self.mainloop()
252+
Subclass must override mainloop() method.
253+
"""
196254

197-
def mainloop(self):
198-
pass
255+
def __call__(self, block=None):
256+
return self.show(block=block)
199257

200258

201259
class RendererBase(object):

lib/matplotlib/backends/backend_agg.py

+6-21
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
from collections import OrderedDict
3030
from math import radians, cos, sin
3131
from matplotlib import verbose, rcParams, __version__
32-
from matplotlib.backend_bases import (RendererBase, FigureManagerBase,
33-
FigureCanvasBase)
32+
from matplotlib.backend_bases import (
33+
_Backend, FigureCanvasBase, FigureManagerBase, RendererBase)
3434
from matplotlib.cbook import maxdict, restrict_dict
3535
from matplotlib.figure import Figure
3636
from matplotlib.font_manager import findfont, get_font
@@ -397,24 +397,6 @@ def post_processing(image, dpi):
397397
gc, l + ox, height - b - h + oy, img)
398398

399399

400-
def new_figure_manager(num, *args, **kwargs):
401-
"""
402-
Create a new figure manager instance
403-
"""
404-
FigureClass = kwargs.pop('FigureClass', Figure)
405-
thisFig = FigureClass(*args, **kwargs)
406-
return new_figure_manager_given_figure(num, thisFig)
407-
408-
409-
def new_figure_manager_given_figure(num, figure):
410-
"""
411-
Create a new figure manager instance for the given figure.
412-
"""
413-
canvas = FigureCanvasAgg(figure)
414-
manager = FigureManagerBase(canvas, num)
415-
return manager
416-
417-
418400
class FigureCanvasAgg(FigureCanvasBase):
419401
"""
420402
The canvas the figure renders into. Calls the draw and print fig
@@ -609,4 +591,7 @@ def print_tif(self, filename_or_obj, *args, **kwargs):
609591
print_tiff = print_tif
610592

611593

612-
FigureCanvas = FigureCanvasAgg
594+
@_Backend.export
595+
class _BackendAgg(_Backend):
596+
FigureCanvas = FigureCanvasAgg
597+
FigureManager = FigureManagerBase

lib/matplotlib/backends/backend_cairo.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,12 @@
5252
del _version_required
5353

5454
from matplotlib.backend_bases import (
55-
RendererBase, GraphicsContextBase, FigureManagerBase, FigureCanvasBase)
56-
from matplotlib.figure import Figure
57-
from matplotlib.mathtext import MathTextParser
58-
from matplotlib.path import Path
59-
from matplotlib.transforms import Bbox, Affine2D
55+
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
56+
RendererBase)
57+
from matplotlib.figure import Figure
58+
from matplotlib.mathtext import MathTextParser
59+
from matplotlib.path import Path
60+
from matplotlib.transforms import Bbox, Affine2D
6061
from matplotlib.font_manager import ttfFontProperty
6162

6263

@@ -549,4 +550,7 @@ def _save(self, fo, fmt, **kwargs):
549550
fo.close()
550551

551552

552-
FigureCanvas = FigureCanvasCairo
553+
@_Backend.export
554+
class _BackendCairo(_Backend):
555+
FigureCanvas = FigureCanvasCairo
556+
FigureManager = FigureManagerBase

lib/matplotlib/backends/backend_gdk.py

+8-19
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
from matplotlib import rcParams
2525
from matplotlib._pylab_helpers import Gcf
2626
from matplotlib.backend_bases import (
27-
RendererBase, GraphicsContextBase, FigureManagerBase, FigureCanvasBase)
27+
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
28+
RendererBase)
2829
from matplotlib.cbook import restrict_dict, warn_deprecated
2930
from matplotlib.figure import Figure
3031
from matplotlib.mathtext import MathTextParser
@@ -381,24 +382,6 @@ def set_linewidth(self, w):
381382
self.gdkGC.line_width = max(1, int(np.round(pixels)))
382383

383384

384-
def new_figure_manager(num, *args, **kwargs):
385-
"""
386-
Create a new figure manager instance
387-
"""
388-
FigureClass = kwargs.pop('FigureClass', Figure)
389-
thisFig = FigureClass(*args, **kwargs)
390-
return new_figure_manager_given_figure(num, thisFig)
391-
392-
393-
def new_figure_manager_given_figure(num, figure):
394-
"""
395-
Create a new figure manager instance for the given figure.
396-
"""
397-
canvas = FigureCanvasGDK(figure)
398-
manager = FigureManagerBase(canvas, num)
399-
return manager
400-
401-
402385
class FigureCanvasGDK (FigureCanvasBase):
403386
def __init__(self, figure):
404387
FigureCanvasBase.__init__(self, figure)
@@ -452,3 +435,9 @@ def _print_image(self, filename, format, *args, **kwargs):
452435
options['quality'] = str(options['quality'])
453436

454437
pixbuf.save(filename, format, options=options)
438+
439+
440+
@_Backend.export
441+
class _BackendGDK(_Backend):
442+
FigureCanvas = FigureCanvasGDKAgg
443+
FigureManager = FigureManagerGDKAgg

lib/matplotlib/backends/backend_gtk.py

+19-42
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,14 @@
2828

2929
import matplotlib
3030
from matplotlib._pylab_helpers import Gcf
31-
from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \
32-
FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase
33-
from matplotlib.backend_bases import ShowBase
31+
from matplotlib.backend_bases import (
32+
_Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
33+
TimerBase, cursors)
3434

3535
from matplotlib.backends.backend_gdk import RendererGDK, FigureCanvasGDK
36-
from matplotlib.cbook import is_writable_file_like
36+
from matplotlib.cbook import is_writable_file_like, warn_deprecated
3737
from matplotlib.figure import Figure
3838
from matplotlib.widgets import SubplotTool
39-
from matplotlib.cbook import warn_deprecated
4039

4140
from matplotlib import (
4241
cbook, colors as mcolors, lines, markers, rcParams, verbose)
@@ -63,41 +62,6 @@ def GTK_WIDGET_DRAWABLE(w):
6362
return flags & gtk.VISIBLE != 0 and flags & gtk.MAPPED != 0
6463

6564

66-
def draw_if_interactive():
67-
"""
68-
Is called after every pylab drawing command
69-
"""
70-
if matplotlib.is_interactive():
71-
figManager = Gcf.get_active()
72-
if figManager is not None:
73-
figManager.canvas.draw_idle()
74-
75-
76-
class Show(ShowBase):
77-
def mainloop(self):
78-
if gtk.main_level() == 0:
79-
gtk.main()
80-
81-
show = Show()
82-
83-
def new_figure_manager(num, *args, **kwargs):
84-
"""
85-
Create a new figure manager instance
86-
"""
87-
FigureClass = kwargs.pop('FigureClass', Figure)
88-
thisFig = FigureClass(*args, **kwargs)
89-
return new_figure_manager_given_figure(num, thisFig)
90-
91-
92-
def new_figure_manager_given_figure(num, figure):
93-
"""
94-
Create a new figure manager instance for the given figure.
95-
"""
96-
canvas = FigureCanvasGTK(figure)
97-
manager = FigureManagerGTK(canvas, num)
98-
return manager
99-
100-
10165
class TimerGTK(TimerBase):
10266
'''
10367
Subclass of :class:`backend_bases.TimerBase` using GTK for timer events.
@@ -521,6 +485,7 @@ def stop_event_loop(self):
521485
FigureCanvasBase.stop_event_loop_default(self)
522486
stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__
523487

488+
524489
class FigureManagerGTK(FigureManagerBase):
525490
"""
526491
Attributes
@@ -866,6 +831,7 @@ def get_filename_from_user (self):
866831

867832
return filename, self.ext
868833

834+
869835
class DialogLineprops(object):
870836
"""
871837
A GUI dialog for controlling lineprops
@@ -1056,5 +1022,16 @@ def error_msg_gtk(msg, parent=None):
10561022
dialog.destroy()
10571023

10581024

1059-
FigureCanvas = FigureCanvasGTK
1060-
FigureManager = FigureManagerGTK
1025+
@_Backend.export
1026+
class _BackendGTK(_Backend):
1027+
FigureCanvas = FigureCanvasGTK
1028+
FigureManager = FigureManagerGTK
1029+
1030+
@staticmethod
1031+
def trigger_manager_draw(manager):
1032+
manager.canvas.draw_idle()
1033+
1034+
@staticmethod
1035+
def mainloop():
1036+
if gtk.main_level() == 0:
1037+
gtk.main()

0 commit comments

Comments
 (0)