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
4 changed files with 62 additions and 9 deletions
Showing only changes of commit 4fbab033f0 - Show all commits

View File

@ -183,7 +183,7 @@ void ContourPlotItem::draw(QPainter* painter, const QwtScaleMap& xMap, const Qwt
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
QPen pen(QColor(0, 0, 0));
pen.setWidthF(0.0); // cosmetic恒 1px不随缩放变粗
pen.setWidthF(1.0); // 1px 黑色等值线
painter->setPen(pen);
for (const auto& ln : lines_) {
if (ln.pts.size() < 2) continue;

View File

@ -10,6 +10,8 @@
#include <qwt_plot.h>
#include <qwt_plot_grid.h>
#include <QSplitter>
#include <QScrollArea>
#include <QFrame>
#include <qwt_plot_rescaler.h>
#include "PanelHeader.hpp"
@ -141,15 +143,24 @@ GridDataChartView::GridDataChartView(QWidget* parent) : QWidget(parent) {
};
auto tabbedBottom = buildTabbedPanel(bottomTabs, {});
// 图表区 | 底部表 用竖直 QSplitter自适应 dock 高度、可拖拽、不强制固定 200px
//(避免内容溢出滚动条;也使本页与「原数据」页高度互不耦合)。
auto* splitter = new QSplitter(Qt::Vertical, this);
// 图表区 | 底部表 竖直 QSplitter可拖拽调整比例。给定最小高度→分割器有最小尺寸。
chartArea->setMinimumHeight(280);
tabbedBottom.container->setMinimumHeight(160);
auto* splitter = new QSplitter(Qt::Vertical);
splitter->addWidget(chartArea);
splitter->addWidget(tabbedBottom.container);
splitter->setStretchFactor(0, 3);
splitter->setStretchFactor(1, 1);
splitter->setChildrenCollapsible(false);
lay->addWidget(splitter, 1);
// 页签内滚动:把(图表+异常)分割器放进 QScrollArea。dock 够高→分割器填满(可拖动调整)
// dock 太矮→在页签内部出现竖滚动条(工具条/页签/标题固定),而非整个面板滚动。
auto* scroll = new QScrollArea(this);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll->setWidget(splitter);
lay->addWidget(scroll, 1);
// ---- 工具条开关 → 重建/重绘 ----
connect(chkShowAnom, &QCheckBox::toggled, this, [this](bool on) {

View File

@ -11,6 +11,7 @@
#include <vtkPointData.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkStripper.h>
#include <vtkUnstructuredGrid.h>
#include <cmath>
@ -152,14 +153,20 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const
out.bands.push_back(std::move(bp));
}
// port1等值线polylines+ DP 简化。仅 makeLines=true 时生成(否则不提取)。
// port1等值线。banded 边输出是大量 2 点短线段——用 vtkStripper 连成长折线,
// 便于沿线标注与减少绘制调用。仅 makeLines=true 时生成。
if (opt.makeLines) {
vtkPolyData* edges = banded->GetContourEdgesOutput();
if (edges) {
edges->BuildCells();
const vtkIdType nLines = edges->GetNumberOfCells();
vtkNew<vtkStripper> stripper;
stripper->SetInputData(edges);
stripper->JoinContiguousSegmentsOn();
stripper->Update();
vtkPolyData* joined = stripper->GetOutput();
joined->BuildCells();
const vtkIdType nLines = joined->GetNumberOfCells();
for (vtkIdType c = 0; c < nLines; ++c) {
vtkCell* cell = edges->GetCell(c);
vtkCell* cell = joined->GetCell(c);
vtkPoints* cp = cell->GetPoints();
ContourLine cl; cl.level = 0.0;
for (vtkIdType p = 0; p < cp->GetNumberOfPoints(); ++p) {

View File

@ -53,3 +53,38 @@ TEST(ContourBands, ProducesNonEmptyBands) {
ASSERT_FALSE(r.bands.empty());
for (const auto& b : r.bands) EXPECT_GE(b.ring.size(), 3u);
}
// 诊断:等值线(lines)应非空——网格跨越多个色阶级时应产生等值线。
TEST(ContourBands, ProducesContourLines) {
Grid g(10, 10);
g.x.resize(10); g.y.resize(10);
for (int i = 0; i < 10; ++i) { g.x[i] = i; g.y[i] = i; }
for (int j = 0; j < 10; ++j)
for (int i = 0; i < 10; ++i) g.valueAt(i, j) = i + j; // 0..18
g.vmin = 0; g.vmax = 18;
ColorScale cs;
cs.addStop(0, Rgba{0,0,255,255}); cs.addStop(6, Rgba{0,255,0,255});
cs.addStop(12, Rgba{255,255,0,255}); cs.addStop(18, Rgba{255,0,0,255});
ContourOptions opt; opt.upsample = 1; opt.smooth = 0; opt.simplifyTol = 0; opt.makeLines = true;
auto r = buildContourBands(g, cs, opt);
EXPECT_FALSE(r.lines.empty());
}
// 诊断(真实参数复现):大网格 + 默认 opt(upsample=2,smooth=0.3,simplifyTol=0.5) + 17 级色阶。
TEST(ContourBands, ProducesLinesRealisticOptions) {
Grid g(60, 40);
g.x.resize(60); g.y.resize(40);
for (int i = 0; i < 60; ++i) g.x[i] = i * 3.0;
for (int j = 0; j < 40; ++j) g.y[j] = -33.0 + j * 1.2;
for (int j = 0; j < 40; ++j)
for (int i = 0; i < 60; ++i)
g.valueAt(i, j) = 2.0 + 340.0 * (0.5 + 0.5 * std::sin(i * 0.2) * std::cos(j * 0.25));
g.vmin = 2; g.vmax = 348;
ColorScale cs;
const double bars[17] = {2.08,4.59,7.81,11.02,14.26,17.25,20.56,23.55,27.05,
31.16,36.83,43.64,51.98,62.09,81.14,108.24,348.52};
for (int i = 0; i < 17; ++i) cs.addStop(bars[i], Rgba{(unsigned char)(i*15),0,200,255});
ContourOptions opt; // 默认 upsample=2, smooth=0.3, simplifyTol=0.5, makeLines=true
auto r = buildContourBands(g, cs, opt);
EXPECT_FALSE(r.lines.empty());
}