From 48d21b82e90647b54cf777ffb4bf6e4cbd307734 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Thu, 11 Jun 2026 18:33:12 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E7=AD=89=E5=80=BC=E7=BA=BF=20vtkSpl?= =?UTF-8?q?ineFilter=20=E6=A0=B7=E6=9D=A1=E5=B9=B3=E6=BB=91(=E5=8E=BBDP?= =?UTF-8?q?=E7=AE=80=E5=8C=96,=E8=B4=B4=E8=BF=91=E5=8E=9F=E7=89=88?= =?UTF-8?q?=E5=9C=86=E6=BB=91=E6=9B=B2=E7=BA=BF)=20+=20=E6=A0=87=E6=B3=A8?= =?UTF-8?q?=E6=B2=BF=E7=BA=BF=E5=91=A8=E6=9C=9F=E9=87=8D=E5=A4=8D+?= =?UTF-8?q?=E9=99=8D=E9=98=88=E5=80=BC(=E5=B0=8F=E7=AD=89=E5=80=BC?= =?UTF-8?q?=E7=BA=BF=E4=B9=9F=E6=A0=87=E6=B3=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/panels/chart/ContourPlotItem.cpp | 53 ++++++++++++++++-------- src/render/ContourBands.cpp | 14 +++++-- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/app/panels/chart/ContourPlotItem.cpp b/src/app/panels/chart/ContourPlotItem.cpp index e5e469d..1ad252e 100644 --- a/src/app/panels/chart/ContourPlotItem.cpp +++ b/src/app/panels/chart/ContourPlotItem.cpp @@ -18,7 +18,8 @@ namespace { constexpr int kFillUpsample = 4; // 填充图像每网格格细分 K(双线性插值平滑带边界) constexpr int kMaxFillDim = 2400; // 填充图像单边像素上限(防极端网格爆内存) constexpr int kLabelFontPx = 10; // 等值线标注字号 -constexpr int kLabelMinSegPx = 60; // 太短的等值线不标注(像素长度阈值,draw 期判定) +constexpr double kLabelMinLenPx = 24.0; // 整条线像素长度小于此不标注(极短碎线) +constexpr double kLabelSpacingPx = 220.0; // 沿线每隔此像素重复一个标注(对齐原版密度) constexpr double kRad2Deg = 57.29577951308232; // 180/π(避免依赖 M_PI) } // namespace @@ -202,25 +203,43 @@ void ContourPlotItem::draw(QPainter* painter, const QwtScaleMap& xMap, const Qwt f.setPixelSize(kLabelFontPx); painter->setFont(f); painter->setPen(QColor(0, 0, 0)); + const QFontMetricsF fm(f); for (const auto& ln : lines_) { if (ln.pts.size() < 2 || std::isnan(ln.level)) continue; - // 取折线中段两点定位置/朝向。 - const std::size_t mid = ln.pts.size() / 2; - const QPointF a = mapPt(ln.pts[mid - 1]); - const QPointF b = mapPt(ln.pts[mid]); - // 整条线像素长度太短不标注(避免密集杂乱)。 - const QPointF s = mapPt(ln.pts.front()); - const QPointF e = mapPt(ln.pts.back()); - if (std::hypot(e.x() - s.x(), e.y() - s.y()) < kLabelMinSegPx) continue; - double ang = std::atan2(b.y() - a.y(), b.x() - a.x()) * kRad2Deg; - if (ang > 90.0) ang -= 180.0; // 保持文字大体正向(不上下颠倒) - if (ang < -90.0) ang += 180.0; + // 映射到像素 + 累计像素弧长。 + std::vector px; + px.reserve(ln.pts.size()); + for (const auto& p : ln.pts) px.push_back(mapPt(p)); + double total = 0.0; + for (std::size_t i = 1; i < px.size(); ++i) + total += std::hypot(px[i].x() - px[i - 1].x(), px[i].y() - px[i - 1].y()); + if (total < kLabelMinLenPx) continue; // 极短碎线不标注 const QString txt = QString::number(ln.level, 'g', 4); - painter->save(); - painter->translate((a.x() + b.x()) * 0.5, (a.y() + b.y()) * 0.5); - painter->rotate(ang); - painter->drawText(QPointF(-12, -2), txt); - painter->restore(); + const double halfW = fm.horizontalAdvance(txt) * 0.5; + // 沿线每隔 kLabelSpacingPx 放一个标注;首个在半间距处(短线即中点附近)。 + double nextAt = std::min(kLabelSpacingPx * 0.5, total * 0.5); + double acc = 0.0; + for (std::size_t i = 1; i < px.size() && nextAt <= total; ++i) { + const double seg = + std::hypot(px[i].x() - px[i - 1].x(), px[i].y() - px[i - 1].y()); + while (nextAt <= acc + seg && nextAt <= total) { + const double t = seg > 1e-6 ? (nextAt - acc) / seg : 0.0; + const QPointF pos(px[i - 1].x() + (px[i].x() - px[i - 1].x()) * t, + px[i - 1].y() + (px[i].y() - px[i - 1].y()) * t); + double ang = + std::atan2(px[i].y() - px[i - 1].y(), px[i].x() - px[i - 1].x()) * + kRad2Deg; + if (ang > 90.0) ang -= 180.0; // 文字大体正向 + if (ang < -90.0) ang += 180.0; + painter->save(); + painter->translate(pos); + painter->rotate(ang); + painter->drawText(QPointF(-halfW, -2), txt); + painter->restore(); + nextAt += kLabelSpacingPx; + } + acc += seg; + } } painter->restore(); } diff --git a/src/render/ContourBands.cpp b/src/render/ContourBands.cpp index 330e903..7a0d6e2 100644 --- a/src/render/ContourBands.cpp +++ b/src/render/ContourBands.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -161,8 +162,15 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const vtkNew stripper; stripper->SetInputData(edges); stripper->JoinContiguousSegmentsOn(); - stripper->Update(); - vtkPolyData* joined = stripper->GetOutput(); + // 样条平滑:把折线拟合成平滑曲线并按弧长重采样(对齐原版圆滑等值线)。 + // 重采样间距取数据 x 跨度的 ~1/300,足够平滑且点数可控。 + const double xspan = g.x.empty() ? 1.0 : (g.x.back() - g.x.front()); + vtkNew spline; + spline->SetInputConnection(stripper->GetOutputPort()); + spline->SetSubdivideToLength(); + spline->SetLength(xspan > 0 ? xspan / 300.0 : 1.0); + spline->Update(); + vtkPolyData* joined = spline->GetOutput(); joined->BuildCells(); const vtkIdType nLines = joined->GetNumberOfCells(); for (vtkIdType c = 0; c < nLines; ++c) { @@ -173,7 +181,7 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const double xyz[3]; cp->GetPoint(p, xyz); cl.pts.push_back(Vec2{xyz[0], xyz[1]}); } - simplifyInPlace(cl.pts, opt.simplifyTol); + // 不再 DP 简化:样条已平滑且点数受重采样间距控制;简化会重新折线化。 if (cl.pts.size() >= 2) out.lines.push_back(std::move(cl)); } }