pennsieve-dashboard 0.3.71 → 1.0.1
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 +169 -165
- package/dist/pennsieve-dashboard.es.js +1737 -1699
- package/dist/pennsieve-dashboard.umd.js +40 -40
- package/dist/style.css +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -7,6 +7,21 @@
|
|
|
7
7
|
|
|
8
8
|
The **PennsieveDashboard** is a web application that provides a user interface for interacting with Pennsieve data, visualizing metrics, and managing dashboards relevant to scientific datasets.
|
|
9
9
|
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Breaking Changes
|
|
13
|
+
|
|
14
|
+
### v1.0.0 — `s3Url` and `apiUrl` removed from `dashboard:globalVars`
|
|
15
|
+
|
|
16
|
+
The dashboard no longer provides `s3Url` or `apiUrl` as top-level properties in the injected `dashboard:globalVars` context. These values now live exclusively in `services` (e.g. `services.s3Url`, `services.ApiUrl`).
|
|
17
|
+
|
|
18
|
+
**Widget libraries must update `useGlobalVars.ts`** to remove `s3Url`/`apiUrl` from `GlobalVarsShape` and read them from `services` instead.
|
|
19
|
+
|
|
20
|
+
| PennsieveDashboard | SparcDashWidgets | PrecisionDashWidgets |
|
|
21
|
+
|--------------------|------------------|----------------------|
|
|
22
|
+
| >= 1.0.0 | >= 1.0.0 | >= 1.0.0 |
|
|
23
|
+
|
|
24
|
+
Pre-1.0 versions of these packages are not compatible with 1.0+. If you're building a custom widget library, use the `services` pattern described in [Building a Component Library](#3-building-a-component-library).
|
|
10
25
|
|
|
11
26
|
---
|
|
12
27
|
|
|
@@ -58,131 +73,7 @@ Before you begin, ensure you have the following installed:
|
|
|
58
73
|
# or
|
|
59
74
|
yarn install
|
|
60
75
|
```
|
|
61
|
-
3.
|
|
62
|
-
For install and testing copy and paste this into your App.vue
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
//App.vue
|
|
66
|
-
<script setup>
|
|
67
|
-
import { computed, ref } from 'vue';
|
|
68
|
-
import {PennsieveDashboard, MarkdownWidget, TextWidget} from 'pennsieve-dashboard'
|
|
69
|
-
import 'element-plus/dist/index.css';
|
|
70
|
-
import 'pennsieve-dashboard/style.css'
|
|
71
|
-
|
|
72
|
-
/*
|
|
73
|
-
Computed
|
|
74
|
-
*/
|
|
75
|
-
const publicationStatus = computed(() => {
|
|
76
|
-
return "foobar"
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const filesCount = computed(() => {
|
|
80
|
-
return "b"
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const collaboratorCounts = computed(()=>{
|
|
84
|
-
return "a"
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const availableWidgets = [
|
|
89
|
-
//name must match componentKey
|
|
90
|
-
{ name: 'TextWidget', component: TextWidget },
|
|
91
|
-
{name:'MarkdownWidget',component:MarkdownWidget}
|
|
92
|
-
]
|
|
93
|
-
const defaultLayout = [
|
|
94
|
-
{
|
|
95
|
-
id: 'MarkdownWidget-6',
|
|
96
|
-
x: 0, y: 0, w: 3, h: 9,
|
|
97
|
-
componentKey: 'MarkdownWidget',
|
|
98
|
-
componentName: 'Markdown Widget',
|
|
99
|
-
component: MarkdownWidget,
|
|
100
|
-
Props:{
|
|
101
|
-
markdownText:[
|
|
102
|
-
'# Human DRG Dataset Dashboard',
|
|
103
|
-
'',
|
|
104
|
-
'This is a dashboard associated with the **NIH HEAL PRECISION Human Pain** consortium project. It aggregates data from several U19 centers in a standardized way. Using the different widgets, you can view, query and export the data in various ways:',
|
|
105
|
-
'',
|
|
106
|
-
'## Widgets',
|
|
107
|
-
'',
|
|
108
|
-
'### UMAP Viewer',
|
|
109
|
-
'This widget provides the UMAP representation of the entire dataset, you can select the color mapping based on different metadata elements.',
|
|
110
|
-
'',
|
|
111
|
-
'### The Data Explorer',
|
|
112
|
-
'Directly query over the data using SQL and export the results as a CSV file.',
|
|
113
|
-
'',
|
|
114
|
-
'### Proportion Viewer',
|
|
115
|
-
'Explore metrics between the different datasets that comprise the aggregated data.'
|
|
116
|
-
].join('\n')
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
id: "TextWidget-1",
|
|
121
|
-
x: 0, y: 0, h: 1, w:4,
|
|
122
|
-
componentName:"Text",
|
|
123
|
-
componentKey:"TextWidget",
|
|
124
|
-
component:TextWidget,
|
|
125
|
-
hideHeader:true,
|
|
126
|
-
Props:{displayText:"Dataset Overview",hideHeader:true}
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
id: "TextWidget-2",
|
|
130
|
-
x: 0, y: 1, h: 2, w:1,
|
|
131
|
-
componentName:"Files",
|
|
132
|
-
componentKey:"TextWidget",
|
|
133
|
-
component:TextWidget,
|
|
134
|
-
Props:{bindedKey:"FileCount"}
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
id: "TextWidget-3",
|
|
138
|
-
x: 1, y: 1, h: 2, w:2,
|
|
139
|
-
componentName:"Status",
|
|
140
|
-
componentKey:"TextWidget",
|
|
141
|
-
component:TextWidget,
|
|
142
|
-
Props:{bindedKey:"Status"}
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
id: "TextWidget-4",
|
|
146
|
-
x: 3, y: 1, h: 2, w:2,
|
|
147
|
-
componentName:"Collaborator Counts",
|
|
148
|
-
componentKey:"TextWidget",
|
|
149
|
-
component:TextWidget,
|
|
150
|
-
Props:{bindedKey:"CollaboratorCounts"}
|
|
151
|
-
}
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
const dashboardOptions = ref({
|
|
155
|
-
globalData: {
|
|
156
|
-
FileCount: filesCount.value,
|
|
157
|
-
Status: publicationStatus.value,
|
|
158
|
-
CollaboratorCounts: collaboratorCounts.value
|
|
159
|
-
},
|
|
160
|
-
availableWidgets,
|
|
161
|
-
defaultLayout,
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
</script>
|
|
165
|
-
<template>
|
|
166
|
-
<PennsieveDashboard class="dashboard-app" :options="dashboardOptions"></PennsieveDashboard>
|
|
167
|
-
|
|
168
|
-
</template>
|
|
169
|
-
|
|
170
|
-
<style scoped>
|
|
171
|
-
/* set style vars from outside the dashboard > customize to match your application */
|
|
172
|
-
.dashboard-app{
|
|
173
|
-
--el-color-primary: #243d8e;
|
|
174
|
-
--el-color-primary-light-3: #fbfdff;
|
|
175
|
-
--el-color-primary-dark-2: #546085;
|
|
176
|
-
--color:#243d8e;
|
|
177
|
-
--el-dialog-width: 90%;
|
|
178
|
-
--dash-secondary: #243d8e;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
</style>
|
|
182
|
-
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
4. Run
|
|
76
|
+
3. Run
|
|
186
77
|
```
|
|
187
78
|
npm run dev
|
|
188
79
|
```
|
|
@@ -251,16 +142,19 @@ Without these, the dashboard and widgets will not render correctly.
|
|
|
251
142
|
|
|
252
143
|
#### 2. Available Widgets
|
|
253
144
|
```js
|
|
145
|
+
import { markRaw } from ‘vue’
|
|
146
|
+
|
|
254
147
|
const availableWidgets = [
|
|
255
|
-
{ name:
|
|
256
|
-
{ name:
|
|
148
|
+
{ name: ‘TextWidget’, component: markRaw(TextWidget) },
|
|
149
|
+
{ name: ‘MarkdownWidget’, component: markRaw(MarkdownWidget) }
|
|
257
150
|
];
|
|
258
151
|
```
|
|
259
152
|
|
|
260
|
-
- This array **registers which widgets the dashboard can use**.
|
|
261
|
-
- Each widget entry must define:
|
|
262
|
-
- `name` – must match the widget’s `componentKey` in the layout.
|
|
263
|
-
- `component` – the imported Vue component.
|
|
153
|
+
- This array **registers which widgets the dashboard can use**.
|
|
154
|
+
- Each widget entry must define:
|
|
155
|
+
- `name` – must match the widget’s `componentKey` in the layout.
|
|
156
|
+
- `component` – the imported Vue component.
|
|
157
|
+
- **Wrap components with `markRaw()`** to prevent Vue reactivity warnings. This is needed because Vue will attempt to make component objects reactive when they’re stored in reactive state (like `ref()` or `reactive()`), which is unnecessary and triggers console warnings. `markRaw()` tells Vue to skip reactivity for these objects.
|
|
264
158
|
- You can extend this list with custom widgets or external widget libraries.
|
|
265
159
|
|
|
266
160
|
---
|
|
@@ -308,6 +202,12 @@ const publicationStatus = computed(() => "foobar");
|
|
|
308
202
|
const filesCount = computed(() => "b");
|
|
309
203
|
const collaboratorCounts = computed(() => "a");
|
|
310
204
|
|
|
205
|
+
const services = {
|
|
206
|
+
s3Url: "https://your-bucket.s3.amazonaws.com/your-data",
|
|
207
|
+
ApiUrl: "https://api.pennsieve.net",
|
|
208
|
+
// Add any other configurables your widgets need (API keys, endpoints, etc.)
|
|
209
|
+
};
|
|
210
|
+
|
|
311
211
|
const dashboardOptions = ref({
|
|
312
212
|
globalData: {
|
|
313
213
|
FileCount: filesCount.value,
|
|
@@ -316,12 +216,14 @@ const dashboardOptions = ref({
|
|
|
316
216
|
},
|
|
317
217
|
availableWidgets,
|
|
318
218
|
defaultLayout,
|
|
219
|
+
services,
|
|
319
220
|
});
|
|
320
221
|
```
|
|
321
222
|
|
|
322
|
-
- **Computed values**: hold live data you want to expose inside the dashboard. These could come from an API call or another reactive source.
|
|
323
|
-
- **globalData**: key/value pairs accessible to widgets. For example, a `TextWidget` can bind to `FileCount` or `Status`.
|
|
324
|
-
- **
|
|
223
|
+
- **Computed values**: hold live data you want to expose inside the dashboard. These could come from an API call or another reactive source.
|
|
224
|
+
- **globalData**: key/value pairs accessible to widgets. For example, a `TextWidget` can bind to `FileCount` or `Status`.
|
|
225
|
+
- **services**: key/value map of configurables (data URLs, API endpoints, API keys) that the dashboard provides to all widgets via `useDashboardGlobalVars().services`. Widgets read values like `services.s3Url` or `services.ScicrunchApiKey` from this object.
|
|
226
|
+
- **dashboardOptions**: combines global data, available widgets, layout, and services into a single configuration object that powers the dashboard.
|
|
325
227
|
|
|
326
228
|
---
|
|
327
229
|
|
|
@@ -387,7 +289,6 @@ When no URL parameters are present, behavior is unchanged — the dashboard uses
|
|
|
387
289
|
|
|
388
290
|
---
|
|
389
291
|
|
|
390
|
-
## Contributing
|
|
391
292
|
## Contributing
|
|
392
293
|
|
|
393
294
|
We welcome contributions from developers and the community. There are two main ways you can get involved:
|
|
@@ -397,52 +298,155 @@ If you encounter a bug, performance problem, or unexpected behavior in the dashb
|
|
|
397
298
|
- Please report it through the official support channels.
|
|
398
299
|
- Include as much detail as possible (steps to reproduce, screenshots, browser/OS version, etc.) so we can investigate and resolve quickly.
|
|
399
300
|
|
|
400
|
-
### 2.
|
|
401
|
-
The dashboard is designed to be extensible. Developers can build their own widget libraries and plug them into the dashboard.
|
|
301
|
+
### 2. Adding a Custom Component to an Existing Widget Library
|
|
402
302
|
|
|
403
|
-
|
|
303
|
+
Each widget library (e.g. SparcDashWidgets, PrecisionDashWidgets) follows the same component convention. Below is a real-world example — the **BiolucidaViewer** from SparcDashWidgets — showing how a component connects to the dashboard’s shared state via `useDashboardGlobalVars()`.
|
|
404
304
|
|
|
405
|
-
|
|
406
|
-
Here’s a minimal example of a custom widget component:
|
|
407
|
-
|
|
408
|
-
```
|
|
409
|
-
//vue
|
|
305
|
+
```vue
|
|
410
306
|
<template>
|
|
411
|
-
<!--
|
|
307
|
+
<!-- Scoped slot exposes the widget name and header icons to the dashboard shell -->
|
|
412
308
|
<slot :widgetName="widgetName" :childIcons="childIcons"></slot>
|
|
413
309
|
|
|
414
|
-
<div
|
|
415
|
-
|
|
310
|
+
<div v-bind="$attrs" class="tw-flex tw-flex-col">
|
|
311
|
+
<div v-if="selectedImage" class="bv-metadata">
|
|
312
|
+
<div><b>Sex:</b> {{ selectedImage.sex }}</div>
|
|
313
|
+
<div><b>Age:</b> {{ selectedImage.ageRange }}</div>
|
|
314
|
+
<div><b>Sample Path:</b> {{ selectedImage.relativePath }}</div>
|
|
315
|
+
</div>
|
|
316
|
+
<div class="tw-h-screen tw-flex tw-justify-center">
|
|
317
|
+
<iframe class="tw-p-1 tw-w-screen" :src="biolucidaPath"></iframe>
|
|
318
|
+
</div>
|
|
416
319
|
</div>
|
|
417
320
|
</template>
|
|
418
321
|
|
|
419
|
-
<script setup
|
|
420
|
-
import { ref,
|
|
421
|
-
import {
|
|
322
|
+
<script setup>
|
|
323
|
+
import { ref, computed, watch, shallowRef } from "vue";
|
|
324
|
+
import { useDashboardGlobalVars } from ‘../../useGlobalVars’
|
|
325
|
+
import { InfoFilled } from "@element-plus/icons-vue";
|
|
326
|
+
|
|
327
|
+
const widgetName = ref(‘MBF Image Viewer’);
|
|
328
|
+
const childIcons = shallowRef([
|
|
329
|
+
{ comp: InfoFilled, tooltip: "Lock Biolucida Viewer" }
|
|
330
|
+
]);
|
|
422
331
|
|
|
423
|
-
|
|
332
|
+
const props = defineProps({
|
|
333
|
+
imageID: 0,
|
|
334
|
+
isLocked: { default: false, type: Boolean }
|
|
335
|
+
});
|
|
424
336
|
|
|
425
|
-
//
|
|
426
|
-
const
|
|
427
|
-
property1?: string
|
|
428
|
-
}>()
|
|
337
|
+
// Access the dashboard’s injected global vars (services, filters, etc.)
|
|
338
|
+
const GlobalVars = useDashboardGlobalVars();
|
|
429
339
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
340
|
+
// Read a filter value reactively
|
|
341
|
+
const selectedImage = computed(() => {
|
|
342
|
+
return GlobalVars?.filters?.SELECTED_IMAGE;
|
|
343
|
+
});
|
|
433
344
|
|
|
434
|
-
|
|
435
|
-
const widgetName = 'example widget'
|
|
436
|
-
const childIcons = shallowRef([
|
|
437
|
-
{ comp: Edit, event: toggleMode, tooltip: 'click to open edit mode' }
|
|
438
|
-
])
|
|
345
|
+
const biolucidaPath = ref("");
|
|
439
346
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
347
|
+
watch(selectedImage, (newVal, oldVal) => {
|
|
348
|
+
if (newVal?.biolucidaID !== oldVal?.biolucidaID) {
|
|
349
|
+
biolucidaPath.value = `https://sparc.biolucida.net/image?c=${newVal?.biolucidaID}`;
|
|
350
|
+
}
|
|
351
|
+
}, { immediate: true });
|
|
443
352
|
</script>
|
|
444
353
|
```
|
|
445
354
|
|
|
355
|
+
The key takeaway: call `useDashboardGlobalVars()` in your component to access the dashboard’s shared state — `services` (data URLs, API keys), `filters`, and filter-setting methods — without any extra wiring. See the [Building a Component Library](#3-building-a-component-library) section below for a full explanation of this composable.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
### 3. Building a Component Library
|
|
360
|
+
|
|
361
|
+
If you’re building a **new widget library** (rather than adding to an existing one), you need to understand how the PennsieveDashboard communicates with widget components.
|
|
362
|
+
|
|
363
|
+
#### How the Dashboard Provides Context
|
|
364
|
+
|
|
365
|
+
When the `PennsieveDashboard` component mounts, it calls `provide("dashboard:globalVars", ...)` to inject a shared context object into the component tree. Any descendant component — including widgets from external libraries — can access this context using Vue’s `inject`.
|
|
366
|
+
|
|
367
|
+
#### The `useGlobalVars.ts` Composable
|
|
368
|
+
|
|
369
|
+
To access the injected context, your library should include a `useGlobalVars.ts` file (you can copy this directly from [SparcDashWidgets](https://github.com/nih-sparc/SparcDashWidgets)):
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
// src/useGlobalVars.ts
|
|
373
|
+
import { inject } from ‘vue’
|
|
374
|
+
|
|
375
|
+
export const DASHBOARD_GLOBAL_VARS_KEY = ‘dashboard:globalVars’ as const
|
|
376
|
+
export type FilterValue = string | number | boolean | null | string[] | number[]
|
|
377
|
+
export type Filters = Record<string, FilterValue>
|
|
378
|
+
export type Services = Record<string, any>
|
|
379
|
+
|
|
380
|
+
export type GlobalVarsShape = {
|
|
381
|
+
services: Services | import(‘vue’).Ref<Services> // All configurables (URLs, API keys, etc.)
|
|
382
|
+
filters: Filters | import(‘vue’).Ref<Filters> // Shared reactive filter state
|
|
383
|
+
setFilter: (key: string, value: FilterValue, isDisplayed?: boolean) => void
|
|
384
|
+
clearFilter: (key: string) => void
|
|
385
|
+
resetFilters?: (next?: Filters) => void
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function useDashboardGlobalVars(required = false) {
|
|
389
|
+
const gv = inject<GlobalVarsShape | null>(DASHBOARD_GLOBAL_VARS_KEY, null)
|
|
390
|
+
if (!gv && required) {
|
|
391
|
+
console.warn(‘[Widget] dashboard:globalVars not provided.’)
|
|
392
|
+
}
|
|
393
|
+
return gv
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### What Each Property Does
|
|
398
|
+
|
|
399
|
+
| Property | Description |
|
|
400
|
+
|----------|-------------|
|
|
401
|
+
| `services` | Key-value map of configurables passed in from the host application — data URLs, API endpoints, API keys, etc. For example: `services.s3Url`, `services.ApiUrl`, `services.ScicrunchApiKey`. |
|
|
402
|
+
| `filters` | Reactive key-value object representing the current dashboard filter state. Widgets read from this to stay in sync. |
|
|
403
|
+
| `setFilter(key, value, isDisplayed?)` | Set a filter value. `isDisplayed` controls whether the filter chip appears in the dashboard header. |
|
|
404
|
+
| `clearFilter(key)` | Remove a single filter by key. |
|
|
405
|
+
| `resetFilters(next?)` | Clear all filters, optionally replacing them with `next`. |
|
|
406
|
+
|
|
407
|
+
#### Using Filters for Cross-Widget Communication
|
|
408
|
+
|
|
409
|
+
Filters are the primary mechanism for widgets to communicate. One widget sets a filter, and other widgets react to the change:
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
const GlobalVars = useDashboardGlobalVars();
|
|
413
|
+
|
|
414
|
+
// Read a service configurable (e.g. data URL, API key)
|
|
415
|
+
const dataUrl = unref(GlobalVars?.services)?.s3Url;
|
|
416
|
+
const apiKey = unref(GlobalVars?.services)?.ScicrunchApiKey;
|
|
417
|
+
|
|
418
|
+
// Set a filter (other widgets will see this reactively)
|
|
419
|
+
GlobalVars?.setFilter(‘SELECTED_GENE’, ‘CDH9’, true);
|
|
420
|
+
|
|
421
|
+
// Read a filter
|
|
422
|
+
const gene = GlobalVars?.filters?.SELECTED_GENE;
|
|
423
|
+
|
|
424
|
+
// Clear a filter
|
|
425
|
+
GlobalVars?.clearFilter(‘SELECTED_GENE’);
|
|
426
|
+
|
|
427
|
+
// Reset all filters
|
|
428
|
+
GlobalVars?.resetFilters();
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
#### Library Structure
|
|
432
|
+
|
|
433
|
+
A typical widget library follows this structure:
|
|
434
|
+
|
|
435
|
+
```
|
|
436
|
+
your-widget-library/
|
|
437
|
+
├── src/
|
|
438
|
+
│ ├── components/ # Widget components (public API)
|
|
439
|
+
│ │ ├── MyWidget/
|
|
440
|
+
│ │ │ └── MyWidget.vue
|
|
441
|
+
│ │ └── index.js # Barrel export
|
|
442
|
+
│ ├── useGlobalVars.ts # Copy from SparcDashWidgets
|
|
443
|
+
│ └── main.ts # Dev entry point
|
|
444
|
+
├── package.json
|
|
445
|
+
└── vite.config.ts # Build in library mode
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Your `vite.config.ts` should build in library mode with `vue`, `pinia`, and `element-plus` as externals. See SparcDashWidgets or PrecisionDashWidgets for working examples.
|
|
449
|
+
|
|
446
450
|
## Contact Support
|
|
447
451
|
|
|
448
452
|
There are several ways to get in contact with our support team.
|