Skip to main content

Loading points and shapes

Points and shapes are parquet-backed and can be large. Both are read lazily, with the parquet decode optionally moved off the main thread.

Shapes​

const shapes = sdata.shapes?.['cell_boundaries'];

const renderData = await shapes.loadRenderData();
renderData.featureIds; // stable ids, one per shape
renderData.rowIndexByFeatureIndex; // Int32Array into the associated table, -1 where unmatched

loadRenderData() detects polygons, circles and points, decodes the geometry, and joins the feature ids to the associated table (see loadFeatureRowIndexByFeatureIndex). loadPolygonShapes(), loadCircleShapes() and loadFeatureIds() are the narrower reads. Shapes are always loaded whole; createShapesLoaderForElement wraps that in the CoreShapesLoader interface a viewport-tiled loader will share.

Points​

const points = sdata.points?.['transcripts'];

const result = await points.loadPoints({ includeFeatureCodes: true });
result.shape; // [axisCount, rowCount]
result.data; // one array per axis (x, y, optionally z)
result.featureCodes; // per-row feature code, with includeFeatureCodes
result.featureCatalog; // names present in the loaded rows
result.preloadTruncated; // true when the memory cap cut the load short

memoryCap bounds the rows retained (default DEFAULT_POINTS_MEMORY_CAP, 4,000,000). A load over the cap truncates and sets preloadTruncated.

Streaming​

streamPoints() is the same load as an async iterable yielding the growing result; loadPoints() drains it. Cancellation is break or an AbortSignal.

for await (const progress of points.streamPoints({ includeFeatureCodes: true })) {
draw(progress.partialResult);
}

onProgress on PointsLoadOptions still works but is deprecated in favour of the stream. coalesceLatest, sampleByStep and drainStream are combinators for the iterable (rate control, final value).

Features​

A points element's categorical identity is the feature_key column (a gene, say). Codes are authoritative only when the file has an explicit {feature_key}_codes column; otherwise they are assigned by the application while building the catalog and are stable only within that catalog. Persist feature names, and convert with resolveFeatureSelectionCodes / featureNamesForCodes.

const catalog = await points.listFeaturesWithCounts({
onPartialCatalog: (names) => showNames(names), // names before counts
});

// Points for a selection from the whole dataset, not just the resident window.
const selected = await points.loadPointsMatchingFeatureCodes({
memoryCap: 1_000_000,
featureCodes: [3, 17],
});

For a feature-ordered file the row-group statistics let the scan skip every group the selection cannot be in; otherwise it reads everything. loadPointsMatchingFeatureCodes has no main-thread fallback, so it needs the parquet worker.

Tiled stores​

Beyond the memory cap, points are read per viewport. getPointsTilingMetadata() reports what the element offers, and createPointsLoaderForElement returns the matching CorePointsLoader (loadInBounds, optionally loadAll):

EncodingSource
morton-tiledA Morton-sorted points.parquet with row-group range reads
points-zarr-tiledA points-zarr group, or one beside points.parquet (ADR 0006); experimental
preloaded-columnarThe resident preload above

PointsElement.storage says whether the element itself is 'parquet' or 'points-zarr'; the parquet-only methods (loadPoints, streamPoints, …) throw for the latter.

The parquet worker​

enableParquetWorker({ workerUrl }) moves decode and scans off the main thread; without it everything but the feature scan falls back to the main thread. See Bundling into an application for the URL.

Failures​

Loaders throw plain Errors. A renderer should not classify them itself: the Resource Resolver turns them into typed SpatialEntryErrors.