feat(ui): 数据集详情三层UI骨架(下划线页签+原数据/网格各自工具条+网格页底部异常列表/描述)

- 新建 DescriptionPanel:只读 QTextEdit,供网格数据底部「描述」页签使用
- 新建 RawDataChartView:工具条(网格/色阶配置/图形类型ComboBox/另存为) + 图表占位(stretch) + 色阶占位(36px)
- 新建 GridDataChartView:工具条(网格/色阶配置/白化/滤波/显示开关/简化容差滑块/异常标注/自动标注/另存为) + 图表占位(stretch) + 色阶占位(36px) + 底部200px双页签(异常列表/描述)
- 重写 DatasetDetailPage:用 buildTabbedPanel 做「原数据/网格数据」下划线页签 + 右侧「导出」HeaderAction
- 删除 DatasetChartView(已被 RawDataChartView/GridDataChartView 替代)
- CMakeLists:注册新 .cpp,移除 DatasetChartView,条件链接 qwt 目标
This commit is contained in:
gaozheng 2026-06-11 15:04:34 +08:00
parent e0c36e3600
commit 7808b8422a
11 changed files with 332 additions and 319 deletions

View File

@ -26,7 +26,9 @@ add_executable(geopro_desktop WIN32
panels/ObjectTreePanel.cpp panels/ObjectTreePanel.cpp
panels/DynamicFormView.cpp panels/DynamicFormView.cpp
panels/ObjectExceptionPanel.cpp panels/ObjectExceptionPanel.cpp
panels/chart/DatasetChartView.cpp panels/DescriptionPanel.cpp
panels/chart/RawDataChartView.cpp
panels/chart/GridDataChartView.cpp
panels/AnomalyTablePanel.cpp panels/AnomalyTablePanel.cpp
panels/DatasetDetailPage.cpp panels/DatasetDetailPage.cpp
panels/DatasetDetailPanel.cpp panels/DatasetDetailPanel.cpp
@ -51,6 +53,11 @@ target_link_libraries(geopro_desktop PRIVATE
geopro_controller # Phase 5WorkbenchNavController geopro_controller # Phase 5WorkbenchNavController
) )
# Qwt CMake qwt 使
if(TARGET qwt)
target_link_libraries(geopro_desktop PRIVATE qwt)
endif()
vtk_module_autoinit(TARGETS geopro_desktop MODULES ${VTK_LIBRARIES}) vtk_module_autoinit(TARGETS geopro_desktop MODULES ${VTK_LIBRARIES})
if(WIN32) if(WIN32)

View File

