44
55from __future__ import annotations
66
7- from typing import Dict , Tuple , Sequence , Iterable
7+ from typing import Dict , Iterable , Sequence , Tuple
88
9- from PyQt5 import QtCore , QtWidgets # type: ignore
10- import pyqtgraph as pg # type: ignore
119import numpy as np
10+ import pyqtgraph as pg # type: ignore
11+ from PyQt5 import QtCore , QtWidgets # type: ignore
1212
1313from robot_log_visualizer .plotter .color_palette import ColorPalette
14+ from robot_log_visualizer .signal_provider .signal_provider import ProviderType
1415
1516# ------------------------------------------------------------------------
1617# Type aliases
@@ -31,7 +32,6 @@ class PyQtGraphViewerCanvas(QtWidgets.QWidget):
3132 def __init__ (
3233 self ,
3334 parent : QtWidgets .QWidget | None ,
34- signal_provider ,
3535 period : float ,
3636 * ,
3737 click_radius : float | None = None ,
@@ -41,16 +41,14 @@ def __init__(
4141
4242 Args:
4343 parent: Parent widget or *None*.
44- signal_provider: Object exposing ``data``, ``initial_time``,
45- ``end_time`` and a **dynamic** ``current_time`` attribute.
4644 period: Update period for the vertical line (seconds).
4745 click_radius: Override :pyattr:`DEFAULT_RADIUS`.
4846 marker_size: Override :pyattr:`DEFAULT_MARKER_SIZE`.
4947 """
5048 super ().__init__ (parent )
5149
5250 # injected dependencies
53- self ._signal_provider = signal_provider
51+ self ._signal_provider = None
5452 self ._period_ms : int = int (period * 1000 )
5553 self ._click_radius : float = click_radius or self .DEFAULT_RADIUS
5654 self ._marker_size : int = marker_size or self .DEFAULT_MARKER_SIZE
@@ -66,21 +64,58 @@ def __init__(
6664 self ._connect_signals ()
6765
6866 # -------------------------------------------------------------#
69- # Public API (called from the outside) #
67+ # Public API (called from the outside) #
7068 # -------------------------------------------------------------#
69+
70+ def set_signal_provider (self , signal_provider ) -> None :
71+ """Set the signal provider to fetch data from.
72+
73+ Args:
74+ signal_provider: An instance of `SignalProvider`.
75+ """
76+
77+ if signal_provider is None :
78+ return
79+
80+ self ._signal_provider = signal_provider
81+
82+ # Connect to real-time updates for real-time provider
83+ if self ._signal_provider .provider_type == ProviderType .REALTIME :
84+ self ._signal_provider .update_index_signal .connect (
85+ self ._update_realtime_curves
86+ )
87+
7188 def update_plots (self , paths : Sequence [Path ], legends : Sequence [Legend ]) -> None :
7289 """Synchronise plots with the *paths* list.
7390
7491 New items are added, disappeared items removed. Existing ones are
7592 left untouched to avoid flicker.
7693 """
94+ if self ._signal_provider is None :
95+ return
96+
97+ # For real-time provider, update the set of selected signals to buffer
98+ if self ._signal_provider .provider_type == ProviderType .REALTIME :
99+ selected_keys = ["::" .join (path ) for path in paths ]
100+ self ._signal_provider .add_signals_to_buffer (selected_keys )
101+
77102 self ._add_missing_curves (paths , legends )
78103 self ._remove_obsolete_curves (paths )
79- # Always show the full time span
80- self ._plot .setXRange (
81- 0.0 ,
82- self ._signal_provider .end_time - self ._signal_provider .initial_time ,
83- )
104+
105+ # Set the X axis range based on the provider type
106+ if self ._signal_provider .provider_type == ProviderType .REALTIME :
107+ # For real-time data, show a fixed window with 0 set at the right edge for the latest data
108+ self ._plot .setXRange (- self ._signal_provider .realtime_fixed_plot_window , 0.0 )
109+ # Disable mouse panning on x axis
110+ self ._plot .plotItem .vb .setMouseEnabled (x = False , y = True )
111+ # For real-time data enable autoscaling of Y axis
112+ self ._plot .plotItem .vb .enableAutoRange (axis = pg .ViewBox .YAxis , enable = True )
113+ else :
114+ # Default behavior
115+ self ._plot .setXRange (
116+ 0.0 ,
117+ self ._signal_provider .end_time - self ._signal_provider .initial_time ,
118+ )
84119
85120 # The following trio is wired to whoever controls the replay/stream
86121 def pause_animation (self ) -> None : # noqa: D401
@@ -173,8 +208,32 @@ def _remove_obsolete_curves(self, paths: Sequence[Path]) -> None:
173208
174209 def _update_vline (self ) -> None :
175210 """Move the vertical line to ``current_time``."""
211+ if self ._signal_provider is None :
212+ return
213+
176214 self ._vline .setValue (self ._signal_provider .current_time )
177215
216+ def _update_realtime_curves (self ):
217+ """Update all curves with the latest data from the signal provider."""
218+
219+ if self ._signal_provider is None :
220+ return
221+ for key , curve in self ._curves .items ():
222+ # Drill down to the data array using the path
223+ path = key .split ("/" )
224+ data = self ._signal_provider .data
225+ for subkey in path [:- 1 ]:
226+ data = data [subkey ]
227+ try :
228+ y = data ["data" ][:, int (path [- 1 ])]
229+ except (IndexError , ValueError ):
230+ y = data ["data" ][:]
231+
232+ # Set the 0 of the x axis to the latest timestamp
233+ latest_time = data ["timestamps" ][- 1 ] if len (data ["timestamps" ]) > 0 else 0
234+ x = data ["timestamps" ] - latest_time
235+ curve .setData (x , y )
236+
178237 def _on_mouse_click (self , event ) -> None : # noqa: N802
179238 """Handle a left‑click: select or unselect the nearest data point."""
180239 if event .button () != QtCore .Qt .MouseButton .LeftButton :
0 commit comments