diff --git a/galleries/examples/event_handling/hover_event_demo.py b/galleries/examples/event_handling/hover_event_demo.py
index 5460a9913fe8..cfa34a4c510b 100644
--- a/galleries/examples/event_handling/hover_event_demo.py
+++ b/galleries/examples/event_handling/hover_event_demo.py
@@ -19,6 +19,9 @@
* list of string literals - hovering is enabled, and hovering over a point
displays the corresponding string literal.
+* dictionary - hovering is enabled, and hovering over a point
+ displays the string literal corresponding to the coordinate tuple.
+
* function - if hover is callable, it is a user supplied function which
takes a ``mouseevent`` object (see below), and returns a tuple of transformed
coordinates
@@ -69,16 +72,26 @@ def hover_handler(event):
from numpy.random import rand
fig, ax = plt.subplots()
-plt.ylabel('some numbers')
ax.plot(rand(3), 'o', hover=['London', 'Paris', 'Barcelona'])
plt.show()
+# %%
+# Hover with dictionary data
+# --------------------------------
+fig, ax = plt.subplots()
+x = rand(3)
+y = rand(3)
+ax.plot(x, y, 'o', hover={
+ (x[0], y[0]): "London",
+ (x[1], y[1]): "Paris",
+ (x[2], y[2]): "Barcelona"})
+plt.show()
+
# %%
# Hover with a callable transformation function
# ---------------------------------------------
fig, ax = plt.subplots()
-plt.ylabel('some numbers')
def user_defined_function(event):
diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py
index 32bb280c86c7..69750c5f7700 100644
--- a/lib/matplotlib/artist.py
+++ b/lib/matplotlib/artist.py
@@ -653,10 +653,18 @@ def set_hover(self, hover):
function which sets the hover message to be displayed.
- A list: If hover is a list of string literals, each string represents
- an additional information assigned to each data point. These arbitrary
- data labels will appear as a tooltip in the bottom right hand corner
+ an additional information assigned to each data point. These data labels
+ will appear as a tooltip in the bottom right hand corner
of the screen when the cursor is detected to be hovering over a data
- point that corresponds with one of the data labels.
+ point that corresponds with the data labels in the same order that
+ was passed in.
+
+ - A dictionary: If hover is a dictionary of key value paris, each key
+ represents a tuple of x and y coordinate and each value represnets
+ additional information assigned to each data point. These data labels
+ will appear as a tooltip in the bottom right hand corner of the
+ screen when the cursor is detected to be hovering over a data point
+ that corresponds with one of the data labels.
"""
self._hover = hover
diff --git a/lib/matplotlib/artist.pyi b/lib/matplotlib/artist.pyi
index 997d84b08631..dbdb313433c4 100644
--- a/lib/matplotlib/artist.pyi
+++ b/lib/matplotlib/artist.pyi
@@ -83,11 +83,12 @@ class Artist:
hover: None
| bool
| list[str]
+ | dict[tuple[float, float], str]
| Callable[[Artist, MouseEvent], tuple[bool, dict[Any, Any]]],
) -> None: ...
def get_hover(
self,
- ) -> None | bool | list[str] | Callable[
+ ) -> None | bool | list[str] | dict[tuple[float, float], str] | Callable[
[Artist, MouseEvent], tuple[bool, dict[Any, Any]]
]: ...
def get_url(self) -> str | None: ...
diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py
index 4d15ecda8bd7..d175564e5865 100644
--- a/lib/matplotlib/backend_bases.py
+++ b/lib/matplotlib/backend_bases.py
@@ -3015,25 +3015,26 @@ def _nonrect(self, x):
return not isinstance(x, Rectangle)
def _tooltip_list(self, event, hover):
- import matplotlib.pyplot as plt
- lines = plt.gca().get_lines()
- num_of_points = 0
- for line in lines:
- num_of_points += 1
- if num_of_points >= len(hover):
+ lines = self.canvas.figure.gca().get_lines()[0]
+ coor_data = list(zip(lines.get_xdata(), lines.get_ydata()))
+
+ if len(coor_data) != len(hover):
raise ValueError("""Number of data points
- does not match up with number of labels""")
+ does not match up with number of labels""")
else:
- mouse_x = event.xdata
- mouse_y = event.ydata
- for line in lines:
- x_data = line.get_xdata()
- y_data = line.get_ydata()
- for i in range(len(x_data)):
- distance = ((event.xdata - x_data[i])**2
- + (event.ydata - y_data[i])**2)**0.5
- if distance < 0.05:
- return "Data Label: " + hover[i]
+ distances = []
+ for a in coor_data:
+ distances.append(((event.xdata - a[0])**2 +
+ (event.ydata - a[1])**2)**0.5)
+ if (min(distances) < 0.05):
+ return f"Data Label: {hover[distances.index(min(distances))]}"
+
+ def _tooltip_dict(self, event, hover):
+ distances = {}
+ for a in hover.keys():
+ distances[a] = ((event.xdata - a[0])**2 + (event.ydata - a[1])**2)**0.5
+ if (min(distances.values()) < 0.05):
+ return f"Data Label: {hover[min(distances, key=distances.get)]}"
def mouse_move(self, event):
self._update_cursor(event)
@@ -3050,6 +3051,8 @@ def mouse_move(self, event):
self.set_hover_message(hover(event))
elif type(hover) == list:
self.set_hover_message(self._tooltip_list(event, hover))
+ elif type(hover) == dict:
+ self.set_hover_message(self._tooltip_dict(event, hover))
else:
self.set_hover_message(self._mouse_event_to_message(event))
else:
diff --git a/lib/matplotlib/backends/_backend_gtk.py b/lib/matplotlib/backends/_backend_gtk.py
index e33813e5d3df..3a79e6d88942 100644
--- a/lib/matplotlib/backends/_backend_gtk.py
+++ b/lib/matplotlib/backends/_backend_gtk.py
@@ -274,6 +274,10 @@ def set_message(self, s):
escaped = GLib.markup_escape_text(s)
self.message.set_markup(f'{escaped}')
+ def set_hover_message(self, s):
+ escaped = GLib.markup_escape_text(s)
+ self.hover_message.set_markup(f'{escaped}')
+
def draw_rubberband(self, event, x0, y0, x1, y1):
height = self.canvas.figure.bbox.height
y1 = height - y1
diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py
index d6acd5547b85..a878b023242f 100644
--- a/lib/matplotlib/backends/backend_gtk3.py
+++ b/lib/matplotlib/backends/backend_gtk3.py
@@ -395,6 +395,11 @@ def __init__(self, toolmanager):
self._message = Gtk.Label()
self._message.set_justify(Gtk.Justification.RIGHT)
self.pack_end(self._message, False, False, 0)
+
+ self.hover_message = Gtk.Label()
+ self.hover_message.set_justify(Gtk.Justification.RIGHT)
+ self.pack_end(self.hover_message, False, False, 0)
+
self.show_all()
self._groups = {}
self._toolitems = {}
@@ -465,6 +470,9 @@ def _add_separator(self):
def set_message(self, s):
self._message.set_label(s)
+ def set_hover_message(self, s):
+ self._hover_message.set_label(s)
+
@backend_tools._register_tool_class(FigureCanvasGTK3)
class SaveFigureGTK3(backend_tools.SaveFigureBase):
diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py
index cb4006048d55..a6eab78e55b2 100644
--- a/lib/matplotlib/backends/backend_gtk4.py
+++ b/lib/matplotlib/backends/backend_gtk4.py
@@ -328,6 +328,10 @@ def __init__(self, canvas):
self.message.set_justify(Gtk.Justification.RIGHT)
self.append(self.message)
+ self.hover_message = Gtk.Label()
+ self.hover_message.set_justify(Gtk.Justification.RIGHT)
+ self.append(self.hover_message)
+
_NavigationToolbar2GTK.__init__(self, canvas)
def save_figure(self, *args):
@@ -493,6 +497,9 @@ def _add_separator(self):
def set_message(self, s):
self._message.set_label(s)
+ def set_hover_message(self, s):
+ self._hover_message.set_label(s)
+
@backend_tools._register_tool_class(FigureCanvasGTK4)
class SaveFigureGTK4(backend_tools.SaveFigureBase):
diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py
index 5c83cbbd9fa7..b72e4b23f5af 100644
--- a/lib/matplotlib/backends/backend_qt.py
+++ b/lib/matplotlib/backends/backend_qt.py
@@ -677,9 +677,19 @@ def __init__(self, canvas, parent=None, coordinates=True):
_enum("QtWidgets.QSizePolicy.Policy").Expanding,
_enum("QtWidgets.QSizePolicy.Policy").Ignored,
))
+ self.hover_message = QtWidgets.QLabel("", self)
+ self.hover_message.setAlignment(QtCore.Qt.AlignmentFlag(
+ _to_int(_enum("QtCore.Qt.AlignmentFlag").AlignRight) |
+ _to_int(_enum("QtCore.Qt.AlignmentFlag").AlignVCenter)))
+ self.hover_message.setSizePolicy(QtWidgets.QSizePolicy(
+ _enum("QtWidgets.QSizePolicy.Policy").Expanding,
+ _enum("QtWidgets.QSizePolicy.Policy").Ignored,
+ ))
+ labelActionHover = self.addWidget(self.hover_message)
+ labelActionHover.setVisible(True)
+
labelAction = self.addWidget(self.locLabel)
labelAction.setVisible(True)
-
NavigationToolbar2.__init__(self, canvas)
def _icon(self, name):
@@ -756,6 +766,11 @@ def set_message(self, s):
if self.coordinates:
self.locLabel.setText(s)
+ def set_hover_message(self, s):
+ self.message.emit(s)
+ if self.coordinates:
+ self.hover_message.setText(s)
+
def draw_rubberband(self, event, x0, y0, x1, y1):
height = self.canvas.figure.bbox.height
y1 = height - y1
diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py
index 70f0c0fff515..c6987eb6b8b6 100644
--- a/lib/matplotlib/backends/backend_wx.py
+++ b/lib/matplotlib/backends/backend_wx.py
@@ -1051,6 +1051,8 @@ def __init__(self, canvas, coordinates=True, *, style=wx.TB_BOTTOM):
self.AddStretchableSpace()
self._label_text = wx.StaticText(self, style=wx.ALIGN_RIGHT)
self.AddControl(self._label_text)
+ self._hover_message = wx.StaticText(self, style=wx.ALIGN_LEFT)
+ self.AddControl(self._hover_message)
self.Realize()
@@ -1143,6 +1145,10 @@ def set_message(self, s):
if self._coordinates:
self._label_text.SetLabel(s)
+ def set_hover_message(self, s):
+ if self._coordinates:
+ self._hover_message.SetLabel(s)
+
def set_history_buttons(self):
can_backward = self._nav_stack._pos > 0
can_forward = self._nav_stack._pos < len(self._nav_stack._elements) - 1
@@ -1163,6 +1169,10 @@ def __init__(self, toolmanager, parent=None, style=wx.TB_BOTTOM):
self._space = self.AddStretchableSpace()
self._label_text = wx.StaticText(self, style=wx.ALIGN_RIGHT)
self.AddControl(self._label_text)
+
+ self._hover_message = wx.StaticText(self, style=wx.ALIGN_LEFT)
+ self.AddControl(self._hover_message)
+
self._toolitems = {}
self._groups = {} # Mapping of groups to the separator after them.
@@ -1240,6 +1250,9 @@ def remove_toolitem(self, name):
def set_message(self, s):
self._label_text.SetLabel(s)
+ def set_hover_message(self, s):
+ self._hover_message.SetLabel(s)
+
@backend_tools._register_tool_class(_FigureCanvasWxBase)
class ConfigureSubplotsWx(backend_tools.ConfigureSubplotsBase):