This commit is contained in:
2026-06-01 23:22:18 +02:00
parent f061da654d
commit d23d0c15be
14 changed files with 1858 additions and 202 deletions
+98
View File
@@ -0,0 +1,98 @@
"""Widget graphique bande passante RX/TX (QPainter, sans dépendance externe)."""
from __future__ import annotations
from collections import deque
from PyQt6.QtWidgets import QWidget
from PyQt6.QtGui import QPainter, QColor, QPen, QBrush, QFont, QPainterPath
from PyQt6.QtCore import Qt, QRect, QPointF
def _fmt(bps: float) -> str:
if bps >= 1_048_576:
return f"{bps/1_048_576:.1f} MB/s"
if bps >= 1_024:
return f"{bps/1_024:.0f} KB/s"
return f"{bps:.0f} B/s"
class BandwidthGraph(QWidget):
"""Affiche RX (bleu) et TX (vert) sur les N dernières secondes."""
POINTS = 30 # nombre de points conservés
def __init__(self, parent=None):
super().__init__(parent)
self._rx: deque[float] = deque([0.0] * self.POINTS, maxlen=self.POINTS)
self._tx: deque[float] = deque([0.0] * self.POINTS, maxlen=self.POINTS)
self.setMinimumHeight(70)
self.setStyleSheet("background: transparent;")
def push(self, rx_bps: float, tx_bps: float) -> None:
self._rx.append(max(0.0, rx_bps))
self._tx.append(max(0.0, tx_bps))
self.update()
def reset(self) -> None:
self._rx = deque([0.0] * self.POINTS, maxlen=self.POINTS)
self._tx = deque([0.0] * self.POINTS, maxlen=self.POINTS)
self.update()
def paintEvent(self, _):
p = QPainter(self)
p.setRenderHint(QPainter.RenderHint.Antialiasing)
w, h = self.width(), self.height()
pad_l, pad_r, pad_t, pad_b = 42, 8, 6, 20
# Fond
p.fillRect(0, 0, w, h, QColor("#17202a"))
plot_w = w - pad_l - pad_r
plot_h = h - pad_t - pad_b
max_val = max(max(self._rx), max(self._tx), 1.0)
def _path(data: deque, color: str, fill: str):
pts = list(data)
path = QPainterPath()
xs = [pad_l + i * plot_w / (len(pts) - 1) for i in range(len(pts))]
ys = [pad_t + plot_h - (v / max_val) * plot_h for v in pts]
path.moveTo(QPointF(xs[0], pad_t + plot_h))
path.lineTo(QPointF(xs[0], ys[0]))
for x, y in zip(xs[1:], ys[1:]):
path.lineTo(QPointF(x, y))
path.lineTo(QPointF(xs[-1], pad_t + plot_h))
path.closeSubpath()
p.fillPath(path, QBrush(QColor(fill)))
pen = QPen(QColor(color))
pen.setWidthF(1.5)
p.setPen(pen)
line = QPainterPath()
line.moveTo(QPointF(xs[0], ys[0]))
for x, y in zip(xs[1:], ys[1:]):
line.lineTo(QPointF(x, y))
p.drawPath(line)
_path(self._rx, "#5dade2", "#1a3a52") # RX bleu
_path(self._tx, "#58d68d", "#1a3d2b") # TX vert
# Axes
p.setPen(QPen(QColor("#2e4057")))
p.drawLine(pad_l, pad_t, pad_l, pad_t + plot_h)
p.drawLine(pad_l, pad_t + plot_h, w - pad_r, pad_t + plot_h)
# Labels Y
font = QFont("Arial", 8)
p.setFont(font)
p.setPen(QPen(QColor("#5d6d7e")))
for frac, label in [(0.0, _fmt(max_val)), (0.5, _fmt(max_val / 2)), (1.0, "0")]:
y = int(pad_t + frac * plot_h)
p.drawText(QRect(0, y - 8, pad_l - 2, 16),
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
label)
# Légende
p.setPen(QPen(QColor("#5dade2")))
p.drawText(pad_l + 4, pad_t + 12, f"{_fmt(self._rx[-1])}")
p.setPen(QPen(QColor("#58d68d")))
p.drawText(pad_l + 90, pad_t + 12, f"{_fmt(self._tx[-1])}")
p.end()