diff --git a/src/AntiConformEnergySpectrumView/AntiConformEnergySpectrumView.cpp b/src/AntiConformEnergySpectrumView/AntiConformEnergySpectrumView.cpp index fae034f..c6f73be 100644 --- a/src/AntiConformEnergySpectrumView/AntiConformEnergySpectrumView.cpp +++ b/src/AntiConformEnergySpectrumView/AntiConformEnergySpectrumView.cpp @@ -15,6 +15,16 @@ #include #include #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include //反符合能谱 AntiConformEnergySpectrumView::AntiConformEnergySpectrumView(QWidget* parent) : MeasureAnalysisView(parent) @@ -155,6 +165,133 @@ void AntiConformEnergySpectrumView::clearAllRoiMarkers() void AntiConformEnergySpectrumView::onActionPlotConfigure() { + QDialog dlg(this); + dlg.setWindowTitle(QStringLiteral(u"图表配置")); + dlg.setMinimumWidth(320); + + QVBoxLayout* layout = new QVBoxLayout(&dlg); + + // X轴标题 + QHBoxLayout* xTitleLayout = new QHBoxLayout(); + QLabel* xTitleLabel = new QLabel(QStringLiteral(u"X轴标题:"), &dlg); + QLineEdit* xTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::xBottom).text(), &dlg); + xTitleLayout->addWidget(xTitleLabel); + xTitleLayout->addWidget(xTitleEdit); + layout->addLayout(xTitleLayout); + + // Y轴标题 + QHBoxLayout* yTitleLayout = new QHBoxLayout(); + QLabel* yTitleLabel = new QLabel(QStringLiteral(u"Y轴标题:"), &dlg); + QLineEdit* yTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::yLeft).text(), &dlg); + yTitleLayout->addWidget(yTitleLabel); + yTitleLayout->addWidget(yTitleEdit); + layout->addLayout(yTitleLayout); + + layout->addSpacing(8); + + // 显示网格 + QCheckBox* gridCheck = new QCheckBox(QStringLiteral(u"显示网格线"), &dlg); + gridCheck->setChecked(_plot->axisEnabled(QwtPlot::xBottom) && _plot->axisEnabled(QwtPlot::yLeft)); + layout->addWidget(gridCheck); + + // 显示图例 + QCheckBox* legendCheck = new QCheckBox(QStringLiteral(u"显示图例"), &dlg); + legendCheck->setChecked(_plot->legend() != nullptr); + layout->addWidget(legendCheck); + + layout->addSpacing(8); + + // 曲线颜色 + QLabel* curveColorLabel = new QLabel(QStringLiteral(u"曲线颜色:"), &dlg); + layout->addWidget(curveColorLabel); + + // 获取所有曲线 + QList curveList; + QMap curveColorMap; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + curveList.append(curve); + curveColorMap.insert(curve, curve->pen().color()); + } + } + + // 为每条曲线创建颜色按钮 + QList colorBtnList; + for (int i = 0; i < curveList.size(); ++i) { + QwtPlotCurve* curve = curveList.at(i); + QHBoxLayout* btnLayout = new QHBoxLayout(); + QLabel* nameLabel = new QLabel(QStringLiteral(u"曲线%1").arg(i + 1), &dlg); + QPushButton* colorBtn = new QPushButton(&dlg); + colorBtn->setFixedSize(60, 24); + QColor color = curveColorMap.value(curve); + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(color.name())); + btnLayout->addWidget(nameLabel); + btnLayout->addWidget(colorBtn); + btnLayout->addStretch(); + layout->addLayout(btnLayout); + colorBtnList.append(colorBtn); + + connect(colorBtn, &QPushButton::clicked, [&dlg, curve, &curveColorMap, colorBtn]() { + QColor current = curveColorMap.value(curve); + QColor selected = QColorDialog::getColor(current, &dlg, QStringLiteral(u"选择曲线颜色")); + if (selected.isValid()) { + curveColorMap[curve] = selected; + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(selected.name())); + } + }); + } + + layout->addStretch(); + + // 确定取消按钮 + QDialogButtonBox* buttonBox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dlg); + layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + + if (dlg.exec() == QDialog::Accepted) { + // 应用X轴标题 + _plot->setAxisTitle(QwtPlot::xBottom, xTitleEdit->text()); + // 应用Y轴标题 + _plot->setAxisTitle(QwtPlot::yLeft, yTitleEdit->text()); + // 应用网格显示 + _plot->enableAxis(QwtPlot::xBottom, gridCheck->isChecked()); + _plot->enableAxis(QwtPlot::yLeft, gridCheck->isChecked()); + // 应用图例显示 + if (legendCheck->isChecked() && _plot->legend() == nullptr) { + QwtLegend* legend = new QwtLegend(); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); + _plot->insertLegend(legend, QwtPlot::RightLegend); + // 给未命名曲线设置默认标题,保证图例显示名称 + int idx = 1; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + if (curve->title().isEmpty()) { + curve->setTitle(QStringLiteral(u"曲线%1").arg(idx)); + } + ++idx; + } + } + } else if (!legendCheck->isChecked() && _plot->legend() != nullptr) { + _plot->insertLegend(nullptr); + } + // 应用曲线颜色 + for (QwtPlotCurve* curve : curveList) { + QPen pen = curve->pen(); + pen.setColor(curveColorMap.value(curve)); + curve->setPen(pen); + } + // 刷新绘图 + _plot->replot(); + } } void AntiConformEnergySpectrumView::onActionRoiTriggered() diff --git a/src/CoincidenceEventTimeView/CoincidenceEventTimeView.cpp b/src/CoincidenceEventTimeView/CoincidenceEventTimeView.cpp index 0bef82f..19e059a 100644 --- a/src/CoincidenceEventTimeView/CoincidenceEventTimeView.cpp +++ b/src/CoincidenceEventTimeView/CoincidenceEventTimeView.cpp @@ -14,6 +14,15 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include struct SpectrumData { int eventId;//事件ID @@ -209,7 +218,133 @@ void CoincidenceEventTimeView::setupMenu() void CoincidenceEventTimeView::onActionPlotConfigure() { + QDialog dlg(this); + dlg.setWindowTitle(QStringLiteral(u"图表配置")); + dlg.setMinimumWidth(320); + QVBoxLayout* layout = new QVBoxLayout(&dlg); + + // X轴标题 + QHBoxLayout* xTitleLayout = new QHBoxLayout(); + QLabel* xTitleLabel = new QLabel(QStringLiteral(u"X轴标题:"), &dlg); + QLineEdit* xTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::xBottom).text(), &dlg); + xTitleLayout->addWidget(xTitleLabel); + xTitleLayout->addWidget(xTitleEdit); + layout->addLayout(xTitleLayout); + + // Y轴标题 + QHBoxLayout* yTitleLayout = new QHBoxLayout(); + QLabel* yTitleLabel = new QLabel(QStringLiteral(u"Y轴标题:"), &dlg); + QLineEdit* yTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::yLeft).text(), &dlg); + yTitleLayout->addWidget(yTitleLabel); + yTitleLayout->addWidget(yTitleEdit); + layout->addLayout(yTitleLayout); + + layout->addSpacing(8); + + // 显示网格 + QCheckBox* gridCheck = new QCheckBox(QStringLiteral(u"显示网格线"), &dlg); + gridCheck->setChecked(_plot->axisEnabled(QwtPlot::xBottom) && _plot->axisEnabled(QwtPlot::yLeft)); + layout->addWidget(gridCheck); + + // 显示图例 + QCheckBox* legendCheck = new QCheckBox(QStringLiteral(u"显示图例"), &dlg); + legendCheck->setChecked(_plot->legend() != nullptr); + layout->addWidget(legendCheck); + + layout->addSpacing(8); + + // 曲线颜色 + QLabel* curveColorLabel = new QLabel(QStringLiteral(u"曲线颜色:"), &dlg); + layout->addWidget(curveColorLabel); + + // 获取所有曲线 + QList curveList; + QMap curveColorMap; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + curveList.append(curve); + curveColorMap.insert(curve, curve->pen().color()); + } + } + + // 为每条曲线创建颜色按钮 + QList colorBtnList; + for (int i = 0; i < curveList.size(); ++i) { + QwtPlotCurve* curve = curveList.at(i); + QHBoxLayout* btnLayout = new QHBoxLayout(); + QLabel* nameLabel = new QLabel(QStringLiteral(u"曲线%1").arg(i + 1), &dlg); + QPushButton* colorBtn = new QPushButton(&dlg); + colorBtn->setFixedSize(60, 24); + QColor color = curveColorMap.value(curve); + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(color.name())); + btnLayout->addWidget(nameLabel); + btnLayout->addWidget(colorBtn); + btnLayout->addStretch(); + layout->addLayout(btnLayout); + colorBtnList.append(colorBtn); + + connect(colorBtn, &QPushButton::clicked, [&dlg, curve, &curveColorMap, colorBtn]() { + QColor current = curveColorMap.value(curve); + QColor selected = QColorDialog::getColor(current, &dlg, QStringLiteral(u"选择曲线颜色")); + if (selected.isValid()) { + curveColorMap[curve] = selected; + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(selected.name())); + } + }); + } + + layout->addStretch(); + + // 确定取消按钮 + QDialogButtonBox* buttonBox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dlg); + layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + + if (dlg.exec() == QDialog::Accepted) { + // 应用X轴标题 + _plot->setAxisTitle(QwtPlot::xBottom, xTitleEdit->text()); + // 应用Y轴标题 + _plot->setAxisTitle(QwtPlot::yLeft, yTitleEdit->text()); + // 应用网格显示 + _plot->enableAxis(QwtPlot::xBottom, gridCheck->isChecked()); + _plot->enableAxis(QwtPlot::yLeft, gridCheck->isChecked()); + // 应用图例显示 + if (legendCheck->isChecked() && _plot->legend() == nullptr) { + QwtLegend* legend = new QwtLegend(); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); + _plot->insertLegend(legend, QwtPlot::RightLegend); + // 给未命名曲线设置默认标题,保证图例显示名称 + int idx = 1; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + if (curve->title().isEmpty()) { + curve->setTitle(QStringLiteral(u"曲线%1").arg(idx)); + } + ++idx; + } + } + } else if (!legendCheck->isChecked() && _plot->legend() != nullptr) { + _plot->insertLegend(nullptr); + } + // 应用曲线颜色 + for (QwtPlotCurve* curve : curveList) { + QPen pen = curve->pen(); + pen.setColor(curveColorMap.value(curve)); + curve->setPen(pen); + } + // 刷新绘图 + _plot->replot(); + } } diff --git a/src/ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.cpp b/src/ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.cpp index a3481d8..e92407e 100644 --- a/src/ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.cpp +++ b/src/ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.cpp @@ -15,6 +15,16 @@ #include #include #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include //符合能谱 struct SpectrumData { @@ -194,7 +204,133 @@ void ConformToTheEnergySpectrum::clearAllRoiMarkers() void ConformToTheEnergySpectrum::onActionPlotConfigure() { + QDialog dlg(this); + dlg.setWindowTitle(QStringLiteral(u"图表配置")); + dlg.setMinimumWidth(320); + QVBoxLayout* layout = new QVBoxLayout(&dlg); + + // X轴标题 + QHBoxLayout* xTitleLayout = new QHBoxLayout(); + QLabel* xTitleLabel = new QLabel(QStringLiteral(u"X轴标题:"), &dlg); + QLineEdit* xTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::xBottom).text(), &dlg); + xTitleLayout->addWidget(xTitleLabel); + xTitleLayout->addWidget(xTitleEdit); + layout->addLayout(xTitleLayout); + + // Y轴标题 + QHBoxLayout* yTitleLayout = new QHBoxLayout(); + QLabel* yTitleLabel = new QLabel(QStringLiteral(u"Y轴标题:"), &dlg); + QLineEdit* yTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::yLeft).text(), &dlg); + yTitleLayout->addWidget(yTitleLabel); + yTitleLayout->addWidget(yTitleEdit); + layout->addLayout(yTitleLayout); + + layout->addSpacing(8); + + // 显示网格 + QCheckBox* gridCheck = new QCheckBox(QStringLiteral(u"显示网格线"), &dlg); + gridCheck->setChecked(_plot->axisEnabled(QwtPlot::xBottom) && _plot->axisEnabled(QwtPlot::yLeft)); + layout->addWidget(gridCheck); + + // 显示图例 + QCheckBox* legendCheck = new QCheckBox(QStringLiteral(u"显示图例"), &dlg); + legendCheck->setChecked(_plot->legend() != nullptr); + layout->addWidget(legendCheck); + + layout->addSpacing(8); + + // 曲线颜色 + QLabel* curveColorLabel = new QLabel(QStringLiteral(u"曲线颜色:"), &dlg); + layout->addWidget(curveColorLabel); + + // 获取所有曲线 + QList curveList; + QMap curveColorMap; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + curveList.append(curve); + curveColorMap.insert(curve, curve->pen().color()); + } + } + + // 为每条曲线创建颜色按钮 + QList colorBtnList; + for (int i = 0; i < curveList.size(); ++i) { + QwtPlotCurve* curve = curveList.at(i); + QHBoxLayout* btnLayout = new QHBoxLayout(); + QLabel* nameLabel = new QLabel(QStringLiteral(u"曲线%1").arg(i + 1), &dlg); + QPushButton* colorBtn = new QPushButton(&dlg); + colorBtn->setFixedSize(60, 24); + QColor color = curveColorMap.value(curve); + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(color.name())); + btnLayout->addWidget(nameLabel); + btnLayout->addWidget(colorBtn); + btnLayout->addStretch(); + layout->addLayout(btnLayout); + colorBtnList.append(colorBtn); + + connect(colorBtn, &QPushButton::clicked, [&dlg, curve, &curveColorMap, colorBtn]() { + QColor current = curveColorMap.value(curve); + QColor selected = QColorDialog::getColor(current, &dlg, QStringLiteral(u"选择曲线颜色")); + if (selected.isValid()) { + curveColorMap[curve] = selected; + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(selected.name())); + } + }); + } + + layout->addStretch(); + + // 确定取消按钮 + QDialogButtonBox* buttonBox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dlg); + layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + + if (dlg.exec() == QDialog::Accepted) { + // 应用X轴标题 + _plot->setAxisTitle(QwtPlot::xBottom, xTitleEdit->text()); + // 应用Y轴标题 + _plot->setAxisTitle(QwtPlot::yLeft, yTitleEdit->text()); + // 应用网格显示 + _plot->enableAxis(QwtPlot::xBottom, gridCheck->isChecked()); + _plot->enableAxis(QwtPlot::yLeft, gridCheck->isChecked()); + // 应用图例显示 + if (legendCheck->isChecked() && _plot->legend() == nullptr) { + QwtLegend* legend = new QwtLegend(); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); + _plot->insertLegend(legend, QwtPlot::RightLegend); + // 给未命名曲线设置默认标题,保证图例显示名称 + int idx = 1; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + if (curve->title().isEmpty()) { + curve->setTitle(QStringLiteral(u"曲线%1").arg(idx)); + } + ++idx; + } + } + } else if (!legendCheck->isChecked() && _plot->legend() != nullptr) { + _plot->insertLegend(nullptr); + } + // 应用曲线颜色 + for (QwtPlotCurve* curve : curveList) { + QPen pen = curve->pen(); + pen.setColor(curveColorMap.value(curve)); + curve->setPen(pen); + } + // 刷新绘图 + _plot->replot(); + } } void ConformToTheEnergySpectrum::slot_roi_data(ROIItem &item) diff --git a/src/CountRateAnalysisView/CountRateAnalysisView.cpp b/src/CountRateAnalysisView/CountRateAnalysisView.cpp index 402e36b..62e02ef 100644 --- a/src/CountRateAnalysisView/CountRateAnalysisView.cpp +++ b/src/CountRateAnalysisView/CountRateAnalysisView.cpp @@ -1,8 +1,16 @@ #include "CountRateAnalysisView.h" #include "ui_CountRateAnalysisView.h" #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -198,5 +206,131 @@ void CountRateAnalysisView::setupMenu() void CountRateAnalysisView::onActionPlotConfigure() { + QDialog dlg(this); + dlg.setWindowTitle(QStringLiteral(u"图表配置")); + dlg.setMinimumWidth(320); + QVBoxLayout* layout = new QVBoxLayout(&dlg); + + // X轴标题 + QHBoxLayout* xTitleLayout = new QHBoxLayout(); + QLabel* xTitleLabel = new QLabel(QStringLiteral(u"X轴标题:"), &dlg); + QLineEdit* xTitleEdit = new QLineEdit(plot->axisTitle(QwtPlot::xBottom).text(), &dlg); + xTitleLayout->addWidget(xTitleLabel); + xTitleLayout->addWidget(xTitleEdit); + layout->addLayout(xTitleLayout); + + // Y轴标题 + QHBoxLayout* yTitleLayout = new QHBoxLayout(); + QLabel* yTitleLabel = new QLabel(QStringLiteral(u"Y轴标题:"), &dlg); + QLineEdit* yTitleEdit = new QLineEdit(plot->axisTitle(QwtPlot::yLeft).text(), &dlg); + yTitleLayout->addWidget(yTitleLabel); + yTitleLayout->addWidget(yTitleEdit); + layout->addLayout(yTitleLayout); + + layout->addSpacing(8); + + // 显示网格 + QCheckBox* gridCheck = new QCheckBox(QStringLiteral(u"显示网格线"), &dlg); + gridCheck->setChecked(plot->axisEnabled(QwtPlot::xBottom) && plot->axisEnabled(QwtPlot::yLeft)); + layout->addWidget(gridCheck); + + // 显示图例 + QCheckBox* legendCheck = new QCheckBox(QStringLiteral(u"显示图例"), &dlg); + legendCheck->setChecked(plot->legend() != nullptr); + layout->addWidget(legendCheck); + + layout->addSpacing(8); + + // 曲线颜色 + QLabel* curveColorLabel = new QLabel(QStringLiteral(u"曲线颜色:"), &dlg); + layout->addWidget(curveColorLabel); + + // 获取所有曲线 + QList curveList; + QMap curveColorMap; + const QwtPlotItemList& items = plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + curveList.append(curve); + curveColorMap.insert(curve, curve->pen().color()); + } + } + + // 为每条曲线创建颜色按钮 + QList colorBtnList; + for (int i = 0; i < curveList.size(); ++i) { + QwtPlotCurve* curve = curveList.at(i); + QHBoxLayout* btnLayout = new QHBoxLayout(); + QLabel* nameLabel = new QLabel(QStringLiteral(u"曲线%1").arg(i + 1), &dlg); + QPushButton* colorBtn = new QPushButton(&dlg); + colorBtn->setFixedSize(60, 24); + QColor color = curveColorMap.value(curve); + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(color.name())); + btnLayout->addWidget(nameLabel); + btnLayout->addWidget(colorBtn); + btnLayout->addStretch(); + layout->addLayout(btnLayout); + colorBtnList.append(colorBtn); + + connect(colorBtn, &QPushButton::clicked, [&dlg, curve, &curveColorMap, colorBtn]() { + QColor current = curveColorMap.value(curve); + QColor selected = QColorDialog::getColor(current, &dlg, QStringLiteral(u"选择曲线颜色")); + if (selected.isValid()) { + curveColorMap[curve] = selected; + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(selected.name())); + } + }); + } + + layout->addStretch(); + + // 确定取消按钮 + QDialogButtonBox* buttonBox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dlg); + layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + + if (dlg.exec() == QDialog::Accepted) { + // 应用X轴标题 + plot->setAxisTitle(QwtPlot::xBottom, xTitleEdit->text()); + // 应用Y轴标题 + plot->setAxisTitle(QwtPlot::yLeft, yTitleEdit->text()); + // 应用网格显示 + plot->enableAxis(QwtPlot::xBottom, gridCheck->isChecked()); + plot->enableAxis(QwtPlot::yLeft, gridCheck->isChecked()); + // 应用图例显示 + if (legendCheck->isChecked() && plot->legend() == nullptr) { + QwtLegend* legend = new QwtLegend(); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); + plot->insertLegend(legend, QwtPlot::RightLegend); + // 给未命名曲线设置默认标题,保证图例显示名称 + int idx = 1; + const QwtPlotItemList& items = plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + if (curve->title().isEmpty()) { + curve->setTitle(QStringLiteral(u"曲线%1").arg(idx)); + } + ++idx; + } + } + } else if (!legendCheck->isChecked() && plot->legend() != nullptr) { + plot->insertLegend(nullptr); + } + // 应用曲线颜色 + for (QwtPlotCurve* curve : curveList) { + QPen pen = curve->pen(); + pen.setColor(curveColorMap.value(curve)); + curve->setPen(pen); + } + // 刷新绘图 + plot->replot(); + } } diff --git a/src/EfficiencyScale.cpp b/src/EfficiencyScale.cpp index ccc0953..02f7d5b 100644 --- a/src/EfficiencyScale.cpp +++ b/src/EfficiencyScale.cpp @@ -1,14 +1,858 @@ #include "EfficiencyScale.h" #include "ui_EfficiencyScale.h" +#include +#include +#include +#include +#include +#include "CustomQwtPlot.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "DataCalcProcess/EnergyEfficiencyFit.h" + EfficiencyScale::EfficiencyScale(QWidget *parent) : QDialog(parent) , ui(new Ui::EfficiencyScale) { ui->setupUi(this); + + QHBoxLayout* inflectionLayout = new QHBoxLayout(); + QLabel* inflectionLabel = new QLabel(QStringLiteral(u"拐点能量(keV):"), this); + QLineEdit* inflectionEdit = new QLineEdit(this); + inflectionEdit->setObjectName("lineEdit_inflection_energy"); + inflectionEdit->setPlaceholderText("0 = 全段统一拟合"); + inflectionLayout->addWidget(inflectionLabel); + inflectionLayout->addWidget(inflectionEdit); + ui->verticalLayout_16->insertLayout(2, inflectionLayout); + + this->_plot = new CustomQwtPlot(this); + ui->layout_fittingCurve->addWidget(this->_plot); + setupPlot(); + + _rawDataCurve = new QwtPlotCurve(QStringLiteral(u"原始数据")); + _rawDataCurve->setStyle(QwtPlotCurve::NoCurve); + _rawDataCurve->setSymbol(new QwtSymbol(QwtSymbol::Ellipse, + QBrush(Qt::red), QPen(Qt::red), QSize(8, 8))); + _rawDataCurve->attach(_plot); + + _fitCurve = new QwtPlotCurve(QStringLiteral(u"拟合曲线")); + _fitCurve->setPen(QPen(Qt::blue, 2)); + _fitCurve->attach(_plot); + ui->table_scale_data->verticalHeader()->hide(); + initComboBoxUi(); + + + // 初始化32个通道数据 + for (int i = 1; i <= 32; ++i) { + QString channelName = QStringLiteral(u"通道%1").arg(i); + m_channelDataMap.insert(channelName, ChannelEfficiencyData()); + } + m_currentChannel = QStringLiteral(u"通道1"); + + // 连接通道切换信号 + connect(ui->comboBox_channel, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_comboBox_channel_currentIndexChanged(int))); + + // 表格设置 + ui->table_scale_data->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + ui->table_scale_data->setEditTriggers(QAbstractItemView::DoubleClicked + | QAbstractItemView::SelectedClicked); + + // 连接表格编辑信号 + connect(ui->table_scale_data, SIGNAL(itemChanged(QTableWidgetItem*)), + this, SLOT(on_table_scale_data_itemChanged(QTableWidgetItem*))); + + // 初始化显示 + refreshTable(); + refreshPlot(); + + // 加载文件列表 + loadAllFilesInTheFolder(); } EfficiencyScale::~EfficiencyScale() { delete ui; } + +void EfficiencyScale::setupPlot() +{ + _plot->setCanvasBackground(Qt::white); + QwtPlotCanvas* canvas = qobject_cast(_plot->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); + _plot->SetAxisDragScale(QwtPlot::xBottom, true); + // 启用鼠标追踪 + _plot->canvas()->setMouseTracking(true); +} + +void EfficiencyScale::loadAllFilesInTheFolder() +{ + const QString& energy_scale_dir_path = QDir(qApp->applicationDirPath()).filePath("configure/EfficiencyScale"); + QDir dir(energy_scale_dir_path); + // 确保目录存在 + if (!dir.exists()) { + dir.mkpath(energy_scale_dir_path); + } + QFileInfoList infoList = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + ui->listWidget->clear(); + for (const QFileInfo &info : infoList) { + QString displayName; + if (info.isFile()) { + displayName = info.baseName(); + } else { + displayName = info.fileName(); + } + QListWidgetItem *item = new QListWidgetItem(displayName); + item->setData(Qt::UserRole, info.absoluteFilePath()); + ui->listWidget->addItem(item); + } + connect(ui->listWidget, &QListWidget::itemDoubleClicked, this, &EfficiencyScale::onItemDoubleClicked); +} + +void EfficiencyScale::initComboBoxUi() +{ + ui->comboBox_fit_type->addItem(QStringLiteral(u"一次拟合")); + ui->comboBox_fit_type->addItem(QStringLiteral(u"二次拟合")); + ui->comboBox_fit_type_1->addItem(QStringLiteral(u"一次拟合")); + ui->comboBox_fit_type_1->addItem(QStringLiteral(u"二次拟合")); + + for(int i = 1;i <= 32; ++i) + { + QString channelName = QStringLiteral(u"通道%1").arg(i); + ui->comboBox_channel->addItem(channelName); + } +} + +void EfficiencyScale::clearPlot() +{ + _rawDataCurve->detach(); + _fitCurve->detach(); + _plot->detachItems(QwtPlotItem::Rtti_PlotCurve, false); + _plot->replot(); +} + + +double EfficiencyScale::calculateCountRate(double netCount, double liveTime) const +{ + if (liveTime <= 0.0) return 0.0; + return netCount / liveTime; // cps: 每秒计数个数 +} + +double EfficiencyScale::calculateEfficiency(double netCount, double liveTime, + double activity, double branchRatio) const +{ + // 探测效率 ε = N / (t * A * Pγ) + // N: 净计数, t: 活时间, A: 活度, Pγ: 分支比 + double denominator = liveTime * activity * branchRatio; + if (denominator <= 0.0) return 0.0; + return netCount / denominator; +} + +void EfficiencyScale::recalculateAllPoints(QVector& points) const +{ + for (auto& pt : points) { + pt.countRate = calculateCountRate(pt.netCount, pt.liveTime); + pt.efficiency = calculateEfficiency(pt.netCount, pt.liveTime, + pt.activity, pt.branchRatio); + } +} + +FitResult EfficiencyScale::polynomialFit(const QVector& points, + int degree, double minEnergy, double maxEnergy) const +{ + FitResult result; + result.degree = degree; + + // 筛选能量范围内的有效点 + QVector eData, epsData; + for (const auto& pt : points) { + if (pt.energy >= minEnergy && pt.energy <= maxEnergy + && pt.efficiency > 0.0 && pt.energy > 0.0) { + eData.append(pt.energy); + epsData.append(pt.efficiency); + } + } + + int n = eData.size(); + if (n <= degree) { + result.valid = false; + return result; + } + + // 转换为 Armadillo 向量 + arma::vec E(n), eps(n); + for (int i = 0; i < n; ++i) { + E(i) = eData[i]; + eps(i) = epsData[i]; + } + + // 调用半经验拟合 + try { + arma::vec coeffs = EnergyEfficiencyFit::SemiEmpiricalFit(E, eps, degree); + // 只取有效阶数的系数(0~degree,共degree+1个) + result.coeffs.clear(); + for (int i = 0; i <= degree; ++i) { + result.coeffs.append(coeffs(i)); + } + result.valid = true; + } catch (...) { + result.valid = false; + } + return result; +} + + +double EfficiencyScale::calculateFitEfficiency(double energy, const FitResult& fit) const +{ + if (!fit.valid || energy <= 0.0) return 0.0; + double lnE = std::log(energy); + double lnEff = 0.0; + double xPow = 1.0; + for (int i = 0; i < fit.coeffs.size(); ++i) { + lnEff += fit.coeffs[i] * xPow; + xPow *= lnE; + } + return std::exp(lnEff); +} + +double EfficiencyScale::calculateTotalEfficiency(double energy, double totalNetCount, + double liveTime, double activity, + double branchRatio) const +{ + // 谱仪总效率:各通道能峰计数率相加后,按相同公式计算 + Q_UNUSED(energy); + return calculateEfficiency(totalNetCount, liveTime, activity, branchRatio); +} + + +ChannelEfficiencyData& EfficiencyScale::currentChannelData() +{ + return m_channelDataMap[m_currentChannel]; +} + +const ChannelEfficiencyData& EfficiencyScale::currentChannelData() const +{ + return m_channelDataMap[m_currentChannel]; +} + +void EfficiencyScale::refreshTable() +{ + const ChannelEfficiencyData& chData = currentChannelData(); + const QVector& points = chData.points; + + // 临时断开信号避免触发itemChanged + disconnect(ui->table_scale_data, SIGNAL(itemChanged(QTableWidgetItem*)), + this, SLOT(on_table_scale_data_itemChanged(QTableWidgetItem*))); + + ui->table_scale_data->setRowCount(points.size()); + + for (int row = 0; row < points.size(); ++row) { + const EfficiencyPoint& pt = points[row]; + // 0: 序号(只读) + QTableWidgetItem* item0 = new QTableWidgetItem(QString::number(row + 1)); + item0->setFlags(item0->flags() & ~Qt::ItemIsEditable); + ui->table_scale_data->setItem(row, 0, item0); + // 1: 能量(keV) —— 可编辑输入 + ui->table_scale_data->setItem(row, 1, new QTableWidgetItem(QString::number(pt.energy, 'f', 2))); + // 2: 净计数N —— 可编辑输入 + ui->table_scale_data->setItem(row, 2, new QTableWidgetItem(QString::number(pt.netCount, 'f', 0))); + // 3: 活时间t(s) —— 可编辑输入 + ui->table_scale_data->setItem(row, 3, new QTableWidgetItem(QString::number(pt.liveTime, 'f', 2))); + // 4: 活度A(Bq) —— 可编辑输入 + ui->table_scale_data->setItem(row, 4, new QTableWidgetItem(QString::number(pt.activity, 'f', 2))); + // 5: 分支比Pγ —— 可编辑输入 + ui->table_scale_data->setItem(row, 5, new QTableWidgetItem(QString::number(pt.branchRatio, 'f', 4))); + // 6: 净计数率(cps) —— 计算值 + QTableWidgetItem* item6 = new QTableWidgetItem(QString::number(pt.countRate, 'f', 4)); + item6->setFlags(item6->flags() & ~Qt::ItemIsEditable); + ui->table_scale_data->setItem(row, 6, item6); + // 7: 探测效率ε —— 计算值 + QTableWidgetItem* item7 = new QTableWidgetItem(QString::number(pt.efficiency, 'f', 8)); + item7->setFlags(item7->flags() & ~Qt::ItemIsEditable); + ui->table_scale_data->setItem(row, 7, item7); + // 8: 拟合效率 —— 拟合后计算 + QTableWidgetItem* item8; + if (pt.fitEfficiency > 0.0) { + item8 = new QTableWidgetItem(QString::number(pt.fitEfficiency, 'f', 8)); + } else { + item8 = new QTableWidgetItem("-"); + } + item8->setFlags(item8->flags() & ~Qt::ItemIsEditable); + ui->table_scale_data->setItem(row, 8, item8); + // 9: 误差(%) —— 拟合后计算 + QTableWidgetItem* item9; + if (pt.error > 0.0) { + item9 = new QTableWidgetItem(QString::number(pt.error, 'f', 4) + "%"); + } else { + item9 = new QTableWidgetItem("-"); + } + item9->setFlags(item9->flags() & ~Qt::ItemIsEditable); + ui->table_scale_data->setItem(row, 9, item9); + // 10: 刻度日期 —— 可编辑 + ui->table_scale_data->setItem(row, 10, new QTableWidgetItem(pt.scaleDate)); + } + + // 重新连接信号 + connect(ui->table_scale_data, SIGNAL(itemChanged(QTableWidgetItem*)), + this, SLOT(on_table_scale_data_itemChanged(QTableWidgetItem*))); +} + +// 表格单元格编辑后更新数据并重算 +void EfficiencyScale::on_table_scale_data_itemChanged(QTableWidgetItem* item) +{ + int row = item->row(); + int col = item->column(); + ChannelEfficiencyData& chData = currentChannelData(); + if (row >= chData.points.size()) return; + + EfficiencyPoint& pt = chData.points[row]; + bool ok = false; + double value = item->text().toDouble(&ok); + + switch (col) { + case 1: // 能量 + if (ok) pt.energy = value; + break; + case 2: // 净计数N + if (ok) pt.netCount = value; + break; + case 3: // 活时间t + if (ok) pt.liveTime = value; + break; + case 4: // 活度A + if (ok) pt.activity = value; + break; + case 5: // 分支比Pγ + if (ok) pt.branchRatio = value; + break; + case 10: // 刻度日期 + pt.scaleDate = item->text(); + return; // 日期不需要重算 + default: + return; // 其他列是只读计算列,不处理 + } + + // 修改了输入参数,重新计算计数率和效率 + recalculateAllPoints(chData.points); + // 刷新表格的计算列(避免递归,直接更新指定单元格) + disconnect(ui->table_scale_data, SIGNAL(itemChanged(QTableWidgetItem*)), + this, SLOT(on_table_scale_data_itemChanged(QTableWidgetItem*))); + ui->table_scale_data->item(row, 6)->setText(QString::number(pt.countRate, 'f', 4)); + ui->table_scale_data->item(row, 7)->setText(QString::number(pt.efficiency, 'e', 6)); + connect(ui->table_scale_data, SIGNAL(itemChanged(QTableWidgetItem*)), + this, SLOT(on_table_scale_data_itemChanged(QTableWidgetItem*))); + + refreshPlot(); +} + +QVector EfficiencyScale::generateFitCurvePoints(const FitResult& highFit, + const FitResult& lowFit, + double inflectionEnergy, + int sampleCount) const +{ + QVector curvePoints; + const ChannelEfficiencyData& chData = currentChannelData(); + if (chData.points.isEmpty()) return curvePoints; + + // 找能量范围 + double minE = chData.points.first().energy; + double maxE = chData.points.first().energy; + for (const auto& pt : chData.points) { + minE = qMin(minE, pt.energy); + maxE = qMax(maxE, pt.energy); + } + if (minE <= 0 || maxE <= 0) return curvePoints; + + // 采样 + double logMin = std::log(minE); + double logMax = std::log(maxE); + double step = (logMax - logMin) / sampleCount; + + for (int i = 0; i <= sampleCount; ++i) { + double logE = logMin + i * step; + double energy = std::exp(logE); + double eff = 0.0; + + if (inflectionEnergy > 0 && energy < inflectionEnergy) { + // 拐点以下用低能拟合 + if (lowFit.valid) { + eff = calculateFitEfficiency(energy, lowFit); + } + } else { + // 拐点以上用高能拟合 + if (highFit.valid) { + eff = calculateFitEfficiency(energy, highFit); + } else if (lowFit.valid) { + eff = calculateFitEfficiency(energy, lowFit); + } + } + + if (eff > 0.0) { + curvePoints.append(QPointF(energy, eff)); + } + } + return curvePoints; +} + +void EfficiencyScale::refreshPlot() +{ + if (!_plot || !_rawDataCurve || !_fitCurve) return; + + const ChannelEfficiencyData& chData = currentChannelData(); + + // 原始数据散点 + QVector rawPoints; + for (const auto& pt : chData.points) { + if (pt.efficiency > 0.0) { + rawPoints.append(QPointF(pt.energy, pt.efficiency)); + } + } + _rawDataCurve->setSamples(rawPoints); + + // 拟合曲线 + QVector fitPoints = generateFitCurvePoints( + chData.highFit, chData.lowFit, chData.inflectionEnergy); + _fitCurve->setSamples(fitPoints); + + _plot->replot(); +} + + +void EfficiencyScale::on_pBtn_Add_clicked() +{ + ChannelEfficiencyData& chData = currentChannelData(); + EfficiencyPoint newPoint; + newPoint.scaleDate = QDateTime::currentDateTime().toString("yyyy-MM-dd"); + // 默认值 + newPoint.energy = 0.0; + newPoint.netCount = 0.0; + newPoint.liveTime = 1.0; + newPoint.activity = 1.0; + newPoint.branchRatio = 1.0; + chData.points.append(newPoint); + + recalculateAllPoints(chData.points); + refreshTable(); + refreshPlot(); +} + +void EfficiencyScale::on_pBtn_Delete_clicked() +{ + int currentRow = ui->table_scale_data->currentRow(); + if (currentRow < 0) { + QMessageBox::warning(this, QStringLiteral(u"提示"), + QStringLiteral(u"请先选中要删除的刻度点")); + return; + } + + ChannelEfficiencyData& chData = currentChannelData(); + if (currentRow >= chData.points.size()) return; + + chData.points.removeAt(currentRow); + recalculateAllPoints(chData.points); + refreshTable(); + refreshPlot(); +} + +void EfficiencyScale::on_pBtn_fitting_4_clicked() +{ + + + // 拐点以上拟合 + ChannelEfficiencyData& chData = currentChannelData(); + + QLineEdit* inflectionEdit = findChild("lineEdit_inflection_energy"); + if (inflectionEdit) { + bool ok = false; + double val = inflectionEdit->text().toDouble(&ok); + chData.inflectionEnergy = (ok && val > 0.0) ? val : 0.0; + } + + if (chData.points.size() < 2) { + QMessageBox::warning(this, QStringLiteral(u"提示"), + QStringLiteral(u"至少需要2个刻度点才能进行拟合")); + return; + } + + int degree = ui->comboBox_fit_type->currentIndex() + 1; // 1或2 + double inflectionE = chData.inflectionEnergy; + + // 如果没有设置拐点,默认全部用高能拟合 + double minE = inflectionE > 0 ? inflectionE : 0.0; + double maxE = 1e9; // 足够大 + + chData.highFit = polynomialFit(chData.points, degree, minE, maxE); + + if (!chData.highFit.valid) { + QMessageBox::critical(this, QStringLiteral(u"错误"), + QStringLiteral(u"拟合失败,请检查数据")); + return; + } + + // 更新各点的拟合效率和误差 + for (auto& pt : chData.points) { + if (inflectionE <= 0 || pt.energy >= inflectionE) { + pt.fitEfficiency = calculateFitEfficiency(pt.energy, chData.highFit); + if (pt.efficiency > 0.0) { + pt.error = std::abs(pt.fitEfficiency - pt.efficiency) / pt.efficiency * 100.0; + } + } + } + + refreshTable(); + refreshPlot(); +} + +void EfficiencyScale::on_pBtn_fitting_3_clicked() +{ + // 拐点以下拟合 + ChannelEfficiencyData& chData = currentChannelData(); + + QLineEdit* inflectionEdit = findChild("lineEdit_inflection_energy"); + if (inflectionEdit) { + bool ok = false; + double val = inflectionEdit->text().toDouble(&ok); + chData.inflectionEnergy = (ok && val > 0.0) ? val : 0.0; + } + + if (chData.points.size() < 2) { + QMessageBox::warning(this, QStringLiteral(u"提示"), + QStringLiteral(u"至少需要2个刻度点才能进行拟合")); + return; + } + + int degree = ui->comboBox_fit_type_1->currentIndex() + 1; // 1或2 + double inflectionE = chData.inflectionEnergy; + + double minE = 0.0; + double maxE = inflectionE > 0 ? inflectionE : 1e9; + + chData.lowFit = polynomialFit(chData.points, degree, minE, maxE); + + if (!chData.lowFit.valid) { + QMessageBox::critical(this, QStringLiteral(u"错误"), + QStringLiteral(u"拟合失败,请检查数据")); + return; + } + + // 更新各点的拟合效率和误差 + for (auto& pt : chData.points) { + if (inflectionE <= 0 || pt.energy < inflectionE) { + pt.fitEfficiency = calculateFitEfficiency(pt.energy, chData.lowFit); + if (pt.efficiency > 0.0) { + pt.error = std::abs(pt.fitEfficiency - pt.efficiency) / pt.efficiency * 100.0; + } + } + } + + refreshTable(); + refreshPlot(); +} + +void EfficiencyScale::on_comboBox_channel_currentIndexChanged(int index) +{ + Q_UNUSED(index); + QLineEdit* inflectionEdit = findChild("lineEdit_inflection_energy"); + if (inflectionEdit) { + const ChannelEfficiencyData& chData = currentChannelData(); + if (chData.inflectionEnergy > 0.0) { + inflectionEdit->setText(QString::number(chData.inflectionEnergy, 'f', 2)); + } else { + inflectionEdit->clear(); + } + } + m_currentChannel = ui->comboBox_channel->currentText(); + refreshTable(); + refreshPlot(); +} + + +bool EfficiencyScale::saveToJsonFile(const QString& filePath) const +{ + QJsonObject rootObj; + rootObj["scaleName"] = ui->lineEdit_name->text(); + rootObj["description"] = ui->plainTextEdit_description_3->toPlainText(); + rootObj["currentChannel"] = m_currentChannel; + + QJsonArray channelsArray; + for (auto it = m_channelDataMap.constBegin(); it != m_channelDataMap.constEnd(); ++it) { + QJsonObject chObj; + chObj["channelName"] = it.key(); + chObj["inflectionEnergy"] = it.value().inflectionEnergy; + + // 刻度点 + QJsonArray pointsArray; + for (const auto& pt : it.value().points) { + QJsonObject ptObj; + ptObj["energy"] = pt.energy; + ptObj["netCount"] = pt.netCount; + ptObj["liveTime"] = pt.liveTime; + ptObj["activity"] = pt.activity; + ptObj["branchRatio"] = pt.branchRatio; + ptObj["countRate"] = pt.countRate; + ptObj["efficiency"] = pt.efficiency; + ptObj["fitEfficiency"] = pt.fitEfficiency; + ptObj["error"] = pt.error; + ptObj["scaleDate"] = pt.scaleDate; + pointsArray.append(ptObj); + } + chObj["points"] = pointsArray; + + // 高能拟合 + QJsonObject highFitObj; + highFitObj["valid"] = it.value().highFit.valid; + highFitObj["degree"] = it.value().highFit.degree; + QJsonArray highCoeffs; + for (double c : it.value().highFit.coeffs) highCoeffs.append(c); + highFitObj["coeffs"] = highCoeffs; + chObj["highFit"] = highFitObj; + + // 低能拟合 + QJsonObject lowFitObj; + lowFitObj["valid"] = it.value().lowFit.valid; + lowFitObj["degree"] = it.value().lowFit.degree; + QJsonArray lowCoeffs; + for (double c : it.value().lowFit.coeffs) lowCoeffs.append(c); + lowFitObj["coeffs"] = lowCoeffs; + chObj["lowFit"] = lowFitObj; + + channelsArray.append(chObj); + } + rootObj["channels"] = channelsArray; + + QJsonDocument doc(rootObj); + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + return false; + } + file.write(doc.toJson()); + file.close(); + return true; +} + +bool EfficiencyScale::loadFromJsonFile(const QString& filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + QByteArray data = file.readAll(); + file.close(); + + QJsonDocument doc = QJsonDocument::fromJson(data); + if (!doc.isObject()) return false; + + QJsonObject rootObj = doc.object(); + ui->lineEdit_name->setText(rootObj["scaleName"].toString()); + ui->plainTextEdit_description_3->setPlainText(rootObj["description"].toString()); + + QJsonArray channelsArray = rootObj["channels"].toArray(); + for (const auto& chVal : channelsArray) { + QJsonObject chObj = chVal.toObject(); + QString chName = chObj["channelName"].toString(); + if (!m_channelDataMap.contains(chName)) continue; + + ChannelEfficiencyData chData; + chData.inflectionEnergy = chObj["inflectionEnergy"].toDouble(); + + // 刻度点 + QJsonArray pointsArray = chObj["points"].toArray(); + for (const auto& ptVal : pointsArray) { + QJsonObject ptObj = ptVal.toObject(); + EfficiencyPoint pt; + pt.energy = ptObj["energy"].toDouble(); + pt.netCount = ptObj["netCount"].toDouble(); + pt.liveTime = ptObj["liveTime"].toDouble(); + pt.activity = ptObj["activity"].toDouble(); + pt.branchRatio = ptObj["branchRatio"].toDouble(); + pt.countRate = ptObj["countRate"].toDouble(); + pt.efficiency = ptObj["efficiency"].toDouble(); + pt.fitEfficiency = ptObj["fitEfficiency"].toDouble(); + pt.error = ptObj["error"].toDouble(); + pt.scaleDate = ptObj["scaleDate"].toString(); + chData.points.append(pt); + } + + // 高能拟合 + QJsonObject highFitObj = chObj["highFit"].toObject(); + chData.highFit.valid = highFitObj["valid"].toBool(); + chData.highFit.degree = highFitObj["degree"].toInt(); + QJsonArray highCoeffs = highFitObj["coeffs"].toArray(); + for (const auto& c : highCoeffs) chData.highFit.coeffs.append(c.toDouble()); + + // 低能拟合 + QJsonObject lowFitObj = chObj["lowFit"].toObject(); + chData.lowFit.valid = lowFitObj["valid"].toBool(); + chData.lowFit.degree = lowFitObj["degree"].toInt(); + QJsonArray lowCoeffs = lowFitObj["coeffs"].toArray(); + for (const auto& c : lowCoeffs) chData.lowFit.coeffs.append(c.toDouble()); + + m_channelDataMap[chName] = chData; + } + + m_currentFilePath = filePath; + refreshTable(); + refreshPlot(); + return true; +} + + +void EfficiencyScale::onItemDoubleClicked(QListWidgetItem *item) +{ + if (!item) return; + QString filePath = item->data(Qt::UserRole).toString(); + if (filePath.isEmpty()) return; + + QFileInfo fileInfo(filePath); + if (!fileInfo.isFile()) return; + + if (loadFromJsonFile(filePath)) { + ui->lineEdit_name->setText(item->text()); + QMessageBox::information(this, QStringLiteral(u"成功"), + QStringLiteral(u"加载成功:%1").arg(item->text())); + } else { + QMessageBox::critical(this, QStringLiteral(u"错误"), + QStringLiteral(u"加载失败:文件格式不正确")); + } +} + +void EfficiencyScale::on_pBtn_Save_clicked() +{ + if (m_currentFilePath.isEmpty()) { + on_pBtn_SaveAs_clicked(); + return; + } + if (saveToJsonFile(m_currentFilePath)) { + QMessageBox::information(this, QStringLiteral(u"成功"), + QStringLiteral(u"保存成功")); + } else { + QMessageBox::critical(this, QStringLiteral(u"错误"), + QStringLiteral(u"保存失败")); + } +} + +void EfficiencyScale::on_pBtn_SaveAs_clicked() +{ + QString defaultDir = QDir(qApp->applicationDirPath()).filePath("configure/EfficiencyScale"); + QString fileName = QFileDialog::getSaveFileName(this, + QStringLiteral(u"另存为"), defaultDir, + QStringLiteral(u"效率刻度文件 (*.json)")); + if (fileName.isEmpty()) return; + + if (saveToJsonFile(fileName)) { + m_currentFilePath = fileName; + loadAllFilesInTheFolder(); // 刷新列表 + QMessageBox::information(this, QStringLiteral(u"成功"), + QStringLiteral(u"保存成功")); + } else { + QMessageBox::critical(this, QStringLiteral(u"错误"), + QStringLiteral(u"保存失败")); + } +} + +void EfficiencyScale::on_pBtn_Add_File_clicked() +{ + QString defaultDir = QDir(qApp->applicationDirPath()).filePath("configure/EfficiencyScale"); + QString fileName = QFileDialog::getSaveFileName(this, + QStringLiteral(u"新建效率刻度文件"), defaultDir, + QStringLiteral(u"效率刻度文件 (*.json)")); + if (fileName.isEmpty()) return; + + // 确保后缀 + if (!fileName.endsWith(".json", Qt::CaseInsensitive)) { + fileName += ".json"; + } + + // 清空当前数据,生成空白刻度 + m_scaleName = QFileInfo(fileName).completeBaseName(); + m_description.clear(); + m_currentChannel = QStringLiteral(u"通道1"); + for (auto it = m_channelDataMap.begin(); it != m_channelDataMap.end(); ++it) { + it.value().points.clear(); + it.value().highFit = FitResult(); + it.value().lowFit = FitResult(); + it.value().inflectionEnergy = 0.0; + } + + // 保存空白文件 + if (saveToJsonFile(fileName)) { + m_currentFilePath = fileName; + ui->lineEdit_name->setText(m_scaleName); + ui->plainTextEdit_description_3->setPlainText(m_description); + loadAllFilesInTheFolder(); + refreshTable(); + refreshPlot(); + QMessageBox::information(this, QStringLiteral(u"成功"), + QStringLiteral(u"新建成功")); + } else { + QMessageBox::critical(this, QStringLiteral(u"错误"), + QStringLiteral(u"新建失败")); + } +} + +void EfficiencyScale::on_pBtn_Delete_File_clicked() +{ + QListWidgetItem* selectedItem = ui->listWidget->currentItem(); + if (!selectedItem) { + QMessageBox::warning(this, QStringLiteral(u"提示"), + QStringLiteral(u"请先选中要删除的文件/文件夹")); + return; + } + QString filePath = selectedItem->data(Qt::UserRole).toString(); + if (filePath.isEmpty()) { + QMessageBox::warning(this, QStringLiteral(u"错误"), + QStringLiteral(u"无法获取文件路径")); + return; + } + QMessageBox::StandardButton ret = QMessageBox::question(this, + QStringLiteral(u"确认删除"), + QStringLiteral(u"是否确定删除%1?\n删除后无法恢复!").arg(selectedItem->text()), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No); + if (ret != QMessageBox::Yes) { + return; + } + bool isDeleted = false; + QFileInfo fileInfo(filePath); + if (fileInfo.isFile()) { + QFile file(filePath); + isDeleted = file.remove(); + } + if (isDeleted) { + loadAllFilesInTheFolder(); + if (m_currentFilePath == filePath) { + m_currentFilePath.clear(); + ui->lineEdit_name->clear(); + ui->table_scale_data->setRowCount(0); + clearPlot(); + _plot->replot(); + } + QMessageBox::information(this, QStringLiteral(u"成功"), + QStringLiteral(u"删除成功")); + } else { + QMessageBox::critical(this, QStringLiteral(u"错误"), + QStringLiteral(u"删除失败:%1").arg(filePath)); + } +} diff --git a/src/EfficiencyScale.h b/src/EfficiencyScale.h index a87ae40..bce971c 100644 --- a/src/EfficiencyScale.h +++ b/src/EfficiencyScale.h @@ -2,11 +2,61 @@ #define EFFICIENCYSCALE_H #include +#include +#include +#include +#include +#include +#include namespace Ui { class EfficiencyScale; } +class CustomQwtPlot; + +// 单个效率刻度点数据结构 +struct EfficiencyPoint +{ + double energy; // 能量(keV) + double netCount; // 净计数 N + double liveTime; // 测量活时间 t (s) + double activity; // 放射源活度 A (Bq) + double branchRatio; // 分支比 Pγ + double countRate; // 净计数率 N/t (cps) —— 计算值 + double efficiency; // 探测效率 ε —— 计算值 + double fitEfficiency; // 拟合效率 —— 拟合后计算值 + double error; // 误差(%) —— 拟合后计算值 + QString scaleDate; // 刻度日期 + + EfficiencyPoint() + : energy(0.0), netCount(0.0), liveTime(0.0), activity(0.0) + , branchRatio(1.0), countRate(0.0), efficiency(0.0) + , fitEfficiency(0.0), error(0.0) {} +}; + +// 多项式拟合结果 +struct FitResult +{ + bool valid; + int degree; // 拟合次数(1=一次,2=二次) + QVector coeffs; // 多项式系数,coeffs[0]为常数项,coeffs[1]一次项... + double inflectionEnergy; // 拐点能量(keV),分段拟合时使用 + + FitResult() : valid(false), degree(1), inflectionEnergy(0.0) {} +}; + +// 单通道效率刻度数据 +struct ChannelEfficiencyData +{ + QVector points; // 刻度点列表 + FitResult highFit; // 拐点以上拟合结果 + FitResult lowFit; // 拐点以下拟合结果 + double inflectionEnergy; // 拐点能量(keV) + + ChannelEfficiencyData() : inflectionEnergy(0.0) {} +}; + class EfficiencyScale : public QDialog { Q_OBJECT @@ -15,8 +65,88 @@ public: explicit EfficiencyScale(QWidget *parent = nullptr); ~EfficiencyScale(); +private: + void setupPlot(); + //加载能量刻度文件夹下所有的文件 + void loadAllFilesInTheFolder(); + //初始化通道数 + void initComboBoxUi(); + void clearPlot(); + + // 计算计数率:countRate = netCount / liveTime (cps) + double calculateCountRate(double netCount, double liveTime) const; + + // 计算单个能量点的探测效率:ε = N / (t * A * Pγ) + double calculateEfficiency(double netCount, double liveTime, + double activity, double branchRatio) const; + + // 重新计算所有点的计数率和探测效率 + void recalculateAllPoints(QVector& points) const; + + // 多项式拟合(最小二乘法),对ln(E)-ln(ε)做拟合 + // degree: 1~5,分别对应一次到五次拟合 + FitResult polynomialFit(const QVector& points, + int degree, double minEnergy, double maxEnergy) const; + + // 根据拟合公式计算指定能量的效率 + double calculateFitEfficiency(double energy, const FitResult& fit) const; + + // 计算谱仪总效率(多通道计数率求和后计算效率) + double calculateTotalEfficiency(double energy, double totalNetCount, + double liveTime, double activity, + double branchRatio) const; + + // 刷新表格数据 + void refreshTable(); + + // 刷新绘图(原始散点 + 拟合曲线) + void refreshPlot(); + + // 生成拟合曲线上的采样点 + QVector generateFitCurvePoints(const FitResult& highFit, + const FitResult& lowFit, + double inflectionEnergy, + int sampleCount = 200) const; + + // 保存当前刻度数据到JSON文件 + bool saveToJsonFile(const QString& filePath) const; + + // 从JSON文件加载刻度数据 + bool loadFromJsonFile(const QString& filePath); + + // 获取当前选中通道的数据引用 + ChannelEfficiencyData& currentChannelData(); + const ChannelEfficiencyData& currentChannelData() const; + +private slots: + void onItemDoubleClicked(QListWidgetItem *item); + void on_pBtn_Save_clicked(); + void on_pBtn_SaveAs_clicked(); + void on_pBtn_Add_clicked(); + void on_pBtn_Delete_clicked(); + void on_pBtn_Add_File_clicked(); + void on_pBtn_Delete_File_clicked(); + + void on_pBtn_fitting_4_clicked(); // 拐点以上拟合 + void on_pBtn_fitting_3_clicked(); // 拐点以下拟合 + + // 通道切换 + void on_comboBox_channel_currentIndexChanged(int index); + + // 表格单元格编辑后重算 + void on_table_scale_data_itemChanged(QTableWidgetItem* item); + private: Ui::EfficiencyScale *ui; + CustomQwtPlot* _plot = nullptr; + QwtPlotCurve *_rawDataCurve; // 原始数据散点曲线 + QwtPlotCurve *_fitCurve; // 拟合曲线 + QString m_currentFilePath; + + QMap m_channelDataMap; // 各通道数据 + QString m_currentChannel; // 当前选中通道名 + QString m_scaleName; // 效率刻度名称 + QString m_description; // 备注说明 }; #endif // EFFICIENCYSCALE_H diff --git a/src/EfficiencyScale.ui b/src/EfficiencyScale.ui index 32ef209..279fa86 100644 --- a/src/EfficiencyScale.ui +++ b/src/EfficiencyScale.ui @@ -33,19 +33,19 @@ 0 - + - + 添加 - + 删除 @@ -87,7 +87,7 @@ - + @@ -132,7 +132,7 @@ - + @@ -155,14 +155,14 @@ - + 保存 - + 另存为 @@ -188,7 +188,7 @@ - + 0 @@ -216,7 +216,7 @@ - + 0 @@ -254,14 +254,14 @@ - + 新增 - + 删除 @@ -287,7 +287,7 @@ - + @@ -306,7 +306,7 @@ 0 - + 0 @@ -318,7 +318,7 @@ - + 0 @@ -330,7 +330,37 @@ - 能量 + 序号 + + + + + 能量(keV) + + + + + 净计数(N) + + + + + 活时间(s) + + + + + 活度(Bq) + + + + + 分支比 + + + + + 净计数率(cps) @@ -345,7 +375,7 @@ - 误差 + 误差(%) @@ -359,6 +389,6 @@ - - + + diff --git a/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp b/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp index fb662c0..1051811 100644 --- a/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp +++ b/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp @@ -406,6 +406,7 @@ void EnergyCountPeakFitView::onActionCurveShowSetting() void EnergyCountPeakFitView::onActionPlotConfigure() { + } void EnergyCountPeakFitView::clearAllSelectionRects() diff --git a/src/EnergyCountPlotView/EnergyCountPlotView.cpp b/src/EnergyCountPlotView/EnergyCountPlotView.cpp index c36b474..46f1024 100644 --- a/src/EnergyCountPlotView/EnergyCountPlotView.cpp +++ b/src/EnergyCountPlotView/EnergyCountPlotView.cpp @@ -15,6 +15,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include //能量计数谱 EnergyCountPlotView::EnergyCountPlotView(QWidget *parent) : MeasureAnalysisView { parent } @@ -285,7 +292,166 @@ void EnergyCountPlotView::onActionCurveShowSetting() void EnergyCountPlotView::onActionPlotConfigure() { + QDialog dlg(this); + dlg.setWindowTitle(QStringLiteral(u"图表配置")); + dlg.setMinimumWidth(520); + QVBoxLayout* layout = new QVBoxLayout(&dlg); + // X轴标题 + QHBoxLayout* xTitleLayout = new QHBoxLayout(); + QLabel* xTitleLabel = new QLabel(QStringLiteral(u"X轴标题:"), &dlg); + QLineEdit* xTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::xBottom).text(), &dlg); + xTitleLayout->addWidget(xTitleLabel); + xTitleLayout->addWidget(xTitleEdit); + layout->addLayout(xTitleLayout); + + // Y轴标题 + QHBoxLayout* yTitleLayout = new QHBoxLayout(); + QLabel* yTitleLabel = new QLabel(QStringLiteral(u"Y轴标题:"), &dlg); + QLineEdit* yTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::yLeft).text(), &dlg); + yTitleLayout->addWidget(yTitleLabel); + yTitleLayout->addWidget(yTitleEdit); + layout->addLayout(yTitleLayout); + + layout->addSpacing(8); + + // 显示网格 + QCheckBox* gridCheck = new QCheckBox(QStringLiteral(u"显示网格线"), &dlg); + gridCheck->setChecked(_plot->axisEnabled(QwtPlot::xBottom) && _plot->axisEnabled(QwtPlot::yLeft)); + layout->addWidget(gridCheck); + + // 显示图例 + QCheckBox* legendCheck = new QCheckBox(QStringLiteral(u"显示图例"), &dlg); + legendCheck->setChecked(_plot->legend() != nullptr); + layout->addWidget(legendCheck); + + layout->addSpacing(8); + + // 曲线颜色 + QLabel* curveColorLabel = new QLabel(QStringLiteral(u"曲线颜色:"), &dlg); + layout->addWidget(curveColorLabel); + + // 获取所有曲线并按通道号排序(与图例显示顺序一致) + QList curveList; + QMap curveColorMap; + QStringList chNameList; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + curveList.append(curve); + curveColorMap.insert(curve, curve->pen().color()); + chNameList.append(curve->title().text()); + } + } + + // 按通道号排序 + std::sort(chNameList.begin(), chNameList.end(), [](const QString& a, const QString& b) { + int num_a = ExtractNumberFromString(a); + int num_b = ExtractNumberFromString(b); + return num_a < num_b; + }); + + // 按排序后的顺序重建曲线列表 + QList sortedCurveList; + for (const QString& chName : chNameList) { + for (QwtPlotCurve* curve : curveList) { + if (curve->title().text() == chName) { + sortedCurveList.append(curve); + break; + } + } + } + curveList = sortedCurveList; + + // 多列网格布局:4列,降低对话框高度 + const int numColumns = 4; + QGridLayout* gridLayout = new QGridLayout(); + gridLayout->setHorizontalSpacing(12); + gridLayout->setVerticalSpacing(4); + + QList colorBtnList; + for (int i = 0; i < curveList.size(); ++i) { + QwtPlotCurve* curve = curveList.at(i); + int row = i / numColumns; + int col = i % numColumns; + + // 每行单元:颜色按钮 + 通道名 + QHBoxLayout* cellLayout = new QHBoxLayout(); + cellLayout->setSpacing(6); + + QPushButton* colorBtn = new QPushButton(&dlg); + colorBtn->setFixedSize(36, 20); + QColor color = curveColorMap.value(curve); + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(color.name())); + + QLabel* nameLabel = new QLabel(curve->title().text(), &dlg); + + cellLayout->addWidget(colorBtn); + cellLayout->addWidget(nameLabel); + cellLayout->addStretch(); + + gridLayout->addLayout(cellLayout, row, col); + colorBtnList.append(colorBtn); + + connect(colorBtn, &QPushButton::clicked, [&dlg, curve, &curveColorMap, colorBtn]() { + QColor current = curveColorMap.value(curve); + QColor selected = QColorDialog::getColor(current, &dlg, QStringLiteral(u"选择曲线颜色")); + if (selected.isValid()) { + curveColorMap[curve] = selected; + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(selected.name())); + } + }); + } + + layout->addLayout(gridLayout); + layout->addStretch(); + + // 确定取消按钮 + QDialogButtonBox* buttonBox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dlg); + layout->addWidget(buttonBox); + connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + + if (dlg.exec() == QDialog::Accepted) { + // 应用X轴标题 + _plot->setAxisTitle(QwtPlot::xBottom, xTitleEdit->text()); + // 应用Y轴标题 + _plot->setAxisTitle(QwtPlot::yLeft, yTitleEdit->text()); + // 应用网格显示 + _plot->enableAxis(QwtPlot::xBottom, gridCheck->isChecked()); + _plot->enableAxis(QwtPlot::yLeft, gridCheck->isChecked()); + // 应用图例显示 + if (legendCheck->isChecked() && _plot->legend() == nullptr) { + QwtLegend* legend = new QwtLegend(); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); + _plot->insertLegend(legend, QwtPlot::RightLegend); + int idx = 1; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + if (curve->title().isEmpty()) { + curve->setTitle(QStringLiteral(u"曲线%1").arg(idx)); + } + ++idx; + } + } + } else if (!legendCheck->isChecked() && _plot->legend() != nullptr) { + _plot->insertLegend(nullptr); + } + // 应用曲线颜色 + for (QwtPlotCurve* curve : curveList) { + QPen pen = curve->pen(); + pen.setColor(curveColorMap.value(curve)); + curve->setPen(pen); + } + // 刷新绘图 + _plot->replot(); + } } void EnergyCountPlotView::onActionRoiTriggered() diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 4eb80a0..154ed30 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -38,6 +38,7 @@ #include "ParticleCountPlotView.h" #include "reportwidget.h" #include "RegionOfInterest.h" +#include "EfficiencyScale.h" #include #include "csv.h" #include @@ -159,6 +160,7 @@ MainWindow::~MainWindow() void MainWindow::onMeasureStarted(bool success, const QString& message) { + LOG_INFO(message); // Q_UNUSED(message); // if (success) { // ui->action_start_measure->setEnabled(false); @@ -174,6 +176,7 @@ void MainWindow::onMeasureStopped(bool success, const QString& message) // ui->action_stop_measure->setEnabled(false); m_AddressCountTimer->stop(); m_EnergyCountTimer->stop(); + LOG_INFO(message); // // 更新树节点状态:把"测量中"改回"已停止" // if (!nodeMap.isEmpty()) { // MeasureAnalysisProjectModel* models = ProjectList::Instance()->GetCurrentProjectModel(); @@ -189,7 +192,8 @@ void MainWindow::onMeasureStopped(bool success, const QString& message) void MainWindow::onDeviceStatusMessage(const QString& msg) { - ShowStatusBarMsg(msg); + // ShowStatusBarMsg(msg); + LOG_INFO(msg); } void MainWindow::initMainWindow() @@ -540,7 +544,7 @@ void MainWindow::onUpdateParticleDataTreeStatus() nodeMap = project_node_items[models->GetProjectName()]; QStandardItem *nodeItem = nodeMap[models->GetProjectName()]; ProjectList::Instance()->SetNodeStatus(nodeItem,"测量中",true); - + LOG_INFO("测量中"); QString dir = ProjectList::Instance()->GetCurrentProjectModel()->GetProjectDir(); QString csvPath = QStringLiteral(u"%1/%2").arg(dir).arg("粒子数据.csv"); models->SetAllChannelParticleDataFilename(csvPath); @@ -888,7 +892,6 @@ void MainWindow::on_action_analysisReport_triggered() widget->show(); } -#include "EfficiencyScale.h" void MainWindow::on_action_efficiency_scale_mrg_triggered() { // QDialog energy_scale_mrg_dlg; diff --git a/src/NuclideAnalysisView/NuclideAnalysisView.cpp b/src/NuclideAnalysisView/NuclideAnalysisView.cpp index f5de4d0..b1382f9 100644 --- a/src/NuclideAnalysisView/NuclideAnalysisView.cpp +++ b/src/NuclideAnalysisView/NuclideAnalysisView.cpp @@ -16,6 +16,17 @@ #include #include #include "AddNuclideDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +//核素分析 // 更新选择框显示 static void updateSelectionMarkers(QwtPlot* plot, QwtPlotMarker*& leftMarker, @@ -275,7 +286,133 @@ void NuclideAnalysisView::loadDataFromFile(const QString& data_name, const QStri void NuclideAnalysisView::onActionPlotConfigure() { + QDialog dlg(this); + dlg.setWindowTitle(QStringLiteral(u"图表配置")); + dlg.setMinimumWidth(320); + QVBoxLayout* layout = new QVBoxLayout(&dlg); + + // X轴标题 + QHBoxLayout* xTitleLayout = new QHBoxLayout(); + QLabel* xTitleLabel = new QLabel(QStringLiteral(u"X轴标题:"), &dlg); + QLineEdit* xTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::xBottom).text(), &dlg); + xTitleLayout->addWidget(xTitleLabel); + xTitleLayout->addWidget(xTitleEdit); + layout->addLayout(xTitleLayout); + + // Y轴标题 + QHBoxLayout* yTitleLayout = new QHBoxLayout(); + QLabel* yTitleLabel = new QLabel(QStringLiteral(u"Y轴标题:"), &dlg); + QLineEdit* yTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::yLeft).text(), &dlg); + yTitleLayout->addWidget(yTitleLabel); + yTitleLayout->addWidget(yTitleEdit); + layout->addLayout(yTitleLayout); + + layout->addSpacing(8); + + // 显示网格 + QCheckBox* gridCheck = new QCheckBox(QStringLiteral(u"显示网格线"), &dlg); + gridCheck->setChecked(_plot->axisEnabled(QwtPlot::xBottom) && _plot->axisEnabled(QwtPlot::yLeft)); + layout->addWidget(gridCheck); + + // 显示图例 + QCheckBox* legendCheck = new QCheckBox(QStringLiteral(u"显示图例"), &dlg); + legendCheck->setChecked(_plot->legend() != nullptr); + layout->addWidget(legendCheck); + + layout->addSpacing(8); + + // 曲线颜色 + QLabel* curveColorLabel = new QLabel(QStringLiteral(u"曲线颜色:"), &dlg); + layout->addWidget(curveColorLabel); + + // 获取所有曲线 + QList curveList; + QMap curveColorMap; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + curveList.append(curve); + curveColorMap.insert(curve, curve->pen().color()); + } + } + + // 为每条曲线创建颜色按钮 + QList colorBtnList; + for (int i = 0; i < curveList.size(); ++i) { + QwtPlotCurve* curve = curveList.at(i); + QHBoxLayout* btnLayout = new QHBoxLayout(); + QLabel* nameLabel = new QLabel(QStringLiteral(u"曲线%1").arg(i + 1), &dlg); + QPushButton* colorBtn = new QPushButton(&dlg); + colorBtn->setFixedSize(60, 24); + QColor color = curveColorMap.value(curve); + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(color.name())); + btnLayout->addWidget(nameLabel); + btnLayout->addWidget(colorBtn); + btnLayout->addStretch(); + layout->addLayout(btnLayout); + colorBtnList.append(colorBtn); + + connect(colorBtn, &QPushButton::clicked, [&dlg, curve, &curveColorMap, colorBtn]() { + QColor current = curveColorMap.value(curve); + QColor selected = QColorDialog::getColor(current, &dlg, QStringLiteral(u"选择曲线颜色")); + if (selected.isValid()) { + curveColorMap[curve] = selected; + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(selected.name())); + } + }); + } + + layout->addStretch(); + + // 确定取消按钮 + QDialogButtonBox* buttonBox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dlg); + layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + + if (dlg.exec() == QDialog::Accepted) { + // 应用X轴标题 + _plot->setAxisTitle(QwtPlot::xBottom, xTitleEdit->text()); + // 应用Y轴标题 + _plot->setAxisTitle(QwtPlot::yLeft, yTitleEdit->text()); + // 应用网格显示 + _plot->enableAxis(QwtPlot::xBottom, gridCheck->isChecked()); + _plot->enableAxis(QwtPlot::yLeft, gridCheck->isChecked()); + // 应用图例显示 + if (legendCheck->isChecked() && _plot->legend() == nullptr) { + QwtLegend* legend = new QwtLegend(); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); + _plot->insertLegend(legend, QwtPlot::RightLegend); + // 给未命名曲线设置默认标题,保证图例显示名称 + int idx = 1; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + if (curve->title().isEmpty()) { + curve->setTitle(QStringLiteral(u"曲线%1").arg(idx)); + } + ++idx; + } + } + } else if (!legendCheck->isChecked() && _plot->legend() != nullptr) { + _plot->insertLegend(nullptr); + } + // 应用曲线颜色 + for (QwtPlotCurve* curve : curveList) { + QPen pen = curve->pen(); + pen.setColor(curveColorMap.value(curve)); + curve->setPen(pen); + } + // 刷新绘图 + _plot->replot(); + } } void NuclideAnalysisView::onActionPlotSelect() diff --git a/src/ParticleCountPlotView/ParticleCountPlotView.cpp b/src/ParticleCountPlotView/ParticleCountPlotView.cpp index dbc8d87..a8e325b 100644 --- a/src/ParticleCountPlotView/ParticleCountPlotView.cpp +++ b/src/ParticleCountPlotView/ParticleCountPlotView.cpp @@ -28,6 +28,14 @@ #include "BatchEnergyScaleDialog.h" #include "FindPeaksResultDialog.h" +#include +#include +#include +#include +#include +#include +#include +//道址计数谱 ParticleCountPlotView::ParticleCountPlotView(QWidget* parent) : MeasureAnalysisView { parent } { @@ -120,7 +128,7 @@ void ParticleCountPlotView::updateView(const QMap& data_files // 清除现有所有曲线 for (QwtPlotCurve* curve : this->_plot->GetCurveList()) { curve->detach(); - delete curve; + // delete curve; } _plot->clearCurve(); @@ -230,33 +238,74 @@ void ParticleCountPlotView::setupEnergyScaleDlg() void ParticleCountPlotView::loadDataFromFile(const QString& data_name, const QString& filename) { - std::string address_str = QString(QStringLiteral(u"道址")).toStdString(); - std::string count_str = QString(QStringLiteral(u"计数")).toStdString(); + // std::string address_str = QString(QStringLiteral(u"道址")).toStdString(); + // std::string count_str = QString(QStringLiteral(u"计数")).toStdString(); - io::CSVReader< - 2, - io::trim_chars<' ', '\t'>, - io::double_quote_escape<',', '"'>, - io::throw_on_overflow, - io::empty_line_comment> - reader(QStrToSysPath(filename)); - reader.read_header(io::ignore_extra_column, address_str, count_str); + // io::CSVReader< + // 2, + // io::trim_chars<' ', '\t'>, + // io::double_quote_escape<',', '"'>, + // io::throw_on_overflow, + // io::empty_line_comment + // > + // reader(QStrToSysPath(filename)); + // reader.read_header(io::ignore_extra_column, address_str, count_str); + // int address; + // unsigned long long particle_count; + // QVector x, y; + // while (reader.read_row(address, particle_count)) { + // x.push_back(address); + // y.push_back(particle_count); + // if ( _x_max < address ) + // _x_max = address; + // if ( _y_max < particle_count ) + // _y_max = particle_count; + // } + // _plot->SetAxisInitRange(QwtPlot::xBottom, 0.0f, _x_max); + // _plot->SetAxisInitRange(QwtPlot::yLeft, 0.0f, _y_max); + + // // 绘制曲线 + // QwtPlotCurve* curve = new QwtPlotCurve(data_name); + // curve->setSamples(x, y); + // _plot->AddCurve(curve); + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + LOG_WARN(QStringLiteral(u"无法打开文件: %1").arg(filename)); + return; + } + + QTextStream in(&file); + in.setCodec("UTF-8"); + + // 跳过表头(道址,计数) + QString header = in.readLine(); + Q_UNUSED(header); + + QVector x, y; int address; unsigned long long particle_count; - QVector x, y; - while (reader.read_row(address, particle_count)) { - x.push_back(address); - y.push_back(particle_count); - if ( _x_max < address ) - _x_max = address; - if ( _y_max < particle_count ) - _y_max = particle_count; + + while (!in.atEnd()) { + QString line = in.readLine(); + QStringList fields = line.split(','); + if (fields.size() >= 2) { + address = fields[0].trimmed().toInt(); + particle_count = fields[1].trimmed().toULongLong(); + x.push_back(address); + y.push_back(particle_count); + if (_x_max < address) + _x_max = address; + if (_y_max < particle_count) + _y_max = particle_count; + } } + file.close(); + _plot->SetAxisInitRange(QwtPlot::xBottom, 0.0f, _x_max); _plot->SetAxisInitRange(QwtPlot::yLeft, 0.0f, _y_max); - // 绘制曲线 QwtPlotCurve* curve = new QwtPlotCurve(data_name); curve->setSamples(x, y); _plot->AddCurve(curve); @@ -535,5 +584,164 @@ void ParticleCountPlotView::onActionEnergyScale() void ParticleCountPlotView::onActionPlotConfigure() { + QDialog dlg(this); + dlg.setWindowTitle(QStringLiteral(u"图表配置")); + dlg.setMinimumWidth(520); + QVBoxLayout* layout = new QVBoxLayout(&dlg); + // X轴标题 + QHBoxLayout* xTitleLayout = new QHBoxLayout(); + QLabel* xTitleLabel = new QLabel(QStringLiteral(u"X轴标题:"), &dlg); + QLineEdit* xTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::xBottom).text(), &dlg); + xTitleLayout->addWidget(xTitleLabel); + xTitleLayout->addWidget(xTitleEdit); + layout->addLayout(xTitleLayout); + + // Y轴标题 + QHBoxLayout* yTitleLayout = new QHBoxLayout(); + QLabel* yTitleLabel = new QLabel(QStringLiteral(u"Y轴标题:"), &dlg); + QLineEdit* yTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::yLeft).text(), &dlg); + yTitleLayout->addWidget(yTitleLabel); + yTitleLayout->addWidget(yTitleEdit); + layout->addLayout(yTitleLayout); + + layout->addSpacing(8); + + // 显示网格 + QCheckBox* gridCheck = new QCheckBox(QStringLiteral(u"显示网格线"), &dlg); + gridCheck->setChecked(_plot->axisEnabled(QwtPlot::xBottom) && _plot->axisEnabled(QwtPlot::yLeft)); + layout->addWidget(gridCheck); + + // 显示图例 + QCheckBox* legendCheck = new QCheckBox(QStringLiteral(u"显示图例"), &dlg); + legendCheck->setChecked(_plot->legend() != nullptr); + layout->addWidget(legendCheck); + + layout->addSpacing(8); + + // 曲线颜色 + QLabel* curveColorLabel = new QLabel(QStringLiteral(u"曲线颜色:"), &dlg); + layout->addWidget(curveColorLabel); + + // 获取所有曲线并按通道号排序(与图例显示顺序一致) + QList curveList; + QMap curveColorMap; + QStringList chNameList; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + curveList.append(curve); + curveColorMap.insert(curve, curve->pen().color()); + chNameList.append(curve->title().text()); + } + } + + // 按通道号排序 + std::sort(chNameList.begin(), chNameList.end(), [](const QString& a, const QString& b) { + int num_a = ExtractNumberFromString(a); + int num_b = ExtractNumberFromString(b); + return num_a < num_b; + }); + + // 按排序后的顺序重建曲线列表 + QList sortedCurveList; + for (const QString& chName : chNameList) { + for (QwtPlotCurve* curve : curveList) { + if (curve->title().text() == chName) { + sortedCurveList.append(curve); + break; + } + } + } + curveList = sortedCurveList; + + // 多列网格布局:4列,降低对话框高度 + const int numColumns = 4; + QGridLayout* gridLayout = new QGridLayout(); + gridLayout->setHorizontalSpacing(12); + gridLayout->setVerticalSpacing(4); + + QList colorBtnList; + for (int i = 0; i < curveList.size(); ++i) { + QwtPlotCurve* curve = curveList.at(i); + int row = i / numColumns; + int col = i % numColumns; + + // 每行单元:颜色按钮 + 通道名 + QHBoxLayout* cellLayout = new QHBoxLayout(); + cellLayout->setSpacing(6); + + QPushButton* colorBtn = new QPushButton(&dlg); + colorBtn->setFixedSize(36, 20); + QColor color = curveColorMap.value(curve); + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(color.name())); + + QLabel* nameLabel = new QLabel(curve->title().text(), &dlg); + + cellLayout->addWidget(colorBtn); + cellLayout->addWidget(nameLabel); + cellLayout->addStretch(); + + gridLayout->addLayout(cellLayout, row, col); + colorBtnList.append(colorBtn); + + connect(colorBtn, &QPushButton::clicked, [&dlg, curve, &curveColorMap, colorBtn]() { + QColor current = curveColorMap.value(curve); + QColor selected = QColorDialog::getColor(current, &dlg, QStringLiteral(u"选择曲线颜色")); + if (selected.isValid()) { + curveColorMap[curve] = selected; + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(selected.name())); + } + }); + } + + layout->addLayout(gridLayout); + layout->addStretch(); + + // 确定取消按钮 + QDialogButtonBox* buttonBox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dlg); + layout->addWidget(buttonBox); + connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + + if (dlg.exec() == QDialog::Accepted) { + // 应用X轴标题 + _plot->setAxisTitle(QwtPlot::xBottom, xTitleEdit->text()); + // 应用Y轴标题 + _plot->setAxisTitle(QwtPlot::yLeft, yTitleEdit->text()); + // 应用网格显示 + _plot->enableAxis(QwtPlot::xBottom, gridCheck->isChecked()); + _plot->enableAxis(QwtPlot::yLeft, gridCheck->isChecked()); + // 应用图例显示 + if (legendCheck->isChecked() && _plot->legend() == nullptr) { + QwtLegend* legend = new QwtLegend(); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); + _plot->insertLegend(legend, QwtPlot::RightLegend); + int idx = 1; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + if (curve->title().isEmpty()) { + curve->setTitle(QStringLiteral(u"曲线%1").arg(idx)); + } + ++idx; + } + } + } else if (!legendCheck->isChecked() && _plot->legend() != nullptr) { + _plot->insertLegend(nullptr); + } + // 应用曲线颜色 + for (QwtPlotCurve* curve : curveList) { + QPen pen = curve->pen(); + pen.setColor(curveColorMap.value(curve)); + curve->setPen(pen); + } + // 刷新绘图 + _plot->replot(); + } } diff --git a/src/ParticleInjectTimeView/ParticleInjectTimeAnalysisView.cpp b/src/ParticleInjectTimeView/ParticleInjectTimeAnalysisView.cpp index 1c69a25..01496a7 100644 --- a/src/ParticleInjectTimeView/ParticleInjectTimeAnalysisView.cpp +++ b/src/ParticleInjectTimeView/ParticleInjectTimeAnalysisView.cpp @@ -19,6 +19,16 @@ #include #include + +#include +#include +#include +#include +#include +#include +#include +#include +//粒子入射时间分析 ParticleInjectTimeAnalysisView::ParticleInjectTimeAnalysisView(QWidget *parent) : MeasureAnalysisView(parent) { @@ -153,7 +163,133 @@ void ParticleInjectTimeAnalysisView::updateData(bool b_init_update) void ParticleInjectTimeAnalysisView::onActionPlotConfigure() { + QDialog dlg(this); + dlg.setWindowTitle(QStringLiteral(u"图表配置")); + dlg.setMinimumWidth(320); + QVBoxLayout* layout = new QVBoxLayout(&dlg); + + // X轴标题 + QHBoxLayout* xTitleLayout = new QHBoxLayout(); + QLabel* xTitleLabel = new QLabel(QStringLiteral(u"X轴标题:"), &dlg); + QLineEdit* xTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::xBottom).text(), &dlg); + xTitleLayout->addWidget(xTitleLabel); + xTitleLayout->addWidget(xTitleEdit); + layout->addLayout(xTitleLayout); + + // Y轴标题 + QHBoxLayout* yTitleLayout = new QHBoxLayout(); + QLabel* yTitleLabel = new QLabel(QStringLiteral(u"Y轴标题:"), &dlg); + QLineEdit* yTitleEdit = new QLineEdit(_plot->axisTitle(QwtPlot::yLeft).text(), &dlg); + yTitleLayout->addWidget(yTitleLabel); + yTitleLayout->addWidget(yTitleEdit); + layout->addLayout(yTitleLayout); + + layout->addSpacing(8); + + // 显示网格 + QCheckBox* gridCheck = new QCheckBox(QStringLiteral(u"显示网格线"), &dlg); + gridCheck->setChecked(_plot->axisEnabled(QwtPlot::xBottom) && _plot->axisEnabled(QwtPlot::yLeft)); + layout->addWidget(gridCheck); + + // 显示图例 + QCheckBox* legendCheck = new QCheckBox(QStringLiteral(u"显示图例"), &dlg); + legendCheck->setChecked(_plot->legend() != nullptr); + layout->addWidget(legendCheck); + + layout->addSpacing(8); + + // 曲线颜色 + QLabel* curveColorLabel = new QLabel(QStringLiteral(u"曲线颜色:"), &dlg); + layout->addWidget(curveColorLabel); + + // 获取所有曲线 + QList curveList; + QMap curveColorMap; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + curveList.append(curve); + curveColorMap.insert(curve, curve->pen().color()); + } + } + + // 为每条曲线创建颜色按钮 + QList colorBtnList; + for (int i = 0; i < curveList.size(); ++i) { + QwtPlotCurve* curve = curveList.at(i); + QHBoxLayout* btnLayout = new QHBoxLayout(); + QLabel* nameLabel = new QLabel(QStringLiteral(u"曲线%1").arg(i + 1), &dlg); + QPushButton* colorBtn = new QPushButton(&dlg); + colorBtn->setFixedSize(60, 24); + QColor color = curveColorMap.value(curve); + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(color.name())); + btnLayout->addWidget(nameLabel); + btnLayout->addWidget(colorBtn); + btnLayout->addStretch(); + layout->addLayout(btnLayout); + colorBtnList.append(colorBtn); + + connect(colorBtn, &QPushButton::clicked, [&dlg, curve, &curveColorMap, colorBtn]() { + QColor current = curveColorMap.value(curve); + QColor selected = QColorDialog::getColor(current, &dlg, QStringLiteral(u"选择曲线颜色")); + if (selected.isValid()) { + curveColorMap[curve] = selected; + colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #999;") + .arg(selected.name())); + } + }); + } + + layout->addStretch(); + + // 确定取消按钮 + QDialogButtonBox* buttonBox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dlg); + layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + + if (dlg.exec() == QDialog::Accepted) { + // 应用X轴标题 + _plot->setAxisTitle(QwtPlot::xBottom, xTitleEdit->text()); + // 应用Y轴标题 + _plot->setAxisTitle(QwtPlot::yLeft, yTitleEdit->text()); + // 应用网格显示 + _plot->enableAxis(QwtPlot::xBottom, gridCheck->isChecked()); + _plot->enableAxis(QwtPlot::yLeft, gridCheck->isChecked()); + // 应用图例显示 + if (legendCheck->isChecked() && _plot->legend() == nullptr) { + QwtLegend* legend = new QwtLegend(); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); + _plot->insertLegend(legend, QwtPlot::RightLegend); + // 给未命名曲线设置默认标题,保证图例显示名称 + int idx = 1; + const QwtPlotItemList& items = _plot->itemList(); + for (QwtPlotItem* item : items) { + if (item->rtti() == QwtPlotItem::Rtti_PlotCurve) { + QwtPlotCurve* curve = static_cast(item); + if (curve->title().isEmpty()) { + curve->setTitle(QStringLiteral(u"曲线%1").arg(idx)); + } + ++idx; + } + } + } else if (!legendCheck->isChecked() && _plot->legend() != nullptr) { + _plot->insertLegend(nullptr); + } + // 应用曲线颜色 + for (QwtPlotCurve* curve : curveList) { + QPen pen = curve->pen(); + pen.setColor(curveColorMap.value(curve)); + curve->setPen(pen); + } + // 刷新绘图 + _plot->replot(); + } } void ParticleInjectTimeAnalysisView::showEvent(QShowEvent *e) diff --git a/src/ParticleTimeDifferenceView/ParticleTimeDifferenceView.cpp b/src/ParticleTimeDifferenceView/ParticleTimeDifferenceView.cpp index 14d7aaa..f2b124a 100644 --- a/src/ParticleTimeDifferenceView/ParticleTimeDifferenceView.cpp +++ b/src/ParticleTimeDifferenceView/ParticleTimeDifferenceView.cpp @@ -175,6 +175,7 @@ void ParticleTimeDifferenceView::loadDataFromFile() void ParticleTimeDifferenceView::onActionPlotConfigure() { + } void ParticleTimeDifferenceView::showEvent(QShowEvent *e)