Buckets:
ktongue/docker_container / simsite /venv /lib /python3.14 /site-packages /matplotlib /backend_tools.py
| """ | |
| Abstract base classes define the primitives for Tools. | |
| These tools are used by `matplotlib.backend_managers.ToolManager` | |
| :class:`ToolBase` | |
| Simple stateless tool | |
| :class:`ToolToggleBase` | |
| Tool that has two states, only one Toggle tool can be | |
| active at any given time for the same | |
| `matplotlib.backend_managers.ToolManager` | |
| """ | |
| import enum | |
| import functools | |
| import re | |
| import time | |
| from types import SimpleNamespace | |
| import uuid | |
| from weakref import WeakKeyDictionary | |
| import numpy as np | |
| import matplotlib as mpl | |
| from matplotlib._pylab_helpers import Gcf | |
| from matplotlib import _api, cbook | |
| class Cursors(enum.IntEnum): # Must subclass int for the macOS backend. | |
| """Backend-independent cursor types.""" | |
| POINTER = enum.auto() | |
| HAND = enum.auto() | |
| SELECT_REGION = enum.auto() | |
| MOVE = enum.auto() | |
| WAIT = enum.auto() | |
| RESIZE_HORIZONTAL = enum.auto() | |
| RESIZE_VERTICAL = enum.auto() | |
| cursors = Cursors # Backcompat. | |
| # _tool_registry, _register_tool_class, and _find_tool_class implement a | |
| # mechanism through which ToolManager.add_tool can determine whether a subclass | |
| # of the requested tool class has been registered (either for the current | |
| # canvas class or for a parent class), in which case that tool subclass will be | |
| # instantiated instead. This is the mechanism used e.g. to allow different | |
| # GUI backends to implement different specializations for ConfigureSubplots. | |
| _tool_registry = set() | |
| def _register_tool_class(canvas_cls, tool_cls=None): | |
| """Decorator registering *tool_cls* as a tool class for *canvas_cls*.""" | |
| if tool_cls is None: | |
| return functools.partial(_register_tool_class, canvas_cls) | |
| _tool_registry.add((canvas_cls, tool_cls)) | |
| return tool_cls | |
| def _find_tool_class(canvas_cls, tool_cls): | |
| """Find a subclass of *tool_cls* registered for *canvas_cls*.""" | |
| for canvas_parent in canvas_cls.__mro__: | |
| for tool_child in _api.recursive_subclasses(tool_cls): | |
| if (canvas_parent, tool_child) in _tool_registry: | |
| return tool_child | |
| return tool_cls | |
| # Views positions tool | |
| _views_positions = 'viewpos' | |
| class ToolBase: | |
| """ | |
| Base tool class. | |
| A base tool, only implements `trigger` method or no method at all. | |
| The tool is instantiated by `matplotlib.backend_managers.ToolManager`. | |
| """ | |
| default_keymap = None | |
| """ | |
| Keymap to associate with this tool. | |
| ``list[str]``: List of keys that will trigger this tool when a keypress | |
| event is emitted on ``self.figure.canvas``. Note that this attribute is | |
| looked up on the instance, and can therefore be a property (this is used | |
| e.g. by the built-in tools to load the rcParams at instantiation time). | |
| """ | |
| description = None | |
| """ | |
| Description of the Tool. | |
| `str`: Tooltip used if the Tool is included in a Toolbar. | |
| """ | |
| image = None | |
| """ | |
| Icon filename. | |
| ``str | None``: Filename of the Toolbar icon; either absolute, or relative to the | |
| directory containing the Python source file where the ``Tool.image`` class attribute | |
| is defined (in the latter case, this cannot be defined as an instance attribute). | |
| In either case, the extension is optional; leaving it off lets individual backends | |
| select the icon format they prefer. If None, the *name* is used as a label in the | |
| toolbar button. | |
| """ | |
| def __init__(self, toolmanager, name): | |
| self._name = name | |
| self._toolmanager = toolmanager | |
| self._figure = None | |
| name = property( | |
| lambda self: self._name, | |
| doc="The tool id (str, must be unique among tools of a tool manager).") | |
| toolmanager = property( | |
| lambda self: self._toolmanager, | |
| doc="The `.ToolManager` that controls this tool.") | |
| canvas = property( | |
| lambda self: self._figure.canvas if self._figure is not None else None, | |
| doc="The canvas of the figure affected by this tool, or None.") | |
| def set_figure(self, figure): | |
| self._figure = figure | |
| figure = property( | |
| lambda self: self._figure, | |
| # The setter must explicitly call self.set_figure so that subclasses can | |
| # meaningfully override it. | |
| lambda self, figure: self.set_figure(figure), | |
| doc="The Figure affected by this tool, or None.") | |
| def _make_classic_style_pseudo_toolbar(self): | |
| """ | |
| Return a placeholder object with a single `canvas` attribute. | |
| This is useful to reuse the implementations of tools already provided | |
| by the classic Toolbars. | |
| """ | |
| return SimpleNamespace(canvas=self.canvas) | |
| def trigger(self, sender, event, data=None): | |
| """ | |
| Called when this tool gets used. | |
| This method is called by `.ToolManager.trigger_tool`. | |
| Parameters | |
| ---------- | |
| event : `.Event` | |
| The canvas event that caused this tool to be called. | |
| sender : object | |
| Object that requested the tool to be triggered. | |
| data : object | |
| Extra data. | |
| """ | |
| pass | |
| class ToolToggleBase(ToolBase): | |
| """ | |
| Toggleable tool. | |
| Every time it is triggered, it switches between enable and disable. | |
| Parameters | |
| ---------- | |
| ``*args`` | |
| Variable length argument to be used by the Tool. | |
| ``**kwargs`` | |
| `toggled` if present and True, sets the initial state of the Tool | |
| Arbitrary keyword arguments to be consumed by the Tool | |
| """ | |
| radio_group = None | |
| """ | |
| Attribute to group 'radio' like tools (mutually exclusive). | |
| `str` that identifies the group or **None** if not belonging to a group. | |
| """ | |
| cursor = None | |
| """Cursor to use when the tool is active.""" | |
| default_toggled = False | |
| """Default of toggled state.""" | |
| def __init__(self, *args, **kwargs): | |
| self._toggled = kwargs.pop('toggled', self.default_toggled) | |
| super().__init__(*args, **kwargs) | |
| def trigger(self, sender, event, data=None): | |
| """Calls `enable` or `disable` based on `toggled` value.""" | |
| if self._toggled: | |
| self.disable(event) | |
| else: | |
| self.enable(event) | |
| self._toggled = not self._toggled | |
| def enable(self, event=None): | |
| """ | |
| Enable the toggle tool. | |
| `trigger` calls this method when `toggled` is False. | |
| """ | |
| pass | |
| def disable(self, event=None): | |
| """ | |
| Disable the toggle tool. | |
| `trigger` call this method when `toggled` is True. | |
| This can happen in different circumstances. | |
| * Click on the toolbar tool button. | |
| * Call to `matplotlib.backend_managers.ToolManager.trigger_tool`. | |
| * Another `ToolToggleBase` derived tool is triggered | |
| (from the same `.ToolManager`). | |
| """ | |
| pass | |
| def toggled(self): | |
| """State of the toggled tool.""" | |
| return self._toggled | |
| def set_figure(self, figure): | |
| toggled = self.toggled | |
| if toggled: | |
| if self.figure: | |
| self.trigger(self, None) | |
| else: | |
| # if no figure the internal state is not changed | |
| # we change it here so next call to trigger will change it back | |
| self._toggled = False | |
| super().set_figure(figure) | |
| if toggled: | |
| if figure: | |
| self.trigger(self, None) | |
| else: | |
| # if there is no figure, trigger won't change the internal | |
| # state we change it back | |
| self._toggled = True | |
| class ToolSetCursor(ToolBase): | |
| """ | |
| Change to the current cursor while inaxes. | |
| This tool, keeps track of all `ToolToggleBase` derived tools, and updates | |
| the cursor when a tool gets triggered. | |
| """ | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self._id_drag = None | |
| self._current_tool = None | |
| self._default_cursor = cursors.POINTER | |
| self._last_cursor = self._default_cursor | |
| self.toolmanager.toolmanager_connect('tool_added_event', | |
| self._add_tool_cbk) | |
| for tool in self.toolmanager.tools.values(): # process current tools | |
| self._add_tool_cbk(mpl.backend_managers.ToolEvent( | |
| 'tool_added_event', self.toolmanager, tool)) | |
| def set_figure(self, figure): | |
| if self._id_drag: | |
| self.canvas.mpl_disconnect(self._id_drag) | |
| super().set_figure(figure) | |
| if figure: | |
| self._id_drag = self.canvas.mpl_connect( | |
| 'motion_notify_event', self._set_cursor_cbk) | |
| def _add_tool_cbk(self, event): | |
| """Process every newly added tool.""" | |
| if getattr(event.tool, 'cursor', None) is not None: | |
| self.toolmanager.toolmanager_connect( | |
| f'tool_trigger_{event.tool.name}', self._tool_trigger_cbk) | |
| def _tool_trigger_cbk(self, event): | |
| self._current_tool = event.tool if event.tool.toggled else None | |
| self._set_cursor_cbk(event.canvasevent) | |
| def _set_cursor_cbk(self, event): | |
| if not event or not self.canvas: | |
| return | |
| if (self._current_tool and getattr(event, "inaxes", None) | |
| and event.inaxes.get_navigate()): | |
| if self._last_cursor != self._current_tool.cursor: | |
| self.canvas.set_cursor(self._current_tool.cursor) | |
| self._last_cursor = self._current_tool.cursor | |
| elif self._last_cursor != self._default_cursor: | |
| self.canvas.set_cursor(self._default_cursor) | |
| self._last_cursor = self._default_cursor | |
| class ToolCursorPosition(ToolBase): | |
| """ | |
| Send message with the current pointer position. | |
| This tool runs in the background reporting the position of the cursor. | |
| """ | |
| def __init__(self, *args, **kwargs): | |
| self._id_drag = None | |
| super().__init__(*args, **kwargs) | |
| def set_figure(self, figure): | |
| if self._id_drag: | |
| self.canvas.mpl_disconnect(self._id_drag) | |
| super().set_figure(figure) | |
| if figure: | |
| self._id_drag = self.canvas.mpl_connect( | |
| 'motion_notify_event', self.send_message) | |
| def send_message(self, event): | |
| """Call `matplotlib.backend_managers.ToolManager.message_event`.""" | |
| if self.toolmanager.messagelock.locked(): | |
| return | |
| from matplotlib.backend_bases import NavigationToolbar2 | |
| message = NavigationToolbar2._mouse_event_to_message(event) | |
| self.toolmanager.message_event(message, self) | |
| class RubberbandBase(ToolBase): | |
| """Draw and remove a rubberband.""" | |
| def trigger(self, sender, event, data=None): | |
| """Call `draw_rubberband` or `remove_rubberband` based on data.""" | |
| if not self.figure.canvas.widgetlock.available(sender): | |
| return | |
| if data is not None: | |
| self.draw_rubberband(*data) | |
| else: | |
| self.remove_rubberband() | |
| def draw_rubberband(self, *data): | |
| """ | |
| Draw rubberband. | |
| This method must get implemented per backend. | |
| """ | |
| raise NotImplementedError | |
| def remove_rubberband(self): | |
| """ | |
| Remove rubberband. | |
| This method should get implemented per backend. | |
| """ | |
| pass | |
| class ToolQuit(ToolBase): | |
| """Tool to call the figure manager destroy method.""" | |
| description = 'Quit the figure' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.quit']) | |
| def trigger(self, sender, event, data=None): | |
| Gcf.destroy_fig(self.figure) | |
| class ToolQuitAll(ToolBase): | |
| """Tool to call the figure manager destroy method.""" | |
| description = 'Quit all figures' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.quit_all']) | |
| def trigger(self, sender, event, data=None): | |
| Gcf.destroy_all() | |
| class ToolGrid(ToolBase): | |
| """Tool to toggle the major grids of the figure.""" | |
| description = 'Toggle major grids' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.grid']) | |
| def trigger(self, sender, event, data=None): | |
| sentinel = str(uuid.uuid4()) | |
| # Trigger grid switching by temporarily setting :rc:`keymap.grid` | |
| # to a unique key and sending an appropriate event. | |
| with (cbook._setattr_cm(event, key=sentinel), | |
| mpl.rc_context({'keymap.grid': sentinel})): | |
| mpl.backend_bases.key_press_handler(event, self.figure.canvas) | |
| class ToolMinorGrid(ToolBase): | |
| """Tool to toggle the major and minor grids of the figure.""" | |
| description = 'Toggle major and minor grids' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.grid_minor']) | |
| def trigger(self, sender, event, data=None): | |
| sentinel = str(uuid.uuid4()) | |
| # Trigger grid switching by temporarily setting :rc:`keymap.grid_minor` | |
| # to a unique key and sending an appropriate event. | |
| with (cbook._setattr_cm(event, key=sentinel), | |
| mpl.rc_context({'keymap.grid_minor': sentinel})): | |
| mpl.backend_bases.key_press_handler(event, self.figure.canvas) | |
| class ToolFullScreen(ToolBase): | |
| """Tool to toggle full screen.""" | |
| description = 'Toggle fullscreen mode' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.fullscreen']) | |
| def trigger(self, sender, event, data=None): | |
| self.figure.canvas.manager.full_screen_toggle() | |
| class AxisScaleBase(ToolToggleBase): | |
| """Base Tool to toggle between linear and logarithmic.""" | |
| def trigger(self, sender, event, data=None): | |
| if event.inaxes is None: | |
| return | |
| super().trigger(sender, event, data) | |
| def enable(self, event=None): | |
| self.set_scale(event.inaxes, 'log') | |
| self.figure.canvas.draw_idle() | |
| def disable(self, event=None): | |
| self.set_scale(event.inaxes, 'linear') | |
| self.figure.canvas.draw_idle() | |
| class ToolYScale(AxisScaleBase): | |
| """Tool to toggle between linear and logarithmic scales on the Y axis.""" | |
| description = 'Toggle scale Y axis' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.yscale']) | |
| def set_scale(self, ax, scale): | |
| ax.set_yscale(scale) | |
| class ToolXScale(AxisScaleBase): | |
| """Tool to toggle between linear and logarithmic scales on the X axis.""" | |
| description = 'Toggle scale X axis' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.xscale']) | |
| def set_scale(self, ax, scale): | |
| ax.set_xscale(scale) | |
| class ToolViewsPositions(ToolBase): | |
| """ | |
| Auxiliary Tool to handle changes in views and positions. | |
| Runs in the background and should get used by all the tools that | |
| need to access the figure's history of views and positions, e.g. | |
| * `ToolZoom` | |
| * `ToolPan` | |
| * `ToolHome` | |
| * `ToolBack` | |
| * `ToolForward` | |
| """ | |
| def __init__(self, *args, **kwargs): | |
| self.views = WeakKeyDictionary() | |
| self.positions = WeakKeyDictionary() | |
| self.home_views = WeakKeyDictionary() | |
| super().__init__(*args, **kwargs) | |
| def add_figure(self, figure): | |
| """Add the current figure to the stack of views and positions.""" | |
| if figure not in self.views: | |
| self.views[figure] = cbook._Stack() | |
| self.positions[figure] = cbook._Stack() | |
| self.home_views[figure] = WeakKeyDictionary() | |
| # Define Home | |
| self.push_current(figure) | |
| # Make sure we add a home view for new Axes as they're added | |
| figure.add_axobserver(lambda fig: self.update_home_views(fig)) | |
| def clear(self, figure): | |
| """Reset the Axes stack.""" | |
| if figure in self.views: | |
| self.views[figure].clear() | |
| self.positions[figure].clear() | |
| self.home_views[figure].clear() | |
| self.update_home_views() | |
| def update_view(self): | |
| """ | |
| Update the view limits and position for each Axes from the current | |
| stack position. If any Axes are present in the figure that aren't in | |
| the current stack position, use the home view limits for those Axes and | |
| don't update *any* positions. | |
| """ | |
| views = self.views[self.figure]() | |
| if views is None: | |
| return | |
| pos = self.positions[self.figure]() | |
| if pos is None: | |
| return | |
| home_views = self.home_views[self.figure] | |
| all_axes = self.figure.get_axes() | |
| for a in all_axes: | |
| if a in views: | |
| cur_view = views[a] | |
| else: | |
| cur_view = home_views[a] | |
| a._set_view(cur_view) | |
| if set(all_axes).issubset(pos): | |
| for a in all_axes: | |
| # Restore both the original and modified positions | |
| a._set_position(pos[a][0], 'original') | |
| a._set_position(pos[a][1], 'active') | |
| self.figure.canvas.draw_idle() | |
| def push_current(self, figure=None): | |
| """ | |
| Push the current view limits and position onto their respective stacks. | |
| """ | |
| if not figure: | |
| figure = self.figure | |
| views = WeakKeyDictionary() | |
| pos = WeakKeyDictionary() | |
| for a in figure.get_axes(): | |
| views[a] = a._get_view() | |
| pos[a] = self._axes_pos(a) | |
| self.views[figure].push(views) | |
| self.positions[figure].push(pos) | |
| def _axes_pos(self, ax): | |
| """ | |
| Return the original and modified positions for the specified Axes. | |
| Parameters | |
| ---------- | |
| ax : matplotlib.axes.Axes | |
| The `.Axes` to get the positions for. | |
| Returns | |
| ------- | |
| original_position, modified_position | |
| A tuple of the original and modified positions. | |
| """ | |
| return (ax.get_position(True).frozen(), | |
| ax.get_position().frozen()) | |
| def update_home_views(self, figure=None): | |
| """ | |
| Make sure that ``self.home_views`` has an entry for all Axes present | |
| in the figure. | |
| """ | |
| if not figure: | |
| figure = self.figure | |
| for a in figure.get_axes(): | |
| if a not in self.home_views[figure]: | |
| self.home_views[figure][a] = a._get_view() | |
| def home(self): | |
| """Recall the first view and position from the stack.""" | |
| self.views[self.figure].home() | |
| self.positions[self.figure].home() | |
| def back(self): | |
| """Back one step in the stack of views and positions.""" | |
| self.views[self.figure].back() | |
| self.positions[self.figure].back() | |
| def forward(self): | |
| """Forward one step in the stack of views and positions.""" | |
| self.views[self.figure].forward() | |
| self.positions[self.figure].forward() | |
| class ViewsPositionsBase(ToolBase): | |
| """Base class for `ToolHome`, `ToolBack` and `ToolForward`.""" | |
| _on_trigger = None | |
| def trigger(self, sender, event, data=None): | |
| self.toolmanager.get_tool(_views_positions).add_figure(self.figure) | |
| getattr(self.toolmanager.get_tool(_views_positions), | |
| self._on_trigger)() | |
| self.toolmanager.get_tool(_views_positions).update_view() | |
| class ToolHome(ViewsPositionsBase): | |
| """Restore the original view limits.""" | |
| description = 'Reset original view' | |
| image = 'mpl-data/images/home' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.home']) | |
| _on_trigger = 'home' | |
| class ToolBack(ViewsPositionsBase): | |
| """Move back up the view limits stack.""" | |
| description = 'Back to previous view' | |
| image = 'mpl-data/images/back' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.back']) | |
| _on_trigger = 'back' | |
| class ToolForward(ViewsPositionsBase): | |
| """Move forward in the view lim stack.""" | |
| description = 'Forward to next view' | |
| image = 'mpl-data/images/forward' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.forward']) | |
| _on_trigger = 'forward' | |
| class ConfigureSubplotsBase(ToolBase): | |
| """Base tool for the configuration of subplots.""" | |
| description = 'Configure subplots' | |
| image = 'mpl-data/images/subplots' | |
| class SaveFigureBase(ToolBase): | |
| """Base tool for figure saving.""" | |
| description = 'Save the figure' | |
| image = 'mpl-data/images/filesave' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.save']) | |
| class ZoomPanBase(ToolToggleBase): | |
| """Base class for `ToolZoom` and `ToolPan`.""" | |
| def __init__(self, *args): | |
| super().__init__(*args) | |
| self._button_pressed = None | |
| self._xypress = None | |
| self._idPress = None | |
| self._idRelease = None | |
| self._idScroll = None | |
| self.base_scale = 2. | |
| self.scrollthresh = .5 # .5 second scroll threshold | |
| self.lastscroll = time.time()-self.scrollthresh | |
| def enable(self, event=None): | |
| """Connect press/release events and lock the canvas.""" | |
| self.figure.canvas.widgetlock(self) | |
| self._idPress = self.figure.canvas.mpl_connect( | |
| 'button_press_event', self._press) | |
| self._idRelease = self.figure.canvas.mpl_connect( | |
| 'button_release_event', self._release) | |
| self._idScroll = self.figure.canvas.mpl_connect( | |
| 'scroll_event', self.scroll_zoom) | |
| def disable(self, event=None): | |
| """Release the canvas and disconnect press/release events.""" | |
| self._cancel_action() | |
| self.figure.canvas.widgetlock.release(self) | |
| self.figure.canvas.mpl_disconnect(self._idPress) | |
| self.figure.canvas.mpl_disconnect(self._idRelease) | |
| self.figure.canvas.mpl_disconnect(self._idScroll) | |
| def trigger(self, sender, event, data=None): | |
| self.toolmanager.get_tool(_views_positions).add_figure(self.figure) | |
| super().trigger(sender, event, data) | |
| new_navigate_mode = self.name.upper() if self.toggled else None | |
| for ax in self.figure.axes: | |
| ax.set_navigate_mode(new_navigate_mode) | |
| def scroll_zoom(self, event): | |
| # https://gist.github.com/tacaswell/3144287 | |
| if event.inaxes is None: | |
| return | |
| if event.button == 'up': | |
| # deal with zoom in | |
| scl = self.base_scale | |
| elif event.button == 'down': | |
| # deal with zoom out | |
| scl = 1/self.base_scale | |
| else: | |
| # deal with something that should never happen | |
| scl = 1 | |
| ax = event.inaxes | |
| ax._set_view_from_bbox([event.x, event.y, scl]) | |
| # If last scroll was done within the timing threshold, delete the | |
| # previous view | |
| if (time.time()-self.lastscroll) < self.scrollthresh: | |
| self.toolmanager.get_tool(_views_positions).back() | |
| self.figure.canvas.draw_idle() # force re-draw | |
| self.lastscroll = time.time() | |
| self.toolmanager.get_tool(_views_positions).push_current() | |
| class ToolZoom(ZoomPanBase): | |
| """A Tool for zooming using a rectangle selector.""" | |
| description = 'Zoom to rectangle' | |
| image = 'mpl-data/images/zoom_to_rect' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.zoom']) | |
| cursor = cursors.SELECT_REGION | |
| radio_group = 'default' | |
| def __init__(self, *args): | |
| super().__init__(*args) | |
| self._ids_zoom = [] | |
| def _cancel_action(self): | |
| for zoom_id in self._ids_zoom: | |
| self.figure.canvas.mpl_disconnect(zoom_id) | |
| self.toolmanager.trigger_tool('rubberband', self) | |
| self.figure.canvas.draw_idle() | |
| self._xypress = None | |
| self._button_pressed = None | |
| self._ids_zoom = [] | |
| return | |
| def _press(self, event): | |
| """Callback for mouse button presses in zoom-to-rectangle mode.""" | |
| # If we're already in the middle of a zoom, pressing another | |
| # button works to "cancel" | |
| if self._ids_zoom: | |
| self._cancel_action() | |
| if event.button == 1: | |
| self._button_pressed = 1 | |
| elif event.button == 3: | |
| self._button_pressed = 3 | |
| else: | |
| self._cancel_action() | |
| return | |
| x, y = event.x, event.y | |
| self._xypress = [] | |
| for i, a in enumerate(self.figure.get_axes()): | |
| if (x is not None and y is not None and a.in_axes(event) and | |
| a.get_navigate() and a.can_zoom()): | |
| self._xypress.append((x, y, a, i, a._get_view())) | |
| id1 = self.figure.canvas.mpl_connect( | |
| 'motion_notify_event', self._mouse_move) | |
| id2 = self.figure.canvas.mpl_connect( | |
| 'key_press_event', self._switch_on_zoom_mode) | |
| id3 = self.figure.canvas.mpl_connect( | |
| 'key_release_event', self._switch_off_zoom_mode) | |
| self._ids_zoom = id1, id2, id3 | |
| self._zoom_mode = event.key | |
| def _switch_on_zoom_mode(self, event): | |
| self._zoom_mode = event.key | |
| self._mouse_move(event) | |
| def _switch_off_zoom_mode(self, event): | |
| self._zoom_mode = None | |
| self._mouse_move(event) | |
| def _mouse_move(self, event): | |
| """Callback for mouse moves in zoom-to-rectangle mode.""" | |
| if self._xypress: | |
| x, y = event.x, event.y | |
| lastx, lasty, a, ind, view = self._xypress[0] | |
| (x1, y1), (x2, y2) = np.clip( | |
| [[lastx, lasty], [x, y]], a.bbox.min, a.bbox.max) | |
| if self._zoom_mode == "x": | |
| y1, y2 = a.bbox.intervaly | |
| elif self._zoom_mode == "y": | |
| x1, x2 = a.bbox.intervalx | |
| self.toolmanager.trigger_tool( | |
| 'rubberband', self, data=(x1, y1, x2, y2)) | |
| def _release(self, event): | |
| """Callback for mouse button releases in zoom-to-rectangle mode.""" | |
| for zoom_id in self._ids_zoom: | |
| self.figure.canvas.mpl_disconnect(zoom_id) | |
| self._ids_zoom = [] | |
| if not self._xypress: | |
| self._cancel_action() | |
| return | |
| done_ax = [] | |
| for cur_xypress in self._xypress: | |
| x, y = event.x, event.y | |
| lastx, lasty, a, _ind, view = cur_xypress | |
| # ignore singular clicks - 5 pixels is a threshold | |
| if abs(x - lastx) < 5 or abs(y - lasty) < 5: | |
| self._cancel_action() | |
| return | |
| # detect twinx, twiny Axes and avoid double zooming | |
| twinx = any(a.get_shared_x_axes().joined(a, a1) for a1 in done_ax) | |
| twiny = any(a.get_shared_y_axes().joined(a, a1) for a1 in done_ax) | |
| done_ax.append(a) | |
| if self._button_pressed == 1: | |
| direction = 'in' | |
| elif self._button_pressed == 3: | |
| direction = 'out' | |
| else: | |
| continue | |
| a._set_view_from_bbox((lastx, lasty, x, y), direction, | |
| self._zoom_mode, twinx, twiny) | |
| self._zoom_mode = None | |
| self.toolmanager.get_tool(_views_positions).push_current() | |
| self._cancel_action() | |
| class ToolPan(ZoomPanBase): | |
| """Pan Axes with left mouse, zoom with right.""" | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.pan']) | |
| description = 'Pan axes with left mouse, zoom with right' | |
| image = 'mpl-data/images/move' | |
| cursor = cursors.MOVE | |
| radio_group = 'default' | |
| def __init__(self, *args): | |
| super().__init__(*args) | |
| self._id_drag = None | |
| def _cancel_action(self): | |
| self._button_pressed = None | |
| self._xypress = [] | |
| self.figure.canvas.mpl_disconnect(self._id_drag) | |
| self.toolmanager.messagelock.release(self) | |
| self.figure.canvas.draw_idle() | |
| def _press(self, event): | |
| if event.button == 1: | |
| self._button_pressed = 1 | |
| elif event.button == 3: | |
| self._button_pressed = 3 | |
| else: | |
| self._cancel_action() | |
| return | |
| x, y = event.x, event.y | |
| self._xypress = [] | |
| for i, a in enumerate(self.figure.get_axes()): | |
| if (x is not None and y is not None and a.in_axes(event) and | |
| a.get_navigate() and a.can_pan()): | |
| a.start_pan(x, y, event.button) | |
| self._xypress.append((a, i)) | |
| self.toolmanager.messagelock(self) | |
| self._id_drag = self.figure.canvas.mpl_connect( | |
| 'motion_notify_event', self._mouse_move) | |
| def _release(self, event): | |
| if self._button_pressed is None: | |
| self._cancel_action() | |
| return | |
| self.figure.canvas.mpl_disconnect(self._id_drag) | |
| self.toolmanager.messagelock.release(self) | |
| for a, _ind in self._xypress: | |
| a.end_pan() | |
| if not self._xypress: | |
| self._cancel_action() | |
| return | |
| self.toolmanager.get_tool(_views_positions).push_current() | |
| self._cancel_action() | |
| def _mouse_move(self, event): | |
| for a, _ind in self._xypress: | |
| # safer to use the recorded button at the _press than current | |
| # button: # multiple button can get pressed during motion... | |
| a.drag_pan(self._button_pressed, event.key, event.x, event.y) | |
| self.toolmanager.canvas.draw_idle() | |
| class ToolHelpBase(ToolBase): | |
| description = 'Print tool list, shortcuts and description' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.help']) | |
| image = 'mpl-data/images/help' | |
| def format_shortcut(key_sequence): | |
| """ | |
| Convert a shortcut string from the notation used in rc config to the | |
| standard notation for displaying shortcuts, e.g. 'ctrl+a' -> 'Ctrl+A'. | |
| """ | |
| return (key_sequence if len(key_sequence) == 1 else | |
| re.sub(r"\+[A-Z]", r"+Shift\g<0>", key_sequence).title()) | |
| def _format_tool_keymap(self, name): | |
| keymaps = self.toolmanager.get_tool_keymap(name) | |
| return ", ".join(self.format_shortcut(keymap) for keymap in keymaps) | |
| def _get_help_entries(self): | |
| return [(name, self._format_tool_keymap(name), tool.description) | |
| for name, tool in sorted(self.toolmanager.tools.items()) | |
| if tool.description] | |
| def _get_help_text(self): | |
| entries = self._get_help_entries() | |
| entries = ["{}: {}\n\t{}".format(*entry) for entry in entries] | |
| return "\n".join(entries) | |
| def _get_help_html(self): | |
| fmt = "<tr><td>{}</td><td>{}</td><td>{}</td></tr>" | |
| rows = [fmt.format( | |
| "<b>Action</b>", "<b>Shortcuts</b>", "<b>Description</b>")] | |
| rows += [fmt.format(*row) for row in self._get_help_entries()] | |
| return ("<style>td {padding: 0px 4px}</style>" | |
| "<table><thead>" + rows[0] + "</thead>" | |
| "<tbody>".join(rows[1:]) + "</tbody></table>") | |
| class ToolCopyToClipboardBase(ToolBase): | |
| """Tool to copy the figure to the clipboard.""" | |
| description = 'Copy the canvas figure to clipboard' | |
| default_keymap = property(lambda self: mpl.rcParams['keymap.copy']) | |
| def trigger(self, *args, **kwargs): | |
| message = "Copy tool is not available" | |
| self.toolmanager.message_event(message, self) | |
| default_tools = {'home': ToolHome, 'back': ToolBack, 'forward': ToolForward, | |
| 'zoom': ToolZoom, 'pan': ToolPan, | |
| 'subplots': ConfigureSubplotsBase, | |
| 'save': SaveFigureBase, | |
| 'grid': ToolGrid, | |
| 'grid_minor': ToolMinorGrid, | |
| 'fullscreen': ToolFullScreen, | |
| 'quit': ToolQuit, | |
| 'quit_all': ToolQuitAll, | |
| 'xscale': ToolXScale, | |
| 'yscale': ToolYScale, | |
| 'position': ToolCursorPosition, | |
| _views_positions: ToolViewsPositions, | |
| 'cursor': ToolSetCursor, | |
| 'rubberband': RubberbandBase, | |
| 'help': ToolHelpBase, | |
| 'copy': ToolCopyToClipboardBase, | |
| } | |
| default_toolbar_tools = [['navigation', ['home', 'back', 'forward']], | |
| ['zoompan', ['pan', 'zoom', 'subplots']], | |
| ['io', ['save', 'help']]] | |
| def add_tools_to_manager(toolmanager, tools=default_tools): | |
| """ | |
| Add multiple tools to a `.ToolManager`. | |
| Parameters | |
| ---------- | |
| toolmanager : `.backend_managers.ToolManager` | |
| Manager to which the tools are added. | |
| tools : {str: class_like}, optional | |
| The tools to add in a {name: tool} dict, see | |
| `.backend_managers.ToolManager.add_tool` for more info. | |
| """ | |
| for name, tool in tools.items(): | |
| toolmanager.add_tool(name, tool) | |
| def add_tools_to_container(container, tools=default_toolbar_tools): | |
| """ | |
| Add multiple tools to the container. | |
| Parameters | |
| ---------- | |
| container : Container | |
| `.backend_bases.ToolContainerBase` object that will get the tools | |
| added. | |
| tools : list, optional | |
| List in the form ``[[group1, [tool1, tool2 ...]], [group2, [...]]]`` | |
| where the tools ``[tool1, tool2, ...]`` will display in group1. | |
| See `.backend_bases.ToolContainerBase.add_tool` for details. | |
| """ | |
| for group, grouptools in tools: | |
| for position, tool in enumerate(grouptools): | |
| container.add_tool(tool, group, position) | |
Xet Storage Details
- Size:
- 33.2 kB
- Xet hash:
- 8e9565e89b2f173ab68678876b8aae0f2281484a49c4b17145d262bd5b0f9215
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.