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
2 changed files with 68 additions and 2 deletions
Showing only changes of commit b7e0a2034d - Show all commits

View File

@ -21,6 +21,55 @@ using geopro::core::Vec2;
using geopro::core::Rgba;
namespace {
// 双线性上采样nx×ny → ((nx-1)*k+1)×((ny-1)*k+1)。NaN 单元参与的目标点置 NaN。
Grid upsampleBilinear(const Grid& g, int k) {
if (k <= 1) return g;
const int nx = g.nx(), ny = g.ny();
const int Nx = (nx - 1) * k + 1, Ny = (ny - 1) * k + 1;
Grid out(Nx, Ny);
out.x.resize(Nx); out.y.resize(Ny);
for (int I = 0; I < Nx; ++I) { double fi = double(I) / k; int i0 = std::min(int(fi), nx - 2);
double t = fi - i0; out.x[I] = g.x[i0] * (1 - t) + g.x[i0 + 1] * t; }
for (int J = 0; J < Ny; ++J) { double fj = double(J) / k; int j0 = std::min(int(fj), ny - 2);
double t = fj - j0; out.y[J] = g.y[j0] * (1 - t) + g.y[j0 + 1] * t; }
for (int J = 0; J < Ny; ++J) {
double fj = double(J) / k; int j0 = std::min(int(fj), ny - 2); double tj = fj - j0;
for (int I = 0; I < Nx; ++I) {
double fi = double(I) / k; int i0 = std::min(int(fi), nx - 2); double ti = fi - i0;
double v00 = g.valueAt(i0, j0), v10 = g.valueAt(i0 + 1, j0);
double v01 = g.valueAt(i0, j0 + 1), v11 = g.valueAt(i0 + 1, j0 + 1);
if (std::isnan(v00) || std::isnan(v10) || std::isnan(v01) || std::isnan(v11))
out.valueAt(I, J) = std::nan("");
else
out.valueAt(I, J) = (v00 * (1 - ti) + v10 * ti) * (1 - tj)
+ (v01 * (1 - ti) + v11 * ti) * tj;
}
}
out.vmin = g.vmin; out.vmax = g.vmax;
return out;
}
// 3x3 盒式平滑NaN 跳过strength 0..1 线性混合原值。
Grid smoothGrid(const Grid& g, double strength) {
if (strength <= 0) return g;
const int nx = g.nx(), ny = g.ny();
Grid out = g;
for (int j = 0; j < ny; ++j)
for (int i = 0; i < nx; ++i) {
double v = g.valueAt(i, j);
if (std::isnan(v)) continue;
double sum = 0; int n = 0;
for (int dj = -1; dj <= 1; ++dj) for (int di = -1; di <= 1; ++di) {
int ii = i + di, jj = j + dj;
if (ii < 0 || ii >= nx || jj < 0 || jj >= ny) continue;
double w = g.valueAt(ii, jj); if (std::isnan(w)) continue; sum += w; ++n;
}
if (n) out.valueAt(i, j) = v * (1 - strength) + (sum / n) * strength;
}
return out;
}
// 用 Grid 构 vtkStructuredGrid点 (x[i], y[j], 0)NaN 值置 0 但记录于掩膜——
// 首版裁剪在 Task 1.4 接入,这里先全量)。
vtkSmartPointer<vtkStructuredGrid> toStructuredGrid(const Grid& g) {
@ -43,7 +92,7 @@ vtkSmartPointer<vtkStructuredGrid> toStructuredGrid(const Grid& g) {
}
} // namespace
ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const ContourOptions&) {
ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const ContourOptions& opt) {
ContourBandsResult out;
const int nx = g.nx(), ny = g.ny();
if (nx < 2 || ny < 2 || g.x.size() < 2 || g.y.size() < 2) return out;
@ -51,7 +100,8 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const
const std::vector<double> stops = cs.stopValues();
if (stops.size() < 2) return out;
auto sg = toStructuredGrid(g);
Grid work = smoothGrid(upsampleBilinear(g, std::max(1, opt.upsample)), opt.smooth);
auto sg = toStructuredGrid(work);
vtkNew<vtkDataSetSurfaceFilter> surf; surf->SetInputData(sg);
vtkNew<vtkBandedPolyDataContourFilter> banded;
banded->SetInputConnection(surf->GetOutputPort());

View File

@ -3,6 +3,22 @@
using namespace geopro::core;
using namespace geopro::render;
// 上采样 2x色带边界更密 → 多边形数应多于不上采样。
TEST(ContourBands, UpsampleIncreasesDetail) {
Grid g(3, 3);
g.x = {0, 1, 2}; g.y = {0, 1, 2};
for (int j = 0; j < 3; ++j)
for (int i = 0; i < 3; ++i) g.valueAt(i, j) = static_cast<double>(i * i + j);
g.vmin = 0; g.vmax = 6;
ColorScale cs;
cs.addStop(0, Rgba{0,0,255,255}); cs.addStop(3, Rgba{0,255,0,255}); cs.addStop(6, Rgba{255,0,0,255});
ContourOptions a; a.upsample = 1; a.smooth = 0; a.simplifyTol = 0;
ContourOptions b; b.upsample = 2; b.smooth = 0; b.simplifyTol = 0;
auto ra = buildContourBands(g, cs, a);
auto rb = buildContourBands(g, cs, b);
EXPECT_GT(rb.bands.size(), ra.bands.size());
}
// 2x2 平滑梯度网格 + 2 段色阶 → 至少 1 个色带多边形,多边形顶点 >=3。
TEST(ContourBands, ProducesNonEmptyBands) {
Grid g(3, 3);