Files
AiAnalysis/plotwidget.cpp
2026-04-30 13:36:32 +08:00

466 lines
12 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "plotwidget.h"
#include <QPainter>
#include <QPainterPath>
#include <QtMath>
#include <QFontMetrics>
#include <algorithm>
PlotWidget::PlotWidget(QWidget *parent)
: QWidget(parent)
, m_totalPoints(0)
, m_displayPoints(3000)
, m_yMin(0)
, m_yMax(65535)
, m_autoYRange(true)
, m_autoScroll(true)
, m_dragging(false)
, m_marginLeft(70)
, m_marginRight(160)
, m_marginTop(30)
, m_marginBottom(50)
, m_bgColor("#1a1a2e")
, m_gridColor("#333355")
, m_gridColorMinor("#252545")
, m_textColor("#cccccc")
, m_axisColor("#888888")
, m_dirty(true)
{
m_data.resize(16);
m_channelVisible.fill(false, 16);
m_colors = defaultColors();
setMouseTracking(true);
setMinimumSize(400, 300);
// 定时刷新约30fps
m_refreshTimer = new QTimer(this);
connect(m_refreshTimer, &QTimer::timeout, this, [this]() {
if (m_dirty) {
m_dirty = false;
update();
}
});
m_refreshTimer->start(33);
}
QList<QColor> PlotWidget::defaultColors()
{
return {
QColor("#FF4444"), QColor("#44FF44"), QColor("#4488FF"), QColor("#FFAA00"),
QColor("#FF44FF"), QColor("#44FFFF"), QColor("#FFFF44"), QColor("#FF8888"),
QColor("#88FF88"), QColor("#8888FF"), QColor("#CC6600"), QColor("#FF66CC"),
QColor("#66FFCC"), QColor("#CCFF66"), QColor("#AA66FF"), QColor("#FF6666"),
};
}
void PlotWidget::addDataPoint(const QVector<double> &values)
{
if (values.size() < 16) return;
for (int i = 0; i < 16; ++i) {
m_data[i].append(values[i]);
}
m_totalPoints++;
m_dirty = true;
if (m_autoYRange && m_totalPoints % 50 == 0) {
autoRangeY();
}
}
void PlotWidget::setAllData(const QVector<QVector<double>> &channels)
{
clear();
if (channels.size() >= 16) {
for (int i = 0; i < 16; ++i) {
m_data[i] = channels[i];
}
m_totalPoints = channels.isEmpty() ? 0 : channels[0].size();
}
m_autoScroll = true;
m_autoYRange = true;
autoRangeY();
m_dirty = true;
update();
}
void PlotWidget::clear()
{
for (auto &ch : m_data) {
ch.clear();
}
m_totalPoints = 0;
m_autoScroll = true;
m_autoYRange = true;
m_dirty = true;
update();
}
void PlotWidget::setChannelVisible(int channel, bool visible)
{
if (channel >= 0 && channel < 16) {
m_channelVisible[channel] = visible;
m_dirty = true;
update(); // 立即刷新,不等定时器
}
}
bool PlotWidget::isChannelVisible(int channel) const
{
return channel >= 0 && channel < 16 ? m_channelVisible[channel] : false;
}
void PlotWidget::setDisplayPointCount(int count)
{
m_displayPoints = qMax(10, count);
m_dirty = true;
}
int PlotWidget::displayPointCount() const
{
return m_displayPoints;
}
void PlotWidget::setYRange(double min, double max)
{
m_yMin = min;
m_yMax = max;
m_autoYRange = false;
m_dirty = true;
}
void PlotWidget::autoRangeY()
{
if (m_totalPoints == 0) {
m_yMin = 0;
m_yMax = 65535;
return;
}
double globalMin = 1e18, globalMax = -1e18;
int startIdx = qMax(0, m_totalPoints - m_displayPoints);
for (int ch = 0; ch < 16; ++ch) {
if (!m_channelVisible[ch]) continue;
const auto &channel = m_data[ch];
for (int i = startIdx; i < channel.size(); ++i) {
double v = channel[i];
if (v < globalMin) globalMin = v;
if (v > globalMax) globalMax = v;
}
}
if (globalMax < globalMin) {
// 没有可见通道或没有数据
return;
}
if (globalMax - globalMin < 1.0) {
globalMin -= 50;
globalMax += 50;
}
double margin = (globalMax - globalMin) * 0.1;
m_yMin = globalMin - margin;
m_yMax = globalMax + margin;
}
void PlotWidget::setTitle(const QString &title)
{
m_title = title;
m_dirty = true;
}
int PlotWidget::totalPoints() const
{
return m_totalPoints;
}
QRectF PlotWidget::plotArea() const
{
return QRectF(m_marginLeft, m_marginTop,
width() - m_marginLeft - m_marginRight,
height() - m_marginTop - m_marginBottom);
}
QPointF PlotWidget::dataToWidget(double x, double y) const
{
QRectF area = plotArea();
double px = area.left() + x * area.width();
double py = area.bottom() - ((y - m_yMin) / (m_yMax - m_yMin)) * area.height();
return QPointF(px, py);
}
// ================ Paint ================
void PlotWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
drawBackground(painter);
drawGrid(painter);
drawCurves(painter);
drawAxes(painter);
drawLegend(painter);
// 标题
if (!m_title.isEmpty()) {
painter.setPen(m_textColor);
QFont titleFont = font();
titleFont.setPointSize(12);
titleFont.setBold(true);
painter.setFont(titleFont);
painter.drawText(QRect(0, 2, width(), m_marginTop - 4), Qt::AlignCenter, m_title);
}
// 数据点计数
painter.setPen(QColor("#666666"));
QFont smallFont = font();
smallFont.setPointSize(8);
painter.setFont(smallFont);
QString info = QString("总点数: %1 显示: %2 范围: [%3, %4]")
.arg(m_totalPoints)
.arg(m_displayPoints)
.arg(m_yMin, 0, 'f', 0)
.arg(m_yMax, 0, 'f', 0);
painter.drawText(QRect(m_marginLeft, height() - m_marginBottom + 30,
width() - m_marginLeft - m_marginRight, 20),
Qt::AlignLeft | Qt::AlignVCenter, info);
}
void PlotWidget::drawBackground(QPainter &painter)
{
painter.fillRect(rect(), m_bgColor);
QRectF area = plotArea();
painter.fillRect(area, QColor("#16213e"));
}
void PlotWidget::drawGrid(QPainter &painter)
{
QRectF area = plotArea();
if (area.width() <= 0 || area.height() <= 0) return;
// 计算合适的刻度
double range = m_yMax - m_yMin;
if (range <= 0) return;
double roughStep = range / 8.0;
double exponent = qFloor(log10(roughStep));
double mantissa = roughStep / qPow(10, exponent);
double gridStep;
if (mantissa < 1.5) gridStep = qPow(10, exponent);
else if (mantissa < 3.5) gridStep = 2 * qPow(10, exponent);
else if (mantissa < 7.5) gridStep = 5 * qPow(10, exponent);
else gridStep = 10 * qPow(10, exponent);
if (gridStep <= 0) gridStep = 1;
double firstGrid = qCeil(m_yMin / gridStep) * gridStep;
QPen majorPen(m_gridColor, 1, Qt::SolidLine);
QPen minorPen(m_gridColorMinor, 1, Qt::DotLine);
// Y轴网格
for (double y = firstGrid; y <= m_yMax; y += gridStep) {
painter.setPen(majorPen);
QPointF p = dataToWidget(0, y);
painter.drawLine(QPointF(area.left(), p.y()), QPointF(area.right(), p.y()));
// 标签
painter.setPen(m_textColor);
QFont gridFont = font();
gridFont.setPointSize(8);
painter.setFont(gridFont);
painter.drawText(QRectF(0, p.y() - 10, m_marginLeft - 5, 20),
Qt::AlignRight | Qt::AlignVCenter,
QString::number(y, 'f', 0));
}
// X轴网格时间/采样点)
int startIdx = qMax(0, m_totalPoints - m_displayPoints);
if (m_autoScroll) {
startIdx = qMax(0, m_totalPoints - m_displayPoints);
}
// 画几条竖线
int xStep = m_displayPoints / 5;
if (xStep < 1) xStep = 1;
// 竖网格线(基于显示的采样点)
for (int i = 0; i <= m_displayPoints; i += xStep) {
double t = (double)i / m_displayPoints;
QPointF p = dataToWidget(t, 0);
painter.setPen(minorPen);
painter.drawLine(QPointF(p.x(), area.top()), QPointF(p.x(), area.bottom()));
painter.setPen(m_textColor);
QFont gridFont = font();
gridFont.setPointSize(8);
painter.setFont(gridFont);
int sampleIdx = startIdx + i;
painter.drawText(QRectF(p.x() - 30, area.bottom() + 3, 60, 15),
Qt::AlignCenter, QString::number(sampleIdx));
}
}
void PlotWidget::drawCurves(QPainter &painter)
{
QRectF area = plotArea();
if (area.width() <= 0 || area.height() <= 0) return;
if (m_totalPoints == 0) return;
int startIdx = qMax(0, m_totalPoints - m_displayPoints);
int visibleCount = m_totalPoints - startIdx;
if (visibleCount < 2) return;
for (int ch = 0; ch < 16; ++ch) {
if (!m_channelVisible[ch]) continue;
const auto &channel = m_data[ch];
if (channel.size() <= startIdx) continue;
QPen pen(m_colors[ch], 1.2);
painter.setPen(pen);
QPainterPath path;
bool first = true;
int endIdx = channel.size();
for (int i = startIdx; i < endIdx; ++i) {
double y = channel[i];
double t = (double)(i - startIdx) / (visibleCount - 1);
QPointF pt = dataToWidget(t, y);
if (first) {
path.moveTo(pt);
first = false;
} else {
path.lineTo(pt);
}
}
painter.drawPath(path);
}
}
void PlotWidget::drawAxes(QPainter &painter)
{
QRectF area = plotArea();
// Y轴
painter.setPen(QPen(m_axisColor, 1.5));
painter.drawLine(area.topLeft(), area.bottomLeft());
// X轴
painter.drawLine(area.bottomLeft(), area.bottomRight());
// Y轴标签
painter.setPen(m_textColor);
QFont labelFont = font();
labelFont.setPointSize(9);
painter.setFont(labelFont);
painter.save();
painter.translate(12, area.center().y());
painter.rotate(-90);
painter.drawText(QRectF(-50, -10, 100, 20), Qt::AlignCenter, "ADC Value");
painter.restore();
}
void PlotWidget::drawLegend(QPainter &painter)
{
QRectF area = plotArea();
int legendX = area.right() + 10;
int legendY = area.top() + 5;
int itemHeight = 18;
painter.setPen(m_textColor);
QFont legendFont = font();
legendFont.setPointSize(8);
painter.setFont(legendFont);
for (int ch = 0; ch < 16; ++ch) {
int y = legendY + ch * itemHeight;
// 颜色方块:勾选=彩色,不勾选=灰白
QColor color = m_channelVisible[ch] ? m_colors[ch] : QColor("#555555");
painter.fillRect(QRect(legendX, y + 2, 12, 12), color);
if (!m_channelVisible[ch]) {
painter.setPen(QPen(QColor("#888888"), 1));
painter.drawRect(QRect(legendX, y + 2, 12, 12));
}
// 通道名
QString label = QString("CH%1").arg(ch+1);
painter.setPen(m_channelVisible[ch] ? m_textColor : QColor("#555555"));
painter.drawText(QRect(legendX + 16, y, 50, itemHeight),
Qt::AlignLeft | Qt::AlignVCenter, label);
}
}
// ================ Mouse Events ================
void PlotWidget::wheelEvent(QWheelEvent *event)
{
QRectF area = plotArea();
if (!area.contains(event->posF())) return;
double zoomFactor = 1.15;
if (event->angleDelta().y() < 0) {
zoomFactor = 1.0 / zoomFactor;
}
double centerY = m_yMin + (m_yMax - m_yMin) / 2.0;
double halfRange = (m_yMax - m_yMin) / 2.0 * zoomFactor;
m_yMin = centerY - halfRange;
m_yMax = centerY + halfRange;
m_autoYRange = false;
m_dirty = true;
}
void PlotWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::RightButton) {
m_dragging = true;
m_dragStart = event->pos(); // mousePressEvent still uses pos()
m_dragYMinStart = m_yMin;
m_dragYMaxStart = m_yMax;
setCursor(Qt::ClosedHandCursor);
}
}
void PlotWidget::mouseMoveEvent(QMouseEvent *event)
{
if (m_dragging) {
QRectF area = plotArea();
double dy = (event->pos().y() - m_dragStart.y()) / area.height() * (m_yMax - m_yMin);
m_yMin = m_dragYMinStart - dy;
m_yMax = m_dragYMaxStart - dy;
m_autoYRange = false;
m_dirty = true;
}
}
void PlotWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::RightButton) {
m_dragging = false;
setCursor(Qt::ArrowCursor);
}
}
void PlotWidget::mouseDoubleClickEvent(QMouseEvent *)
{
// 双击恢复自动范围
m_autoYRange = true;
m_autoScroll = true;
autoRangeY();
m_dirty = true;
}
void PlotWidget::resizeEvent(QResizeEvent *)
{
m_dirty = true;
}