| PrecisionDashWidgets | PennsieveDashboard | SparcDashWidgets | Notes |
|---|---|---|---|
| >= 1.0.0 | >= 1.0.0 | >= 1.0.0 | s3Url/apiUrl removed — use services instead |
| < 1.0.0 | < 1.0.0 | < 1.0.0 | Legacy s3Url/apiUrl on GlobalVarsShape |
Pre-1.0 versions of these packages are not compatible with 1.0+. If you are using SparcDashWidgets or PrecisionDashWidgets, all three packages must be on the same side of the 1.0 boundary.
For full documentation on embedding widgets and building custom widget libraries, see the PennsieveDashboard README.
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.
- Node.js >= 18
- npm >= 9
# Clone the repo
git clone https://github.com/nih-sparc/DashboardRepo.git
cd DashboardRepo/packages/PrecisionDashWidgets
# Install dependencies
npm install
# Start the dev server (opens at http://localhost:5173)
npm run devThe dev server loads src/App.vue, which renders a MultiDashboard with all the widgets wired up against sample data. This is the fastest way to see everything in action.
# Build for distribution (outputs to dist/)
npm run build
# Preview the built package
npm run previewThe build produces two bundles in dist/:
precision-dashwidgets.es.js(ESM)precision-dashwidgets.umd.js(UMD)style.css(combined styles)
The codebase uses a two-layer architecture under src/:
-
src/components/— Lightweight wrapper components that form the public API. Each wrapper handles prop injection (e.g.dataPath,initialGene1), connects to the shared Pinia store, resolves the data URL viauseDashboardGlobalVars(), and delegates all rendering to a corresponding lib component. These are what consumers import. -
src/libs/— The implementation layer where the actual visualization logic lives. Lib components own DuckDB initialization, WebGL/D3 rendering, user interaction handling, and local state. For example,components/GeneExpressionViewer/wrapslibs/GeneExpressionViewer/GeneCoexpressionViewer.vue.
This split keeps the public-facing component API clean and dashboard-aware, while the libs remain self-contained and reusable.
All data access goes through src/libs/dataManager.js, which exports the UMAPGeneViewer class. When a component mounts, it creates a UMAPGeneViewer instance pointed at a base URL (S3, HTTP, etc.) containing Parquet files. The class initializes a DuckDB WASM worker in-browser, fetches the Parquet files as binary buffers, registers them in DuckDB, and then runs SQL queries entirely client-side — no backend needed.
Key operations include loading UMAP/tSNE coordinates, searching genes via full-text SQL queries against gene_stats.parquet, and loading individual gene expression data on-demand from chunked Parquet files referenced in gene_locations.parquet. Results are cached in memory so repeated gene lookups are instant.
Each component currently creates its own UMAPGeneViewer / DuckDB instance. See Known Limitations and the Roadmap for planned shared-instance support.
Cross-widget coordination (e.g. selecting a gene in one widget updates another) is handled by a Pinia store (usePrecisionStore). Dashboard-level context like the data URL and filters flows through Vue's provide/inject via DASHBOARD_GLOBAL_VARS_KEY.
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.
| Prop | Type | Description |
|---|---|---|
dataPath |
string? |
URL to data directory containing Parquet files. Falls back to injected services.s3Url, then the default. |
initialGene1 |
string? |
Pre-selected first gene (e.g. "CDH9") |
initialGene2 |
string? |
Pre-selected second gene (e.g. "TAC1") |
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.
| Prop | Type | Description |
|---|---|---|
dataPath |
string? |
URL to data directory containing Parquet files. Falls back to injected services.s3Url, then the default. |
initialGene |
string? |
Pre-selected gene for the expression panel |
initialMetadataColumn |
string? |
Pre-selected metadata column for the metadata panel |
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.
| Prop | Type | Description |
|---|---|---|
dataPath |
string? |
URL to data directory containing Parquet files. Falls back to injected services.s3Url, then the default. |
initialGene |
string? |
Pre-selected gene |
initialMetadataColumn |
string? |
Pre-selected metadata column for x-axis grouping |
- DataExplorer - Data exploration interface
- UMAP - Standalone UMAP visualization
- ProportionPlot - Cell proportion visualization
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:
| File | Required | Description |
|---|---|---|
umap_complete.parquet |
Yes | UMAP coordinates + cell metadata |
tsne_complete.parquet |
No | tSNE coordinates (enables tSNE option in GeneExpression) |
gene_locations.parquet |
Yes | Mapping of gene names to their data file locations |
gene_stats.parquet |
Yes | Gene-level statistics used for search/autocomplete |
cells.parquet |
Yes | Cell metadata (cell types, clusters, etc.) |
metadata.parquet |
No | Alternative/extended metadata source |
genes.parquet |
Yes | Gene name to ID mapping |
Gene expression values are loaded on-demand from individual or chunked Parquet files referenced in gene_locations.parquet.
When used within the pennsieve-dashboard, components are provided context automatically via Vue's provide/inject and a shared Pinia store.
The package is already a workspace dependency. If needed:
npm install precision-dashwidgetsimport {
GeneExpression,
SideBySide,
ViolinPlot, // or GeneXDistribution
} from 'precision-dashwidgets'
import 'precision-dashwidgets/style.css'The dashboard uses a widget layout system. Register components in your layout config:
const defaultLayout = [
{
id: 'GeneExpression-0',
x: 0, y: 0, w: 3, h: 8,
componentKey: 'GeneExpression',
componentName: 'Gene Co-expression',
component: GeneExpression,
Props: {
initialGene1: 'CDH9',
initialGene2: 'TAC1',
},
},
{
id: 'SideBySide-0',
x: 3, y: 0, w: 3, h: 8,
componentKey: 'SideBySide',
componentName: 'Side by Side',
component: SideBySide,
Props: {
initialGene: 'CDH9',
initialMetadataColumn: 'cell_type',
},
},
{
id: 'ViolinPlot-0',
x: 0, y: 8, w: 3, h: 8,
componentKey: 'ViolinPlot',
componentName: 'Gene Distribution',
component: ViolinPlot,
Props: {
initialGene: 'CDH9',
initialMetadataColumn: 'cell_type',
},
},
]The dashboard provides two context mechanisms:
- Global vars injection (
DASHBOARD_GLOBAL_VARS_KEY) - providesservices(configurables like data URLs, API keys) and sharedfiltersviaprovide/inject. - 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.
These are wired automatically when used inside the dashboard framework.
You can use these components in any Vue 3 application. This requires setting up the dependencies that the dashboard normally provides.
npm install precision-dashwidgetsInstall the required peer dependencies:
npm install vue pinia element-plus @element-plus/icons-vue// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.use(ElementPlus)
app.mount('#app')import 'precision-dashwidgets/style.css'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.
<template>
<div style="height: 600px; width: 100%;">
<GeneExpression
dataPath="https://your-bucket.s3.amazonaws.com/your-dataset"
initialGene1="CDH9"
initialGene2="TAC1"
/>
</div>
<div style="height: 600px; width: 100%;">
<SideBySide
dataPath="https://your-bucket.s3.amazonaws.com/your-dataset"
initialGene="CDH9"
initialMetadataColumn="cell_type"
/>
</div>
<div style="height: 500px; width: 100%;">
<ViolinPlot
dataPath="https://your-bucket.s3.amazonaws.com/your-dataset"
initialGene="CDH9"
initialMetadataColumn="cell_type"
/>
</div>
</template>
<script setup>
import {
GeneExpression,
SideBySide,
ViolinPlot,
} from 'precision-dashwidgets'
</script>Instead of passing dataPath to every component, you can provide DASHBOARD_GLOBAL_VARS_KEY in an ancestor component. All widgets will inherit services.s3Url from it:
<script setup>
import { provide, ref } from 'vue'
import { DASHBOARD_GLOBAL_VARS_KEY } from 'precision-dashwidgets'
provide(DASHBOARD_GLOBAL_VARS_KEY, {
services: ref({ s3Url: 'https://your-bucket.s3.amazonaws.com/your-dataset' }),
filters: ref({}),
setFilter: () => {},
clearFilter: () => {},
})
</script>Resolution order: dataPath prop > injected services.s3Url > built-in default URL.
If you need to read or control widget selections programmatically, import the Pinia store:
import { usePrecisionStore } from 'precision-dashwidgets'
const store = usePrecisionStore()
// Read current selections
console.log(store.selectedGene)
console.log(store.selectedMetadataColumn)
// Set selections programmatically
store.setSelectedGene('TAC1')
store.setSelectedMetadataColumn('cluster')| Property | Used By | Description |
|---|---|---|
selectedGene |
SideBySide | Currently selected gene |
selectedMetadataColumn |
SideBySide, ViolinPlot | Currently selected metadata column |
selectedGene1 |
GeneExpression | First gene in co-expression |
selectedGene2 |
GeneExpression | Second gene in co-expression |
selectedGeneX |
ViolinPlot | Selected gene for distribution plot |
// Components
export { GeneExpression } // Gene co-expression analysis
export { SideBySide } // Dual UMAP comparison
export { ViolinPlot } // Violin/box plot (alias for GeneXDistribution)
export { GeneXDistribution } // Violin/box plot (original name)
export { DataExplorer } // Data exploration
export { UMAP } // Standalone UMAP
export { ProportionPlot } // Proportion visualization
// Utilities
export { usePrecisionStore } // Pinia store for shared widget state
export { useDashboardGlobalVars } // Composable to access injected global vars
export { DASHBOARD_GLOBAL_VARS_KEY } // Injection key for providing global varsYou can point the widgets at any HTTP-accessible directory, as long as it contains Parquet files with the exact names listed in Data Requirements. The internal dataManager appends those filenames to whatever base path you provide (e.g. {dataPath}/umap_complete.parquet).
Currently there is no way to customize file names, column mappings, or the data schema. See the roadmap below for planned support.
- Fixed Parquet file names: Your data directory must use the exact file names the
dataManagerexpects (umap_complete.parquet,genes.parquet, etc.). Custom naming is not yet supported. - 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.
- 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. - Sizing: Components expand to fill their parent container. Wrap them in a container with explicit
heightandwidth. - One DuckDB instance per component: Each widget creates its own
UMAPGeneViewer/ DuckDB instance. If you mount multiple widgets, they each load data independently.
Currently the dataManager has hardcoded file names and column expectations. The plan for making this fully configurable:
Introduce a DataConfig type that maps logical data roles to actual file paths and column names:
interface DataConfig {
// Base URL for all data files
basePath: string
// File name overrides (defaults to current names)
files: {
umap?: string // default: "umap_complete.parquet"
tsne?: string // default: "tsne_complete.parquet"
geneLocations?: string // default: "gene_locations.parquet"
geneStats?: string // default: "gene_stats.parquet"
cells?: string // default: "cells.parquet"
metadata?: string // default: "metadata.parquet"
genes?: string // default: "genes.parquet"
}
// Column name mappings per file
columns: {
umap?: {
x?: string // default: "umap_1"
y?: string // default: "umap_2"
cellId?: string // default: "cell_id"
}
tsne?: {
x?: string // default: "tsne_1"
y?: string // default: "tsne_2"
}
genes?: {
name?: string // default: "gene_name"
id?: string // default: "gene_id"
}
// ... etc for each file
}
}Refactor UMAPGeneViewer to accept a DataConfig instead of a plain basePath string:
loadEssentialData()reads file paths fromconfig.filesinstead of hardcoded names.- All SQL queries use
config.columnsmappings instead of hardcoded column names. - Merge user-provided config with defaults so only overrides need to be specified.
Instead of each component creating its own DuckDB instance:
- Create a
useDataManager()composable that provides a singletonUMAPGeneViewerviaprovide/inject. - Components call
useDataManager()to get the shared instance. - The config is set once at the provider level, all widgets share the connection and cache.
Wire the config through the component layer:
- Wrapper components accept an optional
dataConfigprop. - If not provided, fall back to the injected shared instance.
- The
provideapproach from Phase 3 becomes the recommended setup for standalone users.
This would let standalone users do:
<script setup>
import { provide } from 'vue'
import { createDataProvider } from 'precision-dashwidgets'
const dataProvider = createDataProvider({
basePath: 'https://my-bucket.s3.amazonaws.com/my-data',
files: {
umap: 'embeddings.parquet',
genes: 'gene_index.parquet',
},
columns: {
umap: { x: 'dim1', y: 'dim2' },
},
})
provide('precision:data', dataProvider)
</script>- Vue 3 - Component framework
- DuckDB WASM - Client-side SQL engine for Parquet file queries
- D3.js - SVG-based visualizations (violin plots, axes, legends)
- regl - WebGL rendering for high-performance scatter plots
- Pinia - State management across widgets
- Element Plus - UI components (dropdowns, inputs)
- Vite - Build tooling (library mode)