mirror of
https://github.com/migatu/vtt_work.git
synced 2026-07-14 21:38:39 +00:00
1.12
This commit is contained in:
@@ -35,6 +35,10 @@ try:
|
|||||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
from matplotlib.figure import Figure
|
from matplotlib.figure import Figure
|
||||||
HAS_MPL = True
|
HAS_MPL = True
|
||||||
|
import matplotlib as mpl
|
||||||
|
mpl.rcParams["path.simplify"] = True
|
||||||
|
mpl.rcParams["agg.path.chunksize"] = 10000
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
HAS_MPL = False
|
HAS_MPL = False
|
||||||
|
|
||||||
@@ -1011,6 +1015,84 @@ class App(tk.Tk):
|
|||||||
self.actions_tree.selection_set(kept)
|
self.actions_tree.selection_set(kept)
|
||||||
if focus_before and self.actions_tree.exists(focus_before):
|
if focus_before and self.actions_tree.exists(focus_before):
|
||||||
self.actions_tree.focus(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 ---
|
# --- Poller control / rendering ---
|
||||||
def start_polling(self) -> None:
|
def start_polling(self) -> None:
|
||||||
@@ -1168,6 +1250,7 @@ class App(tk.Tk):
|
|||||||
|
|
||||||
if not hasattr(self, "_plot_windows"):
|
if not hasattr(self, "_plot_windows"):
|
||||||
self._plot_windows = {}
|
self._plot_windows = {}
|
||||||
|
self._plot_timer = None # globalny timer do wykresów
|
||||||
|
|
||||||
w = self._plot_windows.get(key)
|
w = self._plot_windows.get(key)
|
||||||
if w and w.winfo_exists():
|
if w and w.winfo_exists():
|
||||||
@@ -1188,65 +1271,29 @@ class App(tk.Tk):
|
|||||||
|
|
||||||
canvas = FigureCanvasTkAgg(fig, master=win)
|
canvas = FigureCanvasTkAgg(fig, master=win)
|
||||||
canvas.draw()
|
canvas.draw()
|
||||||
|
# ... masz już fig, ax_val, ax_delta, canvas ...
|
||||||
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
self._plot_windows[key] = win
|
self._plot_windows[key] = win
|
||||||
|
win._fig = fig
|
||||||
win._ax_val = ax_val
|
win._ax_val = ax_val
|
||||||
win._ax_delta = ax_delta
|
win._ax_delta = ax_delta
|
||||||
win._canvas = canvas
|
win._canvas = canvas
|
||||||
win._key = key
|
win._key = key
|
||||||
|
win._layout_dirty = True
|
||||||
|
|
||||||
def refresh_plot():
|
# prealokuj „artystów” – jedna linia i zestaw słupków
|
||||||
if not win.winfo_exists():
|
(win._line,) = ax_val.plot([], [], linewidth=1.2)
|
||||||
return
|
win._bars = ax_delta.bar([], [])
|
||||||
vinfo = self.vars.get(key)
|
win._bars_len = 0 # ile aktualnie słupków
|
||||||
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()
|
|
||||||
|
|
||||||
if vals:
|
# tight_layout tylko po resize
|
||||||
ax_val.plot(xs_v, vals, linewidth=1.2)
|
def _on_resize(_evt=None):
|
||||||
mv = sum(vals) / len(vals)
|
win._layout_dirty = True
|
||||||
ax_val.axhline(mv, linestyle="--", linewidth=1.2)
|
canvas.mpl_connect("resize_event", _on_resize)
|
||||||
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)
|
|
||||||
|
|
||||||
|
# uruchom globalny tick jeśli nie działa
|
||||||
|
self._ensure_plot_timer()
|
||||||
|
|
||||||
def open_thresholds_dialog(self):
|
def open_thresholds_dialog(self):
|
||||||
sel = self.tree.selection()
|
sel = self.tree.selection()
|
||||||
@@ -2007,6 +2054,12 @@ class App(tk.Tk):
|
|||||||
self.scheduler.stop()
|
self.scheduler.stop()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
try:
|
||||||
|
if self._plot_timer is not None:
|
||||||
|
self.after_cancel(self._plot_timer)
|
||||||
|
self._plot_timer = None
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
# --- Utils ---
|
# --- Utils ---
|
||||||
|
|||||||
Reference in New Issue
Block a user