Skip to content

Backend class for better code reuse between backend modules #8773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 103 additions & 45 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@
from matplotlib import rcParams
from matplotlib import is_interactive
from matplotlib import get_backend
from matplotlib._pylab_helpers import Gcf
from matplotlib import lines
from matplotlib._pylab_helpers import Gcf

from matplotlib.transforms import Bbox, TransformedBbox, Affine2D

Expand Down Expand Up @@ -141,60 +141,118 @@ def get_registered_canvas_class(format):
return backend_class


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

Subclass must override mainloop() method.
"""
def __call__(self, block=None):
# The following attributes and methods must be overridden by subclasses.

# The `FigureCanvas` and `FigureManager` classes must be defined.
FigureCanvas = None
FigureManager = None

# The following methods must be left as None for non-interactive backends.
# For interactive backends, `trigger_manager_draw` should be a function
# taking a manager as argument and triggering a canvas draw, and `mainloop`
# should be a function taking no argument and starting the backend main
# loop.
trigger_manager_draw = None
mainloop = None

# The following methods will be automatically defined and exported, but
# can be overridden.

@classmethod
def new_figure_manager(cls, num, *args, **kwargs):
"""Create a new figure manager instance.
"""
# This import needs to happen here due to circular imports.
from matplotlib.figure import Figure
fig_cls = kwargs.pop('FigureClass', Figure)
fig = fig_cls(*args, **kwargs)
return cls.new_figure_manager_given_figure(num, fig)

@classmethod
def new_figure_manager_given_figure(cls, num, figure):
"""Create a new figure manager instance for the given figure.
"""
Show all figures. If *block* is not None, then
it is a boolean that overrides all other factors
determining whether show blocks by calling mainloop().
The other factors are:
it does not block if run inside ipython's "%pylab" mode
it does not block in interactive mode.
canvas = cls.FigureCanvas(figure)
manager = cls.FigureManager(canvas, num)
return manager

@classmethod
def draw_if_interactive(cls):
if cls.trigger_manager_draw is not None and is_interactive():
manager = Gcf.get_active()
if manager:
cls.trigger_manager_draw(manager)

@classmethod
def show(cls, block=None):
"""Show all figures.

`show` blocks by calling `mainloop` if *block* is ``True``, or if it
is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
`interactive` mode.
"""
if cls.mainloop is None:
return
managers = Gcf.get_all_fig_managers()
if not managers:
return

for manager in managers:
manager.show()
if block is None:
# Hack: Are we in IPython's pylab mode?
from matplotlib import pyplot
try:
# IPython versions >= 0.10 tack the _needmain attribute onto
# pyplot.show, and always set it to False, when in %pylab mode.
ipython_pylab = not pyplot.show._needmain
except AttributeError:
ipython_pylab = False
block = not ipython_pylab and not is_interactive()
# TODO: The above is a hack to get the WebAgg backend working with
# ipython's `%pylab` mode until proper integration is implemented.
if get_backend() == "WebAgg":
block = True
if block:
cls.mainloop()

# This method is the one actually exporting the required methods.

@staticmethod
def export(cls):
for name in ["FigureCanvas",
"FigureManager",
"new_figure_manager",
"new_figure_manager_given_figure",
"draw_if_interactive",
"show"]:
setattr(sys.modules[cls.__module__], name, getattr(cls, name))

# For back-compatibility, generate a shim `Show` class.

class Show(ShowBase):
def mainloop(self):
return cls.mainloop()

setattr(sys.modules[cls.__module__], "Show", Show)
return cls


class ShowBase(_Backend):
"""
Simple base class to generate a show() callable in backends.

if block is not None:
if block:
self.mainloop()
return
else:
return

# Hack: determine at runtime whether we are
# inside ipython in pylab mode.
from matplotlib import pyplot
try:
ipython_pylab = not pyplot.show._needmain
# IPython versions >= 0.10 tack the _needmain
# attribute onto pyplot.show, and always set
# it to False, when in %pylab mode.
ipython_pylab = ipython_pylab and get_backend() != 'WebAgg'
# TODO: The above is a hack to get the WebAgg backend
# working with ipython's `%pylab` mode until proper
# integration is implemented.
except AttributeError:
ipython_pylab = False