@ -1,52 +1,38 @@
#include "panels/DatasetDetailPage.hpp" #include "panels/DatasetDetailPage.hpp"
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel> #include "Glyphs.hpp"
#include <QToolButton> #include "PanelHeader.hpp"
#include <QButtonGroup> #include "panels/chart/GridDataChartView.hpp"
#include <QCheckBox> #include "panels/chart/RawDataChartView.hpp"
#include "panels/chart/DatasetChartView.hpp"
#include "panels/AnomalyTablePanel.hpp"
namespace geopro::app { namespace geopro::app {
DatasetDetailPage::DatasetDetailPage(QWidget* parent) : QWidget(parent) { DatasetDetailPage::DatasetDetailPage(QWidget* parent) : QWidget(parent) {
auto* lay = new QVBoxLayout(this); auto* lay = new QVBoxLayout(this);
auto* bar = new QHBoxLayout(); lay->setContentsMargins(0, 0, 0, 0);
auto* origin = new QToolButton(this); origin->setText("原数据"); origin->setCheckable(true); lay->setSpacing(0);
auto* grid = new QToolButton(this); grid->setText("网格数据"); grid->setCheckable(true);
grid->setChecked(true);
auto* grp = new QButtonGroup(this); grp->setExclusive(true); grp->addButton(origin); grp->addButton(grid);
auto* showAnom = new QCheckBox("显示异常", this); showAnom->setChecked(true);
auto* showLines = new QCheckBox("显示等值线", this); showLines->setChecked(true);
bar->addWidget(origin); bar->addWidget(grid); bar->addStretch();
bar->addWidget(showAnom); bar->addWidget(showLines);
lay->addLayout(bar);
chart_ = new DatasetChartView(this); rawView_ = new RawDataChartView(this);
anomalyTable_ = new AnomalyTablePanel(this); gridView_ = new GridDataChartView(this);
lay->addWidget(chart_, 3);
lay->addWidget(anomalyTable_, 1);
connect(grid, &QToolButton::clicked, this, [this] { gridMode_ = true; showGridMode(); }); const QVector<PanelTab> tabs = {
connect(origin, &QToolButton::clicked, this, [this] { gridMode_ = false; showScatterMode(); }); {Glyph::Detail, QStringLiteral("原数据"), rawView_, false},
connect(showAnom, &QCheckBox::toggled, chart_, [this](bool on) { chart_->setShowAnomalies(on); }); {Glyph::Dataset, QStringLiteral("网格数据"), gridView_, false},
connect(showLines, &QCheckBox::toggled, chart_, [this](bool on) { };
chart_->setShowContourLines(on); if (gridMode_) showGridMode(); }); const QVector<HeaderAction> actions = {
connect(anomalyTable_, &AnomalyTablePanel::hiddenChanged, chart_, {Glyph::Download, QStringLiteral("导出")},
[this](const std::set<int>& h) { chart_->setHiddenAnomalies(h); }); };
auto tabbedPanel = buildTabbedPanel(tabs, actions);
lay->addWidget(tabbedPanel.container);
} }
void DatasetDetailPage::setData(const geopro::controller::DatasetDetailController::ChartData& d) { void DatasetDetailPage::setData(const geopro::controller::DatasetDetailController::ChartData& d) {
dsId_ = d.dsId; data_ = d; dsId_ = d.dsId;
chart_->setAnomalies(d.anomalies); rawView_->setData(d);
anomalyTable_->setAnomalies(d.anomalies, {}, {}); // 创建时间/备注可后续从 VO 补 gridView_->setData(d);
if (gridMode_) showGridMode(); else showScatterMode();
}
void DatasetDetailPage::showGridMode() {
geopro::render::ContourOptions opt; // 默认 2x+smooth+simplify0.5
chart_->showContour(data_.grid, data_.gridScale, opt);
}
void DatasetDetailPage::showScatterMode() {
chart_->showScatter(data_.scatter, data_.scatterScale);
} }
} // namespace geopro::app } // namespace geopro::app

View File

@ -1,10 +1,14 @@
#pragma once #pragma once
#include <QWidget> #include <QWidget>
#include "DatasetDetailController.hpp" // ChartData #include "DatasetDetailController.hpp"
namespace geopro::app {
class DatasetChartView; class AnomalyTablePanel;
// 单个数据集详情页:标题 + 原数据/网格数据 切换 + 叠加开关 + 图表 + 异常表。 namespace geopro::app {
class RawDataChartView;
class GridDataChartView;
// 单个数据集详情页:下划线页签「原数据 / 网格数据」+ 右侧「导出」操作。
// 内部分别由 RawDataChartView / GridDataChartView 实现各自三层布局。
class DatasetDetailPage : public QWidget { class DatasetDetailPage : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
@ -12,11 +16,9 @@ public:
void setData(const geopro::controller::DatasetDetailController::ChartData& d); void setData(const geopro::controller::DatasetDetailController::ChartData& d);
QString dsId() const { return dsId_; } QString dsId() const { return dsId_; }
private: private:
void showScatterMode(); void showGridMode();
QString dsId_; QString dsId_;
geopro::controller::DatasetDetailController::ChartData data_; RawDataChartView* rawView_;
DatasetChartView* chart_; GridDataChartView* gridView_;
AnomalyTablePanel* anomalyTable_;
bool gridMode_ = true;
}; };
} // namespace geopro::app } // namespace geopro::app

View File

@ -0,0 +1,22 @@
#include "panels/DescriptionPanel.hpp"
#include <QTextEdit>
#include <QVBoxLayout>
namespace geopro::app {
DescriptionPanel::DescriptionPanel(QWidget* parent) : QWidget(parent) {
auto* lay = new QVBoxLayout(this);
lay->setContentsMargins(8, 8, 8, 8);
edit_ = new QTextEdit(this);
edit_->setReadOnly(true);
edit_->setPlaceholderText(QStringLiteral("暂无描述"));
lay->addWidget(edit_);
}
void DescriptionPanel::setText(const QString& text) {
edit_->setPlainText(text);
}
} // namespace geopro::app

View File

@ -0,0 +1,18 @@
#pragma once
#include <QWidget>
class QTextEdit;
namespace geopro::app {
// 数据集描述面板:只读文本,供网格数据底部页签「描述」使用。
class DescriptionPanel : public QWidget {
Q_OBJECT
public:
explicit DescriptionPanel(QWidget* parent = nullptr);
void setText(const QString& text);
private:
QTextEdit* edit_;
};
} // namespace geopro::app

