Skip to content

Commit 56766cc

Browse files
implemented hover feature for other backends; fixed string list; adde… (#9)
* implemented hover feature for other backends; fixed string list; added dict * fix linter issues * more linter issues --------- Co-authored-by: Yanshi Chen <ychen288@u.rochester.edu>
1 parent 2211b57 commit 56766cc

File tree

9 files changed

+96
-24
lines changed

9 files changed

+96
-24
lines changed

galleries/examples/event_handling/hover_event_demo.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
* list of string literals - hovering is enabled, and hovering over a point
2020
displays the corresponding string literal.
2121
22+
* dictionary - hovering is enabled, and hovering over a point
23+
displays the string literal corresponding to the coordinate tuple.
24+
2225
* function - if hover is callable, it is a user supplied function which
2326
takes a ``mouseevent`` object (see below), and returns a tuple of transformed
2427
coordinates
@@ -69,16 +72,26 @@ def hover_handler(event):
6972
from numpy.random import rand
7073

7174
fig, ax = plt.subplots()
72-
plt.ylabel('some numbers')
7375

7476
ax.plot(rand(3), 'o', hover=['London', 'Paris', 'Barcelona'])
7577
plt.show()
7678

79+
# %%
80+
# Hover with dictionary data
81+
# --------------------------------
82+
fig, ax = plt.subplots()
83+
x = rand(3)
84+
y = rand(3)
85+
ax.plot(x, y, 'o', hover={
86+
(x[0], y[0]): "London",
87+
(x[1], y[1]): "Paris",
88+
(x[2], y[2]): "Barcelona"})
89+
plt.show()
90+
7791
# %%
7892
# Hover with a callable transformation function
7993
# ---------------------------------------------
8094
fig, ax = plt.subplots()
81-
plt.ylabel('some numbers')
8295

8396

8497
def user_defined_function(event):

lib/matplotlib/artist.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -653,10 +653,18 @@ def set_hover(self, hover):
653653
function which sets the hover message to be displayed.
654654
655655
- A list: If hover is a list of string literals, each string represents
656-
an additional information assigned to each data point. These arbitrary
657-
data labels will appear as a tooltip in the bottom right hand corner
656+
an additional information assigned to each data point. These data labels
657+
will appear as a tooltip in the bottom right hand corner
658658
of the screen when the cursor is detected to be hovering over a data
659-
point that corresponds with one of the data labels.
659+
point that corresponds with the data labels in the same order that
660+
was passed in.
661+
662+
- A dictionary: If hover is a dictionary of key value paris, each key
663+
represents a tuple of x and y coordinate and each value represnets
664+
additional information assigned to each data point. These data labels
665+
will appear as a tooltip in the bottom right hand corner of the
666+
screen when the cursor is detected to be hovering over a data point
667+
that corresponds with one of the data labels.
660668
"""
661669
self._hover = hover
662670

lib/matplotlib/artist.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,12 @@ class Artist:
8383
hover: None
8484
| bool
8585
| list[str]
86+
| dict[tuple[float, float], str]
8687
| Callable[[Artist, MouseEvent], tuple[bool, dict[Any, Any]]],
8788
) -> None: ...
8889
def get_hover(
8990
self,
90-
) -> None | bool | list[str] | Callable[
91+
) -> None | bool | list[str] | dict[tuple[float, float], str] | Callable[
9192
[Artist, MouseEvent], tuple[bool, dict[Any, Any]]
9293
]: ...
9394
def get_url(self) -> str | None: ...

lib/matplotlib/backend_bases.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3015,25 +3015,26 @@ def _nonrect(self, x):
30153015
return not isinstance(x, Rectangle)
30163016

30173017
def _tooltip_list(self, event, hover):
3018-
import matplotlib.pyplot as plt
3019-
lines = plt.gca().get_lines()
3020-
num_of_points = 0
3021-
for line in lines:
3022-
num_of_points += 1
3023-
if num_of_points >= len(hover):
3018+
lines = self.canvas.figure.gca().get_lines()[0]
3019+
coor_data = list(zip(lines.get_xdata(), lines.get_ydata()))
3020+
3021+
if len(coor_data) != len(hover):
30243022
raise ValueError("""Number of data points
3025-
does not match up with number of labels""")
3023+
does not match up with number of labels""")
30263024
else:
3027-
mouse_x = event.xdata
3028-
mouse_y = event.ydata
3029-
for line in lines:
3030-
x_data = line.get_xdata()
3031-
y_data = line.get_ydata()
3032-
for i in range(len(x_data)):
3033-
distance = ((event.xdata - x_data[i])**2
3034-
+ (event.ydata - y_data[i])**2)**0.5
3035-
if distance < 0.05:
3036-
return "Data Label: " + hover[i]
3025+
distances = []
3026+
for a in coor_data:
3027+
distances.append(((event.xdata - a[0])**2 +
3028+
(event.ydata - a[1])**2)**0.5)
3029+
if (min(distances) < 0.05):
3030+
return f"Data Label: {hover[distances.index(min(distances))]}"
3031+
3032+
def _tooltip_dict(self, event, hover):
3033+
distances = {}
3034+
for a in hover.keys():
3035+
distances[a] = ((event.xdata - a[0])**2 + (event.ydata - a[1])**2)**0.5
3036+
if (min(distances.values()) < 0.05):
3037+
return f"Data Label: {hover[min(distances, key=distances.get)]}"
30373038

