Skip to content
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
17 changes: 15 additions & 2 deletions galleries/examples/event_handling/hover_event_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
14 changes: 11 additions & 3 deletions lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/artist.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
Expand Down
37 changes: 20 additions & 17 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions lib/matplotlib/backends/_backend_gtk.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ def set_message(self, s):
escaped = GLib.markup_escape_text(s)
self.message.set_markup(f'<small>{escaped}</small>')

def set_hover_message(self, s):
escaped = GLib.markup_escape_text(s)
self.hover_message.set_markup(f'<small>{escaped}</small>')

def draw_rubberband(self, event, x0, y0, x1, y1):
height = self.canvas.figure.bbox.height
y1 = height - y1
Expand Down
8 changes: 8 additions & 0 deletions lib/matplotlib/backends/backend_gtk3.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down Expand Up @@ -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):
Expand Down
7 changes: 7 additions & 0 deletions lib/matplotlib/backends/backend_gtk4.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
17 changes: 16 additions & 1 deletion lib/matplotlib/backends/backend_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions lib/matplotlib/backends/backend_wx.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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):
Expand Down