precision-dashwidgets
v1.0.0
Published
## Version Compatibility
Readme
precision-dashwidgets
Version Compatibility
| PrecisionDashWidgets | PennsieveDashboard | Notes |
|----------------------|--------------------|-------|
| >= 1.0.0 | >= 1.0.0 | s3Url/apiUrl removed — use services instead |
| < 1.0.0 | < 1.0.0 | Legacy s3Url/apiUrl on GlobalVarsShape |
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.
Getting Started
Prerequisites
- Node.js >= 18
- npm >= 9
Clone and Run
# 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 the Library
# 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)
Architecture
Libs vs Components
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.
DuckDB and dataManager.js
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.
Shared State
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.
Components
GeneExpression
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") |
SideBySide
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 |
ViolinPlot (also exported as GeneXDistribution)
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 |
Additional Components
- DataExplorer - Data exploration interface
- UMAP - Standalone UMAP visualization
- ProportionPlot - Cell proportion visualization
Data Requirements
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.
Use Case 1: Inside the Pennsieve Dashboard
When used within the pennsieve-dashboard, components are provided context automatically via Vue's provide/inject and a shared Pinia store.
Install
The package is already a workspace dependency. If needed:
npm install precision-dashwidgetsImport Components
import {
GeneExpression,
SideBySide,
ViolinPlot, // or GeneXDistribution
} from 'precision-dashwidgets'
import 'precision-dashwidgets/style.css'Register in a Layout
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',
},
},
]How State Flows in the Dashboard
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.
Use Case 2: Standalone (Without Pennsieve Dashboard)
You can use these components in any Vue 3 application. This requires setting up the dependencies that the dashboard normally provides.
Install
npm install precision-dashwidgetsPeer Dependencies
Install the required peer dependencies:
npm install vue pinia element-plus @element-plus/icons-vueSetup
1. Register Pinia and Element Plus
// 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')2. Import Styles
import 'precision-dashwidgets/style.css'3. Use the Components
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>Alternative: Provide Global Vars (set data path once)
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.
Accessing Shared State
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')Store Properties
| 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 |
Exports
// 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 varsBringing Your Own Data
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. 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.
Known Limitations
- 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.
Roadmap: Configurable Data Schema
Currently the dataManager has hardcoded file names and column expectations. The plan for making this fully configurable:
Phase 1 - Data Config Object
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
}
}Phase 2 - Update dataManager.js
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.
Phase 3 - Shared DuckDB Instance
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.
Phase 4 - Component Props
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>Tech Stack
- 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)
