XNSim/XNMonitor/DataCollectionWidget/DataCollectionWidget.cpp
2025-04-28 12:25:20 +08:00

790 lines
27 KiB
C++
Executable File
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 "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();
}