precision-dashwidgets 0.5.1 → 0.5.3

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/README.md CHANGED
@@ -1,5 +1,415 @@
1
- # Vue 3 + TypeScript + Vite
1
+ # precision-dashwidgets
2
2
 
3
- This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
3
+ Interactive genomics visualization widgets built with Vue 3. These components provide gene expression analysis, dimensionality reduction plots (UMAP/tSNE), and distribution visualizations powered by DuckDB WASM for client-side querying of Parquet data files.
4
4
 
5
- Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
5
+ ## Components
6
+
7
+ ### GeneExpression
8
+
9
+ Gene co-expression analysis widget. Renders a scatter plot (UMAP or tSNE) colored by the co-expression of two selected genes. Includes autocomplete gene search, dual gene selection, and a statistics panel.
10
+
11
+ | Prop | Type | Description |
12
+ |------|------|-------------|
13
+ | `dataPath` | `string?` | URL to data directory containing Parquet files. Falls back to injected `s3Url`, then the default. |
14
+ | `initialGene1` | `string?` | Pre-selected first gene (e.g. `"CDH9"`) |
15
+ | `initialGene2` | `string?` | Pre-selected second gene (e.g. `"TAC1"`) |
16
+
17
+ ### SideBySide
18
+
19
+ Dual-panel comparison widget. Left panel shows a UMAP colored by a selected metadata column (cell type, cluster, etc.). Right panel shows a UMAP colored by expression level of a selected gene. Useful for comparing spatial clustering against gene expression patterns.
20
+
21
+ | Prop | Type | Description |
22
+ |------|------|-------------|
23
+ | `dataPath` | `string?` | URL to data directory containing Parquet files. Falls back to injected `s3Url`, then the default. |
24
+ | `initialGene` | `string?` | Pre-selected gene for the expression panel |
25
+ | `initialMetadataColumn` | `string?` | Pre-selected metadata column for the metadata panel |
26
+
27
+ ### ViolinPlot (also exported as `GeneXDistribution`)
28
+
29
+ Gene expression distribution widget. Renders violin or box plots showing the distribution of a gene's expression across metadata categories. Supports toggling between violin and box plot modes, and can overlay individual data points.
30
+
31
+ | Prop | Type | Description |
32
+ |------|------|-------------|
33
+ | `dataPath` | `string?` | URL to data directory containing Parquet files. Falls back to injected `s3Url`, then the default. |
34
+ | `initialGene` | `string?` | Pre-selected gene |
35
+ | `initialMetadataColumn` | `string?` | Pre-selected metadata column for x-axis grouping |
36
+
37
+ ### Additional Components
38
+
39
+ - **DataExplorer** - Data exploration interface
40
+ - **UMAP** - Standalone UMAP visualization
41
+ - **ProportionPlot** - Cell proportion visualization
42
+
43
+ ## Data Requirements
44
+
45
+ All components use DuckDB WASM to query Parquet files from a remote data path (S3 or any HTTP-accessible location). The data path must contain the following files:
46
+
47
+ | File | Required | Description |
48
+ |------|----------|-------------|
49
+ | `umap_complete.parquet` | Yes | UMAP coordinates + cell metadata |
50
+ | `tsne_complete.parquet` | No | tSNE coordinates (enables tSNE option in GeneExpression) |
51
+ | `gene_locations.parquet` | Yes | Mapping of gene names to their data file locations |
52
+ | `gene_stats.parquet` | Yes | Gene-level statistics used for search/autocomplete |
53
+ | `cells.parquet` | Yes | Cell metadata (cell types, clusters, etc.) |
54
+ | `metadata.parquet` | No | Alternative/extended metadata source |
55
+ | `genes.parquet` | Yes | Gene name to ID mapping |
56
+
57
+ Gene expression values are loaded on-demand from individual or chunked Parquet files referenced in `gene_locations.parquet`.
58
+
59
+ ---
60
+
61
+ ## Use Case 1: Inside the Pennsieve Dashboard
62
+
63
+ When used within the `pennsieve-dashboard`, components are provided context automatically via Vue's `provide`/`inject` and a shared Pinia store.
64
+
65
+ ### Install
66
+
67
+ The package is already a workspace dependency. If needed:
68
+
69
+ ```bash
70
+ npm install precision-dashwidgets
71
+ ```
72
+
73
+ ### Import Components
74
+
75
+ ```js
76
+ import {
77
+ GeneExpression,
78
+ SideBySide,
79
+ ViolinPlot, // or GeneXDistribution
80
+ } from 'precision-dashwidgets'
81
+ import 'precision-dashwidgets/style.css'
82
+ ```
83
+
84
+ ### Register in a Layout
85
+
86
+ The dashboard uses a widget layout system. Register components in your layout config:
87
+
88
+ ```js
89
+ const defaultLayout = [
90
+ {
91
+ id: 'GeneExpression-0',
92
+ x: 0, y: 0, w: 3, h: 8,
93
+ componentKey: 'GeneExpression',
94
+ componentName: 'Gene Co-expression',
95
+ component: GeneExpression,
96
+ Props: {
97
+ initialGene1: 'CDH9',
98
+ initialGene2: 'TAC1',
99
+ },
100
+ },
101
+ {
102
+ id: 'SideBySide-0',
103
+ x: 3, y: 0, w: 3, h: 8,
104
+ componentKey: 'SideBySide',
105
+ componentName: 'Side by Side',
106
+ component: SideBySide,
107
+ Props: {
108
+ initialGene: 'CDH9',
109
+ initialMetadataColumn: 'cell_type',
110
+ },
111
+ },
112
+ {
113
+ id: 'ViolinPlot-0',
114
+ x: 0, y: 8, w: 3, h: 8,
115
+ componentKey: 'ViolinPlot',
116
+ componentName: 'Gene Distribution',
117
+ component: ViolinPlot,
118
+ Props: {
119
+ initialGene: 'CDH9',
120
+ initialMetadataColumn: 'cell_type',
121
+ },
122
+ },
123
+ ]
124
+ ```
125
+
126
+ ### How State Flows in the Dashboard
127
+
128
+ The dashboard provides two context mechanisms:
129
+
130
+ 1. **Global vars injection** (`DASHBOARD_GLOBAL_VARS_KEY`) - provides `s3Url`, `apiUrl`, and shared filters via `provide`/`inject`.
131
+ 2. **Pinia store** (`usePrecisionStore`) - shared reactive state for gene and metadata selections across all widgets. When a user selects a gene in one widget, other widgets can react.
132
+
133
+ These are wired automatically when used inside the dashboard framework.
134
+
135
+ ---
136
+
137
+ ## Use Case 2: Standalone (Without Pennsieve Dashboard)
138
+
139
+ You can use these components in any Vue 3 application. This requires setting up the dependencies that the dashboard normally provides.
140
+
141
+ ### Install
142
+
143
+ ```bash
144
+ npm install precision-dashwidgets
145
+ ```
146
+
147
+ ### Peer Dependencies
148
+
149
+ Install the required peer dependencies:
150
+
151
+ ```bash
152
+ npm install vue pinia element-plus @element-plus/icons-vue
153
+ ```
154
+
155
+ ### Setup
156
+
157
+ #### 1. Register Pinia and Element Plus
158
+
159
+ ```js
160
+ // main.ts
161
+ import { createApp } from 'vue'
162
+ import { createPinia } from 'pinia'
163
+ import ElementPlus from 'element-plus'
164
+ import 'element-plus/dist/index.css'
165
+ import App from './App.vue'
166
+
167
+ const app = createApp(App)
168
+ app.use(createPinia())
169
+ app.use(ElementPlus)
170
+ app.mount('#app')
171
+ ```
172
+
173
+ #### 2. Import Styles
174
+
175
+ ```js
176
+ import 'precision-dashwidgets/style.css'
177
+ ```
178
+
179
+ #### 3. Use the Components
180
+
181
+ The simplest approach is to pass your data URL directly via the `dataPath` prop. Your directory must contain the same Parquet file names listed in [Data Requirements](#data-requirements).
182
+
183
+ ```vue
184
+ <template>
185
+ <div style="height: 600px; width: 100%;">
186
+ <GeneExpression
187
+ dataPath="https://your-bucket.s3.amazonaws.com/your-dataset"
188
+ initialGene1="CDH9"
189
+ initialGene2="TAC1"
190
+ />
191
+ </div>
192
+
193
+ <div style="height: 600px; width: 100%;">
194
+ <SideBySide
195
+ dataPath="https://your-bucket.s3.amazonaws.com/your-dataset"
196
+ initialGene="CDH9"
197
+ initialMetadataColumn="cell_type"
198
+ />
199
+ </div>
200
+
201
+ <div style="height: 500px; width: 100%;">
202
+ <ViolinPlot
203
+ dataPath="https://your-bucket.s3.amazonaws.com/your-dataset"
204
+ initialGene="CDH9"
205
+ initialMetadataColumn="cell_type"
206
+ />
207
+ </div>
208
+ </template>
209
+
210
+ <script setup>
211
+ import {
212
+ GeneExpression,
213
+ SideBySide,
214
+ ViolinPlot,
215
+ } from 'precision-dashwidgets'
216
+ </script>
217
+ ```
218
+
219
+ #### Alternative: Provide Global Vars (set data path once)
220
+
221
+ Instead of passing `dataPath` to every component, you can provide `DASHBOARD_GLOBAL_VARS_KEY` in an ancestor component. All widgets will inherit the `s3Url` from it:
222
+
223
+ ```vue
224
+ <script setup>
225
+ import { provide, ref } from 'vue'
226
+ import { DASHBOARD_GLOBAL_VARS_KEY } from 'precision-dashwidgets'
227
+
228
+ provide(DASHBOARD_GLOBAL_VARS_KEY, {
229
+ s3Url: ref('https://your-bucket.s3.amazonaws.com/your-dataset'),
230
+ apiUrl: ref(''),
231
+ filters: ref({}),
232
+ setFilter: () => {},
233
+ clearFilter: () => {},
234
+ })
235
+ </script>
236
+ ```
237
+
238
+ **Resolution order:** `dataPath` prop > injected `s3Url` > built-in default URL.
239
+
240
+ ### Accessing Shared State
241
+
242
+ If you need to read or control widget selections programmatically, import the Pinia store:
243
+
244
+ ```js
245
+ import { usePrecisionStore } from 'precision-dashwidgets'
246
+
247
+ const store = usePrecisionStore()
248
+
249
+ // Read current selections
250
+ console.log(store.selectedGene)
251
+ console.log(store.selectedMetadataColumn)
252
+
253
+ // Set selections programmatically
254
+ store.setSelectedGene('TAC1')
255
+ store.setSelectedMetadataColumn('cluster')
256
+ ```
257
+
258
+ #### Store Properties
259
+
260
+ | Property | Used By | Description |
261
+ |----------|---------|-------------|
262
+ | `selectedGene` | SideBySide | Currently selected gene |
263
+ | `selectedMetadataColumn` | SideBySide, ViolinPlot | Currently selected metadata column |
264
+ | `selectedGene1` | GeneExpression | First gene in co-expression |
265
+ | `selectedGene2` | GeneExpression | Second gene in co-expression |
266
+ | `selectedGeneX` | ViolinPlot | Selected gene for distribution plot |
267
+
268
+ ---
269
+
270
+ ## Exports
271
+
272
+ ```js
273
+ // Components
274
+ export { GeneExpression } // Gene co-expression analysis
275
+ export { SideBySide } // Dual UMAP comparison
276
+ export { ViolinPlot } // Violin/box plot (alias for GeneXDistribution)
277
+ export { GeneXDistribution } // Violin/box plot (original name)
278
+ export { DataExplorer } // Data exploration
279
+ export { UMAP } // Standalone UMAP
280
+ export { ProportionPlot } // Proportion visualization
281
+
282
+ // Utilities
283
+ export { usePrecisionStore } // Pinia store for shared widget state
284
+ export { useDashboardGlobalVars } // Composable to access injected global vars
285
+ export { DASHBOARD_GLOBAL_VARS_KEY } // Injection key for providing global vars
286
+ ```
287
+
288
+ ## Bringing Your Own Data
289
+
290
+ You can point the widgets at any HTTP-accessible directory, as long as it contains Parquet files with the **exact names** listed in [Data Requirements](#data-requirements). The internal `dataManager` appends those filenames to whatever base path you provide (e.g. `{dataPath}/umap_complete.parquet`).
291
+
292
+ Currently there is no way to customize file names, column mappings, or the data schema. See the roadmap below for planned support.
293
+
294
+ ## Known Limitations
295
+
296
+ - **Fixed Parquet file names**: Your data directory must use the exact file names the `dataManager` expects (`umap_complete.parquet`, `genes.parquet`, etc.). Custom naming is not yet supported.
297
+ - **Fixed column schema**: The components expect specific column names inside the Parquet files (e.g. UMAP coordinates, gene expression values). These cannot be remapped yet.
298
+ - **DuckDB WASM**: Components initialize a DuckDB WASM instance at mount time, which downloads the DuckDB worker from jsDelivr CDN. Ensure your environment allows loading scripts from `cdn.jsdelivr.net`.
299
+ - **Sizing**: Components expand to fill their parent container. Wrap them in a container with explicit `height` and `width`.
300
+ - **One DuckDB instance per component**: Each widget creates its own `UMAPGeneViewer` / DuckDB instance. If you mount multiple widgets, they each load data independently.
301
+
302
+ ---
303
+
304
+ ## Roadmap: Configurable Data Schema
305
+
306
+ Currently the `dataManager` has hardcoded file names and column expectations. The plan for making this fully configurable:
307
+
308
+ ### Phase 1 - Data Config Object
309
+
310
+ Introduce a `DataConfig` type that maps logical data roles to actual file paths and column names:
311
+
312
+ ```ts
313
+ interface DataConfig {
314
+ // Base URL for all data files
315
+ basePath: string
316
+
317
+ // File name overrides (defaults to current names)
318
+ files: {
319
+ umap?: string // default: "umap_complete.parquet"
320
+ tsne?: string // default: "tsne_complete.parquet"
321
+ geneLocations?: string // default: "gene_locations.parquet"
322
+ geneStats?: string // default: "gene_stats.parquet"
323
+ cells?: string // default: "cells.parquet"
324
+ metadata?: string // default: "metadata.parquet"
325
+ genes?: string // default: "genes.parquet"
326
+ }
327
+
328
+ // Column name mappings per file
329
+ columns: {
330
+ umap?: {
331
+ x?: string // default: "umap_1"
332
+ y?: string // default: "umap_2"
333
+ cellId?: string // default: "cell_id"
334
+ }
335
+ tsne?: {
336
+ x?: string // default: "tsne_1"
337
+ y?: string // default: "tsne_2"
338
+ }
339
+ genes?: {
340
+ name?: string // default: "gene_name"
341
+ id?: string // default: "gene_id"
342
+ }
343
+ // ... etc for each file
344
+ }
345
+ }
346
+ ```
347
+
348
+ ### Phase 2 - Update dataManager.js
349
+
350
+ Refactor `UMAPGeneViewer` to accept a `DataConfig` instead of a plain `basePath` string:
351
+
352
+ 1. `loadEssentialData()` reads file paths from `config.files` instead of hardcoded names.
353
+ 2. All SQL queries use `config.columns` mappings instead of hardcoded column names.
354
+ 3. Merge user-provided config with defaults so only overrides need to be specified.
355
+
356
+ ### Phase 3 - Shared DuckDB Instance
357
+
358
+ Instead of each component creating its own DuckDB instance:
359
+
360
+ 1. Create a `useDataManager()` composable that provides a singleton `UMAPGeneViewer` via `provide`/`inject`.
361
+ 2. Components call `useDataManager()` to get the shared instance.
362
+ 3. The config is set once at the provider level, all widgets share the connection and cache.
363
+
364
+ ### Phase 4 - Component Props
365
+
366
+ Wire the config through the component layer:
367
+
368
+ 1. Wrapper components accept an optional `dataConfig` prop.
369
+ 2. If not provided, fall back to the injected shared instance.
370
+ 3. The `provide` approach from Phase 3 becomes the recommended setup for standalone users.
371
+
372
+ This would let standalone users do:
373
+
374
+ ```vue
375
+ <script setup>
376
+ import { provide } from 'vue'
377
+ import { createDataProvider } from 'precision-dashwidgets'
378
+
379
+ const dataProvider = createDataProvider({
380
+ basePath: 'https://my-bucket.s3.amazonaws.com/my-data',
381
+ files: {
382
+ umap: 'embeddings.parquet',
383
+ genes: 'gene_index.parquet',
384
+ },
385
+ columns: {
386
+ umap: { x: 'dim1', y: 'dim2' },
387
+ },
388
+ })
389
+
390
+ provide('precision:data', dataProvider)
391
+ </script>
392
+ ```
393
+
394
+ ## Development
395
+
396
+ ```bash
397
+ # Start dev server
398
+ npm run dev
399
+
400
+ # Build library
401
+ npm run build
402
+
403
+ # Preview build
404
+ npm run preview
405
+ ```
406
+
407
+ ## Tech Stack
408
+
409
+ - **Vue 3** - Component framework
410
+ - **DuckDB WASM** - Client-side SQL engine for Parquet file queries
411
+ - **D3.js** - SVG-based visualizations (violin plots, axes, legends)
412
+ - **regl** - WebGL rendering for high-performance scatter plots
413
+ - **Pinia** - State management across widgets
414
+ - **Element Plus** - UI components (dropdowns, inputs)
415
+ - **Vite** - Build tooling (library mode)
@@ -1,5 +1,5 @@
1
1
  import { i as r } from "./pako.esm-D_m2s4NW.mjs";
2
- import { B as a } from "./index-BHHg5z03.mjs";
2
+ import { B as a } from "./index-DVfUy50r.mjs";
3
3
  class s extends a {
4
4
  decodeBlock(e) {
5
5
  return r(new Uint8Array(e)).buffer;
@@ -0,0 +1,4 @@
1
+ import { a as r } from "./index-Dl_ufo4v.mjs";
2
+ export {
3
+ r as TextViewer
4
+ };
@@ -2,7 +2,7 @@ var Ky = Object.defineProperty;
2
2
  var Oy = (t, A, e) => A in t ? Ky(t, A, { enumerable: !0, configurable: !0, writable: !0, value: e }) : t[A] = e;
3
3
  var f = (t, A, e) => Oy(t, typeof A != "symbol" ? A + "" : A, e);
4
4
  import { defineComponent as pC, ref as ee, watch as jI, onMounted as Hf, onUnmounted as Yf, openBlock as Ne, createElementBlock as ve, unref as Bo, createElementVNode as _A, createCommentVNode as vt, toDisplayString as Mo, Fragment as Wy, createVNode as Vy, nextTick as zy, normalizeStyle as Xy, computed as ll, createTextVNode as El, normalizeClass as jy } from "vue";
5
- import { g as Zy } from "./index-Hx8PFhs9.mjs";
5
+ import { g as Zy } from "./index-Dl_ufo4v.mjs";
6
6
  function sa(t, A) {
7
7
  if (!t)
8
8
  throw new Error(A || "loader assertion failed.");
@@ -28949,19 +28949,19 @@ const U0 = {
28949
28949
  function Ht(t, A) {
28950
28950
  Array.isArray(t) || (t = [t]), t.forEach((e) => Jk.set(e, A));
28951
28951
  }
28952
- Ht([void 0, 1], () => import("./raw-KFPBw5cQ.mjs").then((t) => t.default));
28953
- Ht(5, () => import("./lzw-u9cBMr9g.mjs").then((t) => t.default));
28952
+ Ht([void 0, 1], () => import("./raw-CommQb0l.mjs").then((t) => t.default));
28953
+ Ht(5, () => import("./lzw-Bh2N3zBo.mjs").then((t) => t.default));
28954
28954
  Ht(6, () => {
28955
28955
  throw new Error("old style JPEG compression is not supported.");
28956
28956
  });
28957
- Ht(7, () => import("./jpeg-l5M1J8Gh.mjs").then((t) => t.default));
28958
- Ht([8, 32946], () => import("./deflate-BQVLA3Ul.mjs").then((t) => t.default));
28959
- Ht(32773, () => import("./packbits-dQba4PyI.mjs").then((t) => t.default));
28957
+ Ht(7, () => import("./jpeg-CXAyIdl8.mjs").then((t) => t.default));
28958
+ Ht([8, 32946], () => import("./deflate-CPNmBnej.mjs").then((t) => t.default));
28959
+ Ht(32773, () => import("./packbits-DaRNs4L0.mjs").then((t) => t.default));
28960
28960
  Ht(
28961
28961
  34887,
28962
- () => import("./lerc-BipMr5R9.mjs").then(async (t) => (await t.zstd.init(), t)).then((t) => t.default)
28962
+ () => import("./lerc-DmIa8s0n.mjs").then(async (t) => (await t.zstd.init(), t)).then((t) => t.default)
28963
28963
  );
28964
- Ht(50001, () => import("./webimage-CBOAIeUr.mjs").then((t) => t.default));
28964
+ Ht(50001, () => import("./webimage-BKZpDwde.mjs").then((t) => t.default));
28965
28965
  function Hk(t, A) {
28966
28966
  let e = t.length - A, i = 0;
28967
28967
  do {
@@ -44756,7 +44756,7 @@ Si([8, 32946], () => import("./deflate-DbhbvOaP-D8NSBt31.mjs").then((t) => t.def
44756
44756
  Si(32773, () => import("./packbits-BuzK6gM3-6Um4hupZ.mjs").then((t) => t.default));
44757
44757
  Si(
44758
44758
  34887,
44759
- () => import("./lerc-XufrP0FH-SNCeY3ab.mjs").then(async (t) => (await t.zstd.init(), t)).then((t) => t.default)
44759
+ () => import("./lerc-XufrP0FH-Cp9B8C70.mjs").then(async (t) => (await t.zstd.init(), t)).then((t) => t.default)
44760
44760
  );
44761
44761
  Si(50001, () => import("./webimage--SJddlky-F_hESl4q.mjs").then((t) => t.default));
44762
44762
  function xg(t, A, e, i = 1) {