View File

@ -1,232 +0,0 @@
#include "panels/chart/DatasetChartView.hpp"
#include <algorithm>
#include <QGraphicsScene>
#include <QGraphicsPathItem>
#include <QGraphicsRectItem>
#include <QPainterPath>
#include <QPen>
#include <QBrush>
#include <QColor>
#include <QWheelEvent>
#include <QPainter>
#include <QFontMetrics>
namespace geopro::app {
using geopro::core::Rgba;
static QColor toQ(const Rgba& c) { return QColor(c.r, c.g, c.b, c.a); }
DatasetChartView::DatasetChartView(QWidget* parent)
: QGraphicsView(parent), scene_(new QGraphicsScene(this)) {
setScene(scene_);
setRenderHint(QPainter::Antialiasing, true);
setDragMode(QGraphicsView::ScrollHandDrag);
// 场景 y 向上为正:用 y 翻转的变换QGraphicsView 默认 y 向下)。
scale(1, -1);
}
void DatasetChartView::clearChart() {
// scene_->clear() 删除场景中的所有 item含 anomalyItems_ 所指向的对象);
// anomalyItems_.clear() 随后清空向量中的悬空指针。
// 二者必须成对、顺序不可颠倒:若先 clear() 后仍持有指针rebuildAnomalyItems
// 中的 delete 会对已释放对象 double-free若先清向量、后继续操作旧指针同样 UB。
scene_->clear(); anomalyItems_.clear();
}
void DatasetChartView::showScatter(const geopro::core::ScatterField& f, const geopro::core::ColorScale& cs) {
clearChart();
const double sz = 0.6; // 方块边长(数据单位,近似 web 方点)
const size_t n = std::min({f.x.size(), f.y.size(), f.v.size()});
for (size_t i = 0; i < n; ++i) {
auto* r = scene_->addRect(f.x[i] - sz / 2, f.y[i] - sz / 2, sz, sz,
QPen(Qt::white, 0), QBrush(toQ(cs.colorAt(f.v[i]))));
r->setPen(QPen(Qt::white, 0)); // 白描边
}
rebuildAnomalyItems();
setLegend(cs);
viewport()->update();
fitInView(scene_->itemsBoundingRect(), Qt::KeepAspectRatio); // x:y 等比
}
void DatasetChartView::showContour(const geopro::core::Grid& g, const geopro::core::ColorScale& cs,
const geopro::render::ContourOptions& opt) {
clearChart();
const auto r = geopro::render::buildContourBands(g, cs, opt);
for (const auto& b : r.bands) {
QPainterPath path; if (b.ring.empty()) continue;
path.moveTo(b.ring[0].x, b.ring[0].y);
for (size_t k = 1; k < b.ring.size(); ++k) path.lineTo(b.ring[k].x, b.ring[k].y);
path.closeSubpath();
auto* it = scene_->addPath(path, QPen(Qt::NoPen), QBrush(toQ(b.color)));
it->setZValue(0);
}
if (showContourLines_)
for (const auto& l : r.lines) {
if (l.pts.empty()) continue;
QPainterPath path; path.moveTo(l.pts[0].x, l.pts[0].y);
for (size_t k = 1; k < l.pts.size(); ++k) path.lineTo(l.pts[k].x, l.pts[k].y);
auto* it = scene_->addPath(path, QPen(QColor(0, 0, 0), 0));
it->setZValue(1);
}
rebuildAnomalyItems();
setLegend(cs);
viewport()->update();
fitInView(scene_->itemsBoundingRect(), Qt::IgnoreAspectRatio); // 剖面 X/Y 各自铺满
}
void DatasetChartView::setAnomalies(const std::vector<geopro::core::Anomaly>& list) {
anomalies_ = list; rebuildAnomalyItems();
}
void DatasetChartView::setHiddenAnomalies(const std::set<int>& hidden) { hidden_ = hidden; rebuildAnomalyItems(); }
void DatasetChartView::setShowAnomalies(bool on) { showAnomalies_ = on; rebuildAnomalyItems(); }
void DatasetChartView::setShowContourLines(bool on) { showContourLines_ = on; }
void DatasetChartView::rebuildAnomalyItems() {
for (auto* it : anomalyItems_) { scene_->removeItem(it); delete it; }
anomalyItems_.clear();
if (!showAnomalies_) return;
for (int i = 0; i < static_cast<int>(anomalies_.size()); ++i) {
if (hidden_.count(i)) continue;
const auto& a = anomalies_[i];
if (a.localPts.empty()) continue;
if (static_cast<int>(a.markType) == 1) { // 点:小方块标记
const double s = 0.8;
auto* dot = scene_->addRect(a.localPts[0].x - s/2, a.localPts[0].y - s/2, s, s,
QPen(QColor(QString::fromStdString(a.lineColor)), 0),
QBrush(QColor(QString::fromStdString(a.lineColor))));
dot->setZValue(2);
anomalyItems_.push_back(dot);
continue;
}
if (a.localPts.size() < 2) continue;
QPainterPath path; path.moveTo(a.localPts[0].x, a.localPts[0].y);
for (size_t k = 1; k < a.localPts.size(); ++k) path.lineTo(a.localPts[k].x, a.localPts[k].y);
if (static_cast<int>(a.markType) == 3) path.closeSubpath();
QPen pen(QColor(QString::fromStdString(a.lineColor)), 0);
if (a.dashed) pen.setStyle(Qt::DashLine);
auto* it = scene_->addPath(path, pen);
it->setZValue(2);
anomalyItems_.push_back(it);
}
}
void DatasetChartView::wheelEvent(QWheelEvent* e) {
const double f = e->angleDelta().y() > 0 ? 1.15 : 1 / 1.15;
scale(f, f);
}
// Task 3.2:坐标轴 overlay + 底部色阶图例。
// drawForeground 在每次 repaint 时调用(缩放/平移后自动重绘),无需手动更新。
// 轴线"钉"在 viewport 左/下边:以 viewport()->rect() 为参考,
// 用 mapToScene / mapFromScene 把场景范围映射到像素位置。
void DatasetChartView::drawForeground(QPainter* p, const QRectF& /*rect*/) {
if (legendScale_.empty()) return;
p->save();
// 重置变换,后续全用视口像素坐标绘制
p->resetTransform();
const QRect vr = viewport()->rect();
const int W = vr.width();
const int H = vr.height();
// ── 色阶图例(底部)────────────────────────────────
const int legendH = 16; // 色条高度px
const int legendMarginB = 20; // 底部文字留白
const int legendMarginL = 50; // 左侧轴标签留白
const int legendMarginR = 10;
const int legendY = H - legendMarginB - legendH;
const int legendW = W - legendMarginL - legendMarginR;
const std::vector<double> stops = legendScale_.stopValues();
if (stops.size() >= 2 && legendW > 0) {
const int nSeg = static_cast<int>(stops.size()) - 1;
const double segW = static_cast<double>(legendW) / nSeg;
for (int s = 0; s < nSeg; ++s) {
// 取该段中点值的颜色(与 web 阶梯色一致)
const double midVal = (stops[s] + stops[s + 1]) * 0.5;
const QColor c = toQ(legendScale_.colorAt(midVal));
const int x0 = legendMarginL + static_cast<int>(s * segW);
const int x1 = legendMarginL + static_cast<int>((s + 1) * segW);
p->fillRect(QRect(x0, legendY, x1 - x0, legendH), c);
}
// 边框
p->setPen(QPen(QColor(80, 80, 80), 1));
p->drawRect(QRect(legendMarginL, legendY, legendW, legendH));
// 分段值文字(在每个分割点下方)
p->setPen(Qt::black);
QFont font = p->font();
font.setPixelSize(10);
p->setFont(font);
for (int s = 0; s <= nSeg; ++s) {
const int xTick = legendMarginL + static_cast<int>(s * segW);
const QString label = QString::number(stops[s], 'g', 4);
QRect textR(xTick - 20, legendY + legendH + 2, 40, 14);
p->drawText(textR, Qt::AlignCenter, label);
}
}
// ── 坐标轴(左/下边钉住视口)────────────────────────
// 把视口四个角映射到场景,得到场景可见范围
const QPointF sceneTL = mapToScene(vr.topLeft());
const QPointF sceneBR = mapToScene(vr.bottomRight());
// 因为 y 轴翻转scale(1,-1)),场景坐标 y 向上为正
// sceneTL 的 scene-y 可能大于 sceneBR 的 scene-y需取 min/max
const double sceneXMin = qMin(sceneTL.x(), sceneBR.x());
const double sceneXMax = qMax(sceneTL.x(), sceneBR.x());
const double sceneYMin = qMin(sceneTL.y(), sceneBR.y());
const double sceneYMax = qMax(sceneTL.y(), sceneBR.y());
const int axisLeft = legendMarginL; // 左轴 x 像素
const int axisBottom = legendY - 4; // 下轴 y 像素(色阶条上方留 4px
p->setPen(QPen(QColor(100, 100, 100), 1));
// 左侧轴线(竖)
p->drawLine(axisLeft, 4, axisLeft, axisBottom);
// 下轴线(横)
p->drawLine(axisLeft, axisBottom, W - legendMarginR, axisBottom);
// 刻度数6~8 个)
const int nTick = 7;
QFont tickFont = p->font();
tickFont.setPixelSize(10);
p->setFont(tickFont);
// X 轴刻度(下轴)
const double xRange = sceneXMax - sceneXMin;
if (xRange > 0) {
for (int t = 0; t <= nTick; ++t) {
const double sx = sceneXMin + xRange * t / nTick;
const QPoint vp = mapFromScene(QPointF(sx, sceneYMin)); // 场景 → 视口像素
const int px = vp.x();
if (px < axisLeft || px > W - legendMarginR) continue;
p->setPen(QPen(QColor(100, 100, 100), 1));
p->drawLine(px, axisBottom, px, axisBottom + 4);
p->setPen(Qt::black);
const QString label = QString::number(sx, 'g', 4);
p->drawText(QRect(px - 20, axisBottom + 5, 40, 12), Qt::AlignCenter, label);
}
}
// Y 轴刻度(左轴)
const double yRange = sceneYMax - sceneYMin;
if (yRange > 0) {
for (int t = 0; t <= nTick; ++t) {
const double sy = sceneYMin + yRange * t / nTick;
const QPoint vp = mapFromScene(QPointF(sceneXMin, sy));
const int py = vp.y();
if (py < 4 || py > axisBottom) continue;
p->setPen(QPen(QColor(100, 100, 100), 1));
p->drawLine(axisLeft - 4, py, axisLeft, py);
p->setPen(Qt::black);
const QString label = QString::number(sy, 'g', 4);
p->drawText(QRect(0, py - 7, axisLeft - 5, 14), Qt::AlignRight | Qt::AlignVCenter, label);
}
}
p->restore();
}
} // namespace geopro::app

