feat/dataset-detail-chart #5

Merged
gaozheng merged 74 commits from feat/dataset-detail-chart into main 2026-06-13 17:30:37 +08:00
6 changed files with 111 additions and 8 deletions
Showing only changes of commit 1054c227e1 - Show all commits

View File

@ -32,6 +32,7 @@ add_executable(geopro_desktop WIN32
panels/chart/ColorMapService.cpp panels/chart/ColorMapService.cpp
panels/chart/ColorBarWidget.cpp panels/chart/ColorBarWidget.cpp
panels/chart/ScatterPlotItem.cpp panels/chart/ScatterPlotItem.cpp
panels/chart/LivePanner.cpp
panels/AnomalyTablePanel.cpp panels/AnomalyTablePanel.cpp
panels/DatasetDetailPage.cpp panels/DatasetDetailPage.cpp
panels/DatasetDetailPanel.cpp panels/DatasetDetailPanel.cpp

View File

@ -0,0 +1,61 @@
#include "panels/chart/LivePanner.hpp"
#include <QEvent>
#include <QMouseEvent>
#include <qwt_plot.h>
#include <qwt_scale_div.h>
namespace geopro::app {
LivePanner::LivePanner(QwtPlot* plot, int xAxis, int yAxis, QObject* parent)
: QObject(parent), plot_(plot), xAxis_(xAxis), yAxis_(yAxis) {
if (plot_ && plot_->canvas()) plot_->canvas()->installEventFilter(this);
}
bool LivePanner::eventFilter(QObject* obj, QEvent* ev) {
if (!plot_ || obj != plot_->canvas()) return QObject::eventFilter(obj, ev);
switch (ev->type()) {
case QEvent::MouseButtonPress: {
auto* me = static_cast<QMouseEvent*>(ev);
if (me->button() == Qt::LeftButton) {
panning_ = true;
startPos_ = me->pos();
x0Min_ = plot_->axisScaleDiv(xAxis_).lowerBound();
x0Max_ = plot_->axisScaleDiv(xAxis_).upperBound();
y0Min_ = plot_->axisScaleDiv(yAxis_).lowerBound();
y0Max_ = plot_->axisScaleDiv(yAxis_).upperBound();
return true;
}
break;
}
case QEvent::MouseMove: {
if (!panning_) break;
auto* me = static_cast<QMouseEvent*>(ev);
const int W = plot_->canvas()->width();
const int H = plot_->canvas()->height();
if (W <= 0 || H <= 0) break;
const double dxData = (me->pos().x() - startPos_.x()) * (x0Max_ - x0Min_) / W;
const double dyData = (me->pos().y() - startPos_.y()) * (y0Max_ - y0Min_) / H;
// 拖右(dx>0)→窗口左移(xmin/xmax 减),内容随光标右移;
// 拖下(像素 dy>0)→yLeft 数据向上→窗口上移(ymin/ymax 增),内容随光标下移。
plot_->setAxisScale(xAxis_, x0Min_ - dxData, x0Max_ - dxData);
plot_->setAxisScale(yAxis_, y0Min_ + dyData, y0Max_ + dyData);
plot_->replot();
return true;
}
case QEvent::MouseButtonRelease: {
auto* me = static_cast<QMouseEvent*>(ev);
if (me->button() == Qt::LeftButton) {
panning_ = false;
return true;
}
break;
}
default:
break;
}
return QObject::eventFilter(obj, ev);
}
} // namespace geopro::app

View File

@ -0,0 +1,29 @@
#pragma once
#include <QObject>
#include <QPoint>
class QwtPlot;
namespace geopro::app {
// 实时拖动平移:左键拖动时连续平移两轴并 replot坐标轴 + 内容一起实时移动),
// 区别于 QwtPlotPanner 的"像素抓取"(拖动中轴不动、边缘空白、松手才定稿)。
// 平移为纯平移,不改变纵横比(与 QwtPlotRescaler 锁定的真实比尺兼容)。
class LivePanner : public QObject {
Q_OBJECT
public:
LivePanner(QwtPlot* plot, int xAxis, int yAxis, QObject* parent = nullptr);
protected:
bool eventFilter(QObject* obj, QEvent* ev) override;
private:
QwtPlot* plot_;
int xAxis_;
int yAxis_;
bool panning_ = false;
QPoint startPos_;
double x0Min_ = 0, x0Max_ = 0, y0Min_ = 0, y0Max_ = 0;
};
} // namespace geopro::app

View File

@ -10,11 +10,13 @@
#include <qwt_plot.h> #include <qwt_plot.h>
#include <qwt_plot_canvas.h> #include <qwt_plot_canvas.h>
#include <qwt_plot_panner.h>
#include <qwt_plot_magnifier.h> #include <qwt_plot_magnifier.h>
#include <qwt_plot_marker.h> #include <qwt_plot_marker.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_rescaler.h> #include <qwt_plot_rescaler.h>
#include "panels/chart/LivePanner.hpp"
namespace geopro::app { namespace geopro::app {
RawDataChartView::RawDataChartView(QWidget* parent) : QWidget(parent) { RawDataChartView::RawDataChartView(QWidget* parent) : QWidget(parent) {
@ -71,6 +73,16 @@ RawDataChartView::RawDataChartView(QWidget* parent) : QWidget(parent) {
plot_->setPalette(pal); plot_->setPalette(pal);
} }
// 横纵网格线(对齐原版浅灰网格)。
auto* grid = new QwtPlotGrid();
grid->setMajorPen(QColor(225, 225, 225), 1.0, Qt::SolidLine);
grid->setMinorPen(QColor(240, 240, 240), 1.0, Qt::DotLine);
grid->enableXMin(false);
grid->enableYMin(false);
grid->setXAxis(QwtPlot::xTop);
grid->setYAxis(QwtPlot::yLeft);
grid->attach(plot_);
// 过原点零线(对齐原版 zerolinex=0 竖线 + y=0 横线 → "四象限"观感)。 // 过原点零线(对齐原版 zerolinex=0 竖线 + y=0 横线 → "四象限"观感)。
auto* zeroX = new QwtPlotMarker(); auto* zeroX = new QwtPlotMarker();
zeroX->setLineStyle(QwtPlotMarker::VLine); zeroX->setLineStyle(QwtPlotMarker::VLine);
@ -84,9 +96,8 @@ RawDataChartView::RawDataChartView(QWidget* parent) : QWidget(parent) {
zeroY->setLinePen(QColor(180, 180, 180), 1.0); zeroY->setLinePen(QColor(180, 180, 180), 1.0);
zeroY->attach(plot_); zeroY->attach(plot_);
// 交互Panner左键拖动平移+ Magnifier滚轮缩放 // 交互LivePanner左键拖动实时平移坐标轴+内容一起动)+ Magnifier滚轮缩放
auto* panner = new QwtPlotPanner(plot_->canvas()); new LivePanner(plot_, QwtPlot::xTop, QwtPlot::yLeft, this);
panner->setMouseButton(Qt::LeftButton);
auto* magnifier = new QwtPlotMagnifier(plot_->canvas()); auto* magnifier = new QwtPlotMagnifier(plot_->canvas());
// 反转滚轮方向Qwt 默认 wheelFactor=0.9 → 上滚缩小;取倒数使「上滚=放大」(常规习惯)。 // 反转滚轮方向Qwt 默认 wheelFactor=0.9 → 上滚缩小;取倒数使「上滚=放大」(常规习惯)。

View File

@ -19,10 +19,10 @@ void DatasetDetailController::openDataset(const QString& dsId, const QString& dd
ChartData d; ChartData d;
d.dsId = dsId; d.dsId = dsId;
d.ddCode = ddCode; d.ddCode = ddCode;
// 仅拉原数据视图所需scatter + 散点色阶 + 异常,~0.8s)。
// 网格数据(inversion/rows 服务端网格化 ~4s)推迟到切「网格数据」页时按需加载。
d.scatter = repo_.loadScatter(id); d.scatter = repo_.loadScatter(id);
d.scatterScale = repo_.loadScatterColorScale(id); d.scatterScale = repo_.loadScatterColorScale(id);
d.grid = repo_.loadGrid(id);
d.gridScale = repo_.loadColorScale(id);
d.anomalies = repo_.loadAnomalies(id); d.anomalies = repo_.loadAnomalies(id);
busy_ = false; busy_ = false;
emit chartReady(d); emit chartReady(d);

View File

@ -7,8 +7,9 @@ namespace {
struct StubRepo : data::IDatasetRepository { struct StubRepo : data::IDatasetRepository {
bool fail = false; bool fail = false;
std::vector<data::GsNode> loadStructure() override { return {}; } std::vector<data::GsNode> loadStructure() override { return {}; }
core::Grid loadGrid(const std::string&) override { if (fail) throw std::runtime_error("x"); core::Grid g(2,2); g.x={0,1}; g.y={0,1}; return g; } core::Grid loadGrid(const std::string&) override { core::Grid g(2,2); g.x={0,1}; g.y={0,1}; return g; }
core::ScatterField loadScatter(const std::string&) override { return {}; } // openDataset 现只拉 scatter/scatterScale/anomalies网格懒加载失败路径由 loadScatter 抛出触发。
core::ScatterField loadScatter(const std::string&) override { if (fail) throw std::runtime_error("x"); return {}; }
core::ColorScale loadColorScale(const std::string&) override { return {}; } core::ColorScale loadColorScale(const std::string&) override { return {}; }
core::ColorScale loadScatterColorScale(const std::string&) override { return {}; } core::ColorScale loadScatterColorScale(const std::string&) override { return {}; }
std::vector<core::Anomaly> loadAnomalies(const std::string&) override { return {}; } std::vector<core::Anomaly> loadAnomalies(const std::string&) override { return {}; }