precision-dashwidgets
v1.0.1
Published
## Version Compatibility
Downloads
226
Readme
precision-dashwidgets
Version Compatibility
| 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.
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)
