Spaces:
Running
Running
| import os | |
| import matplotlib as mpl | |
| from matplotlib import _api, cbook | |
| from matplotlib._pylab_helpers import Gcf | |
| from . import _macosx | |
| from .backend_agg import FigureCanvasAgg | |
| from matplotlib.backend_bases import ( | |
| _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, | |
| ResizeEvent, TimerBase, _allow_interrupt) | |
| class TimerMac(_macosx.Timer, TimerBase): | |
| """Subclass of `.TimerBase` using CFRunLoop timer events.""" | |
| # completely implemented at the C-level (in _macosx.Timer) | |
| def _allow_interrupt_macos(): | |
| """A context manager that allows terminating a plot by sending a SIGINT.""" | |
| return _allow_interrupt( | |
| lambda rsock: _macosx.wake_on_fd_write(rsock.fileno()), _macosx.stop) | |
| class FigureCanvasMac(FigureCanvasAgg, _macosx.FigureCanvas, FigureCanvasBase): | |
| # docstring inherited | |
| # Ideally this class would be `class FCMacAgg(FCAgg, FCMac)` | |
| # (FC=FigureCanvas) where FCMac would be an ObjC-implemented mac-specific | |
| # class also inheriting from FCBase (this is the approach with other GUI | |
| # toolkits). However, writing an extension type inheriting from a Python | |
| # base class is slightly tricky (the extension type must be a heap type), | |
| # and we can just as well lift the FCBase base up one level, keeping it *at | |
| # the end* to have the right method resolution order. | |
| # Events such as button presses, mouse movements, and key presses are | |
| # handled in C and events (MouseEvent, etc.) are triggered from there. | |
| required_interactive_framework = "macosx" | |
| _timer_cls = TimerMac | |
| manager_class = _api.classproperty(lambda cls: FigureManagerMac) | |
| def __init__(self, figure): | |
| super().__init__(figure=figure) | |
| self._draw_pending = False | |
| self._is_drawing = False | |
| # Keep track of the timers that are alive | |
| self._timers = set() | |
| def draw(self): | |
| """Render the figure and update the macosx canvas.""" | |
| # The renderer draw is done here; delaying causes problems with code | |
| # that uses the result of the draw() to update plot elements. | |
| if self._is_drawing: | |
| return | |
| with cbook._setattr_cm(self, _is_drawing=True): | |
| super().draw() | |
| self.update() | |
| def draw_idle(self): | |
| # docstring inherited | |
| if not (getattr(self, '_draw_pending', False) or | |
| getattr(self, '_is_drawing', False)): | |
| self._draw_pending = True | |
| # Add a singleshot timer to the eventloop that will call back | |
| # into the Python method _draw_idle to take care of the draw | |
| self._single_shot_timer(self._draw_idle) | |
| def _single_shot_timer(self, callback): | |
| """Add a single shot timer with the given callback""" | |
| def callback_func(callback, timer): | |
| callback() | |
| self._timers.remove(timer) | |
| timer = self.new_timer(interval=0) | |
| timer.single_shot = True | |
| timer.add_callback(callback_func, callback, timer) | |
| self._timers.add(timer) | |
| timer.start() | |
| def _draw_idle(self): | |
| """ | |
| Draw method for singleshot timer | |
| This draw method can be added to a singleshot timer, which can | |
| accumulate draws while the eventloop is spinning. This method will | |
| then only draw the first time and short-circuit the others. | |
| """ | |
| with self._idle_draw_cntx(): | |
| if not self._draw_pending: | |
| # Short-circuit because our draw request has already been | |
| # taken care of | |
| return | |
| self._draw_pending = False | |
| self.draw() | |
| def blit(self, bbox=None): | |
| # docstring inherited | |
| super().blit(bbox) | |
| self.update() | |
| def resize(self, width, height): | |
| # Size from macOS is logical pixels, dpi is physical. | |
| scale = self.figure.dpi / self.device_pixel_ratio | |
| width /= scale | |
| height /= scale | |
| self.figure.set_size_inches(width, height, forward=False) | |
| ResizeEvent("resize_event", self)._process() | |
| self.draw_idle() | |
| def start_event_loop(self, timeout=0): | |
| # docstring inherited | |
| # Set up a SIGINT handler to allow terminating a plot via CTRL-C. | |
| with _allow_interrupt_macos(): | |
| self._start_event_loop(timeout=timeout) # Forward to ObjC implementation. | |
| class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2): | |
| def __init__(self, canvas): | |
| data_path = cbook._get_data_path('images') | |
| _, tooltips, image_names, _ = zip(*NavigationToolbar2.toolitems) | |
| _macosx.NavigationToolbar2.__init__( | |
| self, canvas, | |
| tuple(str(data_path / image_name) + ".pdf" | |
| for image_name in image_names if image_name is not None), | |
| tuple(tooltip for tooltip in tooltips if tooltip is not None)) | |
| NavigationToolbar2.__init__(self, canvas) | |
| def draw_rubberband(self, event, x0, y0, x1, y1): | |
| self.canvas.set_rubberband(int(x0), int(y0), int(x1), int(y1)) | |
| def remove_rubberband(self): | |
| self.canvas.remove_rubberband() | |
| def save_figure(self, *args): | |
| directory = os.path.expanduser(mpl.rcParams['savefig.directory']) | |
| filename = _macosx.choose_save_file('Save the figure', | |
| directory, | |
| self.canvas.get_default_filename()) | |
| if filename is None: # Cancel | |
| return | |
| # Save dir for next time, unless empty str (which means use cwd). | |
| if mpl.rcParams['savefig.directory']: | |
| mpl.rcParams['savefig.directory'] = os.path.dirname(filename) | |
| self.canvas.figure.savefig(filename) | |
| return filename | |
| class FigureManagerMac(_macosx.FigureManager, FigureManagerBase): | |
| _toolbar2_class = NavigationToolbar2Mac | |
| def __init__(self, canvas, num): | |
| self._shown = False | |
| _macosx.FigureManager.__init__(self, canvas) | |
| icon_path = str(cbook._get_data_path('images/matplotlib.pdf')) | |
| _macosx.FigureManager.set_icon(icon_path) | |
| FigureManagerBase.__init__(self, canvas, num) | |
| self._set_window_mode(mpl.rcParams["macosx.window_mode"]) | |
| if self.toolbar is not None: | |
| self.toolbar.update() | |
| if mpl.is_interactive(): | |
| self.show() | |
| self.canvas.draw_idle() | |
| def _close_button_pressed(self): | |
| Gcf.destroy(self) | |
| self.canvas.flush_events() | |
| def destroy(self): | |
| # We need to clear any pending timers that never fired, otherwise | |
| # we get a memory leak from the timer callbacks holding a reference | |
| while self.canvas._timers: | |
| timer = self.canvas._timers.pop() | |
| timer.stop() | |
| super().destroy() | |
| def start_main_loop(cls): | |
| # Set up a SIGINT handler to allow terminating a plot via CTRL-C. | |
| with _allow_interrupt_macos(): | |
| _macosx.show() | |
| def show(self): | |
| if self.canvas.figure.stale: | |
| self.canvas.draw_idle() | |
| if not self._shown: | |
| self._show() | |
| self._shown = True | |
| if mpl.rcParams["figure.raise_window"]: | |
| self._raise() | |
| class _BackendMac(_Backend): | |
| FigureCanvas = FigureCanvasMac | |
| FigureManager = FigureManagerMac | |
| mainloop = FigureManagerMac.start_main_loop | |