30383039
def mouse_move(self, event):
30393040
self._update_cursor(event)
@@ -3050,6 +3051,8 @@ def mouse_move(self, event):
30503051
self.set_hover_message(hover(event))
30513052
elif type(hover) == list:
30523053
self.set_hover_message(self._tooltip_list(event, hover))
3054+
elif type(hover) == dict:
3055+
self.set_hover_message(self._tooltip_dict(event, hover))
30533056
else:
30543057
self.set_hover_message(self._mouse_event_to_message(event))
30553058
else:

lib/matplotlib/backends/_backend_gtk.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ def set_message(self, s):
274274
escaped = GLib.markup_escape_text(s)
275275
self.message.set_markup(f'<small>{escaped}</small>')
276276

277+
def set_hover_message(self, s):
278+
escaped = GLib.markup_escape_text(s)
279+
self.hover_message.set_markup(f'<small>{escaped}</small>')
280+
277281
def draw_rubberband(self, event, x0, y0, x1, y1):
278282
height = self.canvas.figure.bbox.height
279283
y1 = height - y1

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,11 @@ def __init__(self, toolmanager):
395395
self._message = Gtk.Label()
396396
self._message.set_justify(Gtk.Justification.RIGHT)
397397
self.pack_end(self._message, False, False, 0)
398+
399+
self.hover_message = Gtk.Label()
400+
self.hover_message.set_justify(Gtk.Justification.RIGHT)
401+
self.pack_end(self.hover_message, False, False, 0)
402+
398403
self.show_all()
399404
self._groups = {}
400405
self._toolitems = {}
@@ -465,6 +470,9 @@ def _add_separator(self):
465470
def set_message(self, s):
466471
self._message.set_label(s)
467472

473+
def set_hover_message(self, s):
474+
self._hover_message.set_label(s)
475+
468476

469477
@backend_tools._register_tool_class(FigureCanvasGTK3)
470478
class SaveFigureGTK3(backend_tools.SaveFigureBase):

lib/matplotlib/backends/backend_gtk4.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,10 @@ def __init__(self, canvas):
328328
self.message.set_justify(Gtk.Justification.RIGHT)
329329
self.append(self.message)
330330

331+
self.hover_message = Gtk.Label()
332+
self.hover_message.set_justify(Gtk.Justification.RIGHT)
333+
self.append(self.hover_message)
334+
331335
_NavigationToolbar2GTK.__init__(self, canvas)
332336

