Skip to content

Commit 5fae314

Browse files
committed
Add hover cues for editable plot text
1 parent 665f5c3 commit 5fae314

2 files changed

Lines changed: 127 additions & 7 deletions

File tree

src/ert/gui/plotting/widgets/plot_widget.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,21 @@
66
import numpy as np
77
import numpy.typing as npt
88
import pandas as pd
9-
from matplotlib.backend_bases import PickEvent
9+
from matplotlib.backend_bases import Event, MouseEvent, PickEvent
1010
from matplotlib.backends.backend_qt5agg import ( # type: ignore
1111
FigureCanvas,
1212
NavigationToolbar2QT,
1313
)
1414
from matplotlib.figure import Figure
15+
from matplotlib.font_manager import FontProperties
16+
from matplotlib.text import Text
1517
from PyQt6.QtCore import QStringListModel, Qt
1618
from PyQt6.QtCore import pyqtSignal as Signal
1719
from PyQt6.QtCore import pyqtSlot as Slot
18-
from PyQt6.QtGui import QAction
20+
from PyQt6.QtGui import QAction, QCursor
1921
from PyQt6.QtWidgets import (
2022
QComboBox,
23+
QToolTip,
2124
QVBoxLayout,
2225
QWidget,
2326
QWidgetAction,
@@ -139,9 +142,13 @@ def __init__(
139142
self._figure.set_layout_engine("tight")
140143
self._canvas = FigureCanvas(self._figure)
141144
self._canvas.mpl_connect("pick_event", self._on_canvas_pick)
145+
self._canvas.mpl_connect("motion_notify_event", self._on_canvas_motion)
146+
self._canvas.mpl_connect("figure_leave_event", self._on_canvas_leave)
142147
self._canvas.setParent(self)
143148
self._canvas.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
144149
self._canvas.setFocus()
150+
self._hovered_text_artist: Text | None = None
151+
self._hovered_font_properties: FontProperties | None = None
145152

146153
vbox = QVBoxLayout()
147154
vbox.addWidget(self._canvas)
@@ -203,10 +210,58 @@ def updatePlot(
203210
)
204211

205212
def _enable_text_picking(self) -> None:
206-
for axes in self._figure.axes:
207-
axes.xaxis.label.set_picker(True)
208-
axes.yaxis.label.set_picker(True)
209-
axes.title.set_picker(True)
213+
self._hovered_text_artist = None
214+
self._hovered_font_properties = None
215+
QToolTip.hideText()
216+
217+
for text_artist in self._editable_text_artists():
218+
text_artist.set_picker(True)
219+
220+
def _editable_text_artists(self) -> list[Text]:
221+
return [
222+
text_artist
223+
for axes in self._figure.axes
224+
for text_artist in (axes.xaxis.label, axes.yaxis.label, axes.title)
225+
]
226+
227+
def _clear_hovered_text_artist(self) -> None:
228+
if (
229+
self._hovered_text_artist is not None
230+
and self._hovered_font_properties is not None
231+
):
232+
self._hovered_text_artist.set_fontproperties(self._hovered_font_properties)
233+
234+
self._hovered_text_artist = None
235+
self._hovered_font_properties = None
236+
QToolTip.hideText()
237+
238+
def _on_canvas_motion(self, event: MouseEvent) -> None:
239+
hovered_text_artist = next(
240+
(
241+
text_artist
242+
for text_artist in self._editable_text_artists()
243+
if text_artist.contains(event)[0]
244+
),
245+
None,
246+
)
247+
if hovered_text_artist is self._hovered_text_artist:
248+
return
249+
250+
self._clear_hovered_text_artist()
251+
if hovered_text_artist is None:
252+
self._canvas.draw_idle()
253+
return
254+
255+
self._hovered_text_artist = hovered_text_artist
256+
self._hovered_font_properties = hovered_text_artist.get_fontproperties().copy()
257+
hovered_text_artist.set_fontweight("bold")
258+
QToolTip.showText(QCursor.pos(), "Click to edit", self._canvas)
259+
260+
self._canvas.draw_idle()
261+
262+
def _on_canvas_leave(self, _: Event) -> None:
263+
self._clear_hovered_text_artist()
264+
self._canvas.draw_idle()
210265

211266
def _on_canvas_pick(self, event: PickEvent) -> None:
212267
for axes in self._figure.axes:

tests/ert/unit_tests/gui/tools/plot/test_plot_window.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
import pandas as pd
66
import pytest
7+
from matplotlib.backend_bases import Event, MouseEvent
78
from matplotlib.figure import Figure
89
from PyQt6.QtCore import Qt
9-
from PyQt6.QtWidgets import QApplication, QCheckBox, QLabel, QPushButton
10+
from PyQt6.QtWidgets import QApplication, QCheckBox, QLabel, QPushButton, QToolTip
1011
from pytestqt.qtbot import QtBot
1112

1213
from ert.config.distribution import RawSettings
@@ -947,6 +948,70 @@ def _plot_with_title(figure: Figure, *_args, **_kwargs) -> None:
947948
received.assert_called_once_with()
948949

949950

951+
@pytest.mark.parametrize(
952+
"text_kind",
953+
[
954+
pytest.param("x-axis", id="x-axis"),
955+
pytest.param("y-axis", id="y-axis"),
956+
pytest.param("title", id="title"),
957+
],
958+
)
959+
def test_that_hovering_editable_text_shows_it_as_clickable(
960+
qtbot: QtBot,
961+
monkeypatch: pytest.MonkeyPatch,
962+
text_kind: str,
963+
) -> None:
964+
show_text = MagicMock()
965+
monkeypatch.setattr(QToolTip, "showText", show_text)
966+
967+
plotter = MagicMock()
968+
plotter.dimensionality = 1
969+
plotter.requires_observations = False
970+
971+
def _plot_with_editable_text(figure: Figure, *_args, **_kwargs) -> None:
972+
axes = figure.add_subplot(111)
973+
axes.set_xlabel("X label")
974+
axes.set_ylabel("Y label")
975+
axes.set_title("Title")
976+
977+
plotter.plot.side_effect = _plot_with_editable_text
978+
plot_widget = PlotWidget("Any", plotter)
979+
qtbot.addWidget(plot_widget)
980+
plot_widget.updatePlot(
981+
PlotContext(
982+
PlotConfig(),
983+
ensembles=[],
984+
ensembles_color_indexes=[],
985+
key="key",
986+
layer=None,
987+
),
988+
{},
989+
pd.DataFrame(),
990+
{},
991+
None,
992+
)
993+
994+
axes = plot_widget._figure.axes[0]
995+
text = {
996+
"x-axis": axes.xaxis.label,
997+
"y-axis": axes.yaxis.label,
998+
"title": axes.title,
999+
}[text_kind]
1000+
original_properties = text.get_fontproperties().copy()
1001+
x_pos, y_pos = text.get_window_extent().get_points().mean(axis=0)
1002+
event = MouseEvent("motion_notify_event", plot_widget._canvas, x_pos, y_pos)
1003+
plot_widget._on_canvas_motion(event)
1004+
1005+
assert text.get_fontweight() == "bold"
1006+
assert show_text.call_args.args[1:] == ("Click to edit", plot_widget._canvas)
1007+
1008+
plot_widget._on_canvas_leave(Event("figure_leave_event", plot_widget._canvas))
1009+
1010+
assert text.get_fontproperties() == original_properties
1011+
assert plot_widget._hovered_text_artist is None
1012+
assert plot_widget._hovered_font_properties is None
1013+
1014+
9501015
def _create_plot_window_for_text_edit(
9511016
qtbot: QtBot,
9521017
monkeypatch: pytest.MonkeyPatch,

0 commit comments

Comments
 (0)