99 lines
3.5 KiB
Python
99 lines
3.5 KiB
Python
"""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()
|