pluot_widget 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ /target
2
+ target/
3
+ **/*.rs.bk
4
+ Cargo.lock
5
+ bin/
6
+ pkg/
7
+ wasm-pack.log
8
+ .DS_Store
9
+ node_modules/
10
+
11
+ crates/LICENSE
12
+ dist/
13
+ dist-tsc/
14
+ tsconfig.tsbuildinfo
15
+ data/out/
16
+
17
+ zarrs/
18
+
19
+ __pycache__/
20
+ .pytest_cache/
21
+ .ipynb_checkpoints/
22
+ *.so
23
+ .venv/
24
+ .ropeproject/
25
+ __marimo__/
26
+ bindings-python/pluot_widget/src/pluot_widget/static/
27
+ .Rproj.user
28
+
29
+ .astro/
30
+ npm-debug.log*
31
+ yarn-debug.log*
32
+ yarn-error.log*
33
+ pnpm-debug.log*
34
+ .env
35
+ .env.production
36
+
37
+ # Snapshot test outputs (reference snapshots are in tests/snapshots/)
38
+ current/
39
+ snaps-dirty/
40
+
41
+ skills-lock.json
42
+ .claude/settings.local.json
43
+ .claude/scheduled_tasks.lock
44
+ .claude/skills/autoresearch
45
+ .agents/
46
+
47
+ # R stuff
48
+ .Rhistory
49
+ .Rproj.user
50
+ *.o
51
+ AUTHORS
52
+ *.tar.gz
53
+ *.Rcheck
54
+
55
+
56
+ autoresearch/
57
+ circle_diffs/
58
+ text_diffs/
59
+
60
+ crates/pluot_core/src/vendored-fonts/*.ttf
61
+ bindings-js/core/src/vendored-fonts/*.ttf.js
62
+ bindings-r/src/.cargo/
63
+
64
+ .pluot_integration_test_*.sh
65
+
66
+
67
+ bindings-js/**/CHANGELOG.md
68
+ crates/**/CHANGELOG.md
69
+ RELEASE_NOTES.md
70
+
71
+ references/
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.5
2
+ Name: pluot_widget
3
+ Version: 0.0.1
4
+ Summary: AnyWidget for rendering Pluot visualizations client-side via WebAssembly
5
+ Project-URL: repository, https://github.com/keller-mark/pluot
6
+ Author: Mark Keller
7
+ License-Expression: Apache-2.0
8
+ Requires-Python: >=3.12
9
+ Requires-Dist: anywidget==0.11.0
10
+ Requires-Dist: pluot-core==0.1.19
11
+ Requires-Dist: traitlets
12
+ Requires-Dist: zarr>=3
13
+ Description-Content-Type: text/markdown
14
+
15
+ This pluot_widget package will depend on pluot_core (but not pluot or the rust bindings). This will export only the anywidget, and may need to use the common utilities from pluot_core.
@@ -0,0 +1 @@
1
+ This pluot_widget package will depend on pluot_core (but not pluot or the rust bindings). This will export only the anywidget, and may need to use the common utilities from pluot_core.
@@ -0,0 +1,191 @@
1
+ import React, { useMemo } from "react";
2
+ import { createRender, useModel, useModelState } from "@anywidget/react";
3
+ import type { AnyModel } from "@anywidget/types";
4
+ import * as uuid from "@lukeed/uuid";
5
+ import {
6
+ Pluot,
7
+ setStoreByName,
8
+ type AspectRatioAlignmentMode,
9
+ type AspectRatioMode,
10
+ type CameraMatrix,
11
+ type GraphicsFormat,
12
+ type PlotParams,
13
+ type PlotType,
14
+ type StoresInput,
15
+ type ViewMode,
16
+ } from "@pluot/react";
17
+
18
+ type AsyncReadable = Parameters<typeof setStoreByName>[1];
19
+ type RangeQuery = Parameters<NonNullable<AsyncReadable["getRange"]>>[1];
20
+
21
+ type ZarrGetParams = [storeName: string, key: string] | [storeName: string, key: string, rangeQuery: RangeQuery];
22
+ type CommandResponse = { success: boolean };
23
+
24
+ type WidgetModel = {
25
+ width: number;
26
+ height: number;
27
+ camera_view: number[];
28
+ margin_top: number;
29
+ margin_right: number;
30
+ margin_bottom: number;
31
+ margin_left: number;
32
+ aspect_ratio_mode: AspectRatioMode;
33
+ aspect_ratio_alignment_mode: AspectRatioAlignmentMode;
34
+ view_mode: ViewMode;
35
+ plot_id: string;
36
+ plot_type: PlotType;
37
+ stores_metadata: StoresInput;
38
+ plot_params: PlotParams;
39
+ format: GraphicsFormat;
40
+ batch_zarr_gets: boolean;
41
+ };
42
+
43
+ type PendingGet = {
44
+ params: ZarrGetParams;
45
+ resolve: (value: Uint8Array | undefined) => void;
46
+ reject: (reason: unknown) => void;
47
+ };
48
+
49
+ // Custom invoke matching the anywidget-command protocol implemented on the Python side.
50
+ function invoke<T>(
51
+ model: AnyModel<WidgetModel>,
52
+ name: string,
53
+ msg: unknown,
54
+ signal: AbortSignal = AbortSignal.timeout(30000),
55
+ ): Promise<[T, DataView[]]> {
56
+ // crypto.randomUUID is unavailable in non-secure (http://) contexts.
57
+ const id = uuid.v4();
58
+ return new Promise((resolve, reject) => {
59
+ if (signal.aborted) {
60
+ reject(signal.reason);
61
+ return;
62
+ }
63
+ function handler(responseMsg: any, responseBuffers: DataView[]) {
64
+ if (!responseMsg || responseMsg.id !== id) return;
65
+ model.off("msg:custom", handler);
66
+ resolve([responseMsg.response, responseBuffers]);
67
+ }
68
+ signal.addEventListener("abort", () => {
69
+ model.off("msg:custom", handler);
70
+ reject(signal.reason);
71
+ });
72
+ model.on("msg:custom", handler);
73
+ model.send({ id, kind: "anywidget-command", name, msg }, undefined, []);
74
+ });
75
+ }
76
+
77
+ function bufferFromResponse(bufferData: DataView | ArrayBuffer): Uint8Array {
78
+ if (ArrayBuffer.isView(bufferData)) {
79
+ return new Uint8Array(bufferData.buffer, bufferData.byteOffset, bufferData.byteLength);
80
+ }
81
+ return new Uint8Array(bufferData);
82
+ }
83
+
84
+ function createZarrGetter(model: AnyModel<WidgetModel>) {
85
+ let pending: PendingGet[] = [];
86
+ let batchId = 0;
87
+
88
+ async function processBatch(batch: PendingGet[]) {
89
+ try {
90
+ const [dataArr, buffersArr] = await invoke<CommandResponse[]>(
91
+ model,
92
+ "_zarr_get_multi",
93
+ batch.map(d => d.params),
94
+ );
95
+ batch.forEach((item, i) => {
96
+ item.resolve(dataArr[i].success ? bufferFromResponse(buffersArr[i]) : undefined);
97
+ });
98
+ } catch (err) {
99
+ batch.forEach(item => item.reject(err));
100
+ }
101
+ }
102
+
103
+ function flush() {
104
+ const batch = pending;
105
+ pending = [];
106
+ batchId = 0;
107
+ processBatch(batch);
108
+ }
109
+
110
+ function enqueue(params: ZarrGetParams): Promise<Uint8Array | undefined> {
111
+ batchId = batchId || requestAnimationFrame(flush);
112
+ const { promise, resolve, reject } = Promise.withResolvers<Uint8Array | undefined>();
113
+ pending.push({ params, resolve, reject });
114
+ return promise;
115
+ }
116
+
117
+ async function getOne(params: ZarrGetParams): Promise<Uint8Array | undefined> {
118
+ const name = params.length === 2 ? "_zarr_get" : "_zarr_get_range";
119
+ const [data, buffers] = await invoke<CommandResponse>(model, name, params);
120
+ return data.success ? bufferFromResponse(buffers[0]) : undefined;
121
+ }
122
+
123
+ return (params: ZarrGetParams) => model.get("batch_zarr_gets") ? enqueue(params) : getOne(params);
124
+ }
125
+
126
+ // AsyncReadable store that proxies all reads back to the Python kernel.
127
+ function createKernelStore(zarrGet: ReturnType<typeof createZarrGetter>, storeName: string): AsyncReadable {
128
+ return {
129
+ get: (key) => zarrGet([storeName, key]),
130
+ getRange: (key, rangeQuery) => zarrGet([storeName, key, rangeQuery]),
131
+ };
132
+ }
133
+
134
+ function PluotWidget() {
135
+ const model = useModel<WidgetModel>();
136
+ const [width] = useModelState<number>("width");
137
+ const [height] = useModelState<number>("height");
138
+ const [cameraView, setCameraView] = useModelState<number[]>("camera_view");
139
+ const [marginTop] = useModelState<number>("margin_top");
140
+ const [marginRight] = useModelState<number>("margin_right");
141
+ const [marginBottom] = useModelState<number>("margin_bottom");
142
+ const [marginLeft] = useModelState<number>("margin_left");
143
+ const [aspectRatioMode] = useModelState<AspectRatioMode>("aspect_ratio_mode");
144
+ const [aspectRatioAlignmentMode] = useModelState<AspectRatioAlignmentMode>("aspect_ratio_alignment_mode");
145
+ const [viewMode] = useModelState<ViewMode>("view_mode");
146
+ const [plotId] = useModelState<string>("plot_id");
147
+ const [plotType] = useModelState<PlotType>("plot_type");
148
+ const [storesMetadata] = useModelState<StoresInput>("stores_metadata");
149
+ const [plotParams] = useModelState<PlotParams>("plot_params");
150
+ const [format] = useModelState<GraphicsFormat>("format");
151
+
152
+ const zarrGet = useMemo(() => createZarrGetter(model), [model]);
153
+
154
+ // Registered during render rather than in an effect, so that the kernel-backed
155
+ // stores exist before <Pluot/> issues its first read.
156
+ useMemo(() => {
157
+ for (const storeName of Object.keys(storesMetadata ?? {})) {
158
+ setStoreByName(storeName, createKernelStore(zarrGet, storeName));
159
+ }
160
+ }, [zarrGet, storesMetadata]);
161
+
162
+ const cameraMatrix = useMemo(() => Float32Array.from(cameraView), [cameraView]);
163
+
164
+ function setCameraMatrix(next: CameraMatrix) {
165
+ setCameraView(Array.from(next));
166
+ }
167
+
168
+ return (
169
+ <Pluot
170
+ width={width}
171
+ height={height}
172
+ plotId={plotId}
173
+ plotType={plotType}
174
+ plotParams={plotParams}
175
+ stores={storesMetadata}
176
+ registerStores={false}
177
+ viewMode={viewMode}
178
+ format={format}
179
+ marginTop={marginTop}
180
+ marginRight={marginRight}
181
+ marginBottom={marginBottom}
182
+ marginLeft={marginLeft}
183
+ aspectRatioMode={aspectRatioMode}
184
+ aspectRatioAlignmentMode={aspectRatioAlignmentMode}
185
+ cameraMatrix={cameraMatrix}
186
+ setCameraMatrix={setCameraMatrix}
187
+ />
188
+ );
189
+ }
190
+
191
+ export default { render: createRender(PluotWidget) };
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "pluot_widget",
3
+ "private": true,
4
+ "version": "0.1.19",
5
+ "author": "Mark Keller",
6
+ "license": "Apache-2.0",
7
+ "type": "module",
8
+ "scripts": {
9
+ "dev": "vite",
10
+ "build": "vite build",
11
+ "typecheck": "tsc --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "@anywidget/react": "^0.2.0",
15
+ "@lukeed/uuid": "^2.0.1",
16
+ "@pluot/react": "workspace:*",
17
+ "react": "^19.2.0",
18
+ "react-dom": "^19.2.0"
19
+ },
20
+ "devDependencies": {
21
+ "@anywidget/types": "^0.2.0",
22
+ "@anywidget/vite": "^0.2.2",
23
+ "@types/react": "^19.2.14",
24
+ "@types/react-dom": "^19.2.0",
25
+ "@vitejs/plugin-react": "^5.0.0",
26
+ "typescript": "^5.9",
27
+ "vite": "^6.1.1"
28
+ }
29
+ }
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["hatchling", "hatch-jupyter-builder>=0.5.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pluot_widget"
7
+ version = "0.0.1"
8
+ requires-python = ">=3.12"
9
+ license = "Apache-2.0"
10
+ authors = [
11
+ {name = "Mark Keller"}
12
+ ]
13
+ readme = "README.md"
14
+ description = "AnyWidget for rendering Pluot visualizations client-side via WebAssembly"
15
+ dependencies = [
16
+ "pluot_core==0.1.19",
17
+ "zarr>=3",
18
+ "anywidget==0.11.0",
19
+ "traitlets",
20
+ ]
21
+
22
+ [project.urls]
23
+ repository = "https://github.com/keller-mark/pluot"
24
+
25
+ [tool.hatch.build]
26
+ artifacts = ["src/pluot_widget/static/*"]
27
+
28
+ [tool.hatch.build.targets.sdist]
29
+ exclude = ["node_modules"]
30
+
31
+ [tool.hatch.build.hooks.jupyter-builder]
32
+ build-function = "hatch_jupyter_builder.npm_builder"
33
+ ensured-targets = ["src/pluot_widget/static/widget.js"]
34
+ skip-if-exists = ["src/pluot_widget/static/widget.js"]
35
+ # Allows `uv sync` in environments without pnpm or the wasm/JS toolchain (e.g., CI jobs that only test the Rust-backed package).
36
+ optional-editable-build = true
37
+ dependencies = ["hatch-jupyter-builder>=0.5.0"]
38
+
39
+ [tool.hatch.build.hooks.jupyter-builder.build-kwargs]
40
+ npm = "pnpm"
41
+ build_cmd = "build"
@@ -0,0 +1,5 @@
1
+ from .widget import PluotWasmWidget
2
+
3
+ __all__ = [
4
+ "PluotWasmWidget",
5
+ ]