@dtour/viewer 0.1.0 → 0.2.0
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.
- package/dist/Dtour.d.ts +5 -1
- package/dist/Dtour.d.ts.map +1 -1
- package/dist/DtourViewer.d.ts +4 -1
- package/dist/DtourViewer.d.ts.map +1 -1
- package/dist/components/AxisOverlay.d.ts +11 -1
- package/dist/components/AxisOverlay.d.ts.map +1 -1
- package/dist/components/CircularSlider.d.ts +21 -2
- package/dist/components/CircularSlider.d.ts.map +1 -1
- package/dist/components/DtourToolbar.d.ts +2 -1
- package/dist/components/DtourToolbar.d.ts.map +1 -1
- package/dist/components/Gallery.d.ts +3 -3
- package/dist/components/Gallery.d.ts.map +1 -1
- package/dist/components/RevertCameraButton.d.ts +6 -0
- package/dist/components/RevertCameraButton.d.ts.map +1 -0
- package/dist/components/ui/checkbox.d.ts +6 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/hooks/usePlayback.d.ts +7 -5
- package/dist/hooks/usePlayback.d.ts.map +1 -1
- package/dist/hooks/useScatter.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/layout/gallery-positions.d.ts +3 -1
- package/dist/layout/gallery-positions.d.ts.map +1 -1
- package/dist/layout/selector-size.d.ts +4 -2
- package/dist/layout/selector-size.d.ts.map +1 -1
- package/dist/lib/arcball.d.ts +21 -0
- package/dist/lib/arcball.d.ts.map +1 -0
- package/dist/lib/position-remap.d.ts +16 -0
- package/dist/lib/position-remap.d.ts.map +1 -0
- package/dist/lib/throttle-debounce.d.ts +28 -0
- package/dist/lib/throttle-debounce.d.ts.map +1 -0
- package/dist/radial-chart/RadialChart.d.ts +5 -1
- package/dist/radial-chart/RadialChart.d.ts.map +1 -1
- package/dist/spec.d.ts +32 -0
- package/dist/spec.d.ts.map +1 -1
- package/dist/state/atoms.d.ts +67 -0
- package/dist/state/atoms.d.ts.map +1 -1
- package/dist/state/spec-sync.d.ts +2 -0
- package/dist/state/spec-sync.d.ts.map +1 -1
- package/dist/viewer.css +1 -1
- package/dist/viewer.js +11620 -10118
- package/package.json +6 -1
- package/src/Dtour.tsx +82 -9
- package/src/DtourViewer.tsx +480 -100
- package/src/components/AxisOverlay.tsx +332 -182
- package/src/components/CircularSlider.tsx +363 -174
- package/src/components/DtourToolbar.tsx +121 -10
- package/src/components/Gallery.tsx +197 -39
- package/src/components/RevertCameraButton.tsx +39 -0
- package/src/components/ui/checkbox.tsx +32 -0
- package/src/hooks/usePlayback.ts +18 -44
- package/src/hooks/useScatter.ts +21 -5
- package/src/index.ts +16 -3
- package/src/layout/gallery-positions.ts +15 -4
- package/src/layout/selector-size.ts +24 -10
- package/src/lib/arcball.ts +119 -0
- package/src/lib/position-remap.ts +51 -0
- package/src/lib/throttle-debounce.ts +79 -0
- package/src/radial-chart/RadialChart.tsx +45 -6
- package/src/spec.ts +143 -0
- package/src/state/atoms.ts +65 -0
- package/src/state/spec-sync.ts +15 -0
- package/src/styles.css +16 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dtour/viewer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/viewer.js",
|
|
6
6
|
"exports": {
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
},
|
|
11
11
|
"./dist/viewer.css": "./dist/viewer.css"
|
|
12
12
|
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/flekschas/dtour",
|
|
16
|
+
"directory": "packages/viewer"
|
|
17
|
+
},
|
|
13
18
|
"scripts": {
|
|
14
19
|
"build": "vite build && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
15
20
|
"dev": "vite build --watch"
|
package/src/Dtour.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ScatterInstance, ScatterStatus } from '@dtour/scatter';
|
|
2
2
|
import { bitPackIndices } from '@dtour/scatter';
|
|
3
|
-
import { Provider, createStore, useAtomValue, useSetAtom } from 'jotai';
|
|
3
|
+
import { Provider, createStore, useAtomValue, useSetAtom, useStore } from 'jotai';
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { DtourViewer } from './DtourViewer.tsx';
|
|
6
6
|
import { ColorLegend } from './components/ColorLegend.tsx';
|
|
@@ -13,14 +13,20 @@ import type { DtourSpec } from './spec.ts';
|
|
|
13
13
|
import {
|
|
14
14
|
backgroundColorAtom,
|
|
15
15
|
colorMapAtom,
|
|
16
|
+
embeddedConfigAtom,
|
|
17
|
+
frameLoadingsAtom,
|
|
16
18
|
legendSelectionAtom,
|
|
17
19
|
legendVisibleAtom,
|
|
18
20
|
metadataAtom,
|
|
19
21
|
pointColorAtom,
|
|
20
22
|
resolvedThemeAtom,
|
|
23
|
+
showTourDescriptionAtom,
|
|
24
|
+
tourDescriptionAtom,
|
|
25
|
+
tourFrameDescriptionAtom,
|
|
26
|
+
tourModeAtom,
|
|
21
27
|
viewModeAtom,
|
|
22
28
|
} from './state/atoms.ts';
|
|
23
|
-
import { initStoreFromSpec, useSpecSync } from './state/spec-sync.ts';
|
|
29
|
+
import { applySpecToStore, initStoreFromSpec, useSpecSync } from './state/spec-sync.ts';
|
|
24
30
|
|
|
25
31
|
export type DtourHandle = {
|
|
26
32
|
/** Select points by index array or bit-packed mask. */
|
|
@@ -53,6 +59,8 @@ export type DtourProps = {
|
|
|
53
59
|
hideToolbar?: boolean;
|
|
54
60
|
/** Called when the user requests loading new data via the toolbar file picker. */
|
|
55
61
|
onLoadData?: (data: ArrayBuffer, fileName: string) => void;
|
|
62
|
+
/** Called when the user clicks the toolbar logo. */
|
|
63
|
+
onLogoClick?: () => void;
|
|
56
64
|
/** Fires when legend selection changes for a categorical color column. Reports selected label names or empty array when cleared. */
|
|
57
65
|
onSelectionChange?: (labels: string[]) => void;
|
|
58
66
|
/** Per-label color map. Values are hex strings or theme-aware {light, dark} objects. */
|
|
@@ -61,6 +69,8 @@ export type DtourProps = {
|
|
|
61
69
|
portalContainer?: HTMLElement;
|
|
62
70
|
/** Called when the viewer is ready with an API handle for programmatic control. */
|
|
63
71
|
onReady?: (api: DtourHandle) => void;
|
|
72
|
+
/** Rendering backend. Default 'webgpu'. */
|
|
73
|
+
backend?: 'webgpu' | 'webgl';
|
|
64
74
|
};
|
|
65
75
|
|
|
66
76
|
export const Dtour = ({
|
|
@@ -74,10 +84,12 @@ export const Dtour = ({
|
|
|
74
84
|
onStatus,
|
|
75
85
|
hideToolbar = false,
|
|
76
86
|
onLoadData,
|
|
87
|
+
onLogoClick,
|
|
77
88
|
onSelectionChange,
|
|
78
89
|
colorMap,
|
|
79
90
|
portalContainer,
|
|
80
91
|
onReady,
|
|
92
|
+
backend,
|
|
81
93
|
}: DtourProps) => {
|
|
82
94
|
// Each Dtour instance gets its own isolated jotai store.
|
|
83
95
|
// Eagerly apply initial spec values so there's no flash of defaults.
|
|
@@ -102,9 +114,11 @@ export const Dtour = ({
|
|
|
102
114
|
onStatus={onStatus}
|
|
103
115
|
hideToolbar={hideToolbar}
|
|
104
116
|
onLoadData={onLoadData}
|
|
117
|
+
onLogoClick={onLogoClick}
|
|
105
118
|
onSelectionChange={onSelectionChange}
|
|
106
119
|
colorMap={colorMap}
|
|
107
120
|
onReady={onReady}
|
|
121
|
+
backend={backend}
|
|
108
122
|
/>
|
|
109
123
|
</Provider>
|
|
110
124
|
</PortalContainerContext.Provider>
|
|
@@ -123,9 +137,11 @@ const DtourInner = ({
|
|
|
123
137
|
onStatus,
|
|
124
138
|
hideToolbar,
|
|
125
139
|
onLoadData,
|
|
140
|
+
onLogoClick,
|
|
126
141
|
onSelectionChange,
|
|
127
142
|
colorMap,
|
|
128
143
|
onReady,
|
|
144
|
+
backend,
|
|
129
145
|
}: {
|
|
130
146
|
data: ArrayBuffer | undefined;
|
|
131
147
|
views: Float32Array[] | undefined;
|
|
@@ -137,24 +153,64 @@ const DtourInner = ({
|
|
|
137
153
|
onStatus: ((status: ScatterStatus) => void) | undefined;
|
|
138
154
|
hideToolbar: boolean;
|
|
139
155
|
onLoadData: ((data: ArrayBuffer, fileName: string) => void) | undefined;
|
|
156
|
+
onLogoClick: (() => void) | undefined;
|
|
140
157
|
onSelectionChange: ((labels: string[]) => void) | undefined;
|
|
141
158
|
colorMap: Record<string, string | { light: string; dark: string }> | undefined;
|
|
142
159
|
onReady: ((api: DtourHandle) => void) | undefined;
|
|
160
|
+
backend: 'webgpu' | 'webgl' | undefined;
|
|
143
161
|
}) => {
|
|
144
162
|
useSpecSync(spec, onSpecChange);
|
|
145
163
|
useModeCycling();
|
|
146
164
|
useSystemTheme();
|
|
147
165
|
|
|
148
|
-
//
|
|
166
|
+
// ── Apply embedded config from Parquet metadata ──────────────────────
|
|
167
|
+
const embeddedConfig = useAtomValue(embeddedConfigAtom);
|
|
168
|
+
const store = useStore();
|
|
169
|
+
const embeddedAppliedRef = useRef(false);
|
|
170
|
+
|
|
171
|
+
// Reset when data changes so the next file's embedded config can apply
|
|
172
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: data triggers reset
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
embeddedAppliedRef.current = false;
|
|
175
|
+
}, [data]);
|
|
176
|
+
|
|
177
|
+
// Apply embedded spec fields that are NOT overridden by the prop spec
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (!embeddedConfig || embeddedAppliedRef.current) return;
|
|
180
|
+
embeddedAppliedRef.current = true;
|
|
181
|
+
|
|
182
|
+
const fieldsToApply: DtourSpec = {};
|
|
183
|
+
for (const [key, value] of Object.entries(embeddedConfig.spec)) {
|
|
184
|
+
if (spec?.[key as keyof DtourSpec] === undefined) {
|
|
185
|
+
(fieldsToApply as Record<string, unknown>)[key] = value;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
applySpecToStore(store, fieldsToApply);
|
|
189
|
+
}, [embeddedConfig, spec, store]);
|
|
190
|
+
|
|
191
|
+
// Sync colorMap prop → atom (embedded colorMap used as fallback)
|
|
149
192
|
const setColorMap = useSetAtom(colorMapAtom);
|
|
150
193
|
useEffect(() => {
|
|
151
|
-
setColorMap(colorMap ?? null);
|
|
152
|
-
}, [colorMap, setColorMap]);
|
|
194
|
+
setColorMap(colorMap ?? embeddedConfig?.colorMap ?? null);
|
|
195
|
+
}, [colorMap, embeddedConfig, setColorMap]);
|
|
196
|
+
|
|
197
|
+
// Sync tour frame loadings, tour mode, and description strings from embedded config
|
|
198
|
+
const setFrameLoadings = useSetAtom(frameLoadingsAtom);
|
|
199
|
+
const setTourMode = useSetAtom(tourModeAtom);
|
|
200
|
+
const setTourDescription = useSetAtom(tourDescriptionAtom);
|
|
201
|
+
const setTourFrameDescription = useSetAtom(tourFrameDescriptionAtom);
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
setFrameLoadings(embeddedConfig?.tour?.frameLoadings ?? null);
|
|
204
|
+
setTourMode(embeddedConfig?.tour?.tourMode ?? null);
|
|
205
|
+
setTourDescription(embeddedConfig?.tour?.tourDescription ?? null);
|
|
206
|
+
setTourFrameDescription(embeddedConfig?.tour?.tourFrameDescription ?? null);
|
|
207
|
+
}, [embeddedConfig, setFrameLoadings, setTourMode, setTourDescription, setTourFrameDescription]);
|
|
153
208
|
|
|
154
209
|
// Sync resolved theme → background color + CSS class
|
|
155
210
|
const resolvedTheme = useAtomValue(resolvedThemeAtom);
|
|
156
211
|
const setBackgroundColor = useSetAtom(backgroundColorAtom);
|
|
157
212
|
useEffect(() => {
|
|
213
|
+
// sRGB values matching --color-dtour-bg: dark=#000000, light=#ffffff
|
|
158
214
|
setBackgroundColor(resolvedTheme === 'light' ? [1, 1, 1] : [0, 0, 0]);
|
|
159
215
|
}, [resolvedTheme, setBackgroundColor]);
|
|
160
216
|
|
|
@@ -211,6 +267,13 @@ const DtourInner = ({
|
|
|
211
267
|
const isGrand = viewMode === 'grand';
|
|
212
268
|
const legendVisible = useAtomValue(legendVisibleAtom);
|
|
213
269
|
|
|
270
|
+
// Tour description sub-bar
|
|
271
|
+
const showTourDescription = useAtomValue(showTourDescriptionAtom);
|
|
272
|
+
const tourDescription = useAtomValue(tourDescriptionAtom);
|
|
273
|
+
const descriptionVisible =
|
|
274
|
+
showTourDescription && viewMode === 'guided' && tourDescription !== null;
|
|
275
|
+
const effectiveToolbarHeight = hideToolbar ? 0 : descriptionVisible ? 72 : 40;
|
|
276
|
+
|
|
214
277
|
// Sidebar width state — remembered across open/close cycles
|
|
215
278
|
const [sidebarWidth, setSidebarWidth] = useState(200);
|
|
216
279
|
const [dragging, setDragging] = useState(false);
|
|
@@ -253,13 +316,22 @@ const DtourInner = ({
|
|
|
253
316
|
>
|
|
254
317
|
{/* Canvas panel — grows to fill remaining space */}
|
|
255
318
|
<div className="relative flex-1 min-w-0">
|
|
256
|
-
{/* Toolbar
|
|
319
|
+
{/* Toolbar + optional description sub-bar */}
|
|
257
320
|
<div
|
|
258
|
-
className={`absolute inset-x-0 top-0 z-10
|
|
321
|
+
className={`absolute inset-x-0 top-0 z-10 transition-[transform,opacity] duration-300 ease-out ${
|
|
259
322
|
isGrand ? '-translate-y-full' : 'translate-y-0'
|
|
260
323
|
} ${hideToolbar ? 'opacity-0 pointer-events-none' : 'opacity-100'}`}
|
|
261
324
|
>
|
|
262
|
-
<
|
|
325
|
+
<div className="h-10">
|
|
326
|
+
<DtourToolbar onLoadData={onLoadData} onLogoClick={onLogoClick} />
|
|
327
|
+
</div>
|
|
328
|
+
{descriptionVisible && (
|
|
329
|
+
<div className="h-8 flex items-center justify-center border-b border-dtour-surface bg-dtour-bg px-3">
|
|
330
|
+
<span className="text-[11px] text-dtour-text-muted italic">
|
|
331
|
+
<strong>Tour:</strong> {tourDescription}
|
|
332
|
+
</span>
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
263
335
|
</div>
|
|
264
336
|
<div className="absolute inset-0 overflow-hidden">
|
|
265
337
|
<DtourViewer
|
|
@@ -269,8 +341,9 @@ const DtourInner = ({
|
|
|
269
341
|
metricTracks={metricTracks}
|
|
270
342
|
metricBarWidth={metricBarWidth}
|
|
271
343
|
onStatus={onStatus}
|
|
272
|
-
toolbarHeight={
|
|
344
|
+
toolbarHeight={effectiveToolbarHeight}
|
|
273
345
|
onScatterReady={setScatterInstance}
|
|
346
|
+
backend={backend}
|
|
274
347
|
/>
|
|
275
348
|
</div>
|
|
276
349
|
</div>
|