fix(waveform): use thin line when downsampling no-line curves

This commit is contained in:
wakonig_k committed 2026-09-17 16:46:54 +02:00
1 parent e4b02bcdf3
commit 7b54f6246e
4 files changed
+82 -15

No files matched your search

+1
View File
@@ -1056,6 +1056,7 @@ class Curve(RPCBase):
Args:
pen_style(Literal["solid", "dash", "dot", "dashdot", "none"]): Style of the pen.
Use "none" to display markers without connecting lines.
During automatic downsampling, "none" uses a 1 px solid line without markers.
"""
@rpc_call
+11 -5
View File
@@ -137,17 +137,23 @@ class Curve(BECConnector, pg.PlotDataItem):
"none": QtCore.Qt.NoPen,
}
pen_style = pen_style_map.get(self.config.pen_style, QtCore.Qt.SolidLine)
line_fallback = self.config.pen_style == "none" and self.opts["autoDownsample"]
pen_width = self.config.pen_width
if line_fallback:
pen_style = QtCore.Qt.SolidLine
pen_width = 1
pen = pg.mkPen(color=self.config.color, width=self.config.pen_width, style=pen_style)
pen = pg.mkPen(color=self.config.color, width=pen_width, style=pen_style)
self.setPen(pen)
if self.config.symbol:
symbol = None if line_fallback else self.config.symbol
if symbol:
symbol_color = self.config.symbol_color or self.config.color
brush = pg.mkBrush(color=symbol_color)
self.setSymbolBrush(brush)
self.setSymbolSize(self.config.symbol_size)
self.setSymbol(self.config.symbol)
self.setSymbol(symbol)
@property
def dap_params(self):
@@ -279,8 +285,7 @@ class Curve(BECConnector, pg.PlotDataItem):
symbol(str): Symbol of the curve.
"""
self.config.symbol = symbol
self.setSymbol(symbol)
self.updateItems()
self.apply_config()
def set_symbol_color(self, symbol_color: str):
"""
@@ -319,6 +324,7 @@ class Curve(BECConnector, pg.PlotDataItem):
Args:
pen_style(Literal["solid", "dash", "dot", "dashdot", "none"]): Style of the pen.
Use "none" to display markers without connecting lines.
During automatic downsampling, "none" uses a 1 px solid line without markers.
"""
self.config.pen_style = pen_style
self.apply_config()
+12 -7
View File
@@ -1938,7 +1938,8 @@ class Waveform(PlotBase):
Curve items, by deactivating the symbol and activating downsampling auto, method='mean',
if the data length exceeds N points. If the data length is less than N points, the
symbol will be activated and downsampling will be deactivated. Maximum points will be
5x the limit. Curves without a connecting line retain their markers at any length.
5x the limit. The "none" style uses a 1 px solid line without markers while
downsampling, retaining its configured appearance for smaller datasets.
Args:
curve(Curve): The curve to adjust.
@@ -1949,21 +1950,25 @@ class Waveform(PlotBase):
if limit <= 1:
logger.warning("Limit must be greater than 1.")
return
if curve.config.pen_style == "none" and curve.config.symbol is None:
curve.set_symbol("o")
if curve.config.pen_style == "none":
downsampling = data_length > limit
curve.setDownsampling(ds=None if downsampling else 1, auto=downsampling, method=method)
if not downsampling and curve.config.symbol is None:
curve.set_symbol("o")
curve.apply_config()
curve.setClipToView(True)
return
if data_length > limit:
if curve.config.pen_style != "none" and curve.config.symbol is not None:
if curve.config.symbol is not None:
curve.set_symbol(None)
if curve.config.pen_width > 3:
curve.set_pen_width(3)
curve.setDownsampling(ds=None, auto=True, method=method)
curve.setClipToView(True)
elif data_length <= limit:
if curve.config.pen_style != "none":
curve.set_symbol("o")
curve.set_symbol("o")
curve.set_pen_width(4)
curve.setDownsampling(ds=1, auto=None, method=method)
curve.setDownsampling(ds=1, auto=False, method=method)
curve.setClipToView(True)
def setup_dap_for_scan(self):
+58 -3
View File
@@ -1262,10 +1262,10 @@ def test_curve_no_line_style_preserves_markers(qtbot, mocked_client):
np.testing.assert_array_equal(curve.scatter.getData(), [[2, 3, 4], [5, 6, 7]])
@pytest.mark.parametrize("data_length", [500, 1500])
@pytest.mark.parametrize("data_length", [1000, 1001])
@pytest.mark.parametrize("symbol", [None, "d"])
def test_curve_no_line_style_keeps_async_markers(qtbot, mocked_client, data_length, symbol):
"""Async data retain markers, including when automatic styling previously hid them."""
def test_curve_no_line_style_async_downsampling(qtbot, mocked_client, data_length, symbol):
"""No-line curves fall back to a thin line without markers above the async limit."""
wf = create_widget(qtbot, Waveform, client=mocked_client)
curve = wf.plot(
x=[1, 2, 3],
@@ -1280,6 +1280,13 @@ def test_curve_no_line_style_keeps_async_markers(qtbot, mocked_client, data_leng
wf._auto_adjust_async_curve_settings(curve, data_length)
curve.setData(np.arange(data_length), np.arange(data_length))
assert curve.config.pen_style == "none"
assert curve.config.pen_width == 3
if data_length > 1000:
assert curve.curve.opts["pen"].style() == Qt.SolidLine
assert curve.curve.opts["pen"].widthF() == 1
assert not curve.scatter.isVisible()
return
assert curve.curve.opts["pen"].style() == Qt.NoPen
assert curve.scatter.isVisible()
assert curve.config.symbol == (symbol or "o")
@@ -1288,6 +1295,54 @@ def test_curve_no_line_style_keeps_async_markers(qtbot, mocked_client, data_leng
assert curve.scatter.opts["brush"].color().name() == "#654321"
def test_curve_no_line_downsampling_style_updates_and_restore(qtbot, mocked_client):
"""Appearance updates keep the fallback, and smaller data restore the saved markers."""
wf = create_widget(qtbot, Waveform, client=mocked_client)
curve = wf.plot(x=np.arange(1500), y=np.arange(1500))
wf._auto_adjust_async_curve_settings(curve, 1500)
curve.set_pen_style("none")
assert curve.curve.opts["pen"].style() == Qt.SolidLine
assert curve.curve.opts["pen"].widthF() == 1
assert not curve.scatter.isVisible()
curve.set_color("#123456")
curve.set_pen_width(7)
curve.set_symbol("d")
curve.set_symbol_color("#654321")
curve.set_symbol_size(12)
assert curve.curve.opts["pen"].style() == Qt.SolidLine
assert curve.curve.opts["pen"].widthF() == 1
assert curve.curve.opts["pen"].color().name() == "#123456"
assert not curve.scatter.isVisible()
assert curve.config.pen_style == "none"
assert curve.config.pen_width == 7
assert curve.config.symbol == "d"
wf._auto_adjust_async_curve_settings(curve, 1000)
curve.setData(np.arange(1000), np.arange(1000))
assert not curve.opts["autoDownsample"]
assert curve.curve.opts["pen"].style() == Qt.NoPen
assert curve.config.pen_width == 7
assert curve.scatter.isVisible()
assert curve.scatter.opts["symbol"] == "d"
assert curve.scatter.opts["size"] == 12
assert curve.scatter.opts["brush"].color().name() == "#654321"
def test_curve_no_line_after_named_style_downsampling_ends(qtbot, mocked_client):
"""Selecting no line after a named style leaves downsampling does not keep the fallback."""
wf = create_widget(qtbot, Waveform, client=mocked_client)
curve = wf.plot(x=np.arange(1500), y=np.arange(1500))
wf._auto_adjust_async_curve_settings(curve, 1500)
wf._auto_adjust_async_curve_settings(curve, 1000)
curve.setData(np.arange(1000), np.arange(1000))
curve.set_pen_style("none")
assert not curve.opts["autoDownsample"]
assert curve.curve.opts["pen"].style() == Qt.NoPen
assert curve.scatter.isVisible()
def test_curve_no_line_style_restores_existing_styles(qtbot, mocked_client):
"""The no-line style is reversible, and legacy null styles still draw solid lines."""
wf = create_widget(qtbot, Waveform, client=mocked_client)