234 lines
8.6 KiB
C++
234 lines
8.6 KiB
C++
#include "panels/chart/GridDataChartView.hpp"
|
||
|
||
#include <QCheckBox>
|
||
#include <QHBoxLayout>
|
||
#include <QLabel>
|
||
#include <QSlider>
|
||
#include <QToolButton>
|
||
#include <QVBoxLayout>
|
||
|
||
#include <qwt_plot.h>
|
||
#include <qwt_plot_grid.h>
|
||
#include <QSplitter>
|
||
#include <QScrollArea>
|
||
#include <QFrame>
|
||
#include <qwt_plot_rescaler.h>
|
||
|
||
#include "PanelHeader.hpp"
|
||
#include "panels/AnomalyTablePanel.hpp"
|
||
#include "panels/DescriptionPanel.hpp"
|
||
#include "panels/chart/ColorBarWidget.hpp"
|
||
#include "panels/chart/ColorMapService.hpp"
|
||
#include "panels/chart/ContourPlotItem.hpp"
|
||
#include "panels/chart/LivePanner.hpp"
|
||
|
||
namespace geopro::app {
|
||
|
||
GridDataChartView::GridDataChartView(QWidget* parent) : QWidget(parent) {
|
||
auto* lay = new QVBoxLayout(this);
|
||
lay->setContentsMargins(0, 0, 0, 0);
|
||
lay->setSpacing(0);
|
||
|
||
// ---- 工具条 ----
|
||
auto* toolbar = new QWidget(this);
|
||
auto* tbLay = new QHBoxLayout(toolbar);
|
||
tbLay->setContentsMargins(4, 4, 4, 4);
|
||
tbLay->setSpacing(4);
|
||
|
||
auto* btnGrid = new QToolButton(toolbar);
|
||
btnGrid->setText(QStringLiteral("网格"));
|
||
|
||
auto* btnColorScale = new QToolButton(toolbar);
|
||
btnColorScale->setText(QStringLiteral("色阶配置"));
|
||
|
||
auto* btnWhiten = new QToolButton(toolbar);
|
||
btnWhiten->setText(QStringLiteral("白化"));
|
||
|
||
auto* btnFilter = new QToolButton(toolbar);
|
||
btnFilter->setText(QStringLiteral("滤波处理"));
|
||
|
||
auto* chkShowAnom = new QCheckBox(QStringLiteral("显示异常"), toolbar);
|
||
chkShowAnom->setChecked(true);
|
||
|
||
auto* chkShowContourLabel = new QCheckBox(QStringLiteral("显示等值线标注"), toolbar);
|
||
chkShowContourLabel->setChecked(true);
|
||
|
||
auto* chkContourTip = new QCheckBox(QStringLiteral("显示等值线提示信息"), toolbar);
|
||
chkContourTip->setChecked(false);
|
||
|
||
auto* lblSimplify = new QLabel(QStringLiteral("简化容差:"), toolbar);
|
||
|
||
simplifySlider_ = new QSlider(Qt::Horizontal, toolbar);
|
||
simplifySlider_->setRange(0, 100);
|
||
simplifySlider_->setValue(50);
|
||
simplifySlider_->setFixedWidth(80);
|
||
|
||
simplifyValueLabel_ = new QLabel(QStringLiteral("0.5"), toolbar);
|
||
simplifyValueLabel_->setFixedWidth(28);
|
||
|
||
connect(simplifySlider_, &QSlider::valueChanged, this, [this](int v) {
|
||
simplifyValueLabel_->setText(QString::number(v / 100.0, 'f', 1));
|
||
});
|
||
|
||
auto* btnAnomalyLabel = new QToolButton(toolbar);
|
||
btnAnomalyLabel->setText(QStringLiteral("异常标注"));
|
||
|
||
auto* btnAutoLabel = new QToolButton(toolbar);
|
||
btnAutoLabel->setText(QStringLiteral("自动标注"));
|
||
|
||
auto* btnSaveAs = new QToolButton(toolbar);
|
||
btnSaveAs->setText(QStringLiteral("另存为"));
|
||
|
||
tbLay->addWidget(btnGrid);
|
||
tbLay->addWidget(btnColorScale);
|
||
tbLay->addWidget(btnWhiten);
|
||
tbLay->addWidget(btnFilter);
|
||
tbLay->addWidget(chkShowAnom);
|
||
tbLay->addWidget(chkShowContourLabel);
|
||
tbLay->addWidget(chkContourTip);
|
||
tbLay->addWidget(lblSimplify);
|
||
tbLay->addWidget(simplifySlider_);
|
||
tbLay->addWidget(simplifyValueLabel_);
|
||
tbLay->addWidget(btnAnomalyLabel);
|
||
tbLay->addWidget(btnAutoLabel);
|
||
tbLay->addWidget(btnSaveAs);
|
||
tbLay->addStretch();
|
||
|
||
lay->addWidget(toolbar);
|
||
|
||
// ---- QwtPlot(仿 RawDataChartView,差异:x 轴在底部、不画过原点零线、网格线被填充覆盖故省)----
|
||
plot_ = new QwtPlot(this);
|
||
plot_->setObjectName(QStringLiteral("gridPlotArea"));
|
||
plot_->enableAxis(QwtPlot::xBottom, true);
|
||
plot_->enableAxis(QwtPlot::xTop, false);
|
||
plot_->enableAxis(QwtPlot::yLeft, true);
|
||
|
||
// 白底浅色(对齐原版 web 图表)。
|
||
plot_->setCanvasBackground(QBrush(Qt::white));
|
||
plot_->setAutoFillBackground(true);
|
||
{
|
||
QPalette pal = plot_->palette();
|
||
pal.setColor(QPalette::Window, Qt::white);
|
||
pal.setColor(QPalette::WindowText, QColor(90, 90, 90));
|
||
pal.setColor(QPalette::Text, QColor(90, 90, 90));
|
||
plot_->setPalette(pal);
|
||
}
|
||
|
||
// 交互:LivePanner 统一左键实时平移 + 滚轮缩放(消费滚轮事件,不冒泡触发滚动条)。
|
||
new LivePanner(plot_, QwtPlot::xBottom, QwtPlot::yLeft, this);
|
||
|
||
plot_->setMinimumSize(0, 0);
|
||
|
||
// 锁定 x:y 真实比尺(参考轴 xBottom,Expanding)。
|
||
rescaler_ = new QwtPlotRescaler(plot_->canvas(), QwtPlot::xBottom, QwtPlotRescaler::Expanding);
|
||
rescaler_->setAspectRatio(1.0);
|
||
rescaler_->setEnabled(true);
|
||
|
||
// 图表区 = 绘图 + 色阶条(色阶条固定高 36)。
|
||
colorBar_ = new ColorBarWidget(this);
|
||
colorBar_->setObjectName(QStringLiteral("gridColorScaleBar"));
|
||
auto* chartArea = new QWidget(this);
|
||
auto* chartLay = new QVBoxLayout(chartArea);
|
||
chartLay->setContentsMargins(0, 0, 0, 0);
|
||
chartLay->setSpacing(0);
|
||
chartLay->addWidget(plot_, 1);
|
||
chartLay->addWidget(colorBar_);
|
||
|
||
// ---- 底部双页签(异常列表 / 描述)----
|
||
anomalyTable_ = new AnomalyTablePanel(this);
|
||
descriptionPanel_ = new DescriptionPanel(this);
|
||
const QVector<PanelTab> bottomTabs = {
|
||
{Glyph::Anomaly, QStringLiteral("异常列表"), anomalyTable_, true},
|
||
{Glyph::Property, QStringLiteral("描述"), descriptionPanel_, false},
|
||
};
|
||
auto tabbedBottom = buildTabbedPanel(bottomTabs, {});
|
||
|
||
// 图表区 | 底部表 竖直 QSplitter(可拖拽调整比例)。给定最小高度→分割器有最小尺寸。
|
||
chartArea->setMinimumHeight(280);
|
||
tabbedBottom.container->setMinimumHeight(160);
|
||
auto* splitter = new QSplitter(Qt::Vertical);
|
||
splitter->addWidget(chartArea);
|
||
splitter->addWidget(tabbedBottom.container);
|
||
splitter->setStretchFactor(0, 3);
|
||
splitter->setStretchFactor(1, 1);
|
||
splitter->setChildrenCollapsible(false);
|
||
|
||
// 页签内滚动:把(图表+异常)分割器放进 QScrollArea。dock 够高→分割器填满(可拖动调整);
|
||
// dock 太矮→在页签内部出现竖滚动条(工具条/页签/标题固定),而非整个面板滚动。
|
||
auto* scroll = new QScrollArea(this);
|
||
scroll->setWidgetResizable(true);
|
||
scroll->setFrameShape(QFrame::NoFrame);
|
||
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||
scroll->setWidget(splitter);
|
||
lay->addWidget(scroll, 1);
|
||
|
||
// ---- 工具条开关 → 重建/重绘 ----
|
||
connect(chkShowAnom, &QCheckBox::toggled, this, [this](bool on) {
|
||
showAnomalies_ = on;
|
||
if (contourItem_) { contourItem_->setShowAnomalies(on); plot_->replot(); }
|
||
});
|
||
connect(chkShowContourLabel, &QCheckBox::toggled, this, [this](bool on) {
|
||
showLabels_ = on;
|
||
if (contourItem_) { contourItem_->setShowLabels(on); plot_->replot(); }
|
||
});
|
||
}
|
||
|
||
GridDataChartView::~GridDataChartView() {
|
||
// colorSvc_ 非 QObject、无 parent,需手动释放(contourItem_ 由 QwtPlot autoDelete 处理)。
|
||
delete colorSvc_;
|
||
}
|
||
|
||
void GridDataChartView::setData(const geopro::controller::DatasetDetailController::ChartData& d) {
|
||
data_ = d;
|
||
// 开页:仅把 anomalies 喂给底部异常列表;图表区待网格数据懒加载后填充。
|
||
anomalyTable_->setAnomalies(d.anomalies, {}, {});
|
||
}
|
||
|
||
void GridDataChartView::setGridData(const geopro::core::Grid& grid,
|
||
const geopro::core::ColorScale& gridScale,
|
||
const std::vector<geopro::core::Anomaly>& anoms) {
|
||
grid_ = grid;
|
||
gridScale_ = gridScale;
|
||
anoms_ = anoms;
|
||
hasGrid_ = true;
|
||
|
||
// 重建色彩服务(旧 contourItem 已 detach/delete)。
|
||
delete colorSvc_;
|
||
colorSvc_ = new ColorMapService(gridScale_);
|
||
|
||
rebuildContour();
|
||
|
||
// 色阶条 + 底部异常表(懒加载结果含真实异常)。
|
||
colorBar_->setColorScale(gridScale_);
|
||
anomalyTable_->setAnomalies(anoms_, {}, {});
|
||
}
|
||
|
||
void GridDataChartView::rebuildContour() {
|
||
if (!hasGrid_ || !colorSvc_) return;
|
||
|
||
// 卸载旧项:QwtPlot 默认 autoDelete=true(析构时 delete 仍在 dict 的 item)。
|
||
// 必须先 detach()(从 dict 移除)再 delete,否则 QwtPlot 析构时会 double-free。
|
||
if (contourItem_) {
|
||
contourItem_->detach();
|
||
delete contourItem_;
|
||
contourItem_ = nullptr;
|
||
}
|
||
|
||
contourItem_ = new ContourPlotItem();
|
||
contourItem_->setData(grid_, colorSvc_, anoms_, /*showLines*/ true, showLabels_);
|
||
contourItem_->setShowAnomalies(showAnomalies_);
|
||
contourItem_->attach(plot_);
|
||
|
||
// 轴范围 = 数据范围(x=距离、y=深度/高程)。
|
||
const QRectF bbox = contourItem_->boundingRect();
|
||
if (!bbox.isNull()) {
|
||
plot_->setAxisScale(QwtPlot::xBottom, bbox.left(), bbox.right());
|
||
plot_->setAxisScale(QwtPlot::yLeft, bbox.top(), bbox.bottom());
|
||
}
|
||
plot_->updateAxes();
|
||
if (rescaler_) rescaler_->rescale(); // 应用真实比尺
|
||
plot_->replot();
|
||
}
|
||
|
||
} // namespace geopro::app
|