feat(graph): add graph tab for whole-run data
Provides a static line chart plotting multi-sensor temperature values over time when a session file is loaded.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from pygui.backend.models.frame import Frame
|
||||
from pygui.backend.models.frame_player import FramePlayer
|
||||
from pygui.backend.models.frame_player import FramePlayer, all_sensor_series
|
||||
|
||||
|
||||
def frames(n):
|
||||
@@ -29,3 +29,24 @@ def test_at_end():
|
||||
assert not p.at_end
|
||||
p.seek(1)
|
||||
assert p.at_end
|
||||
|
||||
def test_frames_property_exposes_loaded_frames():
|
||||
p = FramePlayer(); p.load(frames(3))
|
||||
assert p.frames == tuple(frames(3))
|
||||
|
||||
|
||||
# ---- all_sensor_series (Graph tab whole-run assembly) ----
|
||||
|
||||
def test_all_sensor_series_empty_frames():
|
||||
assert all_sensor_series([]) == ([], [])
|
||||
|
||||
|
||||
def test_all_sensor_series_names_and_columns():
|
||||
fs = [
|
||||
Frame(seq=0, time=0.0, values=[150.0, 148.0]),
|
||||
Frame(seq=1, time=1.0, values=[151.0, 147.5]),
|
||||
Frame(seq=2, time=2.0, values=[152.0, 147.0]),
|
||||
]
|
||||
names, series = all_sensor_series(fs)
|
||||
assert names == ["Sensor 1", "Sensor 2"]
|
||||
assert series == [[150.0, 151.0, 152.0], [148.0, 147.5, 147.0]]
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Tests for src/pygui/backend/visualization/graph_quick_item.py."""
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("qapp")
|
||||
|
||||
|
||||
def test_no_data_initially():
|
||||
from pygui.backend.visualization.graph_quick_item import GraphQuickItem
|
||||
|
||||
item = GraphQuickItem()
|
||||
assert item.seriesData == []
|
||||
assert item.sensorNames == []
|
||||
|
||||
|
||||
def test_series_data_setter_stores_series():
|
||||
from pygui.backend.visualization.graph_quick_item import GraphQuickItem
|
||||
|
||||
item = GraphQuickItem()
|
||||
item.seriesData = [[150.0, 151.0], [148.0, 147.5]]
|
||||
|
||||
assert item.seriesData == [[150.0, 151.0], [148.0, 147.5]]
|
||||
|
||||
|
||||
def test_series_data_auto_ranges_y_with_padding():
|
||||
from pygui.backend.visualization.graph_quick_item import GraphQuickItem
|
||||
|
||||
item = GraphQuickItem()
|
||||
item.seriesData = [[150.0, 100.0], [200.0, 120.0]]
|
||||
|
||||
# min=100, max=200, range=100, 10% pad => [90, 210]
|
||||
assert item._min_y == pytest.approx(90.0)
|
||||
assert item._max_y == pytest.approx(210.0)
|
||||
|
||||
|
||||
def test_series_data_empty_keeps_default_range():
|
||||
from pygui.backend.visualization.graph_quick_item import GraphQuickItem
|
||||
|
||||
item = GraphQuickItem()
|
||||
item.seriesData = []
|
||||
|
||||
assert item._min_y == 0.0
|
||||
assert item._max_y == 150.0
|
||||
|
||||
|
||||
def test_sensor_names_setter_stores_names():
|
||||
from pygui.backend.visualization.graph_quick_item import GraphQuickItem
|
||||
|
||||
item = GraphQuickItem()
|
||||
item.sensorNames = ["Sensor 1", "Sensor 2"]
|
||||
|
||||
assert item.sensorNames == ["Sensor 1", "Sensor 2"]
|
||||
|
||||
|
||||
def test_non_numeric_values_are_skipped_not_crashing():
|
||||
from pygui.backend.visualization.graph_quick_item import GraphQuickItem
|
||||
|
||||
item = GraphQuickItem()
|
||||
item.seriesData = [[150.0, "bad", 151.0]]
|
||||
|
||||
assert item._min_y == pytest.approx(149.9)
|
||||
assert item._max_y == pytest.approx(151.1)
|
||||
@@ -66,6 +66,51 @@ class TestUpdateTrend:
|
||||
gv.updateTrend("[1, 2, 3]")
|
||||
|
||||
|
||||
class TestUpdateChart:
|
||||
def test_plots_one_series_per_sensor(self):
|
||||
from pygui.backend.visualization.graph_view import GraphView
|
||||
|
||||
gv = GraphView()
|
||||
gv._plot_widget = _make_plot_widget()
|
||||
|
||||
series = [[150.0, 151.0, 152.0], [148.5, 148.4, 148.6]]
|
||||
gv.updateChart("Sensor 1,Sensor 2", json.dumps(series))
|
||||
|
||||
assert gv._plot_widget.clear.called
|
||||
assert gv._plot_widget.plot.call_count == 2
|
||||
assert gv._sensor_names == ["Sensor 1", "Sensor 2"]
|
||||
|
||||
def test_rejects_invalid_json(self):
|
||||
from pygui.backend.visualization.graph_view import GraphView
|
||||
|
||||
gv = GraphView()
|
||||
gv._plot_widget = _make_plot_widget()
|
||||
|
||||
gv.updateChart("Sensor 1", "not json")
|
||||
|
||||
assert not gv._plot_widget.plot.called
|
||||
|
||||
def test_empty_series_clears_without_plotting(self):
|
||||
from pygui.backend.visualization.graph_view import GraphView
|
||||
|
||||
gv = GraphView()
|
||||
gv._plot_widget = _make_plot_widget()
|
||||
|
||||
gv.updateChart("", "[]")
|
||||
|
||||
assert gv._plot_widget.clear.called
|
||||
assert not gv._plot_widget.plot.called
|
||||
|
||||
def test_no_plot_widget(self):
|
||||
from pygui.backend.visualization.graph_view import GraphView
|
||||
|
||||
gv = GraphView()
|
||||
assert gv._plot_widget is None
|
||||
|
||||
# Should not crash
|
||||
gv.updateChart("Sensor 1", json.dumps([[1.0, 2.0]]))
|
||||
|
||||
|
||||
class TestUpdateComparison:
|
||||
def test_parses_valid_json(self):
|
||||
from pygui.backend.visualization.graph_view import GraphView
|
||||
|
||||
@@ -53,6 +53,26 @@ def test_pause_stop_timer(controller):
|
||||
controller.stop()
|
||||
assert controller.frameIndex == 0
|
||||
|
||||
def test_graph_data_empty_before_load(controller):
|
||||
assert controller.graphSensorNames == ""
|
||||
assert controller.graphSeriesJson == "[]"
|
||||
|
||||
def test_graph_data_after_load(controller):
|
||||
controller.loadFile("tests/fixtures/sample_stream.csv")
|
||||
assert controller.graphSensorNames == "Sensor 1,Sensor 2,Sensor 3"
|
||||
series = json.loads(controller.graphSeriesJson)
|
||||
assert series == [
|
||||
[149.0, 149.2, 149.1],
|
||||
[148.5, 148.4, 148.6],
|
||||
[150.6, 150.7, 150.5],
|
||||
]
|
||||
|
||||
def test_graph_data_cleared_after_unload(controller):
|
||||
controller.loadFile("tests/fixtures/sample_stream.csv")
|
||||
controller.unloadFile()
|
||||
assert controller.graphSensorNames == ""
|
||||
assert controller.graphSeriesJson == "[]"
|
||||
|
||||
def test_leaving_live_mode_blanks_wafer_map(controller):
|
||||
"""Stopping a live session (tab switch → setMode) must not leave stale
|
||||
sensor values on the wafer map — it should read as blank."""
|
||||
|
||||
Reference in New Issue
Block a user