From 7e338cdf5bd8ab54bb4875b160880d82c16b2ce5 Mon Sep 17 00:00:00 2001 From: Pierre Raybaut <1311787+PierreRaybaut@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:26:44 +0100 Subject: [PATCH 1/2] fix(signal): keep the X axis aligned with the grid after a refresh After opening a signal or changing the selection, the X axis ticks could be shifted relative to the vertical grid lines and the curves, until the view was panned or zoomed, for signals whose X axis does not start and end on a graduation (e.g. step.npy from the Sigima test data). SignalPlotHandler.refresh_plot replaced the bottom scale draw with a new QwtScaleDraw on every refresh, to undo the date/time formatting, even when the X axis was not in date/time mode. PythonQwt laid the new scale draw out before giving it the geometry of the previous one, with border distances differing from those used to draw the grid and the curves (PlotPyStack/PythonQwt#113, fixed by PlotPyStack/PythonQwt#114). - refresh_plot: restore the default scale draw only when leaving the date/time mode, i.e. when the current one is a QwtDateTimeScaleDraw. - Regression tests: X scale draw kept on refresh, X axis aligned with the canvas map, date/time formatting still removed for regular signals. Closes #457 Assisted-by: Claude Opus 5.5 --- datalab/gui/plothandler.py | 10 +- .../signal/xaxis_scale_draw_unit_test.py | 102 ++++++++++++++++++ 2 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 datalab/tests/features/signal/xaxis_scale_draw_unit_test.py diff --git a/datalab/gui/plothandler.py b/datalab/gui/plothandler.py index 0ee5f204..40abc318 100644 --- a/datalab/gui/plothandler.py +++ b/datalab/gui/plothandler.py @@ -37,7 +37,7 @@ from plotpy.items import CurveItem, GridItem, LegendBoxItem, MaskedXYImageItem from plotpy.plot import PlotOptions from qtpy import QtWidgets as QW -from qwt import QwtScaleDraw +from qwt import QwtDateTimeScaleDraw, QwtScaleDraw from sigima.objects import ImageObj, SignalObj, TypeObj from datalab.adapters_metadata import GeometryAdapter, TableAdapter @@ -725,8 +725,12 @@ def refresh_plot( if has_datetime and datetime_format is not None: self.plot.set_axis_datetime("bottom", format=datetime_format) else: - # Restore default scale draw (remove datetime formatting) - self.plot.setAxisScaleDraw(self.plot.get_axis_id("bottom"), QwtScaleDraw()) + # Restore default scale draw only when leaving the datetime mode: + # replacing it on every refresh could misalign the axis ticks with + # the grid and the curves (see Issue #457) + axis_id = self.plot.get_axis_id("bottom") + if isinstance(self.plot.axisScaleDraw(axis_id), QwtDateTimeScaleDraw): + self.plot.setAxisScaleDraw(axis_id, QwtScaleDraw()) class ImagePlotHandler(BasePlotHandler[ImageObj, MaskedXYImageItem]): diff --git a/datalab/tests/features/signal/xaxis_scale_draw_unit_test.py b/datalab/tests/features/signal/xaxis_scale_draw_unit_test.py new file mode 100644 index 00000000..d4e781b8 --- /dev/null +++ b/datalab/tests/features/signal/xaxis_scale_draw_unit_test.py @@ -0,0 +1,102 @@ +# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. + +""" +X axis scale draw unit tests +---------------------------- + +The X axis of the signal view keeps its scale draw when the view is refreshed, so +that its ticks stay aligned with the grid and the curves (the scale draw is only +replaced when leaving the date/time mode). +""" + +# guitest: show + +from __future__ import annotations + +from datetime import datetime, timedelta + +import numpy as np +import pytest +from plotpy.plot import BasePlot +from qwt import QwtDateTimeScaleDraw +from sigima.objects import SignalObj, create_signal + +from datalab.tests import datalab_test_app_context + +# X bounds which are not graduations of the axis once autoscaled (as for the +# 'step.npy' test file): end labels do not overflow the axis ends +X_MIN, X_MAX = 3.1e-7, 4.08e-7 + + +def _create_step_signal() -> SignalObj: + """Create a signal whose X axis bounds are not graduations""" + x = np.linspace(X_MIN, X_MAX, 500) + return create_signal("Step", x, np.tanh((x - 3.15e-7) * 1e8)) + + +def _create_datetime_signal() -> SignalObj: + """Create a signal with a date/time X axis""" + start = datetime(2025, 1, 1) + timestamps = [start + timedelta(seconds=i) for i in range(100)] + obj = create_signal("Date/time", np.arange(100.0), np.sin(np.arange(100.0) / 10)) + obj.set_x_from_datetime(timestamps, unit="s") + return obj + + +def _x_axis_ends(plot: BasePlot) -> tuple[list[float], list[float]]: + """Return the ends of the X axis scale and of the X canvas map, in canvas + coordinates""" + widget = plot.axisWidget(plot.xBottom) + offset = widget.x() - plot.canvas().x() + smap = widget.scaleDraw().scaleMap() + cmap = plot.canvasMap(plot.xBottom) + return ( + sorted((smap.p1() + offset, smap.p2() + offset)), + sorted((cmap.p1(), cmap.p2())), + ) + + +def test_refresh_keeps_x_scale_draw() -> None: + """Refreshing the signal view keeps the scale draw of the X axis""" + with datalab_test_app_context(console=False, exec_loop=False) as win: + panel = win.signalpanel + plot = panel.plothandler.plot + panel.add_object(_create_step_signal()) + scale_draw = plot.axisScaleDraw(plot.xBottom) + panel.manual_refresh() + assert plot.axisScaleDraw(plot.xBottom) is scale_draw + panel.add_object(_create_step_signal()) + assert plot.axisScaleDraw(plot.xBottom) is scale_draw + + +def test_x_axis_aligned_with_canvas() -> None: + """The X axis ticks are aligned with the grid and the curves after loading""" + with datalab_test_app_context(console=False, exec_loop=False) as win: + panel = win.signalpanel + plot = panel.plothandler.plot + panel.add_object(_create_step_signal()) + scale_ends, canvas_ends = _x_axis_ends(plot) + assert scale_ends == pytest.approx(canvas_ends, abs=0.5) + panel.manual_refresh() + scale_ends, canvas_ends = _x_axis_ends(plot) + assert scale_ends == pytest.approx(canvas_ends, abs=0.5) + + +def test_datetime_x_axis_restored() -> None: + """The date/time X axis formatting is removed when showing regular signals""" + with datalab_test_app_context(console=False, exec_loop=False) as win: + panel = win.signalpanel + plot = panel.plothandler.plot + panel.add_object(_create_datetime_signal()) + assert isinstance(plot.axisScaleDraw(plot.xBottom), QwtDateTimeScaleDraw) + panel.add_object(_create_step_signal()) + scale_draw = plot.axisScaleDraw(plot.xBottom) + assert not isinstance(scale_draw, QwtDateTimeScaleDraw) + panel.manual_refresh() + assert plot.axisScaleDraw(plot.xBottom) is scale_draw + + +if __name__ == "__main__": + test_refresh_keeps_x_scale_draw() + test_x_axis_aligned_with_canvas() + test_datetime_x_axis_restored() From 3e1cbf66fa6047d179bc1bfa6cc91a9a3d824ec0 Mon Sep 17 00:00:00 2001 From: Pierre Raybaut <1311787+PierreRaybaut@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:26:48 +0100 Subject: [PATCH 2/2] docs: X axis alignment (release notes, translations) Add the 1.4.0 bug-fix note on the X axis ticks shifted relative to the grid and the curves, and its French translation. Refs #457 Assisted-by: Claude Opus 5.5 --- doc/locale/fr/LC_MESSAGES/release_notes/release_1.04.po | 3 +++ doc/release_notes/release_1.04.md | 1 + 2 files changed, 4 insertions(+) diff --git a/doc/locale/fr/LC_MESSAGES/release_notes/release_1.04.po b/doc/locale/fr/LC_MESSAGES/release_notes/release_1.04.po index 83fc9ad1..8cdc95b8 100644 --- a/doc/locale/fr/LC_MESSAGES/release_notes/release_1.04.po +++ b/doc/locale/fr/LC_MESSAGES/release_notes/release_1.04.po @@ -495,6 +495,9 @@ msgstr "Correction du sous-échantillonnage automatique des signaux (**Préfére msgid "Fixed changes of the auto downsampling and line width settings only applying to the curves of the selected signals: the other curves kept the former settings, even once selected again. These settings now apply to all curves as soon as the Settings dialog is closed" msgstr "Correction des modifications des préférences de sous-échantillonnage automatique et d'épaisseur de trait qui ne s'appliquaient qu'aux courbes des signaux sélectionnés : les autres courbes conservaient les anciennes préférences, même une fois sélectionnées à nouveau. Ces préférences s'appliquent désormais à toutes les courbes dès la fermeture de la boîte de dialogue des préférences" +msgid "Fixed the X axis ticks being shifted relative to the vertical grid lines and the curves after opening a signal or changing the selection, for signals whose X axis does not start and end on a graduation (e.g., `step.npy` from the Sigima test data): the shift remained until the view was panned or zoomed. The X axis now stays aligned with the grid and the curves (fixes [Issue #457](https://github.com/DataLab-Platform/DataLab/issues/457))" +msgstr "Correction des graduations de l'axe X qui étaient décalées par rapport aux lignes verticales de la grille et aux courbes après l'ouverture d'un signal ou un changement de sélection, pour les signaux dont l'axe X ne commence et ne se termine pas sur une graduation (par exemple, `step.npy` des données de test de Sigima) : le décalage persistait jusqu'à un déplacement ou un zoom de la vue. L'axe X reste désormais aligné avec la grille et les courbes (corrige [Issue #457](https://github.com/DataLab-Platform/DataLab/issues/457))" + msgid "**HDF5 import:**" msgstr "**Import HDF5 :**" diff --git a/doc/release_notes/release_1.04.md b/doc/release_notes/release_1.04.md index 6c42d920..0f2f26a7 100644 --- a/doc/release_notes/release_1.04.md +++ b/doc/release_notes/release_1.04.md @@ -216,6 +216,7 @@ See the [HDF5 browser guide](../features/common/h5browser.rst) for scientific ob * Fixed auto downsampling of signals (**Settings > Visualization > Signals**) only starting above 5 times the configured maximum number of points: with the default value of 100,000 points, signals of a few hundred thousand points were drawn in full, which slowed down the display. Signals with more points than the maximum are now downsampled so that at most this number of points is drawn, and the Settings dialog and the documentation now give the actual default value (100,000 instead of 10,000) (fixes [Issue #455](https://github.com/DataLab-Platform/DataLab/issues/455)) * Fixed changes of the auto downsampling and line width settings only applying to the curves of the selected signals: the other curves kept the former settings, even once selected again. These settings now apply to all curves as soon as the Settings dialog is closed +* Fixed the X axis ticks being shifted relative to the vertical grid lines and the curves after opening a signal or changing the selection, for signals whose X axis does not start and end on a graduation (e.g., `step.npy` from the Sigima test data): the shift remained until the view was panned or zoomed. The X axis now stays aligned with the grid and the curves (fixes [Issue #457](https://github.com/DataLab-Platform/DataLab/issues/457)) **HDF5 import:**