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

Skip to content

Commit 79b4e69

Browse files
committed
Support multi-figure MultiCursor; prepare improving its signature.
Support MultiCursor with Axes spread over different figures. As a consequence, the first parameter of MultiCursor (`canvas`) has become meaningless (requiring the user to pass in `[ax.figure.canvas for ax in axes]` seems pointless); just ignore that argument. While we're at it, also move some parameters of MultiCursor towards being keyword-only, to prepare for a hopefully better signature without the `canvas` parameter at all.
1 parent 0d39a4f commit 79b4e69

File tree

5 files changed

+62
-22
lines changed

5 files changed

+62
-22
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
The ``canvas`` and ``background`` attributes of ``MultiCursor``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
... are deprecated with no replacement.
4+
5+
All parameters to ``MultiCursor`` starting from *useblit*
6+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7+
... are becoming keyword-only (passing them positionally is deprecated).
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
``MultiCursor`` now supports Axes split over multiple figures
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
Previously, `.MultiCursor` only worked if all target Axes belonged to the same
4+
figure.
5+
6+
As a consequence of this change, the first argument to the `.MultiCursor`
7+
constructor has become unused (it was previously the joint canvas of all Axes,
8+
but the canvases are now directly inferred from the list of Axes).

examples/widgets/multicursor.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,27 @@
55
66
Showing a cursor on multiple plots simultaneously.
77
8-
This example generates two subplots and on hovering the cursor over data in one
9-
subplot, the values of that datapoint are shown in both respectively.
8+
This example generates three axes split over two different figures. On
9+
hovering the cursor over data in one subplot, the values of that datapoint are
10+
shown in all axes.
1011
"""
12+
1113
import numpy as np
1214
import matplotlib.pyplot as plt
1315
from matplotlib.widgets import MultiCursor
1416

1517
t = np.arange(0.0, 2.0, 0.01)
1618
s1 = np.sin(2*np.pi*t)
17-
s2 = np.sin(4*np.pi*t)
19+
s2 = np.sin(3*np.pi*t)
20+
s3 = np.sin(4*np.pi*t)
1821

1922
fig, (ax1, ax2) = plt.subplots(2, sharex=True)
2023
ax1.plot(t, s1)
2124
ax2.plot(t, s2)
25+
fig, ax3 = plt.subplots()
26+
ax3.plot(t, s3)
2227

23-
multi = MultiCursor(fig.canvas, (ax1, ax2), color='r', lw=1)
28+
multi = MultiCursor(None, (ax1, ax2, ax3), color='r', lw=1)
2429
plt.show()
2530

2631
#############################################################################

lib/matplotlib/tests/test_widgets.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,11 +1516,12 @@ def test_polygon_selector_box(ax):
15161516
[(True, True), (True, False), (False, True)],
15171517
)
15181518
def test_MultiCursor(horizOn, vertOn):
1519-
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True)
1519+
(ax1, ax3) = plt.figure().subplots(2, sharex=True)
1520+
ax2 = plt.figure().subplots()
15201521

15211522
# useblit=false to avoid having to draw the figure to cache the renderer
15221523
multi = widgets.MultiCursor(
1523-
fig.canvas, (ax1, ax2), useblit=False, horizOn=horizOn, vertOn=vertOn
1524+
None, (ax1, ax2), useblit=False, horizOn=horizOn, vertOn=vertOn
15241525
)
15251526

15261527
# Only two of the axes should have a line drawn on them.

lib/matplotlib/widgets.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,8 +1680,8 @@ class MultiCursor(Widget):
16801680
16811681
Parameters
16821682
----------
1683-
canvas : `matplotlib.backend_bases.FigureCanvasBase`
1684-
The FigureCanvas that contains all the Axes.
1683+
canvas : object
1684+
This parameter is entirely unused and only kept for back-compatibility.
16851685
16861686
axes : list of `matplotlib.axes.Axes`
16871687
The `~.axes.Axes` to attach the cursor to.
@@ -1708,20 +1708,29 @@ class MultiCursor(Widget):
17081708
See :doc:`/gallery/widgets/multicursor`.
17091709
"""
17101710

