diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index ea0f23a..1b82e36 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -26,7 +26,9 @@ add_executable(geopro_desktop WIN32 panels/ObjectTreePanel.cpp panels/DynamicFormView.cpp panels/ObjectExceptionPanel.cpp - panels/chart/DatasetChartView.cpp + panels/DescriptionPanel.cpp + panels/chart/RawDataChartView.cpp + panels/chart/GridDataChartView.cpp panels/AnomalyTablePanel.cpp panels/DatasetDetailPage.cpp panels/DatasetDetailPanel.cpp @@ -51,6 +53,11 @@ target_link_libraries(geopro_desktop PRIVATE geopro_controller # Phase 5:导航编排(WorkbenchNavController) ) +# Qwt(二维科学图表;源码存在时由根 CMake 定义 qwt 目标)。本步骤骨架先链好供后续渲染步骤使用。 +if(TARGET qwt) + target_link_libraries(geopro_desktop PRIVATE qwt) +endif() + vtk_module_autoinit(TARGETS geopro_desktop MODULES ${VTK_LIBRARIES}) if(WIN32) diff --git a/src/app/panels/DatasetDetailPage.cpp b/src/app/panels/DatasetDetailPage.cpp index 6a2f901..2c8b7b7 100644 --- a/src/app/panels/DatasetDetailPage.cpp +++ b/src/app/panels/DatasetDetailPage.cpp @@ -1,52 +1,38 @@ #include "panels/DatasetDetailPage.hpp" + #include -#include -#include -#include -#include -#include -#include "panels/chart/DatasetChartView.hpp" -#include "panels/AnomalyTablePanel.hpp" + +#include "Glyphs.hpp" +#include "PanelHeader.hpp" +#include "panels/chart/GridDataChartView.hpp" +#include "panels/chart/RawDataChartView.hpp" + namespace geopro::app { DatasetDetailPage::DatasetDetailPage(QWidget* parent) : QWidget(parent) { auto* lay = new QVBoxLayout(this); - auto* bar = new QHBoxLayout(); - auto* origin = new QToolButton(this); origin->setText("原数据"); origin->setCheckable(true); - 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); + lay->setContentsMargins(0, 0, 0, 0); + lay->setSpacing(0); - chart_ = new DatasetChartView(this); - anomalyTable_ = new AnomalyTablePanel(this); - lay->addWidget(chart_, 3); - lay->addWidget(anomalyTable_, 1); + rawView_ = new RawDataChartView(this); + gridView_ = new GridDataChartView(this); - connect(grid, &QToolButton::clicked, this, [this] { gridMode_ = true; showGridMode(); }); - connect(origin, &QToolButton::clicked, this, [this] { gridMode_ = false; showScatterMode(); }); - connect(showAnom, &QCheckBox::toggled, chart_, [this](bool on) { chart_->setShowAnomalies(on); }); - connect(showLines, &QCheckBox::toggled, chart_, [this](bool on) { - chart_->setShowContourLines(on); if (gridMode_) showGridMode(); }); - connect(anomalyTable_, &AnomalyTablePanel::hiddenChanged, chart_, - [this](const std::set& h) { chart_->setHiddenAnomalies(h); }); + const QVector tabs = { + {Glyph::Detail, QStringLiteral("原数据"), rawView_, false}, + {Glyph::Dataset, QStringLiteral("网格数据"), gridView_, false}, + }; + const QVector actions = { + {Glyph::Download, QStringLiteral("导出")}, + }; + + auto tabbedPanel = buildTabbedPanel(tabs, actions); + lay->addWidget(tabbedPanel.container); } void DatasetDetailPage::setData(const geopro::controller::DatasetDetailController::ChartData& d) { - dsId_ = d.dsId; data_ = d; - chart_->setAnomalies(d.anomalies); - anomalyTable_->setAnomalies(d.anomalies, {}, {}); // 创建时间/备注可后续从 VO 补 - 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); + dsId_ = d.dsId; + rawView_->setData(d); + gridView_->setData(d); } + } // namespace geopro::app diff --git a/src/app/panels/DatasetDetailPage.hpp b/src/app/panels/DatasetDetailPage.hpp index 72b6727..ba1e29f 100644 --- a/src/app/panels/DatasetDetailPage.hpp +++ b/src/app/panels/DatasetDetailPage.hpp @@ -1,10 +1,14 @@ #pragma once #include -#include "DatasetDetailController.hpp" // ChartData -namespace geopro::app { -class DatasetChartView; class AnomalyTablePanel; +#include "DatasetDetailController.hpp" -// 单个数据集详情页:标题 + 原数据/网格数据 切换 + 叠加开关 + 图表 + 异常表。 +namespace geopro::app { + +class RawDataChartView; +class GridDataChartView; + +// 单个数据集详情页:下划线页签「原数据 / 网格数据」+ 右侧「导出」操作。 +// 内部分别由 RawDataChartView / GridDataChartView 实现各自三层布局。 class DatasetDetailPage : public QWidget { Q_OBJECT public: @@ -12,11 +16,9 @@ public: void setData(const geopro::controller::DatasetDetailController::ChartData& d); QString dsId() const { return dsId_; } private: - void showScatterMode(); void showGridMode(); QString dsId_; - geopro::controller::DatasetDetailController::ChartData data_; - DatasetChartView* chart_; - AnomalyTablePanel* anomalyTable_; - bool gridMode_ = true; + RawDataChartView* rawView_; + GridDataChartView* gridView_; }; + } // namespace geopro::app diff --git a/src/app/panels/DescriptionPanel.cpp b/src/app/panels/DescriptionPanel.cpp new file mode 100644 index 0000000..5289b9d --- /dev/null +++ b/src/app/panels/DescriptionPanel.cpp @@ -0,0 +1,22 @@ +#include "panels/DescriptionPanel.hpp" + +#include +#include + +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 diff --git a/src/app/panels/DescriptionPanel.hpp b/src/app/panels/DescriptionPanel.hpp new file mode 100644 index 0000000..b364fae --- /dev/null +++ b/src/app/panels/DescriptionPanel.hpp @@ -0,0 +1,18 @@ +#pragma once +#include + +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 diff --git a/src/app/panels/chart/DatasetChartView.cpp b/src/app/panels/chart/DatasetChartView.cpp deleted file mode 100644 index d1dff50..0000000 --- a/src/app/panels/chart/DatasetChartView.cpp +++ /dev/null @@ -1,232 +0,0 @@ -#include "panels/chart/DatasetChartView.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -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& list) { - anomalies_ = list; rebuildAnomalyItems(); -} -void DatasetChartView::setHiddenAnomalies(const std::set& 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(anomalies_.size()); ++i) { - if (hidden_.count(i)) continue; - const auto& a = anomalies_[i]; - if (a.localPts.empty()) continue; - if (static_cast(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(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 stops = legendScale_.stopValues(); - if (stops.size() >= 2 && legendW > 0) { - const int nSeg = static_cast(stops.size()) - 1; - const double segW = static_cast(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(s * segW); - const int x1 = legendMarginL + static_cast((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(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 diff --git a/src/app/panels/chart/DatasetChartView.hpp b/src/app/panels/chart/DatasetChartView.hpp deleted file mode 100644 index 8533181..0000000 --- a/src/app/panels/chart/DatasetChartView.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -#include -#include -#include -#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& list); - void setHiddenAnomalies(const std::set& 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 anomalies_; - std::set hidden_; - bool showAnomalies_ = true; - bool showContourLines_ = true; - std::vector anomalyItems_; - geopro::core::ColorScale legendScale_; -}; -} // namespace geopro::app diff --git a/src/app/panels/chart/GridDataChartView.cpp b/src/app/panels/chart/GridDataChartView.cpp new file mode 100644 index 0000000..c9847cc --- /dev/null +++ b/src/app/panels/chart/GridDataChartView.cpp @@ -0,0 +1,120 @@ +#include "panels/chart/GridDataChartView.hpp" + +#include +#include +#include +#include +#include +#include + +#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 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 diff --git a/src/app/panels/chart/GridDataChartView.hpp b/src/app/panels/chart/GridDataChartView.hpp new file mode 100644 index 0000000..978b26f --- /dev/null +++ b/src/app/panels/chart/GridDataChartView.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#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 diff --git a/src/app/panels/chart/RawDataChartView.cpp b/src/app/panels/chart/RawDataChartView.cpp new file mode 100644 index 0000000..5e1eb3e --- /dev/null +++ b/src/app/panels/chart/RawDataChartView.cpp @@ -0,0 +1,62 @@ +#include "panels/chart/RawDataChartView.hpp" + +#include +#include +#include +#include +#include + +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 diff --git a/src/app/panels/chart/RawDataChartView.hpp b/src/app/panels/chart/RawDataChartView.hpp new file mode 100644 index 0000000..37af190 --- /dev/null +++ b/src/app/panels/chart/RawDataChartView.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#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