333337
def save_figure(self, *args):
@@ -493,6 +497,9 @@ def _add_separator(self):
493497
def set_message(self, s):
494498
self._message.set_label(s)
495499

500+
def set_hover_message(self, s):
501+
self._hover_message.set_label(s)
502+
496503

497504
@backend_tools._register_tool_class(FigureCanvasGTK4)
498505
class SaveFigureGTK4(backend_tools.SaveFigureBase):

lib/matplotlib/backends/backend_qt.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,9 +677,19 @@ def __init__(self, canvas, parent=None, coordinates=True):
677677
_enum("QtWidgets.QSizePolicy.Policy").Expanding,
678678
_enum("QtWidgets.QSizePolicy.Policy").Ignored,
679679
))
680+
self.hover_message = QtWidgets.QLabel("", self)
681+
self.hover_message.setAlignment(QtCore.Qt.AlignmentFlag(
682+
_to_int(_enum("QtCore.Qt.AlignmentFlag").AlignRight) |
683+
_to_int(_enum("QtCore.Qt.AlignmentFlag").AlignVCenter)))
684+
self.hover_message.setSizePolicy(QtWidgets.QSizePolicy(
685+
_enum("QtWidgets.QSizePolicy.Policy").Expanding,
686+
_enum("QtWidgets.QSizePolicy.Policy").Ignored,
687+
))
688+
labelActionHover = self.addWidget(self.hover_message)
689+
labelActionHover.setVisible(True)
690+
680691
labelAction = self.addWidget(self.locLabel)
681692
labelAction.setVisible(True)
682-
683693
NavigationToolbar2.__init__(self, canvas)
684694

685695
def _icon(self, name):
@@ -756,6 +766,11 @@ def set_message(self, s):
756766
if self.coordinates:
757767
self.locLabel.setText(s)
758768

769+
def set_hover_message(self, s):
770+
self.message.emit(s)
771+
if self.coordinates:
772+
self.hover_message.setText(s)
773+
759774
def draw_rubberband(self, event, x0, y0, x1, y1):
760775
height = self.canvas.figure.bbox.height
761776
y1 = height - y1

lib/matplotlib/backends/backend_wx.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,8 @@ def __init__(self, canvas, coordinates=True, *, style=wx.TB_BOTTOM):
10511051
self.AddStretchableSpace()
10521052
self._label_text = wx.StaticText(self, style=wx.ALIGN_RIGHT)
10531053
self.AddControl(self._label_text)
1054+
self._hover_message = wx.StaticText(self, style=wx.ALIGN_LEFT)
1055+
self.AddControl(self._hover_message)
10541056

10551057
self.Realize()
10561058

@@ -1143,6 +1145,10 @@ def set_message(self, s):
11431145
if self._coordinates:
11441146
self._label_text.SetLabel(s)
11451147

1148+
def set_hover_message(self, s):
1149+
if self._coordinates:
1150+
self._hover_message.SetLabel(s)
1151+
11461152
def set_history_buttons(self):
11471153
can_backward = self._nav_stack._pos > 0
11481154
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):
11631169
self._space = self.AddStretchableSpace()
11641170
self._label_text = wx.StaticText(self, style=wx.ALIGN_RIGHT)
11651171
self.AddControl(self._label_text)
1172+
1173+
self._hover_message = wx.StaticText(self, style=wx.ALIGN_LEFT)
1174+
self.AddControl(self._hover_message)
1175+
11661176
self._toolitems = {}
11671177
self._groups = {} # Mapping of groups to the separator after them.
11681178

@@ -1240,6 +1250,9 @@ def remove_toolitem(self, name):
12401250
def set_message(self, s):
12411251
self._label_text.SetLabel(s)
12421252

1253+
def set_hover_message(self, s):
1254+
self._hover_message.SetLabel(s)
1255+
12431256

12441257
@backend_tools._register_tool_class(_FigureCanvasWxBase)
12451258
class ConfigureSubplotsWx(backend_tools.ConfigureSubplotsBase):

0 commit comments

Comments
 (0)