790 lines
27 KiB
C++
Executable File
790 lines
27 KiB
C++
Executable File
#include "DataCollectionWidget.h"
|
||
#include <QHBoxLayout>
|
||
#include <QTreeWidget>
|
||
#include <QHeaderView>
|
||
#include <QPushButton>
|
||
#include <QInputDialog>
|
||
#include <QMessageBox>
|
||
#include <QLabel>
|
||
#include <QGridLayout>
|
||
#include <QFile>
|
||
#include <QFileDialog>
|
||
#include <QtXml/QDomDocument>
|
||
|
||
DataCollectionWidget::DataCollectionWidget(QWidget *parent) : QWidget(parent)
|
||
{
|
||
setupTabDataCollection();
|
||
}
|
||
|
||
DataCollectionWidget::~DataCollectionWidget()
|
||
{
|
||
}
|
||
|
||
void DataCollectionWidget::onSetCurrentTabIndex(int index)
|
||
{
|
||
this->currentTabIndex = index;
|
||
if (currentTabIndex == 3) {
|
||
this->show();
|
||
} else {
|
||
this->hide();
|
||
}
|
||
}
|
||
|
||
// 设置数据采集标签页
|
||
void DataCollectionWidget::setupTabDataCollection()
|
||
{
|
||
// 修改这里:从 dataMonitoringContent 改为 dataCollectionContent
|
||
QHBoxLayout *mainLayout = new QHBoxLayout(this); // 创建水平布局
|
||
|
||
// 使用QSplitter进行左右栏的动态分割
|
||
QSplitter *horizontalSplitter = new QSplitter(Qt::Horizontal); // 创建一个水平方向的QSplitter
|
||
|
||
// 左栏布局
|
||
setupTabDataCollectionLeftPanel(horizontalSplitter);
|
||
|
||
// 右栏布局
|
||
setupTabDataCollectionRightPanel(horizontalSplitter);
|
||
|
||
// 设置QSplitter的初始分割比例为1:4,即左栏占五分之一,右栏占五分之四
|
||
horizontalSplitter->setStretchFactor(0, 1); // 左侧面板的拉伸因子为1
|
||
horizontalSplitter->setStretchFactor(1, 4); // 右侧面板的拉伸因子为4
|
||
|
||
// 将QSplitter添加到主布局中
|
||
mainLayout->addWidget(horizontalSplitter);
|
||
}
|
||
|
||
void DataCollectionWidget::setupTabDataCollectionLeftPanel(QSplitter *horizontalSplitter)
|
||
{
|
||
// 创建垂直布局
|
||
QVBoxLayout *leftLayout = new QVBoxLayout();
|
||
|
||
// 创建树形控件
|
||
QTreeWidget *treeWidget = new QTreeWidget();
|
||
treeWidget->setHeaderLabels({"数据采集项", "操作"});
|
||
treeWidget->setColumnCount(2);
|
||
|
||
// 设置列宽模式和固定宽度
|
||
treeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); // 第一列自动伸展
|
||
treeWidget->header()->setSectionResizeMode(1, QHeaderView::Fixed); // 第二列固定宽度
|
||
treeWidget->setColumnWidth(1, 100); // 设置第二列宽度为100像素
|
||
treeWidget->header()->setStretchLastSection(false); // 禁止最后一列自动伸展
|
||
|
||
// 添加树形控件到布局
|
||
leftLayout->addWidget(treeWidget);
|
||
|
||
// 创建水平布局来放置按钮
|
||
QHBoxLayout *buttonLayout1 = new QHBoxLayout();
|
||
|
||
// 创建添加数据和清空数据按钮
|
||
QPushButton *addDataButton = new QPushButton("添加数据");
|
||
QPushButton *addDataProfileButton = new QPushButton("添加采集模板");
|
||
QPushButton *clearDataButton = new QPushButton("清空数据");
|
||
|
||
// 连接添加数据按钮的点击信号
|
||
connect(addDataButton, &QPushButton::clicked, this, [=]() {
|
||
// 创建输入对话框
|
||
bool ok;
|
||
QString dataName = QInputDialog::getText(this, "添加数据接口",
|
||
"请输入数据接口名称:", QLineEdit::Normal, "", &ok);
|
||
|
||
// 如果用户点击确定且输入不为空
|
||
if (ok && !dataName.isEmpty()) {
|
||
// 创建新的树节点
|
||
QTreeWidgetItem *item = new QTreeWidgetItem(treeWidget);
|
||
item->setText(0, dataName); // 设置第一列为数据名称
|
||
|
||
// 创建删除按钮
|
||
QPushButton *deleteButton = new QPushButton("删除");
|
||
treeWidget->setItemWidget(item, 1, deleteButton); // 设置第二列为删除按钮
|
||
|
||
// 连接删除按钮的点击信号
|
||
connect(deleteButton, &QPushButton::clicked, this, [=]() {
|
||
// 获取按钮所在的item的索引
|
||
int index = treeWidget->indexOfTopLevelItem(item);
|
||
if (index != -1) {
|
||
// 删除该项
|
||
delete treeWidget->takeTopLevelItem(index);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// 连接清空数据按钮的点击信号
|
||
connect(clearDataButton, &QPushButton::clicked, this, [=]() {
|
||
// 弹出确认对话框
|
||
QMessageBox::StandardButton reply;
|
||
reply = QMessageBox::question(this, "确认清空", "确定要清空所有数据吗?",
|
||
QMessageBox::Yes | QMessageBox::No);
|
||
|
||
if (reply == QMessageBox::Yes) {
|
||
treeWidget->clear(); // 清空树形控件
|
||
}
|
||
});
|
||
|
||
// 将按钮添加到水平布局中
|
||
buttonLayout1->addWidget(addDataButton);
|
||
buttonLayout1->addWidget(addDataProfileButton);
|
||
buttonLayout1->addWidget(clearDataButton);
|
||
|
||
// 将水平按钮布局添加到左侧主布局中
|
||
leftLayout->addLayout(buttonLayout1);
|
||
|
||
// 创建按钮布局
|
||
QVBoxLayout *buttonLayout = new QVBoxLayout();
|
||
|
||
// 创建上部分网格布局
|
||
QGridLayout *gridLayout = new QGridLayout();
|
||
|
||
// 第一列 - Labels
|
||
QLabel *freqLabel = new QLabel("采集频率");
|
||
QLabel *durationLabel = new QLabel("采集时长");
|
||
QLabel *pathLabel = new QLabel("保存路径");
|
||
gridLayout->addWidget(freqLabel, 0, 0);
|
||
gridLayout->addWidget(durationLabel, 1, 0);
|
||
gridLayout->addWidget(pathLabel, 2, 0);
|
||
|
||
// 第二列 - LineEdits
|
||
QLineEdit *freqEdit = new QLineEdit();
|
||
freqEdit->setPlaceholderText("请输入采集频率");
|
||
QLineEdit *durationEdit = new QLineEdit();
|
||
durationEdit->setPlaceholderText("请输入采集时长");
|
||
QLineEdit *pathEdit = new QLineEdit();
|
||
pathEdit->setPlaceholderText("请输入保存路径");
|
||
gridLayout->addWidget(freqEdit, 0, 1);
|
||
gridLayout->addWidget(durationEdit, 1, 1);
|
||
gridLayout->addWidget(pathEdit, 2, 1);
|
||
|
||
// 设置输入验证器,只允许输入数字和小数点
|
||
QDoubleValidator *doubleValidator = new QDoubleValidator(0, 999999, 6, this);
|
||
doubleValidator->setNotation(QDoubleValidator::StandardNotation);
|
||
freqEdit->setValidator(doubleValidator);
|
||
durationEdit->setValidator(doubleValidator);
|
||
|
||
// 第三列 - 单位标签和选择按钮
|
||
QLabel *hzLabel = new QLabel("Hz");
|
||
QLabel *sLabel = new QLabel("s");
|
||
QPushButton *selectPathButton = new QPushButton("...");
|
||
gridLayout->addWidget(hzLabel, 0, 2);
|
||
gridLayout->addWidget(sLabel, 1, 2);
|
||
gridLayout->addWidget(selectPathButton, 2, 2);
|
||
|
||
// 连接选择路径按钮的点击信号
|
||
connect(selectPathButton, &QPushButton::clicked, this, [=]() {
|
||
QString dir = QFileDialog::getExistingDirectory(this, "选择保存路径", QDir::currentPath(),
|
||
QFileDialog::ShowDirsOnly
|
||
| QFileDialog::DontResolveSymlinks);
|
||
if (!dir.isEmpty()) {
|
||
pathEdit->setText(dir);
|
||
}
|
||
});
|
||
|
||
connect(addDataProfileButton, &QPushButton::clicked, this, [=]() {
|
||
// 弹出文件选择对话框
|
||
QString fileName = QFileDialog::getOpenFileName(this, "选择采集模板", QDir::currentPath(),
|
||
"采集模板文件 (*.xml)");
|
||
if (!fileName.isEmpty()) {
|
||
|
||
QFile file(fileName);
|
||
file.open(QIODevice::ReadOnly | QIODevice::Text);
|
||
QDomDocument doc;
|
||
if (!doc.setContent(&file)) {
|
||
QMessageBox::warning(this, "错误", "无法解析XML文件");
|
||
return;
|
||
}
|
||
|
||
QDomElement root = doc.documentElement();
|
||
if (root.tagName() != "DataCollection") {
|
||
QMessageBox::warning(this, "错误", "无效的XML格式");
|
||
return;
|
||
}
|
||
|
||
// 读取采集数据名称
|
||
QDomElement collectionData = root.firstChildElement("CollectionData");
|
||
if (!collectionData.isNull()) {
|
||
QDomElement dataName = collectionData.firstChildElement("DataName");
|
||
while (!dataName.isNull()) {
|
||
QString name = dataName.text();
|
||
|
||
// 创建新的树节点
|
||
QTreeWidgetItem *item = new QTreeWidgetItem(treeWidget);
|
||
item->setText(0, name);
|
||
|
||
// 创建删除按钮
|
||
QPushButton *deleteButton = new QPushButton("删除");
|
||
treeWidget->setItemWidget(item, 1, deleteButton);
|
||
|
||
// 连接删除按钮的点击信号
|
||
connect(deleteButton, &QPushButton::clicked, this, [=]() {
|
||
int index = treeWidget->indexOfTopLevelItem(item);
|
||
if (index != -1) {
|
||
delete treeWidget->takeTopLevelItem(index);
|
||
}
|
||
});
|
||
|
||
dataName = dataName.nextSiblingElement("DataName");
|
||
}
|
||
}
|
||
|
||
// 读取并设置采集频率
|
||
QDomElement freqElement = root.firstChildElement("DataFrequency");
|
||
if (!freqElement.isNull()) {
|
||
freqEdit->setText(freqElement.text());
|
||
}
|
||
|
||
// 读取并设置采集时长
|
||
QDomElement timeElement = root.firstChildElement("DataTime");
|
||
if (!timeElement.isNull()) {
|
||
durationEdit->setText(timeElement.text());
|
||
}
|
||
|
||
// 读取并设置保存路径
|
||
QDomElement pathElement = root.firstChildElement("DataPath");
|
||
if (!pathElement.isNull()) {
|
||
pathEdit->setText(pathElement.text());
|
||
}
|
||
|
||
file.close();
|
||
}
|
||
});
|
||
|
||
// 添加网格布局到按钮布局
|
||
buttonLayout->addLayout(gridLayout);
|
||
|
||
// 创建水平布局用于底部按钮
|
||
QHBoxLayout *bottomButtonLayout = new QHBoxLayout();
|
||
|
||
// 创建开始和终止按钮
|
||
QPushButton *startButton = new QPushButton("开始采集");
|
||
startButton->setObjectName("startDataCollectionButton");
|
||
QPushButton *stopButton = new QPushButton("终止采集");
|
||
bottomButtonLayout->addWidget(startButton);
|
||
bottomButtonLayout->addWidget(stopButton);
|
||
|
||
connect(startButton, &QPushButton::clicked, this, [=]() {
|
||
if (treeWidget->topLevelItemCount() == 0) {
|
||
QMessageBox::warning(this, "提示", "请先添加数据接口");
|
||
return;
|
||
}
|
||
QStringList collectionDataNames;
|
||
for (int i = 0; i < treeWidget->topLevelItemCount(); ++i) {
|
||
collectionDataNames << treeWidget->topLevelItem(i)->text(0);
|
||
}
|
||
if (freqEdit->text().isEmpty() || durationEdit->text().isEmpty()
|
||
|| pathEdit->text().isEmpty()) {
|
||
QMessageBox::warning(this, "提示", "请填写采集频率、采集时长和保存路径");
|
||
return;
|
||
}
|
||
double collectionFrequency = freqEdit->text().toDouble();
|
||
double collectionTime = durationEdit->text().toDouble();
|
||
QString collectionPath = pathEdit->text();
|
||
DataCollectionThread *dataCollectionThread = new DataCollectionThread(
|
||
collectionDataNames, collectionFrequency, collectionTime, collectionPath);
|
||
connect(dataCollectionThread, &DataCollectionThread::DataCollectionStatus, this,
|
||
&DataCollectionWidget::onDataCollectionStatus);
|
||
connect(dataCollectionThread, &DataCollectionThread::UpdateDataCollectionTime, this,
|
||
&DataCollectionWidget::onUpdateDataCollectionTime);
|
||
connect(this, &DataCollectionWidget::stopDataCollection, dataCollectionThread,
|
||
&DataCollectionThread::onStopDataCollection);
|
||
dataCollectionThread->start();
|
||
startButton->setEnabled(false);
|
||
});
|
||
|
||
connect(stopButton, &QPushButton::clicked, this, [=]() { emit stopDataCollection(); });
|
||
|
||
// 添加底部按钮布局到主按钮布局
|
||
buttonLayout->addLayout(bottomButtonLayout);
|
||
|
||
// 将按钮布局添加到主布局
|
||
leftLayout->addLayout(buttonLayout);
|
||
|
||
// 创建一个QWidget作为左栏的容器,并设置其布局为leftLayout
|
||
QWidget *leftPanel = new QWidget();
|
||
leftPanel->setLayout(leftLayout);
|
||
|
||
horizontalSplitter->addWidget(leftPanel);
|
||
}
|
||
|
||
void DataCollectionWidget::onUpdateDataCollectionTime(const unsigned int newTime)
|
||
{
|
||
QPushButton *startButton = findChild<QPushButton *>("startDataCollectionButton");
|
||
if (startButton) {
|
||
startButton->setText(QString::number(newTime));
|
||
}
|
||
}
|
||
|
||
void DataCollectionWidget::onDataCollectionStatus(const bool &status, const QString &filePath)
|
||
{
|
||
QPushButton *startButton = findChild<QPushButton *>("startDataCollectionButton");
|
||
if (startButton) {
|
||
if (status) {
|
||
QMessageBox::StandardButton reply = QMessageBox::question(
|
||
this, "提示",
|
||
QString("数据采集完成,文件已保存至:%1\n是否立即打开文件?").arg(filePath),
|
||
QMessageBox::Yes | QMessageBox::No);
|
||
|
||
if (reply == QMessageBox::Yes) {
|
||
openDataCollectionFile(filePath);
|
||
}
|
||
} else {
|
||
QMessageBox::information(this, "提示", "数据采集异常终止");
|
||
}
|
||
startButton->setText("开始采集");
|
||
startButton->setEnabled(true);
|
||
}
|
||
}
|
||
|
||
void DataCollectionWidget::setupTabDataCollectionRightPanel(QSplitter *horizontalSplitter)
|
||
{
|
||
QWidget *rightPanel = new QWidget();
|
||
horizontalSplitter->addWidget(rightPanel);
|
||
|
||
// 创建右侧面板的主布局
|
||
QHBoxLayout *rightLayout = new QHBoxLayout(rightPanel);
|
||
|
||
// 创建左侧的TabWidget
|
||
QTabWidget *leftTabWidget = new QTabWidget();
|
||
leftTabWidget->setTabPosition(QTabWidget::North);
|
||
leftTabWidget->setMovable(true);
|
||
leftTabWidget->setTabsClosable(true); // 设置所有标签页可关闭
|
||
dataPlotIndex = 0;
|
||
|
||
// 创建默认的第一个标签页
|
||
QWidget *firstTab = new QWidget();
|
||
QVBoxLayout *firstTabLayout = new QVBoxLayout(firstTab);
|
||
XNCustomPlot *plot = new XNCustomPlot();
|
||
plot->setObjectName(QString("dataPlot%1").arg(dataPlotIndex));
|
||
setupPlotWidget(plot);
|
||
firstTabLayout->addWidget(plot);
|
||
plot->addGraph();
|
||
plot->graph(0)->setPen(QPen(Qt::green, 2));
|
||
plot->addGraph();
|
||
plot->graph(1)->setPen(QPen(Qt::red, 2));
|
||
firstTabLayout->addWidget(plot);
|
||
int firstTabIndex = leftTabWidget->addTab(firstTab, QString("绘图%1").arg(dataPlotIndex));
|
||
dataPlotIndex++;
|
||
|
||
// 添加"+"标签页
|
||
QWidget *addTab = new QWidget();
|
||
int addTabIndex = leftTabWidget->addTab(addTab, "+");
|
||
|
||
// 移除"+"标签页的关闭按钮
|
||
leftTabWidget->tabBar()->setTabButton(addTabIndex, QTabBar::RightSide, nullptr);
|
||
|
||
// 连接标签切换信号
|
||
connect(leftTabWidget, &QTabWidget::currentChanged, [=](int index) {
|
||
if (index == leftTabWidget->count() - 1) {
|
||
if (leftTabWidget->property("ignoreChange").toBool()) {
|
||
leftTabWidget->setCurrentIndex(0);
|
||
return;
|
||
}
|
||
|
||
// 如果点击了"+"标签(且不是因为删除标签页导致的切换)
|
||
int newIndex = leftTabWidget->count() - 1;
|
||
|
||
// 创建新的标签页
|
||
QWidget *newTab = new QWidget();
|
||
QVBoxLayout *newTabLayout = new QVBoxLayout(newTab);
|
||
XNCustomPlot *newPlot = new XNCustomPlot();
|
||
newPlot->setObjectName(QString("dataPlot%1").arg(dataPlotIndex));
|
||
setupPlotWidget(newPlot);
|
||
newPlot->addGraph();
|
||
newPlot->graph(0)->setPen(QPen(Qt::green, 2));
|
||
newPlot->addGraph();
|
||
newPlot->graph(1)->setPen(QPen(Qt::red, 2));
|
||
newTabLayout->addWidget(newPlot);
|
||
|
||
// 在"+"标签之前插入新标签
|
||
leftTabWidget->insertTab(newIndex, newTab, QString("绘图%1").arg(dataPlotIndex));
|
||
dataPlotIndex++;
|
||
|
||
// 选中新创建的标签
|
||
leftTabWidget->setCurrentIndex(newIndex);
|
||
}
|
||
XNCustomPlot *currentPlot = findChild<XNCustomPlot *>(QString("dataPlot%1").arg(index));
|
||
if (currentPlot) {
|
||
currentPlot->setOpenGl(false);
|
||
currentPlot->replot();
|
||
currentPlot->setOpenGl(true);
|
||
currentPlot->replot();
|
||
}
|
||
});
|
||
|
||
// 连接标签关闭信号
|
||
connect(leftTabWidget, &QTabWidget::tabCloseRequested, [=](int index) {
|
||
// 设置标记,表示即将因为删除标签页而改变当前标签
|
||
leftTabWidget->setProperty("ignoreChange", true);
|
||
|
||
// 删除指定的标签页
|
||
leftTabWidget->removeTab(index);
|
||
|
||
// 如果删除后只剩下"+"标签页,则创建一个新的标签页
|
||
if (leftTabWidget->count() == 1) {
|
||
QWidget *newTab = new QWidget();
|
||
QVBoxLayout *newTabLayout = new QVBoxLayout(newTab);
|
||
XNCustomPlot *newPlot = new XNCustomPlot();
|
||
newPlot->setObjectName("dataPlot1");
|
||
setupPlotWidget(newPlot);
|
||
newPlot->addGraph();
|
||
newPlot->graph(0)->setPen(QPen(Qt::green, 2));
|
||
newPlot->addGraph();
|
||
newPlot->graph(1)->setPen(QPen(Qt::red, 2));
|
||
newTabLayout->addWidget(newPlot);
|
||
|
||
// 在"+"标签之前插入新标签
|
||
leftTabWidget->insertTab(0, newTab, QString("绘图%1").arg(dataPlotIndex));
|
||
leftTabWidget->setCurrentIndex(0);
|
||
dataPlotIndex++;
|
||
}
|
||
|
||
// 清除标记
|
||
leftTabWidget->setProperty("ignoreChange", false);
|
||
});
|
||
|
||
// 创建右侧的垂直布局,用于放置两个TreeWidget
|
||
QVBoxLayout *rightTreeLayout = new QVBoxLayout();
|
||
|
||
// 创建上部TreeWidget
|
||
QTreeWidget *collectionDataTreeWidget = new QTreeWidget();
|
||
collectionDataTreeWidget->setHeaderLabel("采集数据");
|
||
collectionDataTreeWidget->setObjectName("collectionDataTreeWidget");
|
||
|
||
connect(collectionDataTreeWidget, &QTreeWidget::itemClicked, this,
|
||
[=](QTreeWidgetItem *item, int column) {
|
||
QString varName = item->text(0);
|
||
if (collectionData.contains(varName)) {
|
||
int currentIndex = leftTabWidget->currentIndex();
|
||
QCustomPlot *currentPlot =
|
||
findChild<QCustomPlot *>(QString("dataPlot%1").arg(currentIndex));
|
||
if (currentPlot) {
|
||
currentPlot->graph(0)->setData(collectionData["Time"],
|
||
collectionData[varName]);
|
||
currentPlot->graph(0)->setPen(QPen(Qt::green, 2));
|
||
currentPlot->graph(0)->setName(varName);
|
||
// 自动调整坐标轴范围以适应数据
|
||
currentPlot->xAxis->rescale();
|
||
currentPlot->yAxis->rescale();
|
||
// 设置图例
|
||
currentPlot->legend->setVisible(true);
|
||
currentPlot->legend->setFont(QFont("Helvetica", 10));
|
||
|
||
currentPlot->replot();
|
||
}
|
||
}
|
||
});
|
||
|
||
// 创建水平布局用于放置按钮
|
||
QHBoxLayout *collectionDataButtonLayout = new QHBoxLayout();
|
||
|
||
// 创建打开和关闭按钮
|
||
QPushButton *openCollectionDataButton = new QPushButton("打开");
|
||
QPushButton *closeCollectionDataButton = new QPushButton("关闭");
|
||
|
||
connect(openCollectionDataButton, &QPushButton::clicked, this, [=]() {
|
||
QString fileName = QFileDialog::getOpenFileName(
|
||
this, "选择采集数据文件", QDir::currentPath(), "采集数据文件 (*.csv)");
|
||
openDataCollectionFile(fileName);
|
||
});
|
||
|
||
connect(closeCollectionDataButton, &QPushButton::clicked, this, [=]() {
|
||
collectionData.clear();
|
||
collectionDataTreeWidget->clear();
|
||
});
|
||
|
||
// 将按钮添加到水平布局
|
||
collectionDataButtonLayout->addWidget(openCollectionDataButton);
|
||
collectionDataButtonLayout->addWidget(closeCollectionDataButton);
|
||
|
||
// 创建一个容器widget来容纳按钮布局
|
||
QWidget *collectionDataButtonContainer = new QWidget();
|
||
collectionDataButtonContainer->setLayout(collectionDataButtonLayout);
|
||
|
||
// 创建下部TreeWidget
|
||
QTreeWidget *referenceDataTreeWidget = new QTreeWidget();
|
||
referenceDataTreeWidget->setHeaderLabel("参考数据");
|
||
|
||
connect(referenceDataTreeWidget, &QTreeWidget::itemClicked, this,
|
||
[=](QTreeWidgetItem *item, int column) {
|
||
QString varName = item->text(0);
|
||
if (referenceData.contains(varName)) {
|
||
int currentIndex = leftTabWidget->currentIndex();
|
||
QCustomPlot *currentPlot =
|
||
findChild<QCustomPlot *>(QString("dataPlot%1").arg(currentIndex));
|
||
if (currentPlot) {
|
||
currentPlot->graph(1)->setData(referenceData["Time"],
|
||
referenceData[varName]);
|
||
currentPlot->graph(1)->setPen(QPen(Qt::red, 2));
|
||
currentPlot->graph(1)->setName(varName);
|
||
// 自动调整坐标轴范围以适应数据
|
||
currentPlot->xAxis->rescale();
|
||
currentPlot->yAxis->rescale();
|
||
// 设置图例
|
||
currentPlot->legend->setVisible(true);
|
||
currentPlot->legend->setFont(QFont("Helvetica", 10));
|
||
currentPlot->replot();
|
||
}
|
||
}
|
||
});
|
||
|
||
QHBoxLayout *referenceDataButtonLayout = new QHBoxLayout();
|
||
|
||
QPushButton *openReferenceDataButton = new QPushButton("打开");
|
||
QPushButton *closeReferenceDataButton = new QPushButton("关闭");
|
||
|
||
connect(openReferenceDataButton, &QPushButton::clicked, this, [=]() {
|
||
QString fileName = QFileDialog::getOpenFileName(
|
||
this, "选择参考数据文件", QDir::currentPath(), "参考数据文件 (*.csv)");
|
||
if (!fileName.isEmpty()) {
|
||
QFile file(fileName);
|
||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
QTextStream in(&file);
|
||
|
||
// 读取第一行作为标题
|
||
QString headerLine = in.readLine();
|
||
QStringList headers = headerLine.split(",");
|
||
|
||
referenceData["Time"] = QVector<double>();
|
||
// 第一次读取时,从headers中获取变量名并初始化vectors
|
||
if (referenceData.isEmpty()) {
|
||
for (int i = 1; i < headers.size(); i++) {
|
||
referenceData[headers[i]] = QVector<double>();
|
||
}
|
||
}
|
||
|
||
// 清空现有数据
|
||
referenceData.clear();
|
||
|
||
while (!in.atEnd()) {
|
||
QString line = in.readLine();
|
||
QStringList fields = line.split(",");
|
||
|
||
referenceData["Time"].append(fields[0].toDouble());
|
||
for (int i = 1; i < fields.size() && i < headers.size(); i++) {
|
||
bool ok;
|
||
double value = fields[i].toDouble(&ok);
|
||
if (ok) {
|
||
referenceData[headers[i]].append(value);
|
||
}
|
||
}
|
||
}
|
||
|
||
file.close();
|
||
|
||
// 清空现有树形结构
|
||
referenceDataTreeWidget->clear();
|
||
|
||
// 将第一列数据名称添加到树形结构中
|
||
for (auto it = referenceData.begin(); it != referenceData.end(); ++it) {
|
||
if (it.key() != "" && it.key() != "Time") {
|
||
QTreeWidgetItem *item = new QTreeWidgetItem(referenceDataTreeWidget);
|
||
item->setText(0, it.key());
|
||
}
|
||
}
|
||
} else {
|
||
QMessageBox::warning(this, "错误", "无法打开文件!");
|
||
}
|
||
}
|
||
});
|
||
|
||
connect(closeReferenceDataButton, &QPushButton::clicked, this, [=]() {
|
||
referenceData.clear();
|
||
referenceDataTreeWidget->clear();
|
||
});
|
||
|
||
referenceDataButtonLayout->addWidget(openReferenceDataButton);
|
||
referenceDataButtonLayout->addWidget(closeReferenceDataButton);
|
||
|
||
QWidget *referenceDataButtonContainer = new QWidget();
|
||
referenceDataButtonContainer->setLayout(referenceDataButtonLayout);
|
||
|
||
// 将两个TreeWidget添加到右侧垂直布局
|
||
rightTreeLayout->addWidget(collectionDataTreeWidget);
|
||
// 将按钮容器添加到右侧垂直布局
|
||
rightTreeLayout->addWidget(collectionDataButtonContainer);
|
||
// 将参考数据TreeWidget添加到右侧垂直布局
|
||
rightTreeLayout->addWidget(referenceDataTreeWidget);
|
||
// 将按钮容器添加到右侧垂直布局
|
||
rightTreeLayout->addWidget(referenceDataButtonContainer);
|
||
|
||
// 创建一个容器widget来容纳右侧的树形视图
|
||
QWidget *rightTreeContainer = new QWidget();
|
||
rightTreeContainer->setLayout(rightTreeLayout);
|
||
|
||
// 创建一个水平分割器来分隔TabWidget和右侧树形视图
|
||
QSplitter *rightSplitter = new QSplitter(Qt::Horizontal);
|
||
rightSplitter->addWidget(leftTabWidget);
|
||
rightSplitter->addWidget(rightTreeContainer);
|
||
rightSplitter->setStretchFactor(0, 4); // TabWidget占据更多空间
|
||
rightSplitter->setStretchFactor(1, 1);
|
||
|
||
// 将分割器添加到右侧面板的主布局
|
||
rightLayout->addWidget(rightSplitter);
|
||
rightLayout->setContentsMargins(0, 0, 0, 0);
|
||
}
|
||
|
||
void DataCollectionWidget::openDataCollectionFile(const QString &filePath)
|
||
{
|
||
if (!filePath.isEmpty()) {
|
||
QFile file(filePath);
|
||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
QTextStream in(&file);
|
||
|
||
// 读取第一行作为标题
|
||
QString headerLine = in.readLine();
|
||
QStringList headers = headerLine.split(",");
|
||
|
||
collectionData["Time"] = QVector<double>();
|
||
// 第一次读取时,从headers中获取变量名并初始化vectors
|
||
if (collectionData.isEmpty()) {
|
||
for (int i = 1; i < headers.size(); i++) {
|
||
collectionData[headers[i]] = QVector<double>();
|
||
}
|
||
}
|
||
|
||
// 清空现有数据
|
||
collectionData.clear();
|
||
|
||
while (!in.atEnd()) {
|
||
QString line = in.readLine();
|
||
QStringList fields = line.split(",");
|
||
|
||
collectionData["Time"].append(fields[0].toDouble());
|
||
// 将每一列的数据添加到对应变量名的vector中
|
||
for (int i = 1; i < fields.size() && i < headers.size(); i++) {
|
||
bool ok;
|
||
double value = fields[i].toDouble(&ok);
|
||
if (ok) {
|
||
collectionData[headers[i]].append(value);
|
||
}
|
||
}
|
||
}
|
||
|
||
file.close();
|
||
|
||
QTreeWidget *collectionDataTreeWidget =
|
||
findChild<QTreeWidget *>("collectionDataTreeWidget");
|
||
if (collectionDataTreeWidget) {
|
||
// 清空现有树形结构
|
||
collectionDataTreeWidget->clear();
|
||
|
||
// 将第一列数据名称添加到树形结构中
|
||
for (auto it = collectionData.begin(); it != collectionData.end(); ++it) {
|
||
if (it.key() != "" && it.key() != "Time") {
|
||
QTreeWidgetItem *item = new QTreeWidgetItem(collectionDataTreeWidget);
|
||
item->setText(0, it.key());
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
QMessageBox::warning(this, "错误", "无法打开文件!");
|
||
}
|
||
}
|
||
}
|
||
|
||
void DataCollectionWidget::setupPlotWidget(XNCustomPlot *customPlot)
|
||
{
|
||
customPlot->setBackground(QBrush(QColor("#19232D")));
|
||
customPlot->xAxis->setLabelColor(Qt::white);
|
||
customPlot->yAxis->setLabelColor(Qt::white);
|
||
customPlot->xAxis->setLabel("时间(s)");
|
||
customPlot->yAxis->setLabel("值");
|
||
customPlot->xAxis->setTickLabelRotation(60);
|
||
customPlot->xAxis->setTickLabelColor(Qt::white);
|
||
customPlot->yAxis->setTickLabelColor(Qt::white);
|
||
customPlot->xAxis->grid()->setVisible(true);
|
||
customPlot->yAxis->grid()->setVisible(true);
|
||
|
||
customPlot->setNotAntialiasedElements(QCP::aeAll); // 关闭抗锯齿
|
||
customPlot->setPlottingHints(QCP::phFastPolylines); // 使用快速绘制
|
||
customPlot->setNoAntialiasingOnDrag(true); // 拖动时关闭抗锯齿
|
||
|
||
// 优化图表性能
|
||
customPlot->setBufferDevicePixelRatio(1.0); // 设置缓冲区像素比
|
||
customPlot->setOpenGl(true); // 启用OpenGL加速(如果支持)
|
||
|
||
// 确保图表可以接收鼠标事件
|
||
customPlot->setMouseTracking(true);
|
||
customPlot->setAttribute(Qt::WA_Hover);
|
||
customPlot->setFocusPolicy(Qt::StrongFocus);
|
||
|
||
// 启用所有交互选项
|
||
customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectItems
|
||
| QCP::iSelectAxes | QCP::iSelectLegend);
|
||
|
||
// 创建新的顶层层级
|
||
customPlot->addLayer("topmost", customPlot->layer("main"), QCustomPlot::limAbove);
|
||
QCPLayer *topmostLayer = customPlot->layer("topmost");
|
||
topmostLayer->setMode(QCPLayer::lmBuffered);
|
||
|
||
// 将所有现有图层移到新层级下方
|
||
for (int i = 0; i < customPlot->layerCount(); ++i) {
|
||
QCPLayer *layer = customPlot->layer(i);
|
||
if (layer != topmostLayer) {
|
||
layer->setMode(QCPLayer::lmBuffered);
|
||
customPlot->moveLayer(layer, topmostLayer, QCustomPlot::limBelow);
|
||
}
|
||
}
|
||
|
||
// 创建坐标标签
|
||
m_coordLabel = new QCPItemText(customPlot);
|
||
m_coordLabel->setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||
m_coordLabel->setTextAlignment(Qt::AlignLeft);
|
||
m_coordLabel->setVisible(false);
|
||
m_coordLabel->setPadding(QMargins(8, 8, 8, 8)); // 增加内边距
|
||
m_coordLabel->setBrush(QBrush(QColor(40, 40, 40, 255)));
|
||
m_coordLabel->setPen(QPen(QColor(80, 80, 80, 255)));
|
||
m_coordLabel->setColor(Qt::white);
|
||
QFont labelFont = QFont("Arial", 16);
|
||
labelFont.setBold(true);
|
||
m_coordLabel->setFont(labelFont);
|
||
m_coordLabel->setLayer("topmost"); // 使用最顶层
|
||
|
||
// 创建点追踪器
|
||
m_tracer = new QCPItemTracer(customPlot);
|
||
m_tracer->setStyle(QCPItemTracer::tsCircle);
|
||
m_tracer->setPen(QPen(Qt::red));
|
||
m_tracer->setBrush(Qt::red);
|
||
m_tracer->setSize(8);
|
||
m_tracer->setVisible(false);
|
||
m_tracer->setLayer("topmost"); // 使用最顶层
|
||
|
||
// 连接鼠标事件
|
||
connect(customPlot, &XNCustomPlot::mousePressed, this, [=](QMouseEvent *event) {
|
||
if (event->button() == Qt::LeftButton && customPlot->graph(0)) {
|
||
double x = customPlot->xAxis->pixelToCoord(event->pos().x());
|
||
double y = customPlot->yAxis->pixelToCoord(event->pos().y());
|
||
|
||
if (!customPlot->graph(0)->data()->isEmpty()) {
|
||
double minDistance = std::numeric_limits<double>::max();
|
||
QCPGraphDataContainer::const_iterator closestPoint;
|
||
bool foundPoint = false;
|
||
|
||
for (auto it = customPlot->graph(0)->data()->begin();
|
||
it != customPlot->graph(0)->data()->end(); ++it) {
|
||
double distance = qSqrt(qPow(it->key - x, 2) + qPow(it->value - y, 2));
|
||
if (distance < minDistance) {
|
||
minDistance = distance;
|
||
closestPoint = it;
|
||
foundPoint = true;
|
||
}
|
||
}
|
||
|
||
if (foundPoint) {
|
||
m_tracer->setGraph(customPlot->graph(0));
|
||
m_tracer->setGraphKey(closestPoint->key);
|
||
m_tracer->setVisible(true);
|
||
|
||
m_coordLabel->position->setCoords(closestPoint->key, closestPoint->value);
|
||
m_coordLabel->setText(QString("时间: %1\n数值: %2")
|
||
.arg(closestPoint->key)
|
||
.arg(closestPoint->value));
|
||
m_coordLabel->setVisible(true);
|
||
|
||
customPlot->replot();
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// 确保在初始化时重新绘制一次
|
||
customPlot->replot();
|
||
} |