View File

@ -1,38 +0,0 @@
#pragma once
#include <set>
#include <vector>
#include <QGraphicsView>
#include "model/Field.hpp"
#include "model/ColorScale.hpp"
#include "model/Anomaly.hpp"
#include "ContourBands.hpp"
namespace geopro::app {
// 平面图表视图:散点(原数据) / 等值面(网格) + 异常叠加 + 色阶图例。VTK 仅经 ContourBands 算几何。
class DatasetChartView : public QGraphicsView {
Q_OBJECT
public:
explicit DatasetChartView(QWidget* parent = nullptr);
void showScatter(const geopro::core::ScatterField& f, const geopro::core::ColorScale& cs);
void showContour(const geopro::core::Grid& g, const geopro::core::ColorScale& cs,
const geopro::render::ContourOptions& opt);
void setAnomalies(const std::vector<geopro::core::Anomaly>& list);
void setHiddenAnomalies(const std::set<int>& hidden); // 下标=list 序
void setShowAnomalies(bool on);
void setShowContourLines(bool on);
void clearChart();
protected:
void wheelEvent(QWheelEvent* e) override; // 滚轮缩放
void drawForeground(QPainter* p, const QRectF& rect) override;
private:
void rebuildAnomalyItems();
void setLegend(const geopro::core::ColorScale& cs) { legendScale_ = cs; }
QGraphicsScene* scene_;
std::vector<geopro::core::Anomaly> anomalies_;
std::set<int> hidden_;
bool showAnomalies_ = true;
bool showContourLines_ = true;
std::vector<class QGraphicsItem*> anomalyItems_;
geopro::core::ColorScale legendScale_;
};
} // namespace geopro::app

