EnergySpectrumAnalyer/src/2DSpectralCompliance/TwoDSpectralCompliance.cpp

435 lines
14 KiB
C++
Raw Normal View History

2026-04-07 18:15:20 +08:00
#include "TwoDSpectralCompliance.h"
#include "ui_TwoDSpectralCompliance.h"
#include "CustomQwtPlot.h"
#include <QwtPlotCanvas>
#include <QwtText>
#include <QwtLegend>
#include <QwtPlotSpectrogram>
#include <QwtLinearColorMap>
#include <QwtMatrixRasterData>
#include <QHBoxLayout>
#include <QVBoxLayout>
2026-04-07 18:15:20 +08:00
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <cmath>
#include <QwtInterval>
#include <qwt_matrix_raster_data.h>
#include <algorithm>
#include <QFormLayout>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QMouseEvent>
#include <QSet>
2026-04-07 18:15:20 +08:00
class HeatMapColorMap : public QwtLinearColorMap
{
public:
HeatMapColorMap() : QwtLinearColorMap(Qt::blue, Qt::red)
2026-04-07 18:15:20 +08:00
{
addColorStop(0.25, Qt::cyan);
2026-04-07 18:15:20 +08:00
addColorStop(0.5, Qt::green);
addColorStop(0.75, Qt::yellow);
addColorStop(0.90, Qt::darkRed);
2026-04-07 18:15:20 +08:00
}
};
TwoDSpectralCompliance::TwoDSpectralCompliance(QWidget *parent) :
MeasureAnalysisView(parent),
ui(new Ui::TwoDSpectralCompliance)
{
ui->setupUi(this);
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
this->_plot = new CustomQwtPlot(this);
layout->addWidget(this->_plot);
setupPlot();
createFloatingInfoWidget();
2026-04-07 18:15:20 +08:00
}
TwoDSpectralCompliance::~TwoDSpectralCompliance()
{
delete ui;
}
void TwoDSpectralCompliance::InitViewWorkspace(const QString &project_name)
{
Q_UNUSED(project_name);
}
void TwoDSpectralCompliance::SetAnalyzeDataFilename(const QMap<QString, QVariant> &data_files_set)
{
if (data_files_set.isEmpty())
return;
QString csvFile = data_files_set.first().toString();
if (csvFile.isEmpty())
return;
readCsv(csvFile);
generateSurfaceData();
updateSpectrogram();
}
2026-04-07 18:15:20 +08:00
void TwoDSpectralCompliance::setupPlot()
{
_plot->setCanvasBackground(Qt::white);
QwtPlotCanvas* canvas = qobject_cast<QwtPlotCanvas*>(_plot->canvas());
if (canvas)
canvas->setFrameStyle(QFrame::NoFrame);
QFont font = this->font();
font.setBold(false);
QwtText energy_label = QStringLiteral(u"初级粒子能量");
2026-04-07 18:15:20 +08:00
energy_label.setFont(font);
QwtText count_label = QStringLiteral(u"次级粒子能量");
2026-04-07 18:15:20 +08:00
count_label.setFont(font);
_plot->setAxisTitle(QwtPlot::xBottom, energy_label);
_plot->setAxisTitle(QwtPlot::yLeft, count_label);
_plot->setAxisAutoScale(QwtPlot::xBottom, true);
_plot->setAxisAutoScale(QwtPlot::yLeft, true);
_plot->enableAxis(QwtPlot::xBottom);
_plot->enableAxis(QwtPlot::yLeft);
QwtLegend* legend = new QwtLegend();
legend->setDefaultItemMode(QwtLegendData::ReadOnly);
_plot->insertLegend(legend, QwtPlot::RightLegend);
_plot->SetAxisDragScale(QwtPlot::xBottom, true);
_plot->SetAxisDragScale(QwtPlot::yLeft, true);
_spectrogram = new QwtPlotSpectrogram();
_spectrogram->setColorMap(new HeatMapColorMap());
_spectrogram->attach(_plot);
}
void TwoDSpectralCompliance::readCsv(const QString &filename)
{
m_rawData.clear();
QFile file(filename);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << QStringLiteral(u"无法打开文件:") << filename;
2026-04-07 18:15:20 +08:00
return;
}
QTextStream stream(&file);
QString header = stream.readLine();
2026-04-07 18:15:20 +08:00
Q_UNUSED(header);
while (!stream.atEnd()) {
QString line = stream.readLine();
if (line.isEmpty())
continue;
QStringList fields = line.split(',');
if (fields.size() < 5)
continue;
EventData data;
data.eventId = fields[0].toInt();
data.board = fields[1].toInt();
data.channel = fields[2].toInt();
data.energy = fields[3].toDouble();
data.timeCounter = fields[4].toULongLong();
m_rawData.append(data);
}
file.close();
}
void TwoDSpectralCompliance::generateSurfaceData()
{
m_surfaceData.clear();
if (m_rawData.isEmpty())
return;
QMap<int, QVector<EventData>> eventMap;
for (const auto& ev : m_rawData) {
eventMap[ev.eventId].append(ev);
}
QMap<QPair<float, float>, int> countMap;
for (auto it = eventMap.begin(); it != eventMap.end(); ++it) {
const auto& events = it.value();
if (events.isEmpty())
continue;
float primaryEnergy = events[0].energy;
float secondarySum = 0.0f;
for (int i = 1; i < events.size(); ++i) {
secondarySum += events[i].energy;
}
auto key = qMakePair(primaryEnergy, secondarySum);
countMap[key]++;
}
for (auto it = countMap.begin(); it != countMap.end(); ++it) {
TwoSurfacePoint point;
point.primaryEnergy = it.key().first;
point.secondaryEnergySum = it.key().second;
point.count = it.value();
m_surfaceData.append(point);
}
}
void TwoDSpectralCompliance::updateSpectrogram()
{
if (m_surfaceData.isEmpty()) {
_spectrogram->setData(new QwtMatrixRasterData());
_plot->replot();
return;
}
float minPrim = 1e9f, maxPrim = -1e9f;
float minSec = 1e9f, maxSec = -1e9f;
int maxCount = 0;
for (const auto& pt : m_surfaceData) {
minPrim = std::min(minPrim, pt.primaryEnergy);
maxPrim = std::max(maxPrim, pt.primaryEnergy);
minSec = std::min(minSec, pt.secondaryEnergySum);
maxSec = std::max(maxSec, pt.secondaryEnergySum);
maxCount = std::max(maxCount, pt.count);
}
double primRange = maxPrim - minPrim;
double secRange = maxSec - minSec;
if (primRange < 1e-6) primRange = 1.0;
if (secRange < 1e-6) secRange = 1.0;
double primStart = minPrim - primRange * 0.05;
double primEnd = maxPrim + primRange * 0.05;
double secStart = minSec - secRange * 0.05;
double secEnd = maxSec + secRange * 0.05;
const int numCols = 200;
const int numRows = 200;
2026-04-07 18:15:20 +08:00
double stepX = (primEnd - primStart) / numCols;
double stepY = (secEnd - secStart) / numRows;
QVector<double> zValues(numRows * numCols, 0.0);
for (const auto& pt : m_surfaceData) {
int col = static_cast<int>((pt.primaryEnergy - primStart) / stepX);
int row = static_cast<int>((pt.secondaryEnergySum - secStart) / stepY);
col = qBound(0, col, numCols - 1);
row = qBound(0, row, numRows - 1);
zValues[row * numCols + col] += pt.count;
}
QwtMatrixRasterData* data = new QwtMatrixRasterData();
data->setInterval(Qt::XAxis, QwtInterval(primStart, primEnd));
data->setInterval(Qt::YAxis, QwtInterval(secStart, secEnd));
data->setInterval(Qt::ZAxis, QwtInterval(0, maxCount));
data->setValueMatrix(zValues, numCols);
_spectrogram->setData(data);
_plot->setAxisScale(QwtPlot::xBottom, primStart, primEnd);
_plot->setAxisScale(QwtPlot::yLeft, secStart, secEnd);
_plot->replot();
}
void TwoDSpectralCompliance::createFloatingInfoWidget()
{
// 悬浮窗口
m_floatingWidget = new QWidget(this);
m_floatingWidget->setObjectName("FloatingInfoWidget");
m_floatingWidget->setStyleSheet(
"QWidget#FloatingInfoWidget {"
" background-color: rgba(240, 240, 240, 240);"
" border: 1px solid gray;"
" border-radius: 5px;"
"}"
);
m_floatingWidget->setFixedSize(320, 220);
m_floatingWidget->setAttribute(Qt::WA_TranslucentBackground, false);
QVBoxLayout* mainLayout = new QVBoxLayout(m_floatingWidget);
mainLayout->setSpacing(5);
mainLayout->setContentsMargins(8, 8, 8, 8);
// 标题栏(可拖动,并包含隐藏按钮)
QWidget* titleBar = new QWidget();
QHBoxLayout* titleLayout = new QHBoxLayout(titleBar);
titleLayout->setContentsMargins(0, 0, 0, 0);
QLabel* titleLabel = new QLabel(QStringLiteral(u"数据统计信息"));
titleLabel->setStyleSheet("font-weight: bold;");
titleLayout->addWidget(titleLabel);
titleLayout->addStretch();
QPushButton* hideBtn = new QPushButton(QStringLiteral(""));
hideBtn->setFixedSize(20, 20);
hideBtn->setText(QStringLiteral(u"X"));
hideBtn->setStyleSheet("border: none; font-weight: bold;");
titleLayout->addWidget(hideBtn);
mainLayout->addWidget(titleBar);
QWidget* contentWidget = new QWidget();
QFormLayout* formLayout = new QFormLayout(contentWidget);
formLayout->setSpacing(8);
formLayout->setLabelAlignment(Qt::AlignRight);
m_totalEventsEdit = new QLineEdit;
m_totalEventsEdit->setReadOnly(true);
m_totalEventsEdit->setFixedWidth(130);
m_totalPointsEdit = new QLineEdit;
m_totalPointsEdit->setReadOnly(true);
m_totalPointsEdit->setFixedWidth(130);
m_maxCountEdit = new QLineEdit;
m_maxCountEdit->setReadOnly(true);
m_maxCountEdit->setFixedWidth(130);
m_primRangeEdit = new QLineEdit;
m_primRangeEdit->setReadOnly(true);
m_primRangeEdit->setFixedWidth(130);
m_secRangeEdit = new QLineEdit;
m_secRangeEdit->setReadOnly(true);
m_secRangeEdit->setFixedWidth(130);
formLayout->addRow(QStringLiteral(u"总事件数:"), m_totalEventsEdit);
formLayout->addRow(QStringLiteral(u"有效数据点:"), m_totalPointsEdit);
formLayout->addRow(QStringLiteral(u"最大计数:"), m_maxCountEdit);
formLayout->addRow(QStringLiteral(u"初级能量范围:"), m_primRangeEdit);
formLayout->addRow(QStringLiteral(u"次级能量和范围:"), m_secRangeEdit);
mainLayout->addWidget(contentWidget);
mainLayout->addStretch();
connect(hideBtn, &QPushButton::clicked, this, &TwoDSpectralCompliance::hideFloatingWidget);
titleBar->setCursor(Qt::SizeAllCursor);
m_floatingWidget->installEventFilter(this);
m_showButton = new QPushButton(this);
m_showButton->setFixedSize(28, 28);
m_showButton->setToolTip(QStringLiteral(u"显示信息窗口"));
m_showButton->setStyleSheet(
"QPushButton {"
" background-color: rgba(200,200,200,200);"
" border: 1px solid #888;"
" border-radius: 14px;"
" font-weight: bold;"
"}"
"QPushButton:hover { background-color: rgba(150,150,150,200); }"
);
m_showButton->setText(QStringLiteral(u""));
connect(m_showButton, &QPushButton::clicked, this, &TwoDSpectralCompliance::showFloatingWidget);
m_floatingWidget->move(10, 10);
updateShowButtonPosition();
m_floatingWidget->hide();
m_showButton->show();
// m_floatingWidget->raise();
updateInfoContent();
}
void TwoDSpectralCompliance::hideFloatingWidget()
{
m_floatingWidget->hide();
m_showButton->show();
updateShowButtonPosition();
}
void TwoDSpectralCompliance::showFloatingWidget()
{
m_floatingWidget->show();
m_floatingWidget->raise();
m_showButton->hide();
updateInfoContent(); // 刷新内容
}
void TwoDSpectralCompliance::updateShowButtonPosition()
{
if (m_showButton && m_showButton->isVisible()) {
m_showButton->move(10, 10);
}
}
void TwoDSpectralCompliance::updateInfoContent()
{
if (!m_totalEventsEdit)
return;
if (m_surfaceData.isEmpty()) {
m_totalEventsEdit->setText(QStringLiteral(u"无数据"));
m_totalPointsEdit->setText(QStringLiteral(u"无数据"));
m_maxCountEdit->setText(QStringLiteral(u"无数据"));
m_primRangeEdit->setText(QStringLiteral(u"无数据"));
m_secRangeEdit->setText(QStringLiteral(u"无数据"));
return;
}
int totalPoints = m_surfaceData.size();
int maxCount = 0;
float minPrim = 1e9f, maxPrim = -1e9f;
float minSec = 1e9f, maxSec = -1e9f;
for (const auto& pt : m_surfaceData) {
minPrim = std::min(minPrim, pt.primaryEnergy);
maxPrim = std::max(maxPrim, pt.primaryEnergy);
minSec = std::min(minSec, pt.secondaryEnergySum);
maxSec = std::max(maxSec, pt.secondaryEnergySum);
maxCount = std::max(maxCount, pt.count);
}
QSet<int> eventIds;
for (const auto& ev : m_rawData) {
eventIds.insert(ev.eventId);
}
int totalEvents = eventIds.size();
m_totalEventsEdit->setText(QString::number(totalEvents));
m_totalPointsEdit->setText(QString::number(totalPoints));
m_maxCountEdit->setText(QString::number(maxCount));
m_primRangeEdit->setText(QString("[%1, %2]").arg(minPrim, 0, 'f', 2).arg(maxPrim, 0, 'f', 2));
m_secRangeEdit->setText(QString("[%1, %2]").arg(minSec, 0, 'f', 2).arg(maxSec, 0, 'f', 2));
}
void TwoDSpectralCompliance::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
updateShowButtonPosition();
if (m_floatingWidget && m_floatingWidget->isVisible()) {
// 限制悬浮窗口不超出父窗口边界
QRect rect = m_floatingWidget->geometry();
int newX = qBound(0, rect.x(), width() - rect.width());
int newY = qBound(0, rect.y(), height() - rect.height());
if (newX != rect.x() || newY != rect.y())
m_floatingWidget->move(newX, newY);
m_floatingWidget->raise();
}
}
bool TwoDSpectralCompliance::eventFilter(QObject *obj, QEvent *event)
{
if (obj == m_floatingWidget) {
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
m_dragging = true;
m_dragPosition = mouseEvent->globalPos() - m_floatingWidget->frameGeometry().topLeft();
event->accept();
return true;
}
} else if (event->type() == QEvent::MouseMove) {
if (m_dragging) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
m_floatingWidget->move(mouseEvent->globalPos() - m_dragPosition);
event->accept();
return true;
}
} else if (event->type() == QEvent::MouseButtonRelease) {
if (m_dragging) {
m_dragging = false;
event->accept();
return true;
}
}
}
return QWidget::eventFilter(obj, event);
}