mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-10-28 08:01:29 +01:00
feat(waveform): add no-line curve style
This commit is contained in:
1 parent
b76b95e7d8
commit
e4b02bcdf3
6 files changed
+136
-15
No files matched your search
@@ -974,7 +974,8 @@ class Curve(RPCBase):
|
||||
- symbol_color: str
|
||||
- symbol_size: int
|
||||
- pen_width: int
|
||||
- pen_style: Literal["solid", "dash", "dot", "dashdot"]
|
||||
- pen_style: Literal["solid", "dash", "dot", "dashdot", "none"]
|
||||
Use "none" to display markers without connecting lines.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
@@ -1046,12 +1047,15 @@ class Curve(RPCBase):
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_pen_style(self, pen_style: "Literal['solid', 'dash', 'dot', 'dashdot']"):
|
||||
def set_pen_style(
|
||||
self, pen_style: "Literal['solid', 'dash', 'dot', 'dashdot', 'none']"
|
||||
) -> "None":
|
||||
"""
|
||||
Change the pen style of the curve.
|
||||
|
||||
Args:
|
||||
pen_style(Literal["solid", "dash", "dot", "dashdot"]): Style of the pen.
|
||||
pen_style(Literal["solid", "dash", "dot", "dashdot", "none"]): Style of the pen.
|
||||
Use "none" to display markers without connecting lines.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
|
||||
@@ -41,8 +41,8 @@ class CurveConfig(ConnectionConfig):
|
||||
)
|
||||
symbol_size: int | None = Field(7, description="The size of the symbol of the curve.")
|
||||
pen_width: int | None = Field(4, description="The width of the pen of the curve.")
|
||||
pen_style: Literal["solid", "dash", "dot", "dashdot"] | None = Field(
|
||||
"solid", description="The style of the pen of the curve."
|
||||
pen_style: Literal["solid", "dash", "dot", "dashdot", "none"] | None = Field(
|
||||
"solid", description="The style of the curve's connecting line; 'none' disables the line."
|
||||
)
|
||||
source: Literal["device", "dap", "custom", "history"] = Field(
|
||||
"custom", description="The source of the curve."
|
||||
@@ -134,6 +134,7 @@ class Curve(BECConnector, pg.PlotDataItem):
|
||||
"dash": QtCore.Qt.DashLine,
|
||||
"dot": QtCore.Qt.DotLine,
|
||||
"dashdot": QtCore.Qt.DashDotLine,
|
||||
"none": QtCore.Qt.NoPen,
|
||||
}
|
||||
pen_style = pen_style_map.get(self.config.pen_style, QtCore.Qt.SolidLine)
|
||||
|
||||
@@ -238,7 +239,8 @@ class Curve(BECConnector, pg.PlotDataItem):
|
||||
- symbol_color: str
|
||||
- symbol_size: int
|
||||
- pen_width: int
|
||||
- pen_style: Literal["solid", "dash", "dot", "dashdot"]
|
||||
- pen_style: Literal["solid", "dash", "dot", "dashdot", "none"]
|
||||
Use "none" to display markers without connecting lines.
|
||||
"""
|
||||
|
||||
# Mapping of keywords to setter methods
|
||||
@@ -310,12 +312,13 @@ class Curve(BECConnector, pg.PlotDataItem):
|
||||
self.config.pen_width = pen_width
|
||||
self.apply_config()
|
||||
|
||||
def set_pen_style(self, pen_style: Literal["solid", "dash", "dot", "dashdot"]):
|
||||
def set_pen_style(self, pen_style: Literal["solid", "dash", "dot", "dashdot", "none"]) -> None:
|
||||
"""
|
||||
Change the pen style of the curve.
|
||||
|
||||
Args:
|
||||
pen_style(Literal["solid", "dash", "dot", "dashdot"]): Style of the pen.
|
||||
pen_style(Literal["solid", "dash", "dot", "dashdot", "none"]): Style of the pen.
|
||||
Use "none" to display markers without connecting lines.
|
||||
"""
|
||||
self.config.pen_style = pen_style
|
||||
self.apply_config()
|
||||
|
||||
@@ -282,8 +282,10 @@ class CurveRow(QTreeWidgetItem):
|
||||
|
||||
# Style in col 5
|
||||
self.style_combo = QComboBox()
|
||||
self.style_combo.addItems(["solid", "dash", "dot", "dashdot"])
|
||||
idx = self.style_combo.findText(self.config.pen_style)
|
||||
for style in ("solid", "dash", "dot", "dashdot"):
|
||||
self.style_combo.addItem(style, style)
|
||||
self.style_combo.addItem("No line", "none")
|
||||
idx = self.style_combo.findData(self.config.pen_style)
|
||||
if idx >= 0:
|
||||
self.style_combo.setCurrentIndex(idx)
|
||||
self.tree.setItemWidget(self, 5, self.style_combo)
|
||||
@@ -495,7 +497,7 @@ class CurveRow(QTreeWidgetItem):
|
||||
# Common style fields
|
||||
self.config.color = self.color_button.color
|
||||
self.config.symbol_color = self.color_button.color
|
||||
self.config.pen_style = self.style_combo.currentText()
|
||||
self.config.pen_style = self.style_combo.currentData()
|
||||
self.config.pen_width = self.width_spin.value()
|
||||
self.config.symbol_size = self.symbol_spin.value()
|
||||
|
||||
|
||||
@@ -1938,7 +1938,7 @@ 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.
|
||||
5x the limit. Curves without a connecting line retain their markers at any length.
|
||||
|
||||
Args:
|
||||
curve(Curve): The curve to adjust.
|
||||
@@ -1949,15 +1949,19 @@ 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")
|
||||
curve.apply_config()
|
||||
if data_length > limit:
|
||||
if curve.config.symbol is not None:
|
||||
if curve.config.pen_style != "none" and 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:
|
||||
curve.set_symbol("o")
|
||||
if curve.config.pen_style != "none":
|
||||
curve.set_symbol("o")
|
||||
curve.set_pen_width(4)
|
||||
curve.setDownsampling(ds=1, auto=None, method=method)
|
||||
curve.setClipToView(True)
|
||||
|
||||
@@ -3,6 +3,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from bec_lib.scan_history import ScanHistory
|
||||
from qtpy.QtCore import Qt
|
||||
from qtpy.QtWidgets import QComboBox, QVBoxLayout
|
||||
|
||||
from bec_widgets.widgets.plots.waveform.settings.curve_settings.curve_setting import CurveSetting
|
||||
@@ -84,6 +85,40 @@ def test_curve_setting_accept_changes(curve_setting_fixture, qtbot):
|
||||
send_spy.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"style, label, expected_pen",
|
||||
[
|
||||
("none", "No line", Qt.NoPen),
|
||||
("solid", "solid", Qt.SolidLine),
|
||||
("dash", "dash", Qt.DashLine),
|
||||
("dot", "dot", Qt.DotLine),
|
||||
("dashdot", "dashdot", Qt.DashDotLine),
|
||||
],
|
||||
)
|
||||
def test_curve_setting_style_round_trip(curve_setting_fixture, style, label, expected_pen):
|
||||
"""Applying and restoring curve styles preserves the selection and rendered pen."""
|
||||
settings, wf = curve_setting_fixture
|
||||
wf.plot("bpm4i")
|
||||
settings.refresh()
|
||||
row = settings.curve_manager.all_items[0]
|
||||
row.style_combo.setCurrentText(label)
|
||||
settings.accept_changes()
|
||||
|
||||
serialized = wf.curve_json
|
||||
assert json.loads(serialized)[0]["pen_style"] == style
|
||||
wf.curve_json = serialized
|
||||
curve = wf.curves[0]
|
||||
curve.setData([1, 2, 3], [4, 5, 6])
|
||||
assert curve.curve.opts["pen"].style() == expected_pen
|
||||
assert curve.scatter.isVisible()
|
||||
assert curve.scatter.opts["symbol"] == "o"
|
||||
|
||||
settings.refresh()
|
||||
restored_row = settings.curve_manager.all_items[0]
|
||||
assert restored_row.style_combo.currentText() == label
|
||||
assert restored_row.style_combo.currentData() == style
|
||||
|
||||
|
||||
def test_curve_setting_switch_device_mode(curve_setting_fixture, qtbot):
|
||||
"""
|
||||
If user chooses device mode from the combo, the device_x line edit should be enabled
|
||||
|
||||
@@ -11,7 +11,7 @@ import pytest
|
||||
from bec_lib import messages
|
||||
from bec_lib.scan_items import ScanItem
|
||||
from pyqtgraph.graphicsItems.DateAxisItem import DateAxisItem
|
||||
from qtpy.QtCore import QTimer
|
||||
from qtpy.QtCore import Qt, QTimer
|
||||
from qtpy.QtWidgets import QApplication, QCheckBox, QDialog, QDialogButtonBox, QDoubleSpinBox
|
||||
|
||||
from bec_widgets.widgets.plots.plot_base import UIMode
|
||||
@@ -1239,6 +1239,79 @@ def test_curve_set_appearance_methods(qtbot, mocked_client):
|
||||
assert c.config.pen_style == "dashdot"
|
||||
|
||||
|
||||
def test_curve_no_line_style_preserves_markers(qtbot, mocked_client):
|
||||
"""Disabling the connecting line preserves markers across appearance and data updates."""
|
||||
wf = create_widget(qtbot, Waveform, client=mocked_client)
|
||||
curve = wf.plot(x=[1, 2, 3], y=[4, 5, 6], symbol="d")
|
||||
curve.set_pen_style("none")
|
||||
assert curve.opts["pen"].style() == Qt.NoPen
|
||||
|
||||
curve.set_color("#123456")
|
||||
curve.set_symbol_color("#654321")
|
||||
curve.set_symbol_size(12)
|
||||
curve.set_pen_width(5)
|
||||
curve.set_data([2, 3, 4], [5, 6, 7])
|
||||
|
||||
assert curve.config.pen_style == "none"
|
||||
assert curve.curve.opts["pen"].style() == Qt.NoPen
|
||||
assert curve.opts["pen"].color().name() == "#123456"
|
||||
assert curve.scatter.isVisible()
|
||||
assert curve.scatter.opts["symbol"] == "d"
|
||||
assert curve.scatter.opts["size"] == 12
|
||||
assert curve.scatter.opts["brush"].color().name() == "#654321"
|
||||
np.testing.assert_array_equal(curve.scatter.getData(), [[2, 3, 4], [5, 6, 7]])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data_length", [500, 1500])
|
||||
@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."""
|
||||
wf = create_widget(qtbot, Waveform, client=mocked_client)
|
||||
curve = wf.plot(
|
||||
x=[1, 2, 3],
|
||||
y=[4, 5, 6],
|
||||
pen_style="none",
|
||||
pen_width=3,
|
||||
symbol=symbol,
|
||||
symbol_color="#654321",
|
||||
symbol_size=12,
|
||||
)
|
||||
|
||||
wf._auto_adjust_async_curve_settings(curve, data_length)
|
||||
curve.setData(np.arange(data_length), np.arange(data_length))
|
||||
|
||||
assert curve.curve.opts["pen"].style() == Qt.NoPen
|
||||
assert curve.scatter.isVisible()
|
||||
assert curve.config.symbol == (symbol or "o")
|
||||
assert curve.scatter.opts["symbol"] == (symbol or "o")
|
||||
assert curve.scatter.opts["size"] == 12
|
||||
assert curve.scatter.opts["brush"].color().name() == "#654321"
|
||||
|
||||
|
||||
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)
|
||||
curve = wf.plot(x=[1, 2, 3], y=[4, 5, 6], pen_style="none")
|
||||
assert curve.curve.opts["pen"].style() == Qt.NoPen
|
||||
|
||||
for style, expected in (
|
||||
("solid", Qt.SolidLine),
|
||||
("dash", Qt.DashLine),
|
||||
("dot", Qt.DotLine),
|
||||
("dashdot", Qt.DashDotLine),
|
||||
):
|
||||
curve.set_pen_style(style)
|
||||
assert curve.curve.opts["pen"].style() == expected
|
||||
assert curve.curve.isVisible()
|
||||
curve.set(pen_style="none")
|
||||
assert curve.curve.opts["pen"].style() == Qt.NoPen
|
||||
|
||||
config = curve.config.model_dump()
|
||||
config["pen_style"] = None
|
||||
curve.apply_config(config)
|
||||
assert curve.curve.opts["pen"].style() == Qt.SolidLine
|
||||
|
||||
|
||||
def test_curve_set_custom_data(qtbot, mocked_client):
|
||||
"""
|
||||
Test that custom curves allow setting new data via set_data.
|
||||
|
||||
Reference in new issue
Block a user