View File

@ -0,0 +1,120 @@
#include "panels/chart/GridDataChartView.hpp"
#include <QCheckBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QSlider>
#include <QToolButton>
#include <QVBoxLayout>
#include "PanelHeader.hpp"
#include "panels/AnomalyTablePanel.hpp"
#include "panels/DescriptionPanel.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);
// ---- 图表占位区stretch ----
plotArea_ = new QWidget(this);
plotArea_->setObjectName(QStringLiteral("gridPlotArea"));
lay->addWidget(plotArea_, 1);
// ---- 色阶占位(固定高 36px ----
auto* colorScaleBar = new QWidget(this);
colorScaleBar->setObjectName(QStringLiteral("gridColorScaleBar"));
colorScaleBar->setFixedHeight(36);
lay->addWidget(colorScaleBar);
// ---- 底部双页签(异常列表 / 描述),固定高约 200px ----
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, {});
auto* bottomContainer = tabbedBottom.container;
bottomContainer->setFixedHeight(200);
lay->addWidget(bottomContainer);
}
void GridDataChartView::setData(const geopro::controller::DatasetDetailController::ChartData& d) {
data_ = d;
// 步骤1只把 anomalies 喂给底部异常列表;图表区留空。
anomalyTable_->setAnomalies(d.anomalies, {}, {});
}
} // namespace geopro::app

