mirror of
https://github.com/migatu/vtt_work.git
synced 2026-07-14 13:34:42 +00:00
1.12
This commit is contained in:
@@ -35,6 +35,10 @@ try:
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
from matplotlib.figure import Figure
|
||||
HAS_MPL = True
|
||||
import matplotlib as mpl
|
||||
mpl.rcParams["path.simplify"] = True
|
||||
mpl.rcParams["agg.path.chunksize"] = 10000
|
||||
|
||||
except Exception:
|
||||
HAS_MPL = False
|
||||
|
||||
@@ -1011,6 +1015,84 @@ class App(tk.Tk):
|
||||
self.actions_tree.selection_set(kept)
|
||||
if focus_before and self.actions_tree.exists(focus_before):
|
||||
self.actions_tree.focus(focus_before)
|
||||
def _ensure_plot_timer(self):
|
||||
if self._plot_timer is None:
|
||||
# odświeżanie wykresów zsynchronizowane z głównym intervalem
|
||||
try:
|
||||
ms = int(max(100, float(self.refresh_var.get()) * 1000))
|
||||
except Exception:
|
||||
ms = 500
|
||||
self._plot_timer = self.after(ms, self._plot_tick)
|
||||
|
||||
def _plot_tick(self):
|
||||
# jeśli nie ma żadnych okien – zatrzymaj timer
|
||||
windows = [w for w in getattr(self, "_plot_windows", {}).values() if w.winfo_exists()]
|
||||
if not windows:
|
||||
self._plot_timer = None
|
||||
return
|
||||
|
||||
for win in windows:
|
||||
key = getattr(win, "_key", None)
|
||||
vi = self.vars.get(key)
|
||||
if not vi:
|
||||
continue
|
||||
|
||||
# ostatnie 100 próbek
|
||||
vals = list(vi.history)[-100:] if hasattr(vi, "history") else []
|
||||
dels = list(vi.history_delta)[-100:] if hasattr(vi, "history_delta") else []
|
||||
|
||||
# --- GÓRNY wykres: linia ---
|
||||
xs_v = list(range(len(vals)))
|
||||
win._line.set_data(xs_v, vals)
|
||||
if vals:
|
||||
vmin, vmax = min(vals), max(vals)
|
||||
span = (vmax - vmin) or 1.0
|
||||
m = 0.08 * span
|
||||
win._ax_val.set_xlim(0, max(1, len(xs_v) - 1))
|
||||
win._ax_val.set_ylim(vmin - m, vmax + m)
|
||||
win._ax_val.set_ylabel("value")
|
||||
|
||||
# --- DOLNY wykres: słupki (bez ponownego tworzenia barów jeśli długość ta sama) ---
|
||||
if len(dels) != win._bars_len:
|
||||
# przebuduj tylko gdy zmienia się liczba słupków
|
||||
for b in win._bars:
|
||||
b.remove()
|
||||
win._bars = win._ax_delta.bar(range(len(dels)), dels)
|
||||
win._bars_len = len(dels)
|
||||
else:
|
||||
# szybka ścieżka – zaktualizuj wysokości
|
||||
for b, h in zip(win._bars, dels):
|
||||
b.set_height(h)
|
||||
|
||||
if dels:
|
||||
dmin, dmax = min(dels), max(dels)
|
||||
span = (dmax - dmin) or 1.0
|
||||
m = 0.08 * span
|
||||
win._ax_delta.set_xlim(0, max(1, len(dels) - 1))
|
||||
win._ax_delta.set_ylim(dmin - m, dmax + m)
|
||||
win._ax_delta.set_ylabel("Δ")
|
||||
win._ax_delta.set_xlabel("last 100 samples")
|
||||
|
||||
# tight_layout tylko po resize (ustawiane flagą)
|
||||
if getattr(win, "_layout_dirty", False):
|
||||
try:
|
||||
win._fig.tight_layout()
|
||||
except Exception:
|
||||
pass
|
||||
win._layout_dirty = False
|
||||
|
||||
# draw_idle = koalescencja
|
||||
try:
|
||||
win._canvas.draw_idle()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# zaplanuj kolejny tick
|
||||
try:
|
||||
ms = int(max(100, float(self.refresh_var.get()) * 1000))
|
||||
except Exception:
|
||||
ms = 500
|
||||
self._plot_timer = self.after(ms, self._plot_tick)
|
||||
|
||||
# --- Poller control / rendering ---
|
||||
def start_polling(self) -> None:
|
||||
@@ -1168,6 +1250,7 @@ class App(tk.Tk):
|
||||
|
||||
if not hasattr(self, "_plot_windows"):
|
||||
self._plot_windows = {}
|
||||
self._plot_timer = None # globalny timer do wykresów
|
||||
|
||||
w = self._plot_windows.get(key)
|
||||
if w and w.winfo_exists():
|
||||
@@ -1188,65 +1271,29 @@ class App(tk.Tk):
|
||||
|
||||
canvas = FigureCanvasTkAgg(fig, master=win)
|
||||
canvas.draw()
|
||||
# ... masz już fig, ax_val, ax_delta, canvas ...
|
||||
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
self._plot_windows[key] = win
|
||||
win._fig = fig
|
||||
win._ax_val = ax_val
|
||||
win._ax_delta = ax_delta
|
||||
win._canvas = canvas
|
||||
win._key = key
|
||||
win._layout_dirty = True
|
||||
|
||||
def refresh_plot():
|
||||
if not win.winfo_exists():
|
||||
return
|
||||
vinfo = self.vars.get(key)
|
||||
if not vinfo:
|
||||
win.after(1000, refresh_plot)
|
||||
return
|
||||
vals = list(vinfo.history)[-100:] if hasattr(vinfo, "history") else []
|
||||
dels = list(vinfo.history_delta)[-100:] if hasattr(vinfo, "history_delta") else []
|
||||
xs_v = list(range(len(vals)))
|
||||
xs_d = list(range(len(dels)))
|
||||
ax_val.clear(); ax_delta.clear()
|
||||
# prealokuj „artystów” – jedna linia i zestaw słupków
|
||||
(win._line,) = ax_val.plot([], [], linewidth=1.2)
|
||||
win._bars = ax_delta.bar([], [])
|
||||
win._bars_len = 0 # ile aktualnie słupków
|
||||
|
||||
if vals:
|
||||
ax_val.plot(xs_v, vals, linewidth=1.2)
|
||||
mv = sum(vals) / len(vals)
|
||||
ax_val.axhline(mv, linestyle="--", linewidth=1.2)
|
||||
vmin, vmax = min(vals), max(vals)
|
||||
span = (vmax - vmin) or 1.0
|
||||
margin = 0.08 * span
|
||||
ax_val.set_ylim(vmin - margin, vmax + margin)
|
||||
ax_val.set_ylabel("value")
|
||||
else:
|
||||
ax_val.set_ylabel("value (no data)")
|
||||
|
||||
if dels:
|
||||
ax_delta.bar(xs_d, dels)
|
||||
md = sum(dels) / len(dels)
|
||||
ax_delta.axhline(md, linestyle="--", linewidth=1.2)
|
||||
dmin, dmax = min(dels), max(dels)
|
||||
span = (dmax - dmin) or 1.0
|
||||
margin = 0.08 * span
|
||||
ax_delta.set_ylim(dmin - margin, dmax + margin)
|
||||
ax_delta.set_ylabel("Δ")
|
||||
else:
|
||||
ax_delta.set_ylabel("Δ (no data)")
|
||||
|
||||
ax_delta.set_xlabel("last 100 samples")
|
||||
fig.tight_layout()
|
||||
try:
|
||||
canvas.draw()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
interval_ms = int(max(250, float(self.refresh_var.get()) * 1000))
|
||||
except Exception:
|
||||
interval_ms = 1000
|
||||
win.after(interval_ms, refresh_plot)
|
||||
|
||||
win.after(200, refresh_plot)
|
||||
# tight_layout tylko po resize
|
||||
def _on_resize(_evt=None):
|
||||
win._layout_dirty = True
|
||||
canvas.mpl_connect("resize_event", _on_resize)
|
||||
|
||||
# uruchom globalny tick jeśli nie działa
|
||||
self._ensure_plot_timer()
|
||||
|
||||
def open_thresholds_dialog(self):
|
||||
sel = self.tree.selection()
|
||||
@@ -2007,6 +2054,12 @@ class App(tk.Tk):
|
||||
self.scheduler.stop()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if self._plot_timer is not None:
|
||||
self.after_cancel(self._plot_timer)
|
||||
self._plot_timer = None
|
||||
except Exception:
|
||||
pass
|
||||
self.destroy()
|
||||
|
||||
# --- Utils ---
|
||||
|
||||
Reference in New Issue
Block a user