From c2cc7f17038242e3943c361c8b537511a9846761 Mon Sep 17 00:00:00 2001
From: Pierre Raybaut <1311787+PierreRaybaut@users.noreply.github.com>
Date: Thu, 29 Oct 2026 06:37:32 +0100
Subject: [PATCH 1/2] feat(signal): curve styles by position, user styles kept,
reset action
- Visible curves are given the automatic style of their position among
the curves shown, in the order of the signals in the panel; curves
whose color or line style was chosen by the user keep it
- Creating plot items for the panel no longer uses the global style
cycle, so that the first curves shown always get the first styles
- View > Reset curve styles now removes the colors and line styles
stored for the selected signals, which get automatic styles again
- Export `CurveStyles` and `LINE_STYLE_KEYS` from datalab.adapters_plotpy
- Requires SigimaX changes of DataLab-Platform/SigimaX#28
Refs #453
Assisted-by: Claude Opus 5.5
---
datalab/adapters_plotpy/__init__.py | 4 +
datalab/adapters_plotpy/objects/adapters.py | 2 +
datalab/gui/actionhandler.py | 8 +-
datalab/gui/panel/signal.py | 21 ++-
datalab/gui/plothandler.py | 43 ++++-
datalab/locale/fr/LC_MESSAGES/datalab.po | 8 +-
.../features/signal/curve_styles_unit_test.py | 172 ++++++++++++++++++
7 files changed, 247 insertions(+), 11 deletions(-)
create mode 100644 datalab/tests/features/signal/curve_styles_unit_test.py
diff --git a/datalab/adapters_plotpy/__init__.py b/datalab/adapters_plotpy/__init__.py
index 61bccf904..6f9a2bf73 100644
--- a/datalab/adapters_plotpy/__init__.py
+++ b/datalab/adapters_plotpy/__init__.py
@@ -10,7 +10,9 @@
__all__ = [
"CURVESTYLES",
+ "LINE_STYLE_KEYS",
"CircularROIPlotPyAdapter",
+ "CurveStyles",
"GeometryPlotPyAdapter",
"ImageObjPlotPyAdapter",
"PolygonalROIPlotPyAdapter",
@@ -51,6 +53,8 @@
from .factories import DataLabPlotPyAdapterFactory
from .objects.adapters import (
CURVESTYLES,
+ LINE_STYLE_KEYS,
+ CurveStyles,
ImageObjPlotPyAdapter,
SignalObjPlotPyAdapter,
TypePlotItem,
diff --git a/datalab/adapters_plotpy/objects/adapters.py b/datalab/adapters_plotpy/objects/adapters.py
index 893260032..3a1307f8c 100644
--- a/datalab/adapters_plotpy/objects/adapters.py
+++ b/datalab/adapters_plotpy/objects/adapters.py
@@ -21,6 +21,7 @@
from sigimax.adapters_plotpy.objects.image import get_obj_coords
from sigimax.adapters_plotpy.objects.signal import (
CURVESTYLES,
+ LINE_STYLE_KEYS,
CurveStyles,
apply_downsampling,
apply_line_width,
@@ -31,6 +32,7 @@
__all__ = [
"CURVESTYLES",
+ "LINE_STYLE_KEYS",
"BaseObjPlotPyAdapter",
"CurveStyles",
"ImageObjPlotPyAdapter",
diff --git a/datalab/gui/actionhandler.py b/datalab/gui/actionhandler.py
index 539f7e663..7ab72c5e2 100644
--- a/datalab/gui/actionhandler.py
+++ b/datalab/gui/actionhandler.py
@@ -1510,12 +1510,14 @@ def create_last_actions(self):
antialiasing_action.setChecked(Conf.sig_antialiasing.get(True))
self.new_action(
_("Reset curve styles"),
- select_condition=SelectCond.always,
+ select_condition=SelectCond.at_least_one_group_or_one_object,
icon_name="reset_curve_styles.svg",
triggered=self.panel.reset_curve_styles,
tip=_(
- "Curve styles are looped over a list of predefined styles.\n"
- "This action resets the list to its initial state."
+ "Curves are given automatic styles according to their "
+ "position on the plot.\n"
+ "This action removes the colors and line styles chosen for "
+ "the selected signals, which are given automatic styles again."
),
toolbar_pos=-1,
)
diff --git a/datalab/gui/panel/signal.py b/datalab/gui/panel/signal.py
index 8a8d18e7e..a21ea4a42 100644
--- a/datalab/gui/panel/signal.py
+++ b/datalab/gui/panel/signal.py
@@ -24,7 +24,7 @@
from sigima.io.signal import SignalIORegistry
from sigima.objects import SignalObj, SignalROI
-from datalab.adapters_plotpy import CURVESTYLES
+from datalab.adapters_plotpy import CURVESTYLES, LINE_STYLE_KEYS
from datalab.config import Conf, _
from datalab.gui import roieditor
from datalab.gui.actionhandler import SignalActionHandler
@@ -173,5 +173,22 @@ def toggle_anti_aliasing(self, state: bool) -> None:
self.plothandler.toggle_anti_aliasing(state)
def reset_curve_styles(self) -> None:
- """Reset curve styles"""
+ """Reset the curve styles of the selected signals
+
+ Colors and line styles chosen by the user are removed from the metadata of
+ the selected signals, whose curves are given automatic styles again.
+ """
CURVESTYLES.reset_styles()
+ modified = False
+ for obj in self.objview.get_sel_objects(include_groups=True):
+ for key in LINE_STYLE_KEYS:
+ if obj.metadata.pop(key, None) is not None:
+ modified = True
+ item = self.plothandler.get(get_uuid(obj))
+ if item is not None and not CURVESTYLES.has_automatic_style(item):
+ CURVESTYLES.restore_automatic_style(item)
+ item.param.update_item(item)
+ modified = True
+ self.refresh_plot("selected", update_items=False)
+ if modified:
+ self.SIG_OBJECT_MODIFIED.emit()
diff --git a/datalab/gui/plothandler.py b/datalab/gui/plothandler.py
index 4f0bbc45e..75e97a196 100644
--- a/datalab/gui/plothandler.py
+++ b/datalab/gui/plothandler.py
@@ -41,7 +41,12 @@
from sigima.objects import ImageObj, SignalObj, TypeObj
from datalab.adapters_metadata import GeometryAdapter, TableAdapter
-from datalab.adapters_plotpy import TypePlotItem, create_adapter_from_object
+from datalab.adapters_plotpy import (
+ CURVESTYLES,
+ CurveStyles,
+ TypePlotItem,
+ create_adapter_from_object,
+)
from datalab.adapters_plotpy.objects.scalar import MergedResultPlotPyAdapter
from datalab.config import Conf, _
from datalab.utils.qthelpers import block_signals, create_progress_bar
@@ -297,7 +302,10 @@ def __add_item_to_plot(self, oid: str) -> TypePlotItem:
"""
obj = self.panel.objmodel[oid]
self.__cached_hashes[obj] = calc_data_hash(obj)
- item: TypePlotItem = create_adapter_from_object(obj).make_item()
+ # Automatic curve styles are given by `update_automatic_styles`, according
+ # to the position of the curves on the plot: the style cycle is not used
+ with CURVESTYLES.suspend():
+ item: TypePlotItem = create_adapter_from_object(obj).make_item()
item.param.label = self.panel.mainwindow.render_object_title(obj.title)
item.param.update_item(item)
item.set_readonly(True)
@@ -412,10 +420,17 @@ def refresh_plot(
only_visible=only_visible,
only_existing=only_existing,
)
+ self.update_automatic_styles()
finally:
self.plot.setAutoReplot(auto_replot)
self.plot.replot()
+ def update_automatic_styles(self) -> None:
+ """Update the automatic styles of the visible plot items
+
+ This base implementation does nothing.
+ """
+
def __refresh_plot(
self,
what: str,
@@ -620,6 +635,30 @@ def get_plot_options(self) -> PlotOptions:
options.curve_antialiasing = self.plot.antialiased
return options
+ def update_automatic_styles(self) -> None:
+ """Give the visible curves the automatic style of their position
+
+ Visible curves are numbered in the order of their signals in the panel.
+ Each curve whose color and line style were not chosen by the user is given
+ the automatic style of its position (see :py:meth:`CurveStyles.style_at`),
+ so that the first curves shown are always given the first styles.
+ """
+ index = 0
+ for oid in self.panel.objmodel.get_object_ids():
+ item = self.get(oid)
+ if item is None or not item.isVisible():
+ continue
+ style = CurveStyles.style_at(index)
+ index += 1
+ if (
+ CURVESTYLES.has_automatic_style(item)
+ and CURVESTYLES.get_automatic_style(item) != style
+ ):
+ CURVESTYLES.set_automatic_style(item, *style)
+ item.param.update_item(item)
+ if item.selected:
+ item.select()
+
def refresh_plot(
self,
what: str,
diff --git a/datalab/locale/fr/LC_MESSAGES/datalab.po b/datalab/locale/fr/LC_MESSAGES/datalab.po
index 0cff789c3..6a2c30248 100644
--- a/datalab/locale/fr/LC_MESSAGES/datalab.po
+++ b/datalab/locale/fr/LC_MESSAGES/datalab.po
@@ -874,11 +874,11 @@ msgid "Reset curve styles"
msgstr "Réinitialiser les styles de courbes"
msgid ""
-"Curve styles are looped over a list of predefined styles.\n"
-"This action resets the list to its initial state."
+"Curves are given automatic styles according to their position on the plot.\n"
+"This action removes the colors and line styles chosen for the selected signals, which are given automatic styles again."
msgstr ""
-"Les styles de courbes sont parcourus dans une liste de styles prédéfinis.\n"
-"Cette action réinitialise la liste à son état initial."
+"Les courbes reçoivent des styles automatiques selon leur position dans le graphique.\n"
+"Cette action supprime les couleurs et les styles de trait choisis pour les signaux sélectionnés, qui reçoivent à nouveau des styles automatiques."
msgid "image"
msgstr "image"
diff --git a/datalab/tests/features/signal/curve_styles_unit_test.py b/datalab/tests/features/signal/curve_styles_unit_test.py
new file mode 100644
index 000000000..af3d5ca51
--- /dev/null
+++ b/datalab/tests/features/signal/curve_styles_unit_test.py
@@ -0,0 +1,172 @@
+# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
+
+"""
+Curve style unit tests
+----------------------
+
+Automatic curve styles depend on the position of the curves on the plot, are not
+stored in signal metadata, and are not shifted by dialogs showing signals. Curve
+styles chosen by the user are kept.
+"""
+
+# guitest: show
+
+from __future__ import annotations
+
+import os.path as osp
+
+import numpy as np
+from plotpy.items import CurveItem
+from qtpy import QtGui as QG
+from sigima.objects import SignalObj, create_signal
+
+from datalab.adapters_plotpy import (
+ CURVESTYLES,
+ LINE_STYLE_KEYS,
+ CurveStyles,
+ create_adapter_from_object,
+)
+from datalab.gui.panel.signal import SignalPanel
+from datalab.objectmodel import get_uuid
+from datalab.tests import datalab_test_app_context, helpers
+
+
+def _create_signals(count: int) -> list[SignalObj]:
+ """Create test signals"""
+ x = np.linspace(0.0, 1.0, 50)
+ return [create_signal(f"Signal {i}", x, x + i) for i in range(count)]
+
+
+def _item(panel: SignalPanel, obj: SignalObj) -> CurveItem:
+ """Return the curve item of a signal"""
+ return panel.plothandler[get_uuid(obj)]
+
+
+def _style(item: CurveItem) -> tuple[str, str]:
+ """Return the color and the line style of a curve item"""
+ return QG.QColor(item.param.line.color).name(), item.param.line.style
+
+
+def _set_user_style(panel: SignalPanel, item: CurveItem, color: str) -> None:
+ """Set a curve color as the user does with the curve parameters dialog"""
+ item.param.line.color = color
+ item.param.update_item(item)
+ panel.plot_item_parameters_changed(item)
+
+
+def test_styles_follow_curve_positions() -> None:
+ """Visible curves are given the automatic styles of their positions"""
+ with datalab_test_app_context(console=False, exec_loop=False) as win:
+ panel = win.signalpanel
+ signals = _create_signals(12)
+ panel.add_objects(signals, set_current=False)
+ panel.objview.select_objects(signals)
+ for index, obj in enumerate(signals):
+ assert _style(_item(panel, obj)) == CurveStyles.style_at(index)
+ assert _style(_item(panel, signals[11]))[1] == "DashLine"
+
+ # Whatever the signals shown, the first curves get the first styles
+ panel.objview.select_objects([signals[7], signals[4]])
+ assert _style(_item(panel, signals[4])) == CurveStyles.style_at(0)
+ assert _style(_item(panel, signals[7])) == CurveStyles.style_at(1)
+
+
+def test_style_cycle_not_used_by_panel_and_dialogs() -> None:
+ """Showing signals, on the panel or in dialogs, does not shift the style cycle"""
+ with datalab_test_app_context(console=False, exec_loop=False) as win:
+ panel = win.signalpanel
+ signals = _create_signals(3)
+ panel.add_objects(signals, set_current=False)
+ CURVESTYLES.reset_styles()
+ panel.objview.select_objects(signals)
+ # As done by the ROI editor or "View in a new window":
+ for obj in signals:
+ item = _item(panel, obj)
+ copy = create_adapter_from_object(obj).make_item(update_from=item)
+ assert _style(copy) == _style(item)
+ new_item = create_adapter_from_object(signals[0]).make_item()
+ assert _style(new_item) == CurveStyles.style_at(0)
+
+
+def test_user_styles_are_kept() -> None:
+ """Curve styles chosen by the user are kept and stored in metadata"""
+ with helpers.WorkdirRestoringTempDir() as tmpdir:
+ with datalab_test_app_context(console=False, exec_loop=False) as win:
+ panel = win.signalpanel
+ signals = _create_signals(3)
+ panel.add_objects(signals, set_current=False)
+ panel.objview.select_objects(signals)
+ _set_user_style(panel, _item(panel, signals[1]), "#000000")
+ assert signals[1].metadata["color"] == "#000000"
+ for obj in (signals[0], signals[2]):
+ for key in LINE_STYLE_KEYS:
+ assert key not in obj.metadata
+
+ # The curve keeps its color and its position in the style cycle
+ panel.objview.select_objects([signals[2], signals[1]])
+ assert _style(_item(panel, signals[1]))[0] == "#000000"
+ assert _style(_item(panel, signals[2])) == CurveStyles.style_at(1)
+
+ # Workspace round trip: only the user-defined style is stored
+ fname = osp.join(tmpdir, "curve_styles.h5")
+ win.save_h5_workspace(fname)
+ panel.remove_all_objects()
+ win.load_h5_workspace([fname], reset_all=True)
+ loaded = panel.objmodel.get_all_objects()
+ assert loaded[1].metadata["color"] == "#000000"
+ for obj in (loaded[0], loaded[2]):
+ for key in LINE_STYLE_KEYS:
+ assert key not in obj.metadata
+ panel.objview.select_objects(loaded)
+ assert _style(_item(panel, loaded[0])) == CurveStyles.style_at(0)
+ assert _style(_item(panel, loaded[1]))[0] == "#000000"
+ assert _style(_item(panel, loaded[2])) == CurveStyles.style_at(2)
+
+
+def test_legacy_styles_are_automatic() -> None:
+ """Styles stored by earlier versions from their style cycle are automatic"""
+ with helpers.WorkdirRestoringTempDir() as tmpdir:
+ with datalab_test_app_context(console=False, exec_loop=False) as win:
+ panel = win.signalpanel
+ signals = _create_signals(2)
+ for obj in signals:
+ obj.metadata["color"] = CurveStyles.LEGACY_COLORS[2]
+ obj.metadata["style"] = "DashDotDotLine"
+ panel.add_objects(signals, set_current=False)
+ panel.objview.select_objects(signals)
+ for index, obj in enumerate(signals):
+ assert _style(_item(panel, obj)) == CurveStyles.style_at(index)
+ win.save_h5_workspace(osp.join(tmpdir, "legacy_styles.h5"))
+ for obj in signals:
+ for key in LINE_STYLE_KEYS:
+ assert key not in obj.metadata
+
+
+def test_reset_curve_styles() -> None:
+ """Resetting curve styles gives the selected curves automatic styles again"""
+ with datalab_test_app_context(console=False, exec_loop=False) as win:
+ panel = win.signalpanel
+ signals = _create_signals(3)
+ panel.add_objects(signals, set_current=False)
+ panel.objview.select_objects(signals)
+ for obj in signals:
+ _set_user_style(panel, _item(panel, obj), "#000000")
+ signals[0].metadata["style"] = "DotLine"
+
+ panel.objview.select_objects(signals[:2])
+ panel.reset_curve_styles()
+ for index, obj in enumerate(signals[:2]):
+ for key in LINE_STYLE_KEYS:
+ assert key not in obj.metadata
+ assert _style(_item(panel, obj)) == CurveStyles.style_at(index)
+ # Signals which are not selected keep their style
+ assert signals[2].metadata["color"] == "#000000"
+ assert _style(_item(panel, signals[2]))[0] == "#000000"
+
+
+if __name__ == "__main__":
+ test_styles_follow_curve_positions()
+ test_style_cycle_not_used_by_panel_and_dialogs()
+ test_user_styles_are_kept()
+ test_legacy_styles_are_automatic()
+ test_reset_curve_styles()
From ebe80a21d529e404a37d66165791d1ace46dac1d Mon Sep 17 00:00:00 2001
From: Pierre Raybaut <1311787+PierreRaybaut@users.noreply.github.com>
Date: Thu, 29 Oct 2026 06:37:42 +0100
Subject: [PATCH 2/2] docs: curve readability (user guide, release notes,
translations)
- View menu: automatic curve styles by position, user styles, and the
new behavior of Reset curve styles
- Settings: thin curves drawn one screen pixel wide on HiDPI screens
- Release notes 1.4.0: Curve readability
- French translations
Refs #453
Assisted-by: Claude Opus 5.5
---
doc/features/common/settings.rst | 4 ++-
doc/features/signal/menu_view.rst | 23 ++++++++++----
.../LC_MESSAGES/features/common/settings.po | 6 ++--
.../LC_MESSAGES/features/signal/menu_view.po | 19 ++++++++----
.../LC_MESSAGES/release_notes/release_1.04.po | 31 +++++++++++++++++--
doc/release_notes/release_1.04.md | 11 +++++++
6 files changed, 76 insertions(+), 18 deletions(-)
diff --git a/doc/features/common/settings.rst b/doc/features/common/settings.rst
index ad0bcd09d..2796661ec 100644
--- a/doc/features/common/settings.rst
+++ b/doc/features/common/settings.rst
@@ -251,7 +251,9 @@ The Signals sub-tab contains settings specific to signal visualizations:
Default line width for curves representing signals. This setting affects all signal
visualizations unless overridden individually. Note: for signals exceeding the line
width performance threshold (see below), the line width is automatically clamped to
- 1.0 for optimal rendering performance.
+ 1.0 for optimal rendering performance. Curves of width 1.0 or less are drawn one
+ screen pixel wide, also on high-DPI screens (e.g. with a 150% display scaling),
+ which keeps their rendering fast.
**Line width performance threshold**
For signals with more than this number of points (default: 1,000), line width is
diff --git a/doc/features/signal/menu_view.rst b/doc/features/signal/menu_view.rst
index 0bc4b0058..fa220c99d 100644
--- a/doc/features/signal/menu_view.rst
+++ b/doc/features/signal/menu_view.rst
@@ -127,12 +127,23 @@ Anti-aliasing makes the curves look smoother, but it may also make them look les
Reset curve styles
^^^^^^^^^^^^^^^^^^
-Reset the curve style cycle to the beginning.
+Give the selected signals automatic curve styles again.
When plotting curves, DataLab automatically assigns a color and a line style to
-each curve. Both parameters are chosen from a predefined list of colors and
-line styles, and are assigned in a round-robin fashion.
+each curve, according to its position among the curves shown on the plot (in the
+order of the signals in the panel): the first curves are drawn with solid lines
+in distinct colors, readable on both light and dark backgrounds, and the next
+ones with dashed lines in the same colors. The first curve shown is thus always
+assigned the first color, whatever the signals shown.
-This menu entry allows you to reset the curve styles, so that the next time
-you plot curves, the first curve will be assigned the first color and the first
-line style of the predefined lists, and the loop will start again from there.
\ No newline at end of file
+Automatic styles are not saved with the signals. If you choose another color or
+line style for a curve (with the "Parameters" option of its context menu), this
+style is saved in the metadata of the signal and is kept afterwards.
+
+This menu entry removes the colors and line styles chosen for the selected signals,
+so that their curves are assigned automatic styles again.
+
+.. note::
+ Workspaces saved with DataLab 1.3 or earlier store the automatic style of
+ each curve: these styles are recognized as automatic styles, and are replaced
+ by the current ones.
\ No newline at end of file
diff --git a/doc/locale/fr/LC_MESSAGES/features/common/settings.po b/doc/locale/fr/LC_MESSAGES/features/common/settings.po
index dd59608f8..da1c34e09 100644
--- a/doc/locale/fr/LC_MESSAGES/features/common/settings.po
+++ b/doc/locale/fr/LC_MESSAGES/features/common/settings.po
@@ -319,8 +319,9 @@ msgstr "Préférences de visualisation - Sous-onglet Signaux"
msgid "**Default line width**"
msgstr "**Épaisseur par défaut**"
-msgid "Default line width for curves representing signals. This setting affects all signal visualizations unless overridden individually. Note: for signals exceeding the line width performance threshold (see below), the line width is automatically clamped to 1.0 for optimal rendering performance."
-msgstr "Épaisseur de trait par défaut pour les courbes représentant les signaux. Ce paramètre affecte toutes les visualisations de signaux, sauf si elles sont remplacées individuellement. Remarque : pour les signaux dépassant le seuil de performance de l'épaisseur de trait (voir ci-dessous), l'épaisseur de trait est automatiquement plafonnée à 1,0 pour des performances de rendu optimales."
+#, python-format
+msgid "Default line width for curves representing signals. This setting affects all signal visualizations unless overridden individually. Note: for signals exceeding the line width performance threshold (see below), the line width is automatically clamped to 1.0 for optimal rendering performance. Curves of width 1.0 or less are drawn one screen pixel wide, also on high-DPI screens (e.g. with a 150% display scaling), which keeps their rendering fast."
+msgstr "Épaisseur de trait par défaut pour les courbes représentant les signaux. Ce paramètre affecte toutes les visualisations de signaux, sauf si elles sont remplacées individuellement. Remarque : pour les signaux dépassant le seuil de performance de l'épaisseur de trait (voir ci-dessous), l'épaisseur de trait est automatiquement plafonnée à 1,0 pour des performances de rendu optimales. Les courbes d'épaisseur 1,0 ou moins sont tracées avec une largeur d'un pixel de l'écran, y compris sur les écrans haute résolution (par exemple avec une mise à l'échelle de l'affichage de 150%), ce qui préserve la rapidité de leur tracé."
msgid "**Line width performance threshold**"
msgstr "**Seuil de performance de l'épaisseur de trait**"
@@ -630,3 +631,4 @@ msgstr "**Arguments de l'éditeur externe**"
msgid "Command-line arguments to pass to the external editor."
msgstr "Arguments de ligne de commande à passer à l'éditeur externe."
+
diff --git a/doc/locale/fr/LC_MESSAGES/features/signal/menu_view.po b/doc/locale/fr/LC_MESSAGES/features/signal/menu_view.po
index 712e6b5c2..bc63255fb 100644
--- a/doc/locale/fr/LC_MESSAGES/features/signal/menu_view.po
+++ b/doc/locale/fr/LC_MESSAGES/features/signal/menu_view.po
@@ -134,11 +134,18 @@ msgstr "L'anticrénelage peut ralentir significativement l'affichage, surtout qu
msgid "Reset curve styles"
msgstr "Réinitialiser les styles de courbes"
-msgid "Reset the curve style cycle to the beginning."
-msgstr "Réinitialise le style de courbe au début du cycle."
+msgid "Give the selected signals automatic curve styles again."
+msgstr "Rend aux signaux sélectionnés des styles de courbes automatiques."
-msgid "When plotting curves, DataLab automatically assigns a color and a line style to each curve. Both parameters are chosen from a predefined list of colors and line styles, and are assigned in a round-robin fashion."
-msgstr "Lors de l'affichage de courbes, DataLab attribue automatiquement une couleur et un style de ligne à chaque courbe. Les deux paramètres sont choisis dans une liste prédéfinie de couleurs et de styles de ligne, et sont attribués de manière cyclique."
+msgid "When plotting curves, DataLab automatically assigns a color and a line style to each curve, according to its position among the curves shown on the plot (in the order of the signals in the panel): the first curves are drawn with solid lines in distinct colors, readable on both light and dark backgrounds, and the next ones with dashed lines in the same colors. The first curve shown is thus always assigned the first color, whatever the signals shown."
+msgstr "Lors de l'affichage de courbes, DataLab attribue automatiquement une couleur et un style de trait à chaque courbe, selon sa position parmi les courbes affichées dans le graphique (dans l'ordre des signaux du panneau) : les premières courbes sont tracées en traits pleins de couleurs bien distinctes, lisibles sur fond clair comme sur fond sombre, et les suivantes en tirets avec les mêmes couleurs. La première courbe affichée reçoit donc toujours la première couleur, quels que soient les signaux affichés."
+
+msgid "Automatic styles are not saved with the signals. If you choose another color or line style for a curve (with the \"Parameters\" option of its context menu), this style is saved in the metadata of the signal and is kept afterwards."
+msgstr "Les styles automatiques ne sont pas enregistrés avec les signaux. Si vous choisissez une autre couleur ou un autre style de trait pour une courbe (avec l'option « Paramètres » de son menu contextuel), ce style est enregistré dans les métadonnées du signal et il est conservé par la suite."
+
+msgid "This menu entry removes the colors and line styles chosen for the selected signals, so that their curves are assigned automatic styles again."
+msgstr "Cette entrée de menu supprime les couleurs et les styles de trait choisis pour les signaux sélectionnés, afin que leurs courbes reçoivent à nouveau des styles automatiques."
+
+msgid "Workspaces saved with DataLab 1.3 or earlier store the automatic style of each curve: these styles are recognized as automatic styles, and are replaced by the current ones."
+msgstr "Les espaces de travail enregistrés avec DataLab 1.3 ou une version antérieure contiennent le style automatique de chaque courbe : ces styles sont reconnus comme des styles automatiques et sont remplacés par les styles actuels."
-msgid "This menu entry allows you to reset the curve styles, so that the next time you plot curves, the first curve will be assigned the first color and the first line style of the predefined lists, and the loop will start again from there."
-msgstr "Cette entrée de menu permet de réinitialiser les styles de courbes, de sorte que la prochaine fois que vous afficherez des courbes, la première courbe sera attribuée la première couleur et le premier style de ligne de la liste prédéfinie, et la boucle recommencera à partir de là."
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 66089c6f7..e88cb0923 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
@@ -347,6 +347,34 @@ msgstr "Les plugins peuvent décrire les signaux et images des fichiers dont les
msgid "🔄 Changes"
msgstr "🔄 Changements"
+msgid "**Curve readability:**"
+msgstr "**Lisibilité des courbes :**"
+
+msgid "Signal curves are easier to read, especially when many curves are shown (closes [Issue #453](https://github.com/DataLab-Platform/DataLab/issues/453)):"
+msgstr "Les courbes des signaux sont plus lisibles, en particulier lorsque de nombreuses courbes sont affichées (corrige [l'issue #453](https://github.com/DataLab-Platform/DataLab/issues/453)) :"
+
+msgid "Curve colors now stand out on both light and dark backgrounds: several colors of the former palette (orange, pink, yellow-green, cyan) were pale on a white background, and gray curves could not be told apart from the grid"
+msgstr "Les couleurs des courbes ressortent désormais sur fond clair comme sur fond sombre : plusieurs couleurs de l'ancienne palette (orange, rose, vert-jaune, cyan) étaient pâles sur fond blanc, et les courbes grises se confondaient avec la grille"
+
+msgid "After the first 10 curves, curves are now drawn with dashed lines only: dash-dot and dash-dot-dot lines, which drew only half of the curve, are no longer used"
+msgstr "Au-delà des 10 premières courbes, les courbes sont désormais tracées uniquement en tirets : les traits mixtes (tiret-point et tiret-point-point), qui ne traçaient que la moitié de la courbe, ne sont plus utilisés"
+
+msgid "The plot grid now uses discreet solid lines instead of gray dotted lines, which competed with dashed curves; it also follows color theme changes"
+msgstr "La grille des graphiques utilise désormais des traits pleins discrets au lieu de pointillés gris, qui se confondaient avec les courbes en tirets ; elle suit également les changements de thème de couleurs"
+
+msgid "Curve colors and line styles now depend on the position of the curves among those shown on the plot, in the order of the signals in the panel: the first curve shown is always drawn in the first color. Opening the ROI editor, **View in a new window** or other dialogs no longer shifts the styles of the next curves, which could reach dotted styles with only a few curves shown"
+msgstr "Les couleurs et les styles de trait des courbes dépendent désormais de la position des courbes parmi celles affichées dans le graphique, dans l'ordre des signaux du panneau : la première courbe affichée est toujours tracée avec la première couleur. L'ouverture de l'éditeur de ROI, de **Voir dans une nouvelle fenêtre** ou d'autres boîtes de dialogue ne décale plus les styles des courbes suivantes, ce qui pouvait faire apparaître des pointillés avec seulement quelques courbes affichées"
+
+msgid "Automatic curve styles are no longer saved in workspaces: only a color or a line style chosen by the user is saved and kept. Workspaces saved with earlier versions are displayed with the new automatic styles"
+msgstr "Les styles de courbes automatiques ne sont plus enregistrés dans les espaces de travail : seuls une couleur ou un style de trait choisis par l'utilisateur sont enregistrés et conservés. Les espaces de travail enregistrés avec les versions précédentes sont affichés avec les nouveaux styles automatiques"
+
+msgid "**View > Reset curve styles** now gives the selected signals automatic curve styles again, removing the colors and line styles chosen for them"
+msgstr "**Affichage > Réinitialiser les styles de courbes** rend désormais aux signaux sélectionnés des styles de courbes automatiques, en supprimant les couleurs et les styles de trait choisis pour eux"
+
+#, python-format
+msgid "On high-DPI screens (e.g. with a 150 % display scaling), curves of width 1 are now drawn one screen pixel wide, which makes their rendering much faster: noisy signals were drawn up to several hundred times slower than at 100 % (requires PlotPy ≥ 2.12.0)"
+msgstr "Sur les écrans haute résolution (par exemple avec une mise à l'échelle de l'affichage de 150 %), les courbes d'épaisseur 1 sont désormais tracées avec une largeur d'un pixel de l'écran, ce qui rend leur tracé beaucoup plus rapide : les signaux bruités étaient tracés jusqu'à plusieurs centaines de fois plus lentement qu'à 100 % (nécessite PlotPy ≥ 2.12.0)"
+
msgid "**Synthetic images in physical coordinates:**"
msgstr "**Images synthétiques en coordonnées physiques :**"
@@ -618,6 +646,3 @@ msgstr "**Sécurité :**"
msgid "Opening a workspace received from someone else can no longer make DataLab load or run other code installed on the computer. The parameters stored with objects, analysis results and History actions name their class, and DataLab imported whatever module they named and called the object named there, which a modified file could use, for example, to make DataLab quit. Only parameter classes of DataLab, Sigima, guidata and installed plugins are now used; other stored parameters are reported as unreadable (fixes [Issue #407](https://github.com/DataLab-Platform/DataLab/issues/407))"
msgstr "Ouvrir un espace de travail reçu d'une autre personne ne permet plus de faire charger ou exécuter par DataLab un autre code installé sur l'ordinateur. Les paramètres enregistrés avec les objets, les résultats d'analyse et les actions de l'historique nomment leur classe, et DataLab importait le module ainsi nommé puis appelait l'objet désigné, ce qu'un fichier modifié pouvait exploiter, par exemple pour faire quitter DataLab. Seules les classes de paramètres de DataLab, Sigima, guidata et des plugins installés sont désormais utilisées ; les autres paramètres enregistrés sont signalés comme illisibles (corrige [l'issue #407](https://github.com/DataLab-Platform/DataLab/issues/407))"
-#~ msgid "Datasets with three dimensions or more (image stacks, spectral cubes...) are now listed in the HDF5 browser, but are not directly importable and have no graphical slice selector"
-#~ msgstr "Les jeux de données à trois dimensions ou plus (piles d'images, cubes spectraux...) sont désormais listés dans l'explorateur HDF5, mais ne sont pas directement importables et n'ont pas de sélecteur graphique de coupes"
-
diff --git a/doc/release_notes/release_1.04.md b/doc/release_notes/release_1.04.md
index 613de9532..f3fd36011 100644
--- a/doc/release_notes/release_1.04.md
+++ b/doc/release_notes/release_1.04.md
@@ -144,6 +144,17 @@ Signals, images and groups can now be deleted directly from the lists, as in Dat
### 🔄 Changes ###
+**Curve readability:**
+
+* Signal curves are easier to read, especially when many curves are shown (closes [Issue #453](https://github.com/DataLab-Platform/DataLab/issues/453)):
+ * Curve colors now stand out on both light and dark backgrounds: several colors of the former palette (orange, pink, yellow-green, cyan) were pale on a white background, and gray curves could not be told apart from the grid
+ * After the first 10 curves, curves are now drawn with dashed lines only: dash-dot and dash-dot-dot lines, which drew only half of the curve, are no longer used
+ * The plot grid now uses discreet solid lines instead of gray dotted lines, which competed with dashed curves; it also follows color theme changes
+* Curve colors and line styles now depend on the position of the curves among those shown on the plot, in the order of the signals in the panel: the first curve shown is always drawn in the first color. Opening the ROI editor, **View in a new window** or other dialogs no longer shifts the styles of the next curves, which could reach dotted styles with only a few curves shown
+* Automatic curve styles are no longer saved in workspaces: only a color or a line style chosen by the user is saved and kept. Workspaces saved with earlier versions are displayed with the new automatic styles
+* **View > Reset curve styles** now gives the selected signals automatic curve styles again, removing the colors and line styles chosen for them
+* On high-DPI screens (e.g. with a 150 % display scaling), curves of width 1 are now drawn one screen pixel wide, which makes their rendering much faster: noisy signals were drawn up to several hundred times slower than at 100 % (requires PlotPy ≥ 2.12.0)
+
**Synthetic images in physical coordinates:**
* The 2D Gaussian, 2D ramp, sinusoidal grating, ring pattern, Siemens star and 2D sinc images were computed over the [xmin, xmax] × [ymin, ymax] domain of their creation parameters, but were displayed in pixel coordinates: their centers, widths and spatial frequencies did not match the axes (a sinusoidal grating of frequency 0.1 over [0, 100] showed 10 periods along an axis graduated from 0 to 1023). These images are now displayed over their domain. The new **Physical coordinates** option of their creation parameters restores pixel coordinates when disabled (fixes [Issue #400](https://github.com/DataLab-Platform/DataLab/issues/400))