View File

@ -0,0 +1,38 @@
#pragma once
#include <QWidget>
#include "DatasetDetailController.hpp"
class QSlider;
class QLabel;
namespace geopro::app {
class AnomalyTablePanel;
class DescriptionPanel;
// 网格数据图表视图步骤1骨架工具条 + 图表占位 + 色阶占位 + 底部双页签(异常列表/描述)。
// 图表渲染由后续步骤填入 plotArea()。
class GridDataChartView : public QWidget {
Q_OBJECT
public:
explicit GridDataChartView(QWidget* parent = nullptr);
// 步骤1把 anomalies 喂给底部异常列表;图表区留空。
void setData(const geopro::controller::DatasetDetailController::ChartData& d);
// 供后续步骤填充 Qwt 画布的占位区域。
QWidget* plotArea() const { return plotArea_; }
// 供后续步骤联动异常叠加。
AnomalyTablePanel* anomalyTable() const { return anomalyTable_; }
private:
geopro::controller::DatasetDetailController::ChartData data_;
QWidget* plotArea_;
AnomalyTablePanel* anomalyTable_;
DescriptionPanel* descriptionPanel_;
QSlider* simplifySlider_;
QLabel* simplifyValueLabel_;
};
} // namespace geopro::app

View File

@ -0,0 +1,62 @@
#include "panels/chart/RawDataChartView.hpp"
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QToolButton>
#include <QVBoxLayout>
namespace geopro::app {
RawDataChartView::RawDataChartView(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* lblCurrentChart = new QLabel(QStringLiteral("当前图形:"), toolbar);
chartTypeCombo_ = new QComboBox(toolbar);
chartTypeCombo_->addItem(QStringLiteral("散点图"));
auto* btnSaveAs = new QToolButton(toolbar);
btnSaveAs->setText(QStringLiteral("另存为"));
tbLay->addWidget(btnGrid);
tbLay->addWidget(btnColorScale);
tbLay->addWidget(lblCurrentChart);
tbLay->addWidget(chartTypeCombo_);
tbLay->addWidget(btnSaveAs);
tbLay->addStretch();
lay->addWidget(toolbar);
// ---- 图表占位区stretch ----
plotArea_ = new QWidget(this);
plotArea_->setObjectName(QStringLiteral("rawPlotArea"));
lay->addWidget(plotArea_, 1);
// ---- 色阶占位(固定高 36px ----
auto* colorScaleBar = new QWidget(this);
colorScaleBar->setObjectName(QStringLiteral("rawColorScaleBar"));
colorScaleBar->setFixedHeight(36);
lay->addWidget(colorScaleBar);
}
void RawDataChartView::setData(const geopro::controller::DatasetDetailController::ChartData& d) {
// 步骤1保存数据图表渲染留给后续步骤。
data_ = d;
}
} // namespace geopro::app

View File

@ -0,0 +1,28 @@
#pragma once
#include <QWidget>
#include "DatasetDetailController.hpp"
class QComboBox;
namespace geopro::app {
// 原数据图表视图步骤1骨架工具条 + 图表占位 + 色阶占位。
// 图表渲染由后续步骤填入 plotArea()。
class RawDataChartView : public QWidget {
Q_OBJECT
public:
explicit RawDataChartView(QWidget* parent = nullptr);
// 步骤1保存数据备后续渲染步骤使用图表区留空。
void setData(const geopro::controller::DatasetDetailController::ChartData& d);
// 供后续步骤填充 Qwt 画布的占位区域。
QWidget* plotArea() const { return plotArea_; }
private:
geopro::controller::DatasetDetailController::ChartData data_;
QWidget* plotArea_;
QComboBox* chartTypeCombo_;
};
} // namespace geopro::app