Skip to content

Commit 1313cb6

Browse files
committed
Migrate layers
1 parent fff3ea2 commit 1313cb6

File tree

5 files changed

+56
-38
lines changed

5 files changed

+56
-38
lines changed

src/components/Viewer.tsx

+13-14
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import DeckGL from "deck.gl";
2-
import { type Layer, OrthographicView } from "deck.gl";
2+
import { OrthographicView } from "deck.gl";
33
import { type WritableAtom, useAtom } from "jotai";
44
import { useAtomValue } from "jotai";
55
import * as React from "react";
66

7-
import type { LayerProps } from "@deck.gl/core/lib/layer";
7+
import type { DeckGLRef, Layer, LayerProps, OrthographicViewState } from "deck.gl";
88
import type { ZarrPixelSource } from "../ZarrPixelSource";
9+
import { useViewState } from "../hooks";
910
import type { ViewState } from "../state";
1011
import { layerAtoms } from "../state";
1112
import { fitBounds, isInterleaved } from "../utils";
1213

1314
type Data = { loader: ZarrPixelSource; rows: number; columns: number };
14-
type VizarrLayer = Layer<unknown, LayerProps<unknown> & Data>;
15+
type VizarrLayer = Layer<LayerProps & Data>;
1516

1617
function getLayerSize(props: Data) {
1718
const { loader } = props;
@@ -28,21 +29,19 @@ function getLayerSize(props: Data) {
2829
return { height, width, maxZoom };
2930
}
3031

31-
function WrappedViewStateDeck(props: {
32-
layers: Array<VizarrLayer | null>;
33-
viewStateAtom: WritableAtom<ViewState | undefined, ViewState>;
34-
}) {
35-
const [viewState, setViewState] = useAtom(props.viewStateAtom);
36-
const deckRef = React.useRef<DeckGL>(null);
32+
function WrappedViewStateDeck(props: { layers: Array<VizarrLayer> }) {
33+
const [viewState, setViewState] = useViewState();
34+
const deckRef = React.useRef<DeckGLRef>(null);
3735
const firstLayerProps = props.layers[0]?.props;
3836

3937
// If viewState hasn't been updated, use the first loader to guess viewState
4038
// TODO: There is probably a better place / way to set the intital view and this is a hack.
41-
if (deckRef.current && !viewState && firstLayerProps?.loader) {
39+
if (deckRef.current?.deck && !viewState && firstLayerProps?.loader) {
4240
const { deck } = deckRef.current;
4341
const { width, height, maxZoom } = getLayerSize(firstLayerProps);
4442
const padding = deck.width < 400 ? 10 : deck.width < 600 ? 30 : 50; // Adjust depending on viewport width.
4543
const bounds = fitBounds([width, height], [deck.width, deck.height], maxZoom, padding);
44+
console.log(bounds);
4645
// FIXME: For some reason deck.width & deck.height when there is no view
4746
// state, resulting in a `NaN` zoom.
4847
// This prevents us from setting the state until we have a valid state
@@ -60,15 +59,15 @@ function WrappedViewStateDeck(props: {
6059
<DeckGL
6160
ref={deckRef}
6261
layers={props.layers}
63-
viewState={viewState}
64-
onViewStateChange={(e) => setViewState(e.viewState)}
62+
viewState={viewState ? { ortho: viewState } : undefined}
63+
onViewStateChange={(e: { viewState: OrthographicViewState }) => setViewState(e.viewState)}
6564
views={[new OrthographicView({ id: "ortho", controller: true })]}
6665
glOptions={glOptions}
6766
/>
6867
);
6968
}
7069

71-
function Viewer(props: { viewStateAtom: WritableAtom<ViewState | undefined, ViewState> }) {
70+
function Viewer() {
7271
const layers: Array<VizarrLayer> = [];
7372
for (const { Layer, layerProps, on, imageLabel } of useAtomValue(layerAtoms)) {
7473
if (on) {
@@ -85,7 +84,7 @@ function Viewer(props: { viewStateAtom: WritableAtom<ViewState | undefined, View
8584
layers.push(layer);
8685
}
8786
}
88-
return <WrappedViewStateDeck viewStateAtom={props.viewStateAtom} layers={layers} />;
87+
return <WrappedViewStateDeck layers={layers} />;
8988
}
9089
}
9190

src/hooks.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { type PrimitiveAtom, useAtom } from "jotai";
1+
import { type PrimitiveAtom, type WritableAtom, useAtom } from "jotai";
22
import * as React from "react";
3-
import type { LayerState, SourceData } from "./state";
3+
import type { LayerState, SourceData, ViewState } from "./state";
44

55
import * as utils from "./utils";
66

@@ -19,3 +19,11 @@ export function useLayerState() {
1919
utils.assert(atom, "useLayerState hook must be used within LayerStateContext.");
2020
return useAtom(atom);
2121
}
22+
23+
export const ViewStateContext = React.createContext<WritableAtom<ViewState | undefined, ViewState> | null>(null);
24+
25+
export function useViewState() {
26+
const atom = React.useContext(ViewStateContext);
27+
utils.assert(atom, "useViewState hook must be used within ViewStateContext.");
28+
return useAtom(atom);
29+
}

src/index.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import ReactDOM from "react-dom/client";
88
import Menu from "./components/Menu";
99
import Viewer from "./components/Viewer";
1010
import "./codecs/register";
11+
import { ViewStateContext } from "./hooks";
1112
import {
1213
type ImageLayerConfig,
1314
type ViewState,
@@ -83,10 +84,10 @@ export function createViewer(element: HTMLElement, options: { menuOpen?: boolean
8384
return (
8485
<>
8586
{sourceError === null && redirectObj === null && (
86-
<>
87+
<ViewStateContext.Provider value={viewStateAtom}>
8788
<Menu open={options.menuOpen ?? true} />
88-
<Viewer viewStateAtom={viewStateAtom} />
89-
</>
89+
<Viewer />
90+
</ViewStateContext.Provider>
9091
)}
9192
{sourceError !== null && (
9293
<div className={classes.errorContainer}>

src/layers/grid-layer.ts

+25-15
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import type { CompositeLayerProps } from "@deck.gl/core/lib/composite-layer";
2-
import { CompositeLayer, type PickInfo, SolidPolygonLayer, TextLayer } from "deck.gl";
1+
import { CompositeLayer, SolidPolygonLayer, TextLayer } from "deck.gl";
32
import pMap from "p-map";
43

5-
import type { SolidPolygonLayerProps, TextLayerProps } from "@deck.gl/layers";
64
import { ColorPaletteExtension, XRLayer } from "@hms-dbmi/viv";
75
import type { SupportedTypedArray } from "@vivjs/types";
6+
import type { CompositeLayerProps, PickingInfo, SolidPolygonLayerProps, TextLayerProps } from "deck.gl";
87
import type { ZarrPixelSource } from "../ZarrPixelSource";
98
import type { BaseLayerProps } from "../state";
109
import { assert } from "../utils";
@@ -19,7 +18,7 @@ export interface GridLoader {
1918
type Polygon = Array<[number, number]>;
2019

2120
export interface GridLayerProps
22-
extends Omit<CompositeLayerProps<unknown>, "modelMatrix" | "opacity" | "onClick" | "id">,
21+
extends Omit<CompositeLayerProps, "modelMatrix" | "opacity" | "onClick" | "id" | "loaders">,
2322
BaseLayerProps {
2423
loaders: GridLoader[];
2524
rows: number;
@@ -86,9 +85,15 @@ function refreshGridData(props: GridLayerProps) {
8685
return pMap(loaders, mapper, { concurrency });
8786
}
8887

89-
export default class GridLayer extends CompositeLayer<unknown, CompositeLayerProps<unknown> & GridLayerProps> {
88+
type SharedLayerState = {
89+
gridData: Awaited<ReturnType<typeof refreshGridData>>;
90+
width: number;
91+
height: number;
92+
};
93+
94+
export default class GridLayer extends CompositeLayer<GridLayerProps> {
9095
initializeState() {
91-
this.state = { gridData: [], width: 0, height: 0 };
96+
this.#state = { gridData: [], width: 0, height: 0 };
9297
refreshGridData(this.props).then((gridData) => {
9398
const { width, height } = validateWidthHeight(gridData);
9499
this.setState({ gridData, width, height });
@@ -117,29 +122,35 @@ export default class GridLayer extends CompositeLayer<unknown, CompositeLayerPro
117122
}
118123
}
119124

120-
getPickingInfo({ info }: { info: PickInfo<unknown> }) {
125+
get #state(): SharedLayerState {
126+
// @ts-expect-error - type safe access to state
127+
return this.state;
128+
}
129+
130+
set #state(value: SharedLayerState) {
131+
this.state = value;
132+
}
133+
134+
getPickingInfo({ info }: { info: PickingInfo<unknown> }) {
121135
// provide Grid row and column info for mouse events (hover & click)
122136
if (!info.coordinate) {
123137
return info;
124138
}
125139
const spacer = this.props.spacer || 0;
126-
const { width, height } = this.state;
140+
const { width, height } = this.#state;
127141
const [x, y] = info.coordinate;
128142
const row = Math.floor(y / (height + spacer));
129143
const column = Math.floor(x / (width + spacer));
130-
return {
131-
...info,
132-
gridCoord: { row, column },
133-
};
144+
return { ...info, gridCoord: { row, column } };
134145
}
135146

136147
renderLayers() {
137-
const { gridData, width, height } = this.state;
148+
const { gridData, width, height } = this.#state;
138149
if (width === 0 || height === 0) return null; // early return if no data
139150

140151
const { rows, columns, spacer = 0, id = "" } = this.props;
141152
type Data = { row: number; col: number; loader: Pick<ZarrPixelSource, "dtype">; data: Array<SupportedTypedArray> };
142-
const layers = gridData.map((d: Data) => {
153+
const layers = gridData.map((d) => {
143154
const y = d.row * (height + spacer);
144155
const x = d.col * (width + spacer);
145156
const layerProps = {
@@ -172,7 +183,6 @@ export default class GridLayer extends CompositeLayer<unknown, CompositeLayerPro
172183
pickable: true, // enable picking
173184
id: `${id}-GridLayer-picking`,
174185
} satisfies SolidPolygonLayerProps<Data>;
175-
// @ts-expect-error - SolidPolygonLayer props are not well typed
176186
const layer = new SolidPolygonLayer<Data, SolidPolygonLayerProps<Data>>({ ...this.props, ...layerProps });
177187
layers.push(layer);
178188
}

src/layers/image-label-layer.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { getImageSize, MultiscaleImageLayer } from "@hms-dbmi/viv";
2-
import { clamp } from "math.gl";
1+
import { MultiscaleImageLayer, getImageSize } from "@hms-dbmi/viv";
32
import { TileLayer } from "deck.gl";
3+
import { clamp } from "math.gl";
44
import * as utils from "../utils";
55

6-
import type { Matrix4 } from "math.gl";
7-
import type { ZarrPixelSource } from "../ZarrPixelSource";
86
import type { PixelData } from "@vivjs/types";
97
import { BitmapLayer } from "deck.gl";
8+
import type { Matrix4 } from "math.gl";
9+
import type { ZarrPixelSource } from "../ZarrPixelSource";
1010

1111
export type ImageLabelLayerProps = {
1212
id: string;

0 commit comments

Comments
 (0)