feat/dataset-detail-chart #5
|
|
@ -32,6 +32,7 @@ add_executable(geopro_desktop WIN32
|
|||
panels/chart/ColorMapService.cpp
|
||||
panels/chart/ColorBarWidget.cpp
|
||||
panels/chart/ScatterPlotItem.cpp
|
||||
panels/chart/LivePanner.cpp
|
||||
panels/AnomalyTablePanel.cpp
|
||||
panels/DatasetDetailPage.cpp
|
||||
panels/DatasetDetailPanel.cpp
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -10,11 +10,13 @@
|
|||
|
||||
#include <qwt_plot.h>
|
||||
#include <qwt_plot_canvas.h>
|
||||
#include <qwt_plot_panner.h>
|
||||
#include <qwt_plot_magnifier.h>
|
||||
#include <qwt_plot_marker.h>
|
||||
#include <qwt_plot_grid.h>
|
||||
#include <qwt_plot_rescaler.h>
|
||||
|
||||
#include "panels/chart/LivePanner.hpp"
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
RawDataChartView::RawDataChartView(QWidget* parent) : QWidget(parent) {
|
||||
|
|
@ -71,6 +73,16 @@ RawDataChartView::RawDataChartView(QWidget* parent) : QWidget(parent) {
|
|||
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_);
|
||||
|
||||
// 过原点零线(对齐原版 zeroline:x=0 竖线 + y=0 横线 → "四象限"观感)。
|
||||
auto* zeroX = new QwtPlotMarker();
|
||||
zeroX->setLineStyle(QwtPlotMarker::VLine);
|
||||
|
|
@ -84,9 +96,8 @@ RawDataChartView::RawDataChartView(QWidget* parent) : QWidget(parent) {
|
|||
zeroY->setLinePen(QColor(180, 180, 180), 1.0);
|
||||
zeroY->attach(plot_);
|
||||
|
||||
// 交互:Panner(左键拖动平移)+ Magnifier(滚轮缩放)
|
||||
auto* panner = new QwtPlotPanner(plot_->canvas());
|
||||
panner->setMouseButton(Qt::LeftButton);
|
||||
// 交互:LivePanner(左键拖动实时平移,坐标轴+内容一起动)+ Magnifier(滚轮缩放)
|
||||
new LivePanner(plot_, QwtPlot::xTop, QwtPlot::yLeft, this);
|
||||
|
||||
auto* magnifier = new QwtPlotMagnifier(plot_->canvas());
|
||||
// 反转滚轮方向:Qwt 默认 wheelFactor=0.9 → 上滚缩小;取倒数使「上滚=放大」(常规习惯)。
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ void DatasetDetailController::openDataset(const QString& dsId, const QString& dd
|
|||
ChartData d;
|
||||
d.dsId = dsId;
|
||||
d.ddCode = ddCode;
|
||||
// 仅拉原数据视图所需(scatter + 散点色阶 + 异常,~0.8s)。
|
||||
// 网格数据(inversion/rows 服务端网格化 ~4s)推迟到切「网格数据」页时按需加载。
|
||||
d.scatter = repo_.loadScatter(id);
|
||||
d.scatterScale = repo_.loadScatterColorScale(id);
|
||||
d.grid = repo_.loadGrid(id);
|
||||
d.gridScale = repo_.loadColorScale(id);
|
||||
d.anomalies = repo_.loadAnomalies(id);
|
||||
busy_ = false;
|
||||
emit chartReady(d);
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ namespace {
|
|||
struct StubRepo : data::IDatasetRepository {
|
||||
bool fail = false;
|
||||
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::ScatterField loadScatter(const std::string&) override { return {}; }
|
||||
core::Grid loadGrid(const std::string&) override { core::Grid g(2,2); g.x={0,1}; g.y={0,1}; return g; }
|
||||
// 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 loadScatterColorScale(const std::string&) override { return {}; }
|
||||
std::vector<core::Anomaly> loadAnomalies(const std::string&) override { return {}; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue