feat/dataset-detail-chart #5
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,52 +1,38 @@
|
|||
#include "panels/DatasetDetailPage.hpp"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QToolButton>
|
||||
#include <QButtonGroup>
|
||||
#include <QCheckBox>
|
||||
#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<int>& h) { chart_->setHiddenAnomalies(h); });
|
||||
const QVector<PanelTab> tabs = {
|
||||
{Glyph::Detail, QStringLiteral("原数据"), rawView_, false},
|
||||
{Glyph::Dataset, QStringLiteral("网格数据"), gridView_, false},
|
||||
};
|
||||
const QVector<HeaderAction> 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
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
#pragma once
|
||||
#include <QWidget>
|
||||
#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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue