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
2 changes: 1 addition & 1 deletion doc/release_notes/release_1.01.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* Automatic curve colors now have a contrast ratio of at least 3:1 against both the light and the dark plot backgrounds: several colors of the former palette (orange, pink, yellow-green, cyan) were pale on a white background, and the gray curve color could not be told apart from the grid.
* Automatic line styles now alternate only between solid and dashed lines: dash-dot and dash-dot-dot lines, which drew only half of the curve with thin lines, are no longer used.
* The plot grid now uses discreet solid lines, adapted to the color theme, instead of gray dotted lines competing with dashed curves. The grid is now also updated after a color theme change.
* Automatic curve styles are no longer stored in signal metadata: only a color or a line style chosen by the user is stored, and then restored. Colors and line styles stored by earlier versions from their automatic style cycle are recognized and handled as automatic styles.
* Automatic curve styles are no longer stored in signal metadata: only a color or a line style chosen by the user is stored, and then always restored, whatever its value. `CurveStyles.is_legacy_automatic` recognizes the colors and line styles that earlier versions stored from their automatic style cycle, so that applications may convert the data saved by these versions when loading them.
* Making a plot item from an existing one (`make_item(update_from=...)`) keeps its style instead of using the next style of the cycle, so that dialogs showing a signal no longer shift the styles of the next curves.
* New `CurveStyles` methods: `style_at` returns the automatic style at a given position, `set_automatic_style`, `get_automatic_style`, `has_automatic_style` and `restore_automatic_style` manage the automatic style of a curve, and `is_legacy_automatic` recognizes the styles stored by earlier versions.

Expand Down
22 changes: 11 additions & 11 deletions sigimax/adapters_plotpy/objects/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,15 @@ def style_generator() -> Generator[tuple[str, str], None, None]:

@staticmethod
def is_legacy_automatic(color: str | None, linestyle: str | None) -> bool:
"""Return True if a stored style comes from the automatic style cycle of
"""Return True if a stored style may come from the automatic style cycle of
earlier versions, which stored automatic styles in signal metadata

Signal metadata are not converted when curves are displayed: a stored color
and line style are always restored, whatever their values. Applications
may use this method to convert, when loading them, data saved by earlier
versions (e.g. by removing such styles from the signal metadata, so that
curves are given the current automatic styles).

Args:
color: stored color (None if not stored)
linestyle: stored line style (None if not stored)
Expand Down Expand Up @@ -299,22 +305,16 @@ def update_plot_item_parameters(self, item: CurveItem) -> None:
have been overriden by object metadata entries or other object data. The goal
is to update the plot item accordingly.

A color and a line style stored by earlier versions from their automatic
style cycle are ignored, so that the curve keeps its automatic style.
A color or a line style stored in the signal metadata is always restored:
it has been chosen by the user (automatic styles are not stored).

This is *almost* the inverse operation of `update_metadata_from_plot_item`.

Args:
item: plot item
"""
metadata = self.obj.metadata
line_source = metadata
if CURVESTYLES.is_legacy_automatic(*(metadata.get(k) for k in LINE_STYLE_KEYS)):
line_source = {
k: v for k, v in metadata.items() if k not in LINE_STYLE_KEYS
}
update_dataset(item.param.line, line_source)
update_dataset(item.param.symbol, metadata)
update_dataset(item.param.line, self.obj.metadata)
update_dataset(item.param.symbol, self.obj.metadata)
super().update_plot_item_parameters(item)

def update_metadata_from_plot_item(self, item: CurveItem) -> None:
Expand Down
62 changes: 48 additions & 14 deletions sigimax/tests/adapters_plotpy/test_curve_styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,29 +138,63 @@ def test_explicit_style_roundtrip() -> None:
assert not CURVESTYLES.has_automatic_style(item2)


def _save_and_reload(adapter: SignalObjPlotPyAdapter, item, cycles: int = 2):
"""Store the curve style in metadata and make a new item from them, as done
when saving and reloading a workspace, several times"""
for _cycle in range(cycles):
adapter.update_metadata_from_plot_item(item)
item = adapter.make_item()
return item


@pytest.mark.parametrize("linestyle", CurveStyles.LEGACY_LINESTYLES)
def test_legacy_style_is_automatic(linestyle: str) -> None:
"""Styles stored by earlier versions from their style cycle are automatic"""
def test_stored_style_is_kept(linestyle: str) -> None:
"""A stored style is always restored, even if it belongs to the automatic style
cycle of earlier versions (legacy data is converted by applications on load)"""
with qt_app_context(exec_loop=False):
adapter = _make_signal_adapter()
adapter.obj.metadata["color"] = CurveStyles.LEGACY_COLORS[1]
color = CurveStyles.LEGACY_COLORS[1]
adapter.obj.metadata["color"] = color
adapter.obj.metadata["style"] = linestyle
item = adapter.make_item()
assert CURVESTYLES.has_automatic_style(item)
assert _line_style(item)[0] in CurveStyles.COLORS
adapter.update_metadata_from_plot_item(item)
for key in LINE_STYLE_KEYS:
assert key not in adapter.obj.metadata
item = _save_and_reload(adapter, adapter.make_item())
assert _line_style(item) == (color, linestyle)
assert not CURVESTYLES.has_automatic_style(item)
assert adapter.obj.metadata["color"] == color
assert adapter.obj.metadata["style"] == linestyle


def test_partial_legacy_style_is_explicit() -> None:
"""A stored color without line style (e.g. set by a script) is explicit"""
def test_user_style_from_legacy_cycle_roundtrip() -> None:
"""A style chosen by the user is kept, even if it matches a style of the
automatic style cycle of earlier versions"""
with qt_app_context(exec_loop=False):
adapter = _make_signal_adapter()
adapter.obj.metadata["color"] = CurveStyles.LEGACY_COLORS[1]
item = adapter.make_item()
assert _line_style(item)[0] == CurveStyles.LEGACY_COLORS[1]
assert not CURVESTYLES.has_automatic_style(item)
item.param.line.color = "#1f77b4"
item.param.line.style = "SolidLine"
item.param.update_item(item)
assert CurveStyles.is_legacy_automatic("#1f77b4", "SolidLine")
item = _save_and_reload(adapter, item)
assert _line_style(item) == ("#1f77b4", "SolidLine")
assert adapter.obj.metadata["color"] == "#1f77b4"
assert adapter.obj.metadata["style"] == "SolidLine"


@pytest.mark.parametrize(
"color, linestyle, expected",
[
("#1f77b4", "SolidLine", True),
("#17BECF", "DashDotDotLine", True), # Case insensitive colors
("#1f77b4", "DotLine", False), # Not a style of the legacy cycle
("#2a77ae", "SolidLine", False), # Color of the current palette
("#1f77b4", None, False), # Line style not stored
(None, "SolidLine", False), # Color not stored
(1, "SolidLine", False), # Invalid values
],
)
def test_is_legacy_automatic(color, linestyle, expected: bool) -> None:
"""Styles of the automatic style cycle of earlier versions are recognized"""
with qt_app_context(exec_loop=False):
assert CurveStyles.is_legacy_automatic(color, linestyle) is expected


def test_restore_automatic_style() -> None:
Expand Down
Loading