A monorepo containing Vue 3 visualization components for the Pennsieve platform.
| Package | Description |
|---|---|
@pennsieve-viz/core |
Main library — DataExplorer, UMAP, Markdown, TextViewer, OrthogonalFrame, plus lazy re-exports of all viewer packages |
@pennsieve-viz/tsviewer |
Timeseries data viewer and annotator |
@pennsieve-viz/micro-ct |
OME-TIFF / TIFF viewer components for microscopy data |
@pennsieve-viz/orthogonal |
Neuroglancer-based orthogonal viewer for OME-Zarr volumes |
pennsieve-visualization/
├── packages/
│ ├── core/ # @pennsieve-viz/core
│ │ └── src/
│ │ ├── data-explorer/
│ │ ├── umap/
│ │ ├── markdown/
│ │ ├── text-viewer/
│ │ ├── ai-plotly/ # beta
│ │ ├── proportion-plot/ # beta
│ │ ├── orthogonal/ # OrthogonalFrame (iframe wrapper)
│ │ ├── duckdb/ # DuckDB interface types
│ │ └── composables/
│ ├── orthogonal/ # @pennsieve-viz/orthogonal
│ ├── ts-viewer/ # @pennsieve-viz/tsviewer
│ └── micro-ct/ # @pennsieve-viz/micro-ct
├── src/ # Dev playground app (not published)
│ ├── store/duckdbStore.js
│ └── main.js
├── pnpm-workspace.yaml
└── package.json
- Node.js >= 18
- pnpm >= 8
-
Clone the repo
git clone https://github.com/Pennsieve/Pennsieve-Visualization.git cd Pennsieve-Visualization -
Install pnpm (if you don't have it)
npm install -g pnpm
-
Install dependencies
pnpm install
-
Build all packages — this is required before running the dev server, because
coreimports frommicro-ctandts-viewerand needs their built output:pnpm build
-
Start the dev server
pnpm dev
Opens at
http://localhost:5173and servespackages/core/src/App.vueas a playground for testing components.
Why do I need to build first? The core package imports components from
@pennsieve-viz/micro-ctand@pennsieve-viz/tsvieweras dependencies. Without building them, those imports will fail and the dev server won't start.
pnpm dev only hot-reloads changes inside packages/core/. If you edit other packages, rebuild them and refresh:
pnpm build:micro-ct # after editing packages/micro-ct/
pnpm build:tsviewer # after editing packages/ts-viewer/
pnpm build:orthogonal # after editing packages/orthogonal/The orthogonal viewer has its own dev server for the embed app:
# Run the embed app standalone
pnpm --filter @pennsieve-viz/orthogonal dev:embed
# To test with the core playground, run both:
# Terminal 1: embed app on port 5174
pnpm --filter @pennsieve-viz/orthogonal dev:embed
# Terminal 2: core playground on port 5173
pnpm devpnpm build # micro-ct + tsviewer + core (all packages)
pnpm build:core # @pennsieve-viz/core only
pnpm build:micro-ct # @pennsieve-viz/micro-ct only
pnpm build:tsviewer # @pennsieve-viz/tsviewer only
pnpm build:orthogonal # @pennsieve-viz/orthogonal library
pnpm build:orthogonal-embed # orthogonal embed app (dist-embed/)pnpm clean # Remove all dist folders
pnpm lint # Lint all packages
pnpm type-check # Type check all packagesThis monorepo uses Changesets for version management.
pnpm changeset # Create a changeset (select packages + change type)
pnpm version # Update versions and changelogs
pnpm release # Build + publish to npmFor a single package manually:
pnpm --filter @pennsieve-viz/core build
cd packages/core && npm publish --access publicpnpm add @pennsieve-viz/core
# Optional viewer packages (only needed if importing directly, not via core lazy exports)
pnpm add @pennsieve-viz/tsviewer
pnpm add @pennsieve-viz/micro-ct
pnpm add @pennsieve-viz/orthogonal neuroglancerDataExplorer and UMAP use DuckDB for in-browser SQL queries. Your app must provide a DuckDB store via Vue's provide/inject:
-
Install DuckDB:
pnpm add @duckdb/duckdb-wasm
-
Create a DuckDB store that implements the
DuckDBStoreInterface(seesrc/store/duckdbStore.jsfor a reference implementation). -
Provide it in your app entry:
import { createApp } from 'vue' import { createPinia } from 'pinia' import { useDuckDBStore } from './store/duckdbStore' const app = createApp(App) const pinia = createPinia() app.use(pinia) const duckdbStore = useDuckDBStore() app.provide('duckdb', duckdbStore) app.mount('#app')
Components that don't use DuckDB (Markdown, TextViewer, OrthogonalFrame, TSViewer) work without this setup.
import '@pennsieve-viz/core/style.css'<script setup>
import { DataExplorer, UMAP, Markdown, TextViewer } from '@pennsieve-viz/core'
import { OrthogonalFrame } from '@pennsieve-viz/core'
</script><script setup>
import {
DataExplorerLazy,
UMAPLazy,
MarkdownLazy,
TextViewerLazy,
// These lazy-load from their respective packages:
TSViewer,
OmeViewer,
TiffViewer,
OrthogonalViewer
} from '@pennsieve-viz/core'
</script><script setup>
import { ProportionPlotBeta, AiPlotlyBeta } from '@pennsieve-viz/core'
</script>OrthogonalFrame wraps the Neuroglancer viewer in an iframe for full isolation. Point it at the Pennsieve-hosted embed app (or your own):
<OrthogonalFrame
:source="zarrUrl"
layout="4panel"
:embed-url="'https://your-cloudfront-domain.com/embed.html'"
@ready="onReady"
@error="onError"
/>| Prop | Type | Default | Description |
|---|---|---|---|
source |
string |
— | OME-Zarr source URL (required) |
layout |
'4panel' | '3d' | 'xy' | 'xz' | 'yz' |
'4panel' |
Viewer layout |
embedUrl |
string |
'/' |
Base URL of the hosted embed app |
pnpm add vue pinia element-plus
pnpm add @aws-amplify/auth # optional, for authenticated endpointsimport '@pennsieve-viz/tsviewer/style.css'<script setup>
import { TSViewer, createViewerStore, clearViewerStore } from '@pennsieve-viz/tsviewer'
// Create an isolated store instance (supports multiple viewers on one page)
const viewerStore = createViewerStore('my-viewer')
viewerStore.setViewerConfig({
timeseriesDiscoverApi: 'https://api.pennsieve.io/timeseries'
})
viewerStore.fetchAndSetActiveViewer({ packageId: 'your-package-id' })
// Cleanup when done
onUnmounted(() => clearViewerStore('my-viewer'))
</script>
<template>
<TSViewer :pkg="packageData" />
</template>| Prop | Type | Default | Description |
|---|---|---|---|
pkg |
Object |
{} |
Package metadata object |
isPreview |
Boolean |
false |
Preview mode (no toolbar) |
sidePanelOpen |
Boolean |
false |
Side panel state (affects layout) |
const store = createViewerStore('instance-id')
store.setViewerConfig({ timeseriesDiscoverApi: '...' })
store.fetchAndSetActiveViewer({ packageId: '...' })
store.viewerChannels // all channels
store.viewerSelectedChannels // selected channels
store.setSelectedChannels([...])
store.viewerAnnotations
store.createAnnotation(annotation)
store.updateAnnotation(annotation)
store.deleteAnnotation(annotation)
store.resetViewer()
// Cleanup
clearViewerStore('instance-id')
clearAllViewerStores()pnpm add vue @deck.gl/core @deck.gl/extensions @deck.gl/geo-layers @deck.gl/layers @deck.gl/mesh-layers @luma.gl/constants @luma.gl/core @luma.gl/engine @luma.gl/shadertools @luma.gl/webglimport '@pennsieve-viz/micro-ct/style.css'<script setup>
import { OmeViewer, TiffViewer } from '@pennsieve-viz/micro-ct'
</script>
<template>
<OmeViewer :source="omeTiffUrl" />
<TiffViewer :source="tiffUrl" />
</template>Exports: OmeViewer, OmeViewerControls, OmeOrthogonalViewer, TiffViewer, useOmeLoader.
-
Create a folder in
packages/core/src/my-component/withindex.tsandMyComponent.vue -
Export from
packages/core/src/index.ts:export * from './my-component' export const MyComponentLazy = defineAsyncComponent( () => import('./my-component').then(m => m.MyComponent) )
-
Test in
packages/core/src/App.vuewithpnpm dev