XNSim/XNMonitor/DataCollectionWidget/DataCollectionWidget.cpp

790 lines
27 KiB
C++
Raw Normal View History

2025-04-28 12:25:20 +08:00
#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();
}