DSCAnalysisTool/src/ui/centralwidget.cpp
2025-04-10 17:33:38 +08:00

646 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <QHBoxLayout>
#include <QRandomGenerator>
#include <cmath>
#include <limits>
#include <qcustomplot.h>
#include "centralwidget.h"
#include "filemanager.h"
#include "pointcalculate.h"
#include "logger.h"
CentralWidget::CentralWidget(QWidget *parent)
: QWidget(parent),
_customPlot(new QCustomPlot(this))
,_analysisMode(AnalysisMode::None)
{
setMouseTracking(true);
setStyleSheet("background-color: lightgray;");
resize(888, 666);
QHBoxLayout *layout = new QHBoxLayout();
layout->setMargin(0);
layout->addWidget(_customPlot);
this->setLayout(layout);
// 创建两条竖线
_line1 = new QCPItemStraightLine(_customPlot);
_line1->point1->setCoords(20, _customPlot->yAxis->range().lower);
_line1->point2->setCoords(20, _customPlot->yAxis->range().upper);
_line1->setPen(QPen(Qt::red));
_line1->setSelectable(true);
_line2 = new QCPItemStraightLine(_customPlot);
_line2->point1->setCoords(40, _customPlot->yAxis->range().lower);
_line2->point2->setCoords(40, _customPlot->yAxis->range().upper);
_line2->setPen(QPen(Qt::red));
_line2->setSelectable(true);
// 安装事件过滤器
// _graph = _customPlot->addGraph(0);
// _eventHandler = new EventHandler(_customPlot, _line1, _line2, _graph, nullptr);
_eventHandler = new EventHandler(_customPlot, _line1, _line2, nullptr);
_eventHandler->setEnable(true);
_customPlot->installEventFilter(_eventHandler);
_customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
connect(_eventHandler,&EventHandler::sigSendLineXCoord,
this,&CentralWidget::sigSendLineXCoord);
// connect(_customPlot, &QCustomPlot::selectionChangedByUser,
// this, &CentralWidget::slotSelectionChanged);
setEventHandlerEnable(false);
//
#if 0
// init data
QVector<double> x(101), y(101);
for (int i = 0; i < 101; i++) {
x[i] = i / 50.0 - 1;//设置x的范围为-1~1
y[i] = x[i] * x[i];
}
_customPlot->graph(0)->setData(x, y);
//设置坐标轴标签
_customPlot->xAxis->setLabel("x coordinates");
_customPlot->yAxis->setLabel("y coordinates");
//设置坐标轴范围,以便我们可以看到全部数据
_customPlot->xAxis->setRange(0, 1);
_customPlot->yAxis->setRange(0, 100);
_customPlot->replot();
#endif
// startTimer(1000);
}
CentralWidget::~CentralWidget()
{
FileManager::close();
}
void CentralWidget::setAnalysisMode(const CentralWidget::AnalysisMode mode)
{
_analysisMode = mode;
switch (mode)
{
case AnalysisMode::NumericalLabel:
case AnalysisMode::StartPoint:
case AnalysisMode::StopPoint:
case AnalysisMode::PeakSynthesisAnalysis:
case AnalysisMode::GlassTransition:
setEventHandlerEnable(true);
break;
default:
break;
}
}
void CentralWidget::slotModeModify(const Global::Mode mode)
{
if (Global::Mode::ExperimentStart == mode)
{
#if 0
if (_customPlot->graphCount() > 0 && _currentGraph)
{
// 清除第一个图表上的数据
_currentGraph->setData(QVector<double>(), QVector<double>());
}
#endif
// 创建画布,设置画布上的点数据
// _graph = _customPlot->addGraph();
// 设置坐标轴标签
_customPlot->xAxis->setLabel("Temp/℃");
_customPlot->yAxis->setLabel("DSC/mW");
// 设置坐标轴范围,以便我们可以看到全部数据
_customPlot->xAxis->setRange(0, 400);
_customPlot->yAxis->setRange(-20, 20);
}
else if (Global::Mode::Analysis == mode)
{
qDebug() << "file close...";
FileManager::close();
}
}
void CentralWidget::slotRecvCommonData(const CommonData &cd)
{
qDebug() << "slotRevCommonData";
_customPlot->graph(0)->addData(cd.sample_temp, cd.dsc); // 添加数据到曲线
_customPlot->replot();
_customPlot->rescaleAxes();
//
FileManager::writeExperimentFile(cd);
}
void CentralWidget::slotRecvAnalysisFileName(const QString &fileName)
{
qDebug() << "slotRecvAnalysisFileName" << fileName;
//
// clearData(ClearDataMode::All);
//
_dataVtr.clear();
FileManager::readExperimentFile(fileName, _dataVtr);
if (_dataVtr.size() < 0)
{
return;
}
//
// _customPlot->xAxis->setRange(0, 500);
// _customPlot->yAxis->setRange(-20, 20);
PointCalculate::setExperimentData(_dataVtr);
QPair<QPointF,QPointF>startEndPointPair = PointCalculate::getStartAndEndPoint();
QPointF endPoint = startEndPointPair.second;
_customPlot->xAxis->setRange(0, endPoint.x() / 3 * 4);
QPointF peakPoint = PointCalculate::getPeakPoint();
float absY = std::abs(peakPoint.y());
_customPlot->yAxis->setRange(- absY * 2,absY *2);
// 设置坐标轴标签
_customPlot->yAxis->setLabel("DSC/mW");
_customPlot->xAxis->setLabel("Temp/℃");
QVector<double> tVtr,xVtr, yVtr;
int index = 0;
for (FileManager::ExperimentData &ed : _dataVtr)
{
tVtr.push_back(index++);
xVtr.push_back(ed.sampleTemp);
yVtr.push_back(ed.dsc);
}
_currentCurve = new QCPCurve(_customPlot->xAxis, _customPlot->yAxis);
_currentCurve->setData(tVtr, xVtr, yVtr);
// _currentCurve->setPen(QPen(Qt::red)); // 设置线条颜色为红色
// _currentCurve->setBrush(QBrush(QColor(255, 0, 0, 20))); // 设置填充颜色并带有透明度
_currentCurve->setSelectable(QCP::stWhole); // 设置曲线可选
// _currentCurve->setSelectable(QCP::stPlottable); // 设置曲线可选
Global::instance()->_curveDataVtr.push_back(
qMakePair<QCPCurve*,QVector<FileManager::ExperimentData>>(
_currentCurve,_dataVtr));
// 清除第一个图表上的数据
#if 0
if (_customPlot->graphCount() > 0 && _graph)
{
// 清除第一个图表上的数据
_graph->setData(QVector<double>(), QVector<double>());
}
#endif
#if 0
_currentGraph = _customPlot->addGraph();
_graphVtr.push_back(_currentGraph);
_currentGraph->addData(xVtr, yVtr);
#endif
_customPlot->replot();
}
void CentralWidget::slotSelectionChanged()
{
logde<<"selectedPlottables:"<< _customPlot->selectedPlottables().size();
logde<<"selectedGraphs:"<< _customPlot->selectedGraphs().size();
logde<<"selectedItems:"<< _customPlot->selectedItems().size();
logde<<"plottableCount:"<< _customPlot->plottableCount();
return;
for(int i = 0;i < _customPlot->plottableCount(); i++) {
QCPCurve *curve = qobject_cast<QCPCurve *>(_customPlot->plottable(i));
if(curve){
if (curve->selected()) {
logde<<"selected...";
curve->setPen(QPen(Qt::green));
_customPlot->replot();
} else {
curve->setPen(QPen(Qt::blue));
_customPlot->replot();
}
}
logde << "Current pen color:" << curve->pen().color().name().toStdString();
}
// _customPlot->replot();
// _customPlot->update();
#if 0
for (QCPAbstractPlottable *plottable : _customPlot->plottable()) {
QCPCurve *curve = qobject_cast<QCPCurve *>(plottable);
if (curve) {
if (curve->selected()) {
// 曲线被选中,设置为橙色
curve->setPen(QPen(Qt::green));
} else {
#if 0
// 曲线未被选中,恢复默认颜色
if (curve == _curve1) {
curve->setPen(QPen(Qt::blue));
} else if (curve == _curve2) {
curve->setPen(QPen(Qt::red));
}
#endif
}
}
}
#endif
#if 0
// 检查是否有曲线被选中
if (_customPlot->selectedPlottables().size() > 0) {
QCPCurve *selectedCurve = qobject_cast<QCPCurve *>(_customPlot->selectedPlottables().first());
if (selectedCurve) {
selectedCurve->setPen(QPen(Qt::green));
#if 0
// 弹出颜色选择对话框
QColor color = QColorDialog::getColor(selectedCurve->pen().color(), this);
if (color.isValid()) {
// 改变曲线颜色
selectedCurve->setPen(QPen(color));
}
#endif
}else{
}
}
#endif
}
void CentralWidget::slotAnalysisSettingApply()
{
switch (_analysisMode) {
case AnalysisMode::NumericalLabel:
{
QPointF selectPoint = PointCalculate::getClosestPointByX(
_line1->point1->coords().x());
drawText(selectPoint,PointCalculate::textFormatNumbericalLabel(selectPoint));
break;
}
case AnalysisMode::StartPoint:
case AnalysisMode::StopPoint:{
double x1 = _line1->point1->coords().x();
double x2 = _line2->point1->coords().x();
PointCalculate::setRegionPointX(x1,x2);
QPair<QPointF, QPointF> startEndPointPair =
PointCalculate::calculateStartAndEndPoint();
if(_analysisMode == AnalysisMode::StartPoint){
drawText(startEndPointPair.first,
PointCalculate::textFormatStartPoint(startEndPointPair.first));
}else{
drawText(startEndPointPair.second,
PointCalculate::textFormatEndPoint(startEndPointPair.second));
}
break;
}
case AnalysisMode::PeakSynthesisAnalysis:
{
double x1 = _line1->point1->coords().x();
double x2 = _line2->point1->coords().x();
fillGraph(x1,x2);
PointCalculate::setRegionPointX(x1,x2);
//enthalpy
double enthalpyValue = PointCalculate::calculateArea();
//peak
QPointF peakPoint = PointCalculate::getPeakPoint();
//start point and end point
QPair<QPointF, QPointF> startEndPointPair =
PointCalculate::calculateStartAndEndPoint();
logde<<"start,end:"<<startEndPointPair.first.x()<<","
<<startEndPointPair.second.x();
drawText(peakPoint,PointCalculate::textFormatPeakPoint(enthalpyValue,
peakPoint.x(),
startEndPointPair.first.x(),
startEndPointPair.second.x()));
//
break;
}
case AnalysisMode::GlassTransition:
{
glassTransitionHandle();
//
break;
}
default:
break;
}
}
void CentralWidget::slotAnalysisSettingConfirm()
{
slotAnalysisSettingApply();
setAnalysisMode(CentralWidget::AnalysisMode::None);
_line1->setVisible(false);
_line2->setVisible(false);
emit sigRightDockWidgetHide();
}
void CentralWidget::slotAnalysisSettingUndo()
{
clearData(ClearDataMode::Undo);
}
void CentralWidget::slotAnalysisSettingCancel()
{
clearData(ClearDataMode::Undo);
setAnalysisMode(CentralWidget::AnalysisMode::None);
_line1->setVisible(false);
_line2->setVisible(false);
emit sigRightDockWidgetHide();
}
void CentralWidget::slotAnalysisSettingLineXPoint(const int index, const double)
{
}
void CentralWidget::timerEvent(QTimerEvent *event)
{
// key的单位是秒
double key = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000.0;
// 添加数据
// 使用随机数产生一条曲线
// double value0 = realDataI();
double value0 = QRandomGenerator::global()->bounded(10.123);
_customPlot->graph(0)->addData(key, value0); // 添加数据到曲线
// 删除8秒之前的数据。这里的8要和下面设置横坐标宽度的8配合起来
// 才能起到想要的效果,可以调整这两个值,观察显示的效果。
_customPlot->graph(0)->data()->remove(key - 80);
// 自动设定graph(1)曲线y轴的范围如果不设定有可能看不到图像
// 也可以用ui->customPlot->yAxis->setRange(up,low)手动设定y轴范围
_customPlot->graph(0)->rescaleValueAxis(true);
// 这里的8是指横坐标时间宽度为8秒如果想要横坐标显示更多的时间
// 就把8调整为比较大到值比如要显示60秒那就改成60。
// 这时removeDataBefore(key-8)中的8也要改成60否则曲线显示不完整。
_customPlot->yAxis->setRange(0, 20); // 设定x轴的范围
_customPlot->xAxis->setRange(key + 0.25, 80, Qt::AlignRight); // 设定x轴的范围
_customPlot->replot();
}
void CentralWidget::contextMenuEvent(QContextMenuEvent *event)
{
qDebug()<<"left menu...";
QPoint point = event->globalPos();
emit sigContextMenuShow(point);
}
void CentralWidget::glassTransitionHandle()
{
QPointF point1 = PointCalculate::getClosestPointByX(_line1->point1->coords().x());
QPointF point2 = PointCalculate::getClosestPointByX(_line2->point1->coords().x());
logde<<"point1:"<<point1.x()<<","<<point1.y();
logde<<"point2:"<<point2.x()<<","<<point2.y();
#if 0
QCPItemStraightLine *line1 = new QCPItemStraightLine(_customPlot);
line1->point1->setCoords(point1.x() - 5,point1.y());
line1->point2->setCoords(point1.x() + 5,point1.y());
line1->setPen(QPen(Qt::darkRed));
line1->setSelectable(false);
line1->setVisible(true);
QCPItemStraightLine *line2 = new QCPItemStraightLine(_customPlot);
line2->point1->setCoords(point2.x() - 5,point2.y());
line2->point2->setCoords(point2.x() + 5,point2.y());
line2->setPen(QPen(Qt::darkGreen));
line2->setSelectable(false);
line2->setVisible(true);
_customPlot->replot();
#endif
//
PointCalculate::LineStruct lineStrc1,lineStrc2,tangetLineStrc;
lineStrc1.slope = 0.0;
lineStrc1.intercept = point1.y();
lineStrc2.slope = 0.0;
lineStrc2.intercept = point2.y();
QPair<float,float> tangentLinePair = PointCalculate::getCurveInflectionPointTangent(point1.x(),point2.x());
tangetLineStrc.slope = tangentLinePair.first;
tangetLineStrc.intercept = tangentLinePair.second;
QPointF intersection1 = PointCalculate::getIntersectionBySlope(tangetLineStrc,lineStrc1);
QPointF intersection2 = PointCalculate::getIntersectionBySlope(tangetLineStrc,lineStrc2);
// logde<<"intersection1 x:"<<intersection1.x();
// logde<<"intersection2 x:"<<intersection2.x();
//
float averageY = (point1.y() + point2.y()) /2;
logde<<"average Y:"<<averageY;
QPointF averagePoint = PointCalculate::getClosestPointByY(point1.x(),point2.x(),averageY);
// logde<<"average y:"<<averageY;
logde<<"averagePoint x,y:"<<averagePoint.x()<<","
<<averagePoint.y();
logde<<"glass T1:"<<intersection1.x()
<<",Tg:"<<averagePoint.x()
<<",T2:"<<intersection2.x();
QString str = PointCalculate::textFormatGlassTranstion(intersection2.x(),
averagePoint.x(),
intersection1.x());
drawText(averagePoint,str);
#if 0
// 创建一个圆形绘图项
QCPItemEllipse *circle = new QCPItemEllipse(_customPlot);
// _customPlot->addItem(circle);
// 设置圆形的位置和大小
circle->topLeft->setCoords(-5, 5);
circle->bottomRight->setCoords(5, -5);
// 设置圆形的填充和边框颜色
circle->setBrush(QBrush(Qt::cyan));
circle->setPen(QPen(Qt::blue));
#endif
#if 0
QCPItemStraightLine *line3 = new QCPItemStraightLine(_customPlot);
line3->point1->setCoords(averagePoint.x() + 1,averagePoint.y() + 1);
line3->point2->setCoords(averagePoint.x() - 1,averagePoint.y() - 1);
line3->setPen(QPen(Qt::black));
line3->setSelectable(false);
line3->setVisible(true);
_customPlot->replot();
#endif
}
void CentralWidget::setEventHandlerEnable(const bool flag)
{
logde<<"setEventHandlerEnable..."<<flag;
// line visiable func
auto lineVisiableFunc = [&](QCPItemStraightLine* line){
line->setSelected(flag);
line->setVisible(flag);
};
// todo. 当竖线隐藏时,需要设置不可选择模式。
// _eventHandler->setEnable(flag);
#if 1
// move line to suitable position.
double xMax = _customPlot->xAxis->range().upper; // X 轴的最大值
logde<<"xMax:"<<xMax;
_line1->point1->setCoords(xMax / 3,_line1->point1->coords().y());
_line1->point2->setCoords(xMax / 3,_line1->point2->coords().y());
_line2->point1->setCoords(xMax / 3 * 2,_line2->point1->coords().y());
_line2->point2->setCoords(xMax / 3 * 2,_line2->point2->coords().y());
#endif
lineVisiableFunc(_line1);
if(AnalysisMode::NumericalLabel != _analysisMode){
lineVisiableFunc(_line2);
}
//
_customPlot->replot();
}
void CentralWidget::drawText(const QPointF point, const QString text)
{
// 创建标注文字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();
}
void CentralWidget::fillGraph(const double x1, const double x2)
{
//todo.未寻找x1\x2之间最大值。
double y1 = PointCalculate::getClosestPointByX(x1).y();
double y2 = PointCalculate::getClosestPointByX(x2).y();
QVector<double> xVtr,yVtr;
xVtr.push_back(x1);
xVtr.push_back(x2);
yVtr.push_back(y1);
yVtr.push_back(y2);
QCPGraph *mainGraph = _customPlot->addGraph();
mainGraph->setData(xVtr, yVtr);
// 样式配置
mainGraph->setPen(QPen(Qt::red, 1));
#if 0
_currentGraph->setBrush(QBrush(Qt::lightGray));
_currentGraph->setChannelFillGraph(mainGraph);
#endif
// customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));
// _customPlot->graph(1);
_customPlot->replot();
}
void CentralWidget::clearData(const CentralWidget::ClearDataMode mode)
{
if(mode == ClearDataMode::All){
// Clear the data of graph.
#if 0
if (_customPlot->graphCount() > 0 && _currentGraph)
{
_currentGraph->setData(QVector<double>(), QVector<double>());
}
#endif
// Set lines visiable false.
_line1->setVisible(false);
_line2->setVisible(false);
}
#if 0
// Clear filled area.
if(_currentGraph){
_currentGraph->setBrush(QBrush(Qt::transparent));
}
// Clear graph on plot.
for (int i = _customPlot->graphCount() - 1; i >= 0; --i) {
QCPGraph *graph = _customPlot->graph(i);
if (graph != _currentGraph) {
_customPlot->removeGraph(graph);
}
}
#endif
// Delete items.
QList<QCPAbstractItem *> itemsToKeep;
itemsToKeep << _line1 << _line2;
for (int i = _customPlot->itemCount() - 1; i >= 0; --i) {
QCPAbstractItem *item = _customPlot->item(i);
if (!itemsToKeep.contains(item)) {
_customPlot->removeItem(item);
}
}
// 重绘图表以显示更改
_customPlot->replot();
}
void CentralWidget::clearAllData()
{
clearData(ClearDataMode::All);
}