From 86dbbe706d4521f6015939330bba5d028eeed9e3 Mon Sep 17 00:00:00 2001 From: yuntang <123@qq.com> Date: Wed, 26 Mar 2025 17:30:02 +0800 Subject: [PATCH] 2025-03-26T17:30:00 --- src/AnalysTool.pro | 2 + src/data/filemanager.h | 2 + src/data/peakpoint.cpp | 232 +++++++++++++++++++++++++++++++++++++ src/data/peakpoint.h | 40 +++++++ src/ui/centralwidget.cpp | 98 ++++++++++------ src/ui/centralwidget.h | 7 +- src/ui/draglinehandler.cpp | 6 +- 7 files changed, 347 insertions(+), 40 deletions(-) create mode 100644 src/data/peakpoint.cpp create mode 100644 src/data/peakpoint.h diff --git a/src/AnalysTool.pro b/src/AnalysTool.pro index 2fe5441..0031704 100644 --- a/src/AnalysTool.pro +++ b/src/AnalysTool.pro @@ -17,6 +17,7 @@ DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ data/filemanager.cpp \ + data/peakpoint.cpp \ global.cpp \ rightwidget.cpp \ ui/analysissettingform.cpp \ @@ -33,6 +34,7 @@ SOURCES += \ HEADERS += \ data/filemanager.h \ + data/peakpoint.h \ defines.h \ global.h \ rightwidget.h \ diff --git a/src/data/filemanager.h b/src/data/filemanager.h index 61b4cf7..2e2c430 100644 --- a/src/data/filemanager.h +++ b/src/data/filemanager.h @@ -17,6 +17,7 @@ struct ExperimentData { float sampleTemp; float dsc; }; + struct ExpeInfo { QString sampleName; @@ -24,6 +25,7 @@ struct ExpeInfo QString date; QString userName; }; + extern ExpeInfo _expeInfo; extern QFile _expeFile; diff --git a/src/data/peakpoint.cpp b/src/data/peakpoint.cpp new file mode 100644 index 0000000..63dbf54 --- /dev/null +++ b/src/data/peakpoint.cpp @@ -0,0 +1,232 @@ +#include + +#include "peakpoint.h" + +QVectorPeakPoint:: _dataVtr; +double PeakPoint::_leftPointX,PeakPoint::_rightPointX; +QPointF PeakPoint::_peakPoint; + QPointF PeakPoint::_leftSelectedPoint,PeakPoint::_rightSelectedPoint; + +void PeakPoint::setExperimentData(const QVector &dataVtr) +{ + _dataVtr = dataVtr; +} + +QPointF PeakPoint::findPeakPoint(){ + int n = _dataVtr.size(); + + if (n < 3) { + return QPointF(); // 至少需要三个点才能找到波峰 + } + + QPointF uniquePeak; + // 初始化为 double 类型能表示的最小负数,确保能处理负数 y 值 + double maxY = 0.0; + bool findFlag = false; + + // 直接在遍历过程中找出最大波峰 + for (int i = 1; i < n - 1; ++i) { + const double currentX = _dataVtr.at(i).sampleTemp; + const double currentY = _dataVtr.at(i).dsc; + + if(currentX < _leftPointX){ + continue; + } + if(currentX > _rightPointX){ + break; + } + + const double preY = _dataVtr.at(i - 1).dsc; + const double lastY = _dataVtr.at(i + 1).dsc; + + findFlag = false; + if(currentY >= preY && currentY >= lastY){ + findFlag = true; + }else if(currentY <= preY && currentY <= lastY){ + findFlag = true; + } + + if(findFlag){ + double absY = std::abs(currentY); + if(absY >= maxY){ + maxY = absY; + uniquePeak = QPointF(currentX,currentY); + } + } + } + + _peakPoint = uniquePeak; + + return uniquePeak; +} + + +QPair PeakPoint::calculateMaxDiffPointDetail( + const PeakPoint::MaxDiffPointDetailType type) +{ +#if 1 + double maxDiff = std::numeric_limits::min(); + QPointF currentPoint,lastPoint; + + for (int i = 0; i < _dataVtr.size() - 1; ++i) { + const double currentX = _dataVtr.at(i).sampleTemp; + const double currentY = _dataVtr.at(i).dsc; + + if(type == MaxDiffPointDetailType::Left){ + if(currentX <= _leftPointX){ + continue; + } + if(currentX >= _peakPoint.x()){ + break; + } + }else{ + if(currentX <= _peakPoint.x()){ + continue; + } + if(currentX >= _rightPointX){ + break; + } + } + // + const double lastX = _dataVtr.at(i + 1).sampleTemp; + const double lastY = _dataVtr.at(i + 1).dsc; + double diff = std::abs(currentY - lastY); + if(diff > maxDiff){ + maxDiff = diff; + + currentPoint.setX(currentX); + currentPoint.setY(currentY); + lastPoint.setX(lastX); + lastPoint.setY(lastY); + } + } +#endif + + return qMakePair(currentPoint,lastPoint); +} + + +QPair PeakPoint::calculateMaxDiffPointLeft() +{ + return calculateMaxDiffPointDetail(MaxDiffPointDetailType::Left); +} + +QPair PeakPoint::calculateMaxDiffPointRight() +{ + return calculateMaxDiffPointDetail(MaxDiffPointDetailType::Right); +} + +double PeakPoint::findClosestY(double targetX) +{ + double minDiff = std::numeric_limits::max(); + double closestY = 0.0; + + for(FileManager::ExperimentData &ed:_dataVtr){ + // 计算当前 x 与目标 x 的差值的绝对值 + double diff = std::abs(ed.sampleTemp - targetX); + // 更新最小差值和对应的 y 值 + if (diff < minDiff) { + minDiff = diff; + closestY = ed.dsc; + } + } + + return closestY; +} + +QPointF PeakPoint::findClosestPointByX(double x) { + int left = 0; + int right = _dataVtr.size() - 1; + + QPointF targetPoint; + targetPoint.setX(_dataVtr.value(0).sampleTemp); + targetPoint.setY(_dataVtr.value(0).dsc); + + while (left <= right) { + int mid = left + (right - left) / 2; + FileManager::ExperimentData& ed = _dataVtr[mid]; + + if (std::abs(ed.sampleTemp - x) < std::abs(targetPoint.x() - x)) { + targetPoint.setX(ed.sampleTemp); + targetPoint.setY(ed.dsc); + } + + if(ed.sampleTemp < x){ + left = mid + 1; + } else { + right = mid - 1; + } + } + + return targetPoint; +} + +void PeakPoint::setRegionPointX(const double left, const double right) +{ + _leftPointX = left; + _rightPointX = right; + + _leftSelectedPoint = findClosestPointByX(_leftPointX); + _rightSelectedPoint = findClosestPointByX(_rightPointX); + + qDebug()<<"left,right point:"<<_leftSelectedPoint + <<","<<_rightSelectedPoint; +} + +QString PeakPoint::textFormat(const double enthalpyValue, + const double peakValue, + const double startPoint, + const double endPoint) +{ + return QString("峰的综合信息:\n" + "焓值:%1 J/g \n" + "峰值:%2℃ \n" + "起始点:%3℃ \n" + "终止点:%4℃" + ).arg(QString::number(enthalpyValue, 'f', 2)) + .arg(QString::number(peakValue, 'f', 2)) + .arg(QString::number(startPoint, 'f', 2)) + .arg(QString::number(endPoint, 'f', 2)); +} + +// 计算两条直线的交点 +QPointF PeakPoint::calculateIntersection(const QPointF p1,const QPointF p2, + const QPointF p3, const QPointF p4){ + // 直线的一般式: A1x + B1y + C1 = 0 和 A2x + B2y + C2 = 0 + double A1 = p2.y() - p1.y(); + double B1 = p1.x() - p2.x(); + double C1 = A1 * p1.x() + B1 * p1.y(); + + double A2 = p4.y() - p3.y(); + double B2 = p3.x() - p4.x(); + double C2 = A2 * p3.x() + B2 * p3.y(); + + double determinant = A1 * B2 - A2 * B1; + + if (determinant == 0) { + // 两条直线平行或重合,无交点或无限交点 + return {0, 0}; + } else { + double x = (B2 * C1 - B1 * C2) / determinant; + double y = (A1 * C2 - A2 * C1) / determinant; + return {x, y}; + } +} + +double PeakPoint::calculateArea(const std::vector &points) { + double integral = 0.0; + size_t n = points.size(); + + if (n < 2) { + return integral; // 至少需要两个点才能计算积分 + } + + for (size_t i = 1; i < n; ++i) { + double dx = points[i].x() - points[i - 1].x(); + double avg_y = (points[i].y() + points[i - 1].y()) / 2.0; + integral += dx * avg_y; + } + + return integral; +} + diff --git a/src/data/peakpoint.h b/src/data/peakpoint.h new file mode 100644 index 0000000..3cf22c8 --- /dev/null +++ b/src/data/peakpoint.h @@ -0,0 +1,40 @@ +#ifndef PEAKPOINT_H +#define PEAKPOINT_H + +#include + +#include "filemanager.h" + + +namespace PeakPoint{ +void setExperimentData(const QVector&); +void setRegionPointX(const double,const double); +double findClosestY(double targetX); +QPointF findPeakPoint(); +QString textFormat(const double enthalpyValue, + const double peakValue, + const double startPoint, + const double endPoint); + + +//private +QPair calculateMaxDiffPointLeft(); +QPair calculateMaxDiffPointRight(); +enum MaxDiffPointDetailType{ + Left, + Right +}; +QPair calculateMaxDiffPointDetail(const MaxDiffPointDetailType type); + +QPointF calculateIntersection(const QPointF p1,const QPointF p2, + const QPointF p3, const QPointF p4); +double calculateArea(const std::vector &points); +QPointF findClosestPointByX(double target); + +extern QVector _dataVtr; +extern double _leftPointX,_rightPointX; +extern QPointF _peakPoint; +extern QPointF _leftSelectedPoint,_rightSelectedPoint; +} + +#endif // PEAKPOINT_H diff --git a/src/ui/centralwidget.cpp b/src/ui/centralwidget.cpp index 32d5856..c1e654f 100644 --- a/src/ui/centralwidget.cpp +++ b/src/ui/centralwidget.cpp @@ -6,6 +6,7 @@ #include "centralwidget.h" #include "filemanager.h" +#include "peakpoint.h" CentralWidget::CentralWidget(QWidget *parent) : QWidget(parent), @@ -132,10 +133,11 @@ void CentralWidget::slotRecvAnalysisFileName(const QString &fileName) { qDebug() << "slotRecvAnalysisFileName" << fileName; - QVector dataVtr; - FileManager::readExperimentFile(fileName, dataVtr); + // QVector dataVtr; + _dataVtr.clear(); + FileManager::readExperimentFile(fileName, _dataVtr); - if (dataVtr.size() < 0) + if (_dataVtr.size() < 0) { return; } @@ -148,6 +150,8 @@ void CentralWidget::slotRecvAnalysisFileName(const QString &fileName) _customPlot->addGraph(); #endif + PeakPoint::setExperimentData(_dataVtr); + // 设置坐标轴标签 _customPlot->yAxis->setLabel("DSC/mW"); _customPlot->xAxis->setLabel("Temp/℃"); @@ -156,7 +160,7 @@ void CentralWidget::slotRecvAnalysisFileName(const QString &fileName) _customPlot->yAxis->setRange(-20, 20); QVector xVtr, yVtr; - for (FileManager::ExperimentData &ed : dataVtr) + for (FileManager::ExperimentData &ed : _dataVtr) { xVtr.push_back(ed.sampleTemp); yVtr.push_back(ed.dsc); @@ -178,8 +182,7 @@ void CentralWidget::slotAnalysisSettingApply() switch (_nanlysisMode) { case AnalysisMode::NumericalLabel: { - double x = _line1->point1->coords().x(); - drawText(x,"11111"); + drawText(_line1->point1->coords(),"11111"); break; } case AnalysisMode::PeakSynthesisAnalysis: @@ -188,8 +191,28 @@ void CentralWidget::slotAnalysisSettingApply() double x2 = _line2->point1->coords().x(); fillGraph(x1,x2); - // - break; + + PeakPoint::setRegionPointX(x1,x2); + + QPointF point = PeakPoint::findPeakPoint(); + qDebug()<<"peak point:"<replot(); } -void CentralWidget::drawText(const double x, const QString text) +void CentralWidget::drawText(const QPointF point, const QString text) { + // double y = PeakPoint::findClosestY(x); + // 创建标注文字(QCPItemText) + QCPItemText *textLabel = new QCPItemText(_customPlot); + textLabel->setPositionAlignment(Qt::AlignBottom | Qt::AlignHCenter); // 对齐方式 + textLabel->position->setType(QCPItemPosition::ptPlotCoords); // 使用数据坐标 + textLabel->position->setCoords(point.x() + 20, point.y()); // 设置文本位置在指定点上方 + + textLabel->setText(text); // 设置文本内容 + // textLabel->setFont(QFont("Arial", 10)); + textLabel->setPen(QPen(Qt::lightGray)); // 文字边框 + textLabel->setBrush(Qt::white); // 文字背景 + + // 创建指向点的线段(QCPItemLine) + QCPItemLine *arrow = new QCPItemLine(_customPlot); + arrow->start->setParentAnchor(textLabel->left); // 线段起点绑定到标注文字底部 + arrow->end->setCoords(point.x(),point.y()); // 线段终点设置为指定的点 + arrow->setHead(QCPLineEnding::esSpikeArrow); // 添加箭头 + arrow->setPen(QPen(Qt::red, 1)); + + // 重绘图表以显示文本标签和箭头 + _customPlot->replot(); + #if 0 // 创建标注文字(QCPItemText) QCPItemText *textLabel = new QCPItemText(_customPlot); @@ -311,32 +356,13 @@ void CentralWidget::drawText(const double x, const QString text) } #endif - double y = findClosestY(x); - // 创建标注文字(QCPItemText) - QCPItemText *textLabel = new QCPItemText(_customPlot); - textLabel->setPositionAlignment(Qt::AlignBottom | Qt::AlignHCenter); // 对齐方式 - textLabel->position->setType(QCPItemPosition::ptPlotCoords); // 使用数据坐标 - textLabel->position->setCoords(x, y + 10); // 设置文本位置在指定点上方 - - textLabel->setText(text); // 设置文本内容 - textLabel->setFont(QFont("Arial", 10)); - textLabel->setPen(QPen(Qt::black)); // 文字边框 - textLabel->setBrush(Qt::white); // 文字背景 - - // 创建指向点的线段(QCPItemLine) - QCPItemLine *arrow = new QCPItemLine(_customPlot); - arrow->start->setParentAnchor(textLabel->bottom); // 线段起点绑定到标注文字底部 - arrow->end->setCoords(x, y); // 线段终点设置为指定的点 - arrow->setHead(QCPLineEnding::esSpikeArrow); // 添加箭头 - arrow->setPen(QPen(Qt::red, 2)); - - // 重绘图表以显示文本标签和箭头 - _customPlot->replot(); } +#if 0 double CentralWidget::findClosestY(double targetX) { // 获取曲线数据容器 - QSharedPointer> dataContainer = _graph->data(); + QSharedPointer> dataContainer = + _graph->data(); // 初始化最小差值和对应的 y 值 double minDiff = std::numeric_limits::max(); double closestY = 0.0; @@ -358,12 +384,14 @@ double CentralWidget::findClosestY(double targetX) { return closestY; } +#endif void CentralWidget::fillGraph(const double x1, const double x2) { - //未寻找x1\x2之间最大值。 - double y1 = findClosestY(x1); - double y2 = findClosestY(x2); + //todo.未寻找x1\x2之间最大值。 + + double y1 = PeakPoint::findClosestY(x1); + double y2 = PeakPoint::findClosestY(x2); QVector xVtr,yVtr; xVtr.push_back(x1); @@ -386,6 +414,7 @@ void CentralWidget::fillGraph(const double x1, const double x2) _customPlot->replot(); } + void CentralWidget::clearAllData() { @@ -418,6 +447,7 @@ void CentralWidget::clearAllData() } _customPlot->replot(); #endif + #if 1 _line1->setVisible(false); _line2->setVisible(false); diff --git a/src/ui/centralwidget.h b/src/ui/centralwidget.h index a6decc9..7643140 100644 --- a/src/ui/centralwidget.h +++ b/src/ui/centralwidget.h @@ -8,6 +8,7 @@ #include "protocol.h" #include "global.h" #include "draglinehandler.h" +#include "filemanager.h" class CentralWidget:public QWidget { @@ -43,16 +44,16 @@ protected: void contextMenuEvent(QContextMenuEvent *event); private: void setEventHandlerEnable(const bool); - void drawText(const double,const QString); - double findClosestY(double targetX); + void drawText(const QPointF,const QString); +// double findClosestY(double targetX); void fillGraph(const double x1,const double x2); - void findPeakPoint(); private: AnalysisMode _nanlysisMode; QCustomPlot *_customPlot; QCPGraph* _graph; DragLineHandler* _eventHandler; QCPItemStraightLine *_line1,*_line2; + QVector _dataVtr; }; #endif // CENTRALWIDGET_H diff --git a/src/ui/draglinehandler.cpp b/src/ui/draglinehandler.cpp index 6d5b3cf..6e2acbb 100644 --- a/src/ui/draglinehandler.cpp +++ b/src/ui/draglinehandler.cpp @@ -16,7 +16,7 @@ DragLineHandler::~DragLineHandler() bool DragLineHandler::eventFilter(QObject *obj, QEvent *event) { if(!_enableFlag){ - qDebug()<<"_enableFlag false."; +// qDebug()<<"_enableFlag false."; #if 0 if(mPlot){ mPlot->setCursor(Qt::ArrowCursor); @@ -29,11 +29,11 @@ bool DragLineHandler::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::MouseMove) { - qDebug()<<"mouse move..."; +// qDebug()<<"mouse move..."; QMouseEvent *mouseEvent = static_cast(event); QPoint mousePos = mouseEvent->pos(); - qDebug()<<"x:"<