Skip to content

Commit cf51167

Browse files
committed
Backend class to factor out common code.
1 parent d412228 commit cf51167

26 files changed

+470
-859
lines changed

lib/matplotlib/backend_bases.py

+103-45
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
from matplotlib import rcParams
5656
from matplotlib import is_interactive
5757
from matplotlib import get_backend
58-
from matplotlib._pylab_helpers import Gcf
5958
from matplotlib import lines
59+
from matplotlib._pylab_helpers import Gcf
6060

6161
from matplotlib.transforms import Bbox, TransformedBbox, Affine2D
6262

@@ -141,60 +141,118 @@ def get_registered_canvas_class(format):
141141
return backend_class
142142

143143

144-
class ShowBase(object):
145-
"""
146-
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.
147150

148-
Subclass must override mainloop() method.
149-
"""
150-
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.
151181
"""
152-
Show all figures. If *block* is not None, then
153-
it is a boolean that overrides all other factors
154-
determining whether show blocks by calling mainloop().
155-
The other factors are:
156-
it does not block if run inside ipython's "%pylab" mode
157-
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(manager)
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.
158200
"""
201+
if cls.mainloop is None:
202+
return
159203
managers = Gcf.get_all_fig_managers()
160204
if not managers:
161205
return
162-
163206
for manager in managers:
164207
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 with
219+
# ipython's `%pylab` mode until proper integration is implemented.
220+
if get_backend() == "WebAgg":
221+
block = True
222+
if block:
223+
cls.mainloop()
224+
225+
# This method is the one actually exporting the required methods.
226+
227+
@staticmethod
228+
def export(cls):
229+
for name in ["FigureCanvas",
230+
"FigureManager",
231+
"new_figure_manager",
232+
"new_figure_manager_given_figure",
233+
"draw_if_interactive",
234+
"show"]:
235+
setattr(sys.modules[cls.__module__], name, getattr(cls, name))
236+
237+
# For back-compatibility, generate a shim `Show` class.
238+
239+
class Show(ShowBase):
240+
def mainloop(self):
241+
return cls.mainloop()
242+
243+
setattr(sys.modules[cls.__module__], "Show", Show)
244+
return cls
245+
246+
247+
class ShowBase(_Backend):
248+
"""
249+
Simple base class to generate a show() callable in backends.
165250
166-
if block is not None:
167-
if block:
168-
self.mainloop()
169-
return
170-
else:
171-
return
172-
173-
# Hack: determine at runtime whether we are
174-
# inside ipython in pylab mode.
175-
from matplotlib import pyplot
176-
try:
177-
ipython_pylab = not pyplot.show._needmain
178-
# IPython versions >= 0.10 tack the _needmain
179-
# attribute onto pyplot.show, and always set
180-
# it to False, when in %pylab mode.
181-
ipython_pylab = ipython_pylab and get_backend() != 'WebAgg'
182-
# TODO: The above is a hack to get the WebAgg backend
183-
# working with ipython's `%pylab` mode until proper
184-
# integration is implemented.
185-
except AttributeError:
186-
ipython_pylab = False
187-
188-
# Leave the following as a separate step in case we
189-
# want to control this behavior with an rcParam.
190-
if ipython_pylab:
191-
return
192-
193-
if not is_interactive() or get_backend() == 'WebAgg':
194-
self.mainloop()
251+
Subclass must override mainloop() method.
252+
"""
195253

196-
def mainloop(self):
197-
pass
254+
def __call__(self, block=None):
255+
return self.show(block=block)
198256

199257

200258
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
@@ -394,24 +394,6 @@ def post_processing(image, dpi):
394394
gc, l + ox, height - b - h + oy, img)
395395

396396

397-
def new_figure_manager(num, *args, **kwargs):
398-
"""
399-
Create a new figure manager instance
400-
"""
401-
FigureClass = kwargs.pop('FigureClass', Figure)
402-
thisFig = FigureClass(*args, **kwargs)
403-
return new_figure_manager_given_figure(num, thisFig)
404-
405-
406-
def new_figure_manager_given_figure(num, figure):
407-
"""
408-
Create a new figure manager instance for the given figure.
409-
"""
410-
canvas = FigureCanvasAgg(figure)
411-
manager = FigureManagerBase(canvas, num)
412-
return manager
413-
414-
415397
class FigureCanvasAgg(FigureCanvasBase):
416398
"""
417399
The canvas the figure renders into. Calls the draw and print fig
@@ -606,4 +588,7 @@ def print_tif(self, filename_or_obj, *args, **kwargs):
606588
print_tiff = print_tif
607589

608590

609-
FigureCanvas = FigureCanvasAgg
591+
@_Backend.export
592+
class _BackendAgg(_Backend):
593+
FigureCanvas = FigureCanvasAgg
594+
FigureManager = FigureManagerBase

lib/matplotlib/backends/backend_cairo.py

+10-24
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

@@ -452,24 +453,6 @@ def set_linewidth(self, w):
452453
self.ctx.set_line_width(self.renderer.points_to_pixels(w))
453454

454455

455-
def new_figure_manager(num, *args, **kwargs): # called by backends/__init__.py
456-
"""
457-
Create a new figure manager instance
458-
"""
459-
FigureClass = kwargs.pop('FigureClass', Figure)
460-
thisFig = FigureClass(*args, **kwargs)
461-
return new_figure_manager_given_figure(num, thisFig)
462-
463-
464-
def new_figure_manager_given_figure(num, figure):
465-
"""
466-
Create a new figure manager instance for the given figure.
467-
"""
468-
canvas = FigureCanvasCairo(figure)
469-
manager = FigureManagerBase(canvas, num)
470-
return manager
471-
472-
473456
class FigureCanvasCairo(FigureCanvasBase):
474457
def print_png(self, fobj, *args, **kwargs):
475458
width, height = self.get_width_height()
@@ -555,4 +538,7 @@ def _save(self, fo, fmt, **kwargs):
555538
fo.close()
556539

557540

558-
FigureCanvas = FigureCanvasCairo
541+
@_Backend.export
542+
class _BackendCairo(_Backend):
543+
FigureCanvas = FigureCanvasCairo
544+
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 = FigureCanvasGDK
443+
FigureManager = FigureManagerBase

0 commit comments

Comments
 (0)