feat(wasm): examples/wasm-viewer, an HDF5/NetCDF-4 viewer page
Drop a file (or pass ?file=<url>&path=<object>), browse the tree lazily, see a dataset's type, shape, max shape and attributes, and page through its values as 50x12 hyperslab windows (leading dims of 3-D+ data held at chosen indices). build.sh produces pkg/ (not committed) with wasm-bindgen --target web and checks the CLI matches the crate version. test/run.sh builds it and runs test.mjs under Node against the h5py/ netCDF4 fixture (250 checks: every dataset whole and as a strided hyperslab, listings, attributes, error paths, the page's DOM-free helpers), then browser.sh renders the page in headless Chromium for eight objects and checks the DOM. The fixture gains LZ4 (read) and Zstd (refused: links C) datasets and a compound attribute (value null plus its type). ci-test.sh runs it when node and wasm-bindgen exist; the CI container has neither, so CI relies on the native h5py_interop test. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// DOM-free helpers for the viewer, so Node can test them (test/test.mjs).
|
||||
|
||||
/** Child path of `name` in the group at `parent`. */
|
||||
export function joinPath(parent, name) {
|
||||
return parent === "/" ? `/${name}` : `${parent}/${name}`;
|
||||
}
|
||||
|
||||
/** One value as display text. */
|
||||
export function formatValue(v) {
|
||||
if (v === null || v === undefined) return "";
|
||||
if (typeof v === "bigint") return v.toString();
|
||||
if (typeof v === "number") {
|
||||
if (Number.isInteger(v)) return String(v);
|
||||
return String(Number(v.toPrecision(7)));
|
||||
}
|
||||
if (typeof v === "string") return JSON.stringify(v);
|
||||
if (ArrayBuffer.isView(v) || Array.isArray(v)) {
|
||||
const items = Array.from(v.slice(0, 16), formatValue);
|
||||
if (v.length > 16) items.push(`… (${v.length} values)`);
|
||||
return `[${items.join(", ")}]`;
|
||||
}
|
||||
return String(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* The window of a dataset to show: a hyperslab over its `shape` with the
|
||||
* last dimension as columns, the one before as rows, and any leading
|
||||
* dimensions held at `fixed` indices. `row`/`col` are the window's top-left
|
||||
* corner. Returns null for a scalar (read it whole).
|
||||
*/
|
||||
export function viewWindow(shape, { row = 0, col = 0, rows = 50, cols = 12, fixed = [] } = {}) {
|
||||
const rank = shape.length;
|
||||
if (rank === 0) return null;
|
||||
const clamp = (x, n) => Math.max(0, Math.min(x, Math.max(0, n - 1)));
|
||||
if (rank === 1) {
|
||||
const r0 = clamp(row, shape[0]);
|
||||
const n = Math.max(0, Math.min(rows, shape[0] - r0));
|
||||
return { start: [r0], count: [n], rows: n, cols: 1, row: r0, col: 0 };
|
||||
}
|
||||
const lead = shape.slice(0, rank - 2).map((n, i) => clamp(fixed[i] ?? 0, n));
|
||||
const nr = shape[rank - 2];
|
||||
const nc = shape[rank - 1];
|
||||
const r0 = clamp(row, nr);
|
||||
const c0 = clamp(col, nc);
|
||||
const r = Math.max(0, Math.min(rows, nr - r0));
|
||||
const c = Math.max(0, Math.min(cols, nc - c0));
|
||||
return {
|
||||
start: [...lead, r0, c0],
|
||||
count: [...lead.map(() => 1), r, c],
|
||||
rows: r,
|
||||
cols: c,
|
||||
row: r0,
|
||||
col: c0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Split row-major `data` into `rows` x `cols` cells of display text; each
|
||||
* cell holds `per` consecutive values (the elements of an array datatype).
|
||||
*/
|
||||
export function toRows(data, rows, cols, per = 1) {
|
||||
const out = [];
|
||||
for (let r = 0; r < rows; r++) {
|
||||
const row = [];
|
||||
for (let c = 0; c < cols; c++) {
|
||||
const i = (r * cols + c) * per;
|
||||
row.push(per === 1 ? formatValue(data[i]) : formatValue(data.slice(i, i + per)));
|
||||
}
|
||||
out.push(row);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Number of values an array datatype packs into each element. */
|
||||
export function perElement(elementShape) {
|
||||
return elementShape.reduce((a, b) => a * b, 1);
|
||||
}
|
||||
Reference in New Issue
Block a user