From 2e7528ac405952d01a86f95e587f01f8ed59c635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B5=B7?= Date: Fri, 13 Mar 2026 15:36:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=B3=B0=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=8E=A7=E5=88=B6;Plot=E7=9A=84X=E8=BD=B4?= =?UTF-8?q?=E7=BC=A9=E6=94=BE=E6=8B=96=E5=8A=A8;=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=83=A8=E5=88=86BUG;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CustomQwtPlot.cpp | 158 +++++++++++++++++++ src/CustomQwtPlot.h | 40 +++++ src/MeasureAnalysisParticleCountPlotView.cpp | 51 +++++- src/MeasureAnalysisParticleCountPlotView.h | 2 +- src/MeasureAnalysisTreeView.cpp | 2 +- src/main.cpp | 2 +- 6 files changed, 244 insertions(+), 11 deletions(-) diff --git a/src/CustomQwtPlot.cpp b/src/CustomQwtPlot.cpp index d2aeb67..663a088 100644 --- a/src/CustomQwtPlot.cpp +++ b/src/CustomQwtPlot.cpp @@ -4,12 +4,35 @@ #include #include #include +#include +#include +#include #include +#include +#include CustomQwtPlot::CustomQwtPlot(QWidget *parent) : QwtPlot(parent) { +} +void CustomQwtPlot::SetEventFilterFunc(std::function event_filter_func) +{ + this->_event_filter_func = event_filter_func; +} + +void CustomQwtPlot::SetXaxisDragScale(bool enable) +{ + QwtScaleWidget *x_scale = axisWidget(QwtPlot::xBottom); + x_scale->setMouseTracking(enable); + x_scale->installEventFilter(enable ? this : nullptr); +} + +void CustomQwtPlot::ResetPlot() +{ + this->setAxisScale(QwtPlot::xBottom, _init_x_min, _init_x_max); + this->setAxisScale(QwtPlot::yLeft, _init_y_min, _init_y_max); + this->replot(); } QwtPlotCurve *CustomQwtPlot::GetCurve(const QString &curve_name) @@ -81,6 +104,141 @@ void CustomQwtPlot::CleanMarkers() this->_markers.clear(); } +QwtPlotZoneItem* CustomQwtPlot::GetZoneItem(const QString& zone_item_name) +{ + return _zone_items.value(zone_item_name); +} + +QList CustomQwtPlot::GetZoneItemList() const +{ + return this->_zone_items.values(); +} + +void CustomQwtPlot::AddZoneItem(QwtPlotZoneItem *zone_item, const QString &zone_item_name) +{ + if (zone_item) { + zone_item->setPen(Qt::transparent); // 无边框 + zone_item->setBrush(QColor(0, 120, 215, 95)); // 半透明蓝色(RGBA:A=80 透明度) + zone_item->attach(this); + _zone_items[zone_item_name] = zone_item; + } +} + +void CustomQwtPlot::RemoveZoneItem(const QString& zone_item_name) +{ + QwtPlotZoneItem* zone_item = GetZoneItem(zone_item_name); + if (zone_item) { + zone_item->detach(); + delete zone_item; + } + _zone_items.remove(zone_item_name); +} + +void CustomQwtPlot::CleanZoneItems() +{ + QList zone_items = GetZoneItemList(); + for (auto zone_item : zone_items) { + if (zone_item) { + zone_item->detach(); + delete zone_item; + } + } + this->_zone_items.clear(); +} + +bool CustomQwtPlot::eventFilter(QObject *obj, QEvent *event) +{ + if (this->_event_filter_func) { + bool filted = this->_event_filter_func(obj, event); + if (filted) { + return filted; + } + } + if (dynamic_cast(obj) != this->axisWidget(QwtPlot::xBottom)) + return false; + QMouseEvent *me = static_cast(event); + QwtScaleMap map = this->canvasMap(QwtPlot::xBottom); + // ===================== 1. 鼠标拖动 X 轴 ===================== + if (event->type() == QEvent::MouseButtonPress) { + this->_is_dragging = true; + this->_last_pos = me->pos(); + return true; + } else if (event->type() == QEvent::MouseMove && this->_is_dragging) { + // 计算坐标偏移 + double old_x = map.invTransform(_last_pos.x()); + double new_x = map.invTransform(me->pos().x()); + double dx = old_x - new_x; + // 移动 X 轴 + double x_min = axisScaleDiv(QwtPlot::xBottom).lowerBound() + dx; + double x_max = axisScaleDiv(QwtPlot::xBottom).upperBound() + dx; + this->setAxisScale(QwtPlot::xBottom, x_min, x_max); + this->replot(); + this->_last_pos = me->pos(); + return true; + } else if (event->type() == QEvent::MouseButtonRelease) { + this->_is_dragging = false; + return true; + } + // ===================== 2. 滚轮缩放 X 轴 ===================== + if (event->type() == QEvent::Wheel) { + QWheelEvent *we = static_cast(event); + double scale = we->angleDelta().y() > 0 ? 0.8 : 1.25; // 放大/缩小 + // 以鼠标所在位置为中心缩放 + double mouse_x = map.invTransform(we->pos().x()); + double x1 = this->axisScaleDiv(QwtPlot::xBottom).lowerBound(); + double x2 = this->axisScaleDiv(QwtPlot::xBottom).upperBound(); + double new1 = mouse_x - (mouse_x - x1) * scale; + double new2 = mouse_x + (x2 - mouse_x) * scale; + this->setAxisScale(QwtPlot::xBottom, new1, new2); + this->replot(); + return true; + } + return QwtPlot::eventFilter(obj, event); +} + +void CustomQwtPlot::showEvent(QShowEvent *event) +{ + Q_UNUSED(event); + this->updateAxes(); + double x_min = this->axisScaleDiv(QwtPlot::xBottom).lowerBound(); + double x_max = this->axisScaleDiv(QwtPlot::xBottom).upperBound(); + double y_min = this->axisScaleDiv(QwtPlot::yLeft).lowerBound(); + double y_max = this->axisScaleDiv(QwtPlot::yLeft).upperBound(); + this->_init_x_min = x_min; + this->_init_x_max = x_max; + this->_init_y_min = y_min; + this->_init_y_max = y_max; +} + +CustomQwtPlotXaxisPanner::CustomQwtPlotXaxisPanner(QWidget *canvas) + : QwtPlotPanner(canvas) +{ +} + +void CustomQwtPlotXaxisPanner::moveCanvas(int dx, int dy) +{ + QwtPlotPanner::moveCanvas(dx, 0); +} + +CustomQwtPlotXaxisMagnifier::CustomQwtPlotXaxisMagnifier(QWidget *canvas) + : QwtPlotMagnifier(canvas) +{ +} + +void CustomQwtPlotXaxisMagnifier::rescale(double factor) +{ + factor = qBound(0.1, factor, 10.0); + + QwtScaleMap x_map = plot()->canvasMap(QwtPlot::xBottom); + double center = x_map.invTransform(plot()->canvas()->width() / 2); + plot()->setAxisScale( + QwtPlot::xBottom, + center - (center - x_map.s1()) * factor, + center + (x_map.s2() - center) * factor + ); + plot()->replot(); +} + QColor getDistinctColorForManyCurves(int curve_index) { // 1. 定义基础色相(覆盖不同主色系,0-360度) diff --git a/src/CustomQwtPlot.h b/src/CustomQwtPlot.h index 09ea207..a38f51e 100644 --- a/src/CustomQwtPlot.h +++ b/src/CustomQwtPlot.h @@ -3,15 +3,22 @@ #include #include +#include +#include class QwtPlotCurve; class QwtPlotMarker; +class QwtPlotZoneItem; class CustomQwtPlot : public QwtPlot { public: explicit CustomQwtPlot(QWidget* parent = nullptr); + void SetEventFilterFunc(std::function event_filter_func); + void SetXaxisDragScale(bool enable); + void ResetPlot(); + QwtPlotCurve* GetCurve(const QString& curve_name); QList GetCurveList() const; void AddCurve(QwtPlotCurve* curve); @@ -22,11 +29,44 @@ public: void RemoveMarker(const QString& marker_name, const QString& postion); void CleanMarkers(); + QwtPlotZoneItem* GetZoneItem(const QString& zone_item_name); + QList GetZoneItemList() const; + void AddZoneItem(QwtPlotZoneItem *zone_item, const QString &zone_item_name); + void RemoveZoneItem(const QString& zone_item_name); + void CleanZoneItems(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + virtual void showEvent(QShowEvent *event) override; +private: + double _init_x_min = 0, _init_x_max = 10; + double _init_y_min = 0, _init_y_max = 10; + bool _is_dragging = false; + QPoint _last_pos; + std::function _event_filter_func = nullptr; private: QMap _curves; QMap > _markers; + QMap _zone_items; }; +class CustomQwtPlotXaxisPanner : public QwtPlotPanner +{ +public: + CustomQwtPlotXaxisPanner(QWidget *canvas); +protected: + virtual void moveCanvas(int dx, int dy) override; +}; + +class CustomQwtPlotXaxisMagnifier : public QwtPlotMagnifier +{ +public: + CustomQwtPlotXaxisMagnifier(QWidget *canvas); +protected: + virtual void rescale(double factor) override; +}; + + QColor getDistinctColorForManyCurves(int curve_index); diff --git a/src/MeasureAnalysisParticleCountPlotView.cpp b/src/MeasureAnalysisParticleCountPlotView.cpp index dd28b8b..8f74dbe 100644 --- a/src/MeasureAnalysisParticleCountPlotView.cpp +++ b/src/MeasureAnalysisParticleCountPlotView.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include static auto extractNumber = [](const QString& str) { int ret_num = 0; @@ -97,6 +99,12 @@ void MeasureAnalysisParticleCountPlotView::setupMenu() connect(this, &MeasureAnalysisParticleCountPlotView::customContextMenuRequested, [this](const QPoint &pos){ this->_menu->exec(this->mapToGlobal(pos)); }); + QAction* action_plot_reset = this->_menu->addAction(QStringLiteral(u"还原")); + action_plot_reset->setObjectName("curve_show_setting"); + connect(action_plot_reset, &QAction::triggered, [this](){ + this->_plot->ResetPlot(); + }); + this->_menu->addSeparator(); QAction* action_set_curve_show = this->_menu->addAction(QStringLiteral(u"选择曲线")); action_set_curve_show->setObjectName("curve_show_setting"); connect(action_set_curve_show, &QAction::triggered, this, &MeasureAnalysisParticleCountPlotView::onCurveShowSetting); @@ -137,8 +145,12 @@ void MeasureAnalysisParticleCountPlotView::setupPlot() // 设置QWT图例 QwtLegend* legend = new QwtLegend(); - legend->setDefaultItemMode(QwtLegendData::Checkable); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); _plot->insertLegend(legend, QwtPlot::RightLegend); + + // new CustomQwtPlotXaxisPanner(_plot->canvas()); + new CustomQwtPlotXaxisMagnifier(_plot->canvas()); + _plot->SetXaxisDragScale(true); } void MeasureAnalysisParticleCountPlotView::loadDataFromFile(const QString& data_name, const QString& filename) @@ -239,10 +251,11 @@ void MeasureAnalysisParticleCountPlotView::updatePlotPeakInfo(QVariantMap peak_i int right_bound = peak_infos["right_bound"].toInt(); int peak_width = peak_infos["peak_width"].toInt(); bool is_checked = peak_infos["checked"].toBool(); - + bool is_show_peak_area = peak_infos["show_peak_area"].toBool(); const QString& postion = QString::number(peak_pos); + QwtPlotMarker* peak_marker = this->_plot->GetMarker(channel, postion); - if (!peak_marker && is_checked) { + if ((!peak_marker) && is_checked) { peak_marker = new QwtPlotMarker(); peak_marker->setLineStyle(QwtPlotMarker::VLine); peak_marker->setValue(peak_pos, 0.0); @@ -250,13 +263,22 @@ void MeasureAnalysisParticleCountPlotView::updatePlotPeakInfo(QVariantMap peak_i const QString& label_text = QStringLiteral(u"峰位:%1\n峰宽:%2\n左界:%3\n右界:%4\n").arg(postion).arg(peak_width).arg(left_bound).arg(right_bound); peak_marker->setLabel(label_text); this->_plot->AddMarker(peak_marker, channel, postion); - } else { + } else if (!is_checked) { this->_plot->RemoveMarker(channel, postion); } + QwtPlotZoneItem* peak_area_zone_item = this->_plot->GetZoneItem(channel); + if (!peak_area_zone_item && is_show_peak_area) { + peak_area_zone_item = new QwtPlotZoneItem; + peak_area_zone_item->setOrientation(Qt::Vertical); + peak_area_zone_item->setInterval(left_bound, right_bound); + this->_plot->AddZoneItem(peak_area_zone_item, channel); + } else if ((!is_checked) || (!is_show_peak_area)) { + this->_plot->RemoveZoneItem(channel); + } this->_plot->replot(); } -void MeasureAnalysisParticleCountPlotView::updatePlotPeakInfoByTableItem(QTableWidgetItem *item, bool checked) +void MeasureAnalysisParticleCountPlotView::updatePlotPeakInfoByTableItem(QTableWidgetItem *item, bool checked, bool show_peak_area) { if (item) { auto peaks_result_table = item->tableWidget(); @@ -274,6 +296,7 @@ void MeasureAnalysisParticleCountPlotView::updatePlotPeakInfoByTableItem(QTableW peak_infos["right_bound"] = right_bound; peak_infos["peak_width"] = peak_width; peak_infos["checked"] = is_checked || checked; + peak_infos["show_peak_area"] = show_peak_area || checked; this->updatePlotPeakInfo(peak_infos); qDebug() << channel << ", " << peak_pos << ", " << bool(is_checked || checked); } @@ -312,7 +335,17 @@ void MeasureAnalysisParticleCountPlotView::onCurveShowSetting() if (num_columns == 0) num_columns = 1; QVBoxLayout* checkbox_layout = new QVBoxLayout(); QHBoxLayout* checkbox_column_layout = new QHBoxLayout(); + QStringList list_ch_names; for (QwtPlotCurve* curve : this->_plot->GetCurveList()) { + list_ch_names.append(curve->title().text()); + } + std::sort(list_ch_names.begin(), list_ch_names.end(), [](const QString& a, const QString& b) { + int num_a = extractNumber(a); + int num_b = extractNumber(b); + return num_a < num_b; + }); + for (const QString& ch_name : list_ch_names) { + QwtPlotCurve* curve = this->_plot->GetCurve(ch_name); QCheckBox* check_box = new QCheckBox(curve->title().text()); check_box->setChecked(curve->isVisible()); checkbox_column_layout->addWidget(check_box); @@ -419,6 +452,7 @@ void MeasureAnalysisParticleCountPlotView::onFindPeaksResult() this->loadPeaksResultToTable(peaks_result_table); connect(filter_channel_combo_box, &QComboBox::currentTextChanged, [this, peaks_result_table](const QString& text){ + peaks_result_table->setCurrentItem(nullptr); auto row_count = peaks_result_table->rowCount(); if (text == QString(QStringLiteral(u"所有通道"))) { for (int i = 0; i < row_count - 1; i++) { @@ -433,6 +467,7 @@ void MeasureAnalysisParticleCountPlotView::onFindPeaksResult() } }); connect(btn_all_select, &QPushButton::clicked, [peaks_result_table](){ + peaks_result_table->setCurrentItem(nullptr); auto row_count = peaks_result_table->rowCount(); for (int i = 0; i < row_count - 1; i++) { if (peaks_result_table->isRowHidden(i)) { @@ -445,6 +480,7 @@ void MeasureAnalysisParticleCountPlotView::onFindPeaksResult() } }); connect(btn_reserve_select, &QPushButton::clicked, [peaks_result_table](){ + peaks_result_table->setCurrentItem(nullptr); auto row_count = peaks_result_table->rowCount(); for (int i = 0; i < row_count - 1; i++) { if (peaks_result_table->isRowHidden(i)) { @@ -488,8 +524,8 @@ void MeasureAnalysisParticleCountPlotView::onFindPeaksResult() connect(peaks_result_table, &QTableWidget::currentItemChanged, [this, peaks_result_table](QTableWidgetItem *current, QTableWidgetItem *previous){ bool is_watch_item_changed = peaks_result_table->property("WatchItemChanged").toBool(); if (is_watch_item_changed) { - this->updatePlotPeakInfoByTableItem(previous, false); - this->updatePlotPeakInfoByTableItem(current, true); + this->updatePlotPeakInfoByTableItem(previous, false, false); + this->updatePlotPeakInfoByTableItem(current, true, true); } }); @@ -541,7 +577,6 @@ void MeasureAnalysisParticleCountPlotView::onAutoFindPeaks() auto_find_peaks_task->SetFinishedNotifier(this, "onAutoFindPeaksFinished", project_name); auto_find_peaks_task->StartTask(); } - } void MeasureAnalysisParticleCountPlotView::onManualFindPeaks() diff --git a/src/MeasureAnalysisParticleCountPlotView.h b/src/MeasureAnalysisParticleCountPlotView.h index 0819ae8..5748048 100644 --- a/src/MeasureAnalysisParticleCountPlotView.h +++ b/src/MeasureAnalysisParticleCountPlotView.h @@ -27,7 +27,7 @@ private: void loadDataFromFile(const QString &data_name, const QString& filename); void loadPeaksResultToTable(QTableWidget* peaks_result_table); void updatePlotPeakInfo(QVariantMap peak_infos); - void updatePlotPeakInfoByTableItem(QTableWidgetItem* item, bool checked = false); + void updatePlotPeakInfoByTableItem(QTableWidgetItem* item, bool checked = false, bool show_peak_area = false); private slots: void onAutoFindPeaksFinished(const QString& project_name); diff --git a/src/MeasureAnalysisTreeView.cpp b/src/MeasureAnalysisTreeView.cpp index 3070564..71508c7 100644 --- a/src/MeasureAnalysisTreeView.cpp +++ b/src/MeasureAnalysisTreeView.cpp @@ -13,7 +13,7 @@ MeasureAnalysisTreeView::MeasureAnalysisTreeView(QWidget* parent) // header_view->setSectionResizeMode(QHeaderView::Interactive); // header_view->setMinimumSectionSize(10); // header_view->resizeSection(1, 20); - header_view->setSectionResizeMode(0, QHeaderView::Stretch); + header_view->setSectionResizeMode(0, QHeaderView::Interactive); header_view->setSectionResizeMode(_model->columnCount() - 1, QHeaderView::Fixed); header_view->resizeSection(_model->columnCount() - 1, 20); diff --git a/src/main.cpp b/src/main.cpp index 10ae752..ed3fcc8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,7 +48,7 @@ int main(int argc, char *argv[]) app.setFont(f); // 设置应用图标和关闭属性 // 设置样式 - app.setStyle(QStyleFactory::create("Fusion")); + // app.setStyle(QStyleFactory::create("Fusion")); app.setWindowIcon(QIcon(":/logo/256.png")); app.setQuitOnLastWindowClosed(true);