Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Refactor backend loading#9551
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Non-interactive FigureManager classes are now aliases of FigureManagerBase | ||
`````````````````````````````````````````````````````````````````````````` | ||
The `FigureManagerPdf`, `FigureManagerPS`, and `FigureManagerSVG` classes, | ||
which were previously empty subclasses of `FigureManagerBase` (i.e., not | ||
adding or overriding any attribute or method), are now direct aliases for | ||
`FigureManagerBase`. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -124,121 +124,6 @@ def get_registered_canvas_class(format): | ||
return backend_class | ||
class RendererBase(object): | ||
"""An abstract base class to handle drawing/rendering operations. | ||
@@ -3328,3 +3213,122 @@ def set_message(self, s): | ||
Message text | ||
""" | ||
pass | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Is this block just a big copy/paste? (so we don't have to bother reviewing the code if it is) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. It is,except for the fact that the FigureManager attribute now defaults to FigureManagerBase. | ||
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. | ||
# `backend_version` may be overridden by the subclass. | ||
backend_version = "unknown" | ||
# The `FigureCanvas` class must be defined. | ||
FigureCanvas = None | ||
# For interactive backends, the `FigureManager` class must be overridden. | ||
FigureManager = FigureManagerBase | ||
# 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. | ||
""" | ||
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. | ||
""" | ||
managers = Gcf.get_all_fig_managers() | ||
if not managers: | ||
return | ||
for manager in managers: | ||
# Emits a warning if the backend is non-interactive. | ||
manager.canvas.figure.show() | ||
if cls.mainloop is None: | ||
return | ||
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 ["backend_version", | ||
"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. | ||
Subclass must override mainloop() method. | ||
""" | ||
def __call__(self, block=None): | ||
return self.show(block=block) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
importimportlib | ||
import logging | ||
import traceback | ||
import matplotlib | ||
from matplotlib.backend_bases import _Backend | ||
_log = logging.getLogger(__name__) | ||
@@ -15,10 +15,11 @@ | ||
def pylab_setup(name=None): | ||
""" | ||
Return new_figure_manager, draw_if_interactive and show for pyplot. | ||
This provides the backend-specific functions that are used by pyplot to | ||
abstract away the difference between backends. | ||
Parameters | ||
---------- | ||
@@ -39,54 +40,25 @@ def pylab_setup(name=None): | ||
show : function | ||
Show (and possibly block) any unshown figures. | ||
""" | ||
# Import the requested backend into a generic module object. | ||
if name is None: | ||
name = matplotlib.get_backend() | ||
backend_name = (name[9:] if name.startswith("module://") | ||
else "matplotlib.backends.backend_{}".format(name.lower())) | ||
backend_mod = importlib.import_module(backend_name) | ||
# Create a local Backend class whose body corresponds to the contents of | ||
# the backend module. This allows the Backend class to fill in the missing | ||
# methods through inheritance. | ||
Backend = type("Backend", (_Backend,), vars(backend_mod)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. So we create the class, use the inheritance to fill in the missing functions and then return class level functions (which 'forget' they were part of the class)? That is quite clever. To check, if a backend is already using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Indeed, backends that were using | ||
# Need to keep a global reference to the backend for compatibility reasons. | ||
# See https://github.com/matplotlib/matplotlib/issues/6092 | ||
global backend | ||
backend = name | ||
_log.debug('backend %s version %s', name, Backend.backend_version) | ||
return (backend_mod, | ||
Backend.new_figure_manager, | ||
Backend.draw_if_interactive, | ||
Backend.show) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1695,8 +1695,7 @@ def pstoeps(tmpfile, bbox=None, rotated=False): | ||
shutil.move(epsfile, tmpfile) | ||
FigureManagerPS = FigureManagerBase | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. There is a very subtle API change in here if people are looking at ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I added an API note. The real intent here is the following: I think the "correct" way to refer to a backend's FigureCanvas, FigureManager, etc. class is under the unsuffixed names (FigureCanvas instead of FigureCanvasPS, etc.) The reason is that 1) these unsuffixed names need to be defined anyways for the backend machinery to pick them up, and 2) some of these classes (FigureManager, Toolbar, though not FigureCanvas of course) can be shared between multiple backends (for example, the xxxcairo backends and mplcairo just reuses the FigureManager classes for each of the GUI toolkits). So sure, I can define (e.g.) FigureManagerQt5 and FigureManagerQt5Agg and FigureManagerQt5Cairo and FigureManagerMplCairoQt to all be aliases (or trivial subclasses) of one another, but that's just muddying the picture IMO; promoting the use of the same FigureManager name everywhere seems clearer. | ||
# The following Python dictionary psDefs contains the entries for the | ||
@@ -1742,4 +1741,3 @@ class FigureManagerPS(FigureManagerBase): | ||
@_Backend.export | ||
class _BackendPS(_Backend): | ||
FigureCanvas = FigureCanvasPS | ||