Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Support multi-figure MultiCursor; prepare improving its signature. #23348

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
Jun 28, 2022
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
7 changes: 7 additions & 0 deletions doc/api/next_api_changes/deprecations/23348-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The ``canvas`` and ``background`` attributes of ``MultiCursor``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... are deprecated with no replacement.

All parameters to ``MultiCursor`` starting from *useblit*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... are becoming keyword-only (passing them positionally is deprecated).
8 changes: 8 additions & 0 deletions doc/users/next_whats_new/multicursor_multifigure.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
``MultiCursor`` now supports Axes split over multiple figures
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously, `.MultiCursor` only worked if all target Axes belonged to the same
figure.

As a consequence of this change, the first argument to the `.MultiCursor`
constructor has become unused (it was previously the joint canvas of all Axes,
but the canvases are now directly inferred from the list of Axes).
13 changes: 9 additions & 4 deletions examples/widgets/multicursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,27 @@

Showing a cursor on multiple plots simultaneously.

This example generates two subplots and on hovering the cursor over data in one
subplot, the values of that datapoint are shown in both respectively.
This example generates three axes split over two different figures. On
hovering the cursor over data in one subplot, the values of that datapoint are
shown in all axes.
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import MultiCursor

t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)
s2 = np.sin(4*np.pi*t)
s2 = np.sin(3*np.pi*t)
s3 = np.sin(4*np.pi*t)

fig, (ax1, ax2) = plt.subplots(2, sharex=True)
ax1.plot(t, s1)
ax2.plot(t, s2)
fig, ax3 = plt.subplots()
ax3.plot(t, s3)

multi = MultiCursor(fig.canvas, (ax1, ax2), color='r', lw=1)
multi = MultiCursor(None, (ax1, ax2, ax3), color='r', lw=1)
plt.show()

#############################################################################
Expand Down
5 changes: 3 additions & 2 deletions lib/matplotlib/tests/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1516,11 +1516,12 @@ def test_polygon_selector_box(ax):
[(True, True), (True, False), (False, True)],
)
def test_MultiCursor(horizOn, vertOn):
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True)
(ax1, ax3) = plt.figure().subplots(2, sharex=True)
ax2 = plt.figure().subplots()

# useblit=false to avoid having to draw the figure to cache the renderer
multi = widgets.MultiCursor(
fig.canvas, (ax1, ax2), useblit=False, horizOn=horizOn, vertOn=vertOn
None, (ax1, ax2), useblit=False, horizOn=horizOn, vertOn=vertOn
)

# Only two of the axes should have a line drawn on them.
Expand Down
59 changes: 38 additions & 21 deletions lib/matplotlib/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1680,8 +1680,8 @@ class MultiCursor(Widget):

Parameters
----------
canvas : `matplotlib.backend_bases.FigureCanvasBase`
The FigureCanvas that contains all the Axes.
canvas : object
This parameter is entirely unused and only kept for back-compatibility.

axes : list of `matplotlib.axes.Axes`
The `~.axes.Axes` to attach the cursor to.
Expand All @@ -1708,21 +1708,29 @@ class MultiCursor(Widget):
See :doc:`/gallery/widgets/multicursor`.
"""

@_api.make_keyword_only("3.6", "useblit")
def __init__(self, canvas, axes, useblit=True, horizOn=False, vertOn=True,
**lineprops):
self.canvas = canvas
# canvas is stored only to provide the deprecated .canvas attribute;
# once it goes away the unused argument won't need to be stored at all.
self._canvas = canvas

self.axes = axes
self.horizOn = horizOn
self.vertOn = vertOn

self._canvas_infos = {
ax.figure.canvas: {"cids": [], "background": None} for ax in axes}

xmin, xmax = axes[-1].get_xlim()
ymin, ymax = axes[-1].get_ylim()
xmid = 0.5 * (xmin + xmax)
ymid = 0.5 * (ymin + ymax)

self.visible = True
self.useblit = useblit and self.canvas.supports_blit
self.background = None
self.useblit = (
useblit
and all(canvas.supports_blit for canvas in self._canvas_infos))
self.needclear = False

if self.useblit:
Expand All @@ -1742,33 +1750,39 @@ def __init__(self, canvas, axes, useblit=True, horizOn=False, vertOn=True,

self.connect()

canvas = _api.deprecate_privatize_attribute("3.6")
background = _api.deprecated("3.6")(lambda self: (
self._backgrounds[self.axes[0].figure.canvas] if self.axes else None))

def connect(self):
"""Connect events."""
self._cidmotion = self.canvas.mpl_connect('motion_notify_event',
self.onmove)
self._ciddraw = self.canvas.mpl_connect('draw_event', self.clear)
for canvas, info in self._canvas_infos.items():
info["cids"] = [
canvas.mpl_connect('motion_notify_event', self.onmove),
canvas.mpl_connect('draw_event', self.clear),
]

def disconnect(self):
"""Disconnect events."""
self.canvas.mpl_disconnect(self._cidmotion)
self.canvas.mpl_disconnect(self._ciddraw)
for canvas, info in self._canvas_infos.items():
for cid in info["cids"]:
canvas.mpl_disconnect(cid)
info["cids"].clear()

def clear(self, event):
"""Clear the cursor."""
if self.ignore(event):
return
if self.useblit:
self.background = (
self.canvas.copy_from_bbox(self.canvas.figure.bbox))
for canvas, info in self._canvas_infos.items():
info["background"] = canvas.copy_from_bbox(canvas.figure.bbox)
for line in self.vlines + self.hlines:
line.set_visible(False)

def onmove(self, event):
if self.ignore(event):
return
if event.inaxes not in self.axes:
return
if not self.canvas.widgetlock.available(self):
if (self.ignore(event)
or event.inaxes not in self.axes
or not event.canvas.widgetlock.available(self)):
return
self.needclear = True
if not self.visible:
Expand All @@ -1785,17 +1799,20 @@ def onmove(self, event):

def _update(self):
if self.useblit:
if self.background is not None:
self.canvas.restore_region(self.background)
for canvas, info in self._canvas_infos.items():
if info["background"]:
canvas.restore_region(info["background"])
if self.vertOn:
for ax, line in zip(self.axes, self.vlines):
ax.draw_artist(line)
if self.horizOn:
for ax, line in zip(self.axes, self.hlines):
ax.draw_artist(line)
self.canvas.blit()
for canvas in self._canvas_infos:
canvas.blit()
else:
self.canvas.draw_idle()
for canvas in self._canvas_infos:
canvas.draw_idle()


class _SelectorWidget(AxesWidget):
Expand Down