#include "TwoDSpectralCompliance.h" #include "ui_TwoDSpectralCompliance.h" #include "CustomQwtPlot.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "BusyIndicator.h" #include class HeatMapColorMap : public QwtLinearColorMap { public: HeatMapColorMap() : QwtLinearColorMap(Qt::blue, Qt::red) { addColorStop(0.25, Qt::cyan); addColorStop(0.5, Qt::green); addColorStop(0.75, Qt::yellow); addColorStop(0.90, Qt::darkRed); } }; 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(); _busy_indicator = new BusyIndicator(this); } TwoDSpectralCompliance::~TwoDSpectralCompliance() { delete ui; } void TwoDSpectralCompliance::InitViewWorkspace(const QString &project_name) { Q_UNUSED(project_name); } void TwoDSpectralCompliance::SetAnalyzeDataFilename(const QMap &data_files_set) { if (data_files_set.isEmpty()) return; QString csvFile = data_files_set.first().toString(); if (csvFile.isEmpty()) return; auto functionToRun = [this, csvFile](){ _busy_indicator->Start(); readCsv(csvFile); generateSurfaceData(); updateSpectrogram(); _busy_indicator->Stop(); }; QThread* load_data_thread = QThread::create(functionToRun); load_data_thread->start(); } void TwoDSpectralCompliance::setupPlot() { _plot->setCanvasBackground(Qt::white); QwtPlotCanvas* canvas = qobject_cast(_plot->canvas()); if (canvas) canvas->setFrameStyle(QFrame::NoFrame); QFont font = this->font(); font.setBold(false); QwtText energy_label = QStringLiteral(u"初级粒子能量"); energy_label.setFont(font); QwtText count_label = QStringLiteral(u"次级粒子能量"); 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; return; } QTextStream stream(&file); QString header = stream.readLine(); 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> eventMap; for (const auto& ev : m_rawData) { eventMap[ev.eventId].append(ev); } QMap, 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; double stepX = (primEnd - primStart) / numCols; double stepY = (secEnd - secStart) / numRows; QVector zValues(numRows * numCols, 0.0); for (const auto& pt : m_surfaceData) { int col = static_cast((pt.primaryEnergy - primStart) / stepX); int row = static_cast((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 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(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(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); } void TwoDSpectralCompliance::showEvent(QShowEvent *e) { Q_UNUSED(e); if (_busy_indicator) { _busy_indicator->setGeometry(this->rect()); this->update(); } }