1711+
@_api.make_keyword_only("3.6", "useblit")
17111712
def __init__(self, canvas, axes, useblit=True, horizOn=False, vertOn=True,
17121713
**lineprops):
1713-
self.canvas = canvas
1714+
# canvas is stored only to provide the deprecated .canvas attribute;
1715+
# once it goes away the unused argument won't need to be stored at all.
1716+
self._canvas = canvas
1717+
17141718
self.axes = axes
17151719
self.horizOn = horizOn
17161720
self.vertOn = vertOn
17171721

1722+
self._canvas_infos = {
1723+
ax.figure.canvas: {"cids": [], "background": None} for ax in axes}
1724+
17181725
xmin, xmax = axes[-1].get_xlim()
17191726
ymin, ymax = axes[-1].get_ylim()
17201727
xmid = 0.5 * (xmin + xmax)
17211728
ymid = 0.5 * (ymin + ymax)
17221729

17231730
self.visible = True
1724-
self.useblit = useblit and self.canvas.supports_blit
1731+
self.useblit = (
1732+
useblit
1733+
and all(canvas.supports_blit for canvas in self._canvas_infos))
17251734
self.background = None
17261735
self.needclear = False
17271736

@@ -1742,24 +1751,31 @@ def __init__(self, canvas, axes, useblit=True, horizOn=False, vertOn=True,
17421751

17431752
self.connect()
17441753

1754+
canvas = _api.deprecate_privatize_attribute("3.6")
1755+
background = _api.deprecated("3.6")(
1756+
lambda self: self._backgrounds[self.axes[0].figure.canvas])
1757+
17451758
def connect(self):
17461759
"""Connect events."""
1747-
self._cidmotion = self.canvas.mpl_connect('motion_notify_event',
1748-
self.onmove)
1749-
self._ciddraw = self.canvas.mpl_connect('draw_event', self.clear)
1760+
for canvas, info in self._canvas_infos.items():
1761+
info["cids"] = [
1762+
canvas.mpl_connect('motion_notify_event', self.onmove),
1763+
canvas.mpl_connect('draw_event', self.clear),
1764+
]
17501765

17511766
def disconnect(self):
17521767
"""Disconnect events."""
1753-
self.canvas.mpl_disconnect(self._cidmotion)
1754-
self.canvas.mpl_disconnect(self._ciddraw)
1768+
for canvas, info in self._canvas_infos.items():
1769+
for cid in info["cids"]:
1770+
canvas.mpl_disconnect(cid)
17551771

17561772
def clear(self, event):
17571773
"""Clear the cursor."""
17581774
if self.ignore(event):
17591775
return
17601776
if self.useblit:
1761-
self.background = (
1762-
self.canvas.copy_from_bbox(self.canvas.figure.bbox))
1777+
for canvas, info in self._canvas_infos.items():
1778+
info["background"] = canvas.copy_from_bbox(canvas.figure.bbox)
17631779
for line in self.vlines + self.hlines:
17641780
line.set_visible(False)
17651781

@@ -1768,7 +1784,7 @@ def onmove(self, event):
17681784
return
17691785
if event.inaxes not in self.axes:
17701786
return
1771-
if not self.canvas.widgetlock.available(self):
1787+
if not event.canvas.widgetlock.available(self):
17721788
return
17731789
self.needclear = True
17741790
if not self.visible:
@@ -1785,17 +1801,20 @@ def onmove(self, event):
17851801

17861802
def _update(self):
17871803
if self.useblit:
1788-
if self.background is not None:
1789-
self.canvas.restore_region(self.background)
1804+
for canvas, info in self._canvas_infos.items():
1805+
if info["background"]:
1806+
canvas.restore_region(info["background"])
17901807
if self.vertOn:
17911808
for ax, line in zip(self.axes, self.vlines):
17921809
ax.draw_artist(line)
17931810
if self.horizOn:
17941811
for ax, line in zip(self.axes, self.hlines):
17951812
ax.draw_artist(line)
1796-
self.canvas.blit()
1813+
for canvas in self._canvas_infos:
1814+
canvas.blit()
17971815
else:
1798-
self.canvas.draw_idle()
1816+
for canvas in self._canvas_infos:
1817+
canvas.draw_idle()
17991818

18001819

18011820
class _SelectorWidget(AxesWidget):

0 commit comments

Comments
 (0)