"""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()