# Leave the following as a separate step in case we
# want to control this behavior with an rcParam.
if ipython_pylab:
return

if not is_interactive() or get_backend() == 'WebAgg':
self.mainloop()
Subclass must override mainloop() method.
"""

def mainloop(self):
pass
def __call__(self, block=None):
return self.show(block=block)


class RendererBase(object):
Expand Down
27 changes: 6 additions & 21 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
from collections import OrderedDict
from math import radians, cos, sin
from matplotlib import verbose, rcParams, __version__
from matplotlib.backend_bases import (RendererBase, FigureManagerBase,
FigureCanvasBase)
from matplotlib.backend_bases import (
_Backend, FigureCanvasBase, FigureManagerBase, RendererBase)
from matplotlib.cbook import maxdict, restrict_dict
from matplotlib.figure import Figure
from matplotlib.font_manager import findfont, get_font
Expand Down Expand Up @@ -394,24 +394,6 @@ def post_processing(image, dpi):
gc, l + ox, height - b - h + oy, img)


def new_figure_manager(num, *args, **kwargs):
"""
Create a new figure manager instance
"""
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasAgg(figure)
manager = FigureManagerBase(canvas, num)
return manager


class FigureCanvasAgg(FigureCanvasBase):
"""
The canvas the figure renders into. Calls the draw and print fig
Expand Down Expand Up @@ -606,4 +588,7 @@ def print_tif(self, filename_or_obj, *args, **kwargs):
print_tiff = print_tif


FigureCanvas = FigureCanvasAgg
@_Backend.export
class _BackendAgg(_Backend):
FigureCanvas = FigureCanvasAgg
FigureManager = FigureManagerBase
34 changes: 10 additions & 24 deletions lib/matplotlib/backends/backend_cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@
del _version_required

from matplotlib.backend_bases import (
RendererBase, GraphicsContextBase, FigureManagerBase, FigureCanvasBase)
from matplotlib.figure import Figure
from matplotlib.mathtext import MathTextParser
from matplotlib.path import Path
from matplotlib.transforms import Bbox, Affine2D
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
RendererBase)
from matplotlib.figure import Figure
from matplotlib.mathtext import MathTextParser
from matplotlib.path import Path
from matplotlib.transforms import Bbox, Affine2D
from matplotlib.font_manager import ttfFontProperty


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


def new_figure_manager(num, *args, **kwargs): # called by backends/__init__.py
"""
Create a new figure manager instance
"""
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasCairo(figure)
manager = FigureManagerBase(canvas, num)
return manager


class FigureCanvasCairo(FigureCanvasBase):
def print_png(self, fobj, *args, **kwargs):
width, height = self.get_width_height()
Expand Down Expand Up @@ -555,4 +538,7 @@ def _save(self, fo, fmt, **kwargs):
fo.close()


FigureCanvas = FigureCanvasCairo
@_Backend.export
class _BackendCairo(_Backend):
FigureCanvas = FigureCanvasCairo
FigureManager = FigureManagerBase
27 changes: 8 additions & 19 deletions lib/matplotlib/backends/backend_gdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
from matplotlib import rcParams
from matplotlib._pylab_helpers import Gcf
from matplotlib.backend_bases import (
RendererBase, GraphicsContextBase, FigureManagerBase, FigureCanvasBase)
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
RendererBase)
from matplotlib.cbook import restrict_dict, warn_deprecated
from matplotlib.figure import Figure
from matplotlib.mathtext import MathTextParser
Expand Down Expand Up @@ -381,24 +382,6 @@ def set_linewidth(self, w):
self.gdkGC.line_width = max(1, int(np.round(pixels)))


def new_figure_manager(num, *args, **kwargs):
"""
Create a new figure manager instance
"""
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasGDK(figure)
manager = FigureManagerBase(canvas, num)
return manager


class FigureCanvasGDK (FigureCanvasBase):
def __init__(self, figure):
FigureCanvasBase.__init__(self, figure)
Expand Down Expand Up @@ -452,3 +435,9 @@ def _print_image(self, filename, format, *args, **kwargs):
options['quality'] = str(options['quality'])

pixbuf.save(filename, format, options=options)


@_Backend.export
class _BackendGDK(_Backend):
FigureCanvas = FigureCanvasGDK
FigureManager = FigureManagerBase
Loading