diff --git a/src/app/panels/chart/ContourPlotItem.cpp b/src/app/panels/chart/ContourPlotItem.cpp index cb45281..e5e469d 100644 --- a/src/app/panels/chart/ContourPlotItem.cpp +++ b/src/app/panels/chart/ContourPlotItem.cpp @@ -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; diff --git a/src/app/panels/chart/GridDataChartView.cpp b/src/app/panels/chart/GridDataChartView.cpp index f7f84bb..31c5046 100644 --- a/src/app/panels/chart/GridDataChartView.cpp +++ b/src/app/panels/chart/GridDataChartView.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #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) { diff --git a/src/render/ContourBands.cpp b/src/render/ContourBands.cpp index 3f6a53c..330e903 100644 --- a/src/render/ContourBands.cpp +++ b/src/render/ContourBands.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -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 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) { diff --git a/tests/render/test_contour_bands.cpp b/tests/render/test_contour_bands.cpp index c4b7b48..b87998b 100644 --- a/tests/render/test_contour_bands.cpp +++ b/tests/render/test_contour_bands.cpp @@ -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()); +}