#include "CurvePlotWidget.h" #include #include #include #include #include CurvePlotWidget::CurvePlotWidget(QWidget *parent) : QWidget(parent) { setMinimumSize(300, 200); } void CurvePlotWidget::setTimeRange(double secs) { m_tRange = qMax(secs, 1.0); m_xMax = qMax(200, int(width() / 2)); for (auto &c : m_curves) { c.tBuf.resize(m_xMax); c.yBuf.resize(m_xMax); c.head = 0; } update(); } void CurvePlotWidget::initCurve(int id, const CurveCfg &cfg) { Curve c; c.cfg = cfg; // 确保阈值有效 Q_ASSERT(cfg.thWarning < cfg.thFault); Q_ASSERT(cfg.thFault <= cfg.yMax); c.tBuf.resize(m_xMax); c.yBuf.resize(m_xMax); c.tBuf.fill(std::numeric_limits::quiet_NaN()); c.yBuf.fill(std::numeric_limits::quiet_NaN()); c.head = 0; m_curves.insert(id, c); } void CurvePlotWidget::clearAllCurves() { m_curves.clear(); m_t0set = false; update(); } void CurvePlotWidget::appendPoint(int curveId, double timeStamp, double yValue) { if (!m_curves.contains(curveId)) return; Curve &c = m_curves[curveId]; if (!m_t0set) { m_t0 = timeStamp; m_t0set = true; } double t = timeStamp - m_t0; c.tBuf[c.head] = t; c.yBuf[c.head] = yValue; c.head = (c.head + 1) % m_xMax; update(); } void CurvePlotWidget::paintEvent(QPaintEvent *) { QPainter p(this); p.setRenderHint(QPainter::Antialiasing); QRectF plotRect(m_leftMargin, m_topMargin, width() - m_leftMargin - m_rightMargin, height() - m_topMargin - m_bottomMargin); /* ---------- 1. 顶部图例 ---------- */ int legendX = m_leftMargin + 5; int legendY = m_topMargin - 15; for (const auto &c : m_curves) { p.setPen(QPen(c.cfg.normal, 2)); p.drawLine(legendX, legendY, legendX + 15, legendY); p.setPen(QPen(Qt::white)); p.drawText(legendX + 20, legendY + 4, c.cfg.name); legendX += 20 + p.fontMetrics().horizontalAdvance(c.cfg.name) + 15; } /* ---------- 2. 坐标轴 ---------- */ p.setPen(Qt::white); /* Y 轴刻度 */ if (!m_curves.isEmpty()) { const Curve &c = *m_curves.begin(); double yMax = c.cfg.yMax; for (int i = 0; i <= 4; ++i) { double y = yMax * i / 4.0; QString txt = QString::number(y, 'f', 1); int ty = int(plotRect.bottom() - i * plotRect.height() / 4.0); p.drawText(5, ty + 4, txt); } } /* ---------- X 轴刻度(真实时钟) ---------- */ // 当前这一帧可见窗口的绝对时间范围 double tLeftEpoch = QDateTime::currentDateTime().toSecsSinceEpoch() - m_tRange; double tRightEpoch = QDateTime::currentDateTime().toSecsSinceEpoch(); for (int i = 0; i <= 4; ++i) { double tEpoch = tLeftEpoch + m_tRange * i / 4.0; // 绝对秒 QDateTime tm = QDateTime::fromSecsSinceEpoch(qint64(tEpoch)); // 真实时钟 QString txt = tm.toString("hh:mm:ss"); int tx = int(plotRect.left() + i * plotRect.width() / 4.0); p.drawText(tx - 25, height() - 5, txt); } /* ---------- 4. 曲线 ---------- */ for (const auto &c : m_curves) { if (m_xMax <= 1) continue; // 改成同时缓存 QPointF 和 对应的 y 值 struct Pt { QPointF pos; double y; }; QVector pts; pts.reserve(m_xMax); for (int i = 0; i < m_xMax; ++i) { int idx = (c.head + i) % m_xMax; double t = c.tBuf[idx]; double y = c.yBuf[idx]; if (std::isnan(t) || std::isnan(y)) continue; pts.append({mapToWidget(t, y, c), y}); } if (pts.size() < 2) continue; // 逐段画线,颜色直接用 pts[k].y for (int k = 0; k < pts.size() - 1; ++k) { double y = pts[k].y; int level = 0; if (y >= c.cfg.thFault) level = 2; else if (y >= c.cfg.thWarning) level = 1; QColor col = level == 0 ? c.cfg.normal : level == 1 ? c.cfg.warning : c.cfg.fault; p.setPen(QPen(col, 2)); p.drawLine(pts[k].pos, pts[k + 1].pos); } } } QPointF CurvePlotWidget::mapToWidget(double t, double y, const Curve &c) const { QRectF plotRect(m_leftMargin, m_topMargin, width() - m_leftMargin - m_rightMargin, height() - m_topMargin - m_bottomMargin); double x = plotRect.left() + (t / m_tRange) * plotRect.width(); double norm = qBound(0.0, y / c.cfg.yMax, 1.0); double wy = plotRect.bottom() - norm * plotRect.height(); return QPointF(x, wy); } void CurvePlotWidget::resizeEvent(QResizeEvent *) { setTimeRange(m_tRange); // 按新宽度重新计算点数 }