@weng-lab/genomebrowser-ui 0.3.6-beta.0 → 0.4.0-beta.0
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/.env.local +1 -1
- package/dist/TrackSelect/Custom/TfPeaks.d.ts +1 -2
- package/dist/TrackSelect/Custom/green-motifs.json.d.ts +20430 -0
- package/dist/TrackSelect/Folders/biosamples/shared/BiosampleViewSelector.d.ts +7 -0
- package/dist/TrackSelect/Folders/biosamples/shared/createFolder.d.ts +1 -13
- package/dist/TrackSelect/Folders/biosamples/shared/toTrack.d.ts +20 -0
- package/dist/TrackSelect/Folders/biosamples/shared/types.d.ts +4 -13
- package/dist/TrackSelect/Folders/genes/shared/createFolder.d.ts +1 -3
- package/dist/TrackSelect/Folders/genes/shared/toTrack.d.ts +18 -0
- package/dist/TrackSelect/Folders/index.d.ts +6 -12
- package/dist/TrackSelect/Folders/mohd/data/human.json.d.ts +2948 -0
- package/dist/TrackSelect/Folders/mohd/human.d.ts +1 -0
- package/dist/TrackSelect/Folders/mohd/shared/MohdGroupingCell.d.ts +2 -0
- package/dist/TrackSelect/Folders/mohd/shared/MohdTreeItem.d.ts +3 -0
- package/dist/TrackSelect/Folders/mohd/shared/MohdViewSelector.d.ts +7 -0
- package/dist/TrackSelect/Folders/mohd/shared/columns.d.ts +5 -0
- package/dist/TrackSelect/Folders/mohd/shared/config.d.ts +42 -0
- package/dist/TrackSelect/Folders/mohd/shared/createFolder.d.ts +9 -0
- package/dist/TrackSelect/Folders/mohd/shared/toTrack.d.ts +9 -0
- package/dist/TrackSelect/Folders/mohd/shared/types.d.ts +40 -0
- package/dist/TrackSelect/Folders/other-tracks/shared/toTrack.d.ts +5 -0
- package/dist/TrackSelect/Folders/other-tracks/shared/types.d.ts +1 -0
- package/dist/TrackSelect/Folders/types.d.ts +23 -55
- package/dist/TrackSelect/TrackSelect.d.ts +10 -7
- package/dist/TrackSelect/TreeView/TreeViewWrapper.d.ts +1 -1
- package/dist/TrackSelect/buildSelectedTree.d.ts +15 -0
- package/dist/TrackSelect/managedTracks.d.ts +13 -0
- package/dist/TrackSelect/resolveFolderView.d.ts +2 -0
- package/dist/TrackSelect/trackContext.d.ts +5 -0
- package/dist/TrackSelect/types.d.ts +12 -33
- package/dist/genomebrowser-ui.es.js +2470 -2258
- package/dist/genomebrowser-ui.es.js.map +1 -1
- package/dist/lib.d.ts +4 -4
- package/dist/muiLicense.d.ts +1 -0
- package/package.json +6 -3
- package/src/TrackSelect/Custom/TfPeaks.tsx +81 -60
- package/src/TrackSelect/Dialogs/ResetDialog.tsx +3 -2
- package/src/TrackSelect/FolderList/FolderCard.tsx +1 -1
- package/src/TrackSelect/Folders/biosamples/shared/BiosampleViewSelector.tsx +33 -0
- package/src/TrackSelect/Folders/biosamples/shared/createFolder.ts +39 -58
- package/src/TrackSelect/Folders/biosamples/shared/toTrack.ts +138 -0
- package/src/TrackSelect/Folders/biosamples/shared/types.ts +4 -16
- package/src/TrackSelect/Folders/genes/shared/createFolder.ts +10 -31
- package/src/TrackSelect/Folders/genes/shared/toTrack.ts +59 -0
- package/src/TrackSelect/Folders/index.ts +15 -18
- package/src/TrackSelect/Folders/mohd/data/human.json +2945 -0
- package/src/TrackSelect/Folders/mohd/human.ts +10 -0
- package/src/TrackSelect/Folders/mohd/shared/MohdGroupingCell.tsx +68 -0
- package/src/TrackSelect/Folders/mohd/shared/MohdTreeItem.tsx +17 -0
- package/src/TrackSelect/Folders/mohd/shared/MohdViewSelector.tsx +33 -0
- package/src/TrackSelect/Folders/mohd/shared/columns.tsx +79 -0
- package/src/TrackSelect/Folders/mohd/shared/config.tsx +71 -0
- package/src/TrackSelect/Folders/mohd/shared/createFolder.ts +144 -0
- package/src/TrackSelect/Folders/mohd/shared/toTrack.ts +164 -0
- package/src/TrackSelect/Folders/mohd/shared/types.ts +46 -0
- package/src/TrackSelect/Folders/other-tracks/shared/createFolder.ts +13 -14
- package/src/TrackSelect/Folders/other-tracks/shared/toTrack.ts +17 -0
- package/src/TrackSelect/Folders/other-tracks/shared/types.ts +1 -0
- package/src/TrackSelect/Folders/types.ts +26 -69
- package/src/TrackSelect/TrackSelect.tsx +299 -255
- package/src/TrackSelect/TreeView/CustomTreeItem.tsx +6 -6
- package/src/TrackSelect/TreeView/TreeViewWrapper.tsx +84 -6
- package/src/TrackSelect/buildSelectedTree.ts +145 -0
- package/src/TrackSelect/managedTracks.ts +92 -0
- package/src/TrackSelect/resolveFolderView.ts +20 -0
- package/src/TrackSelect/trackContext.ts +9 -0
- package/src/TrackSelect/types.ts +14 -39
- package/src/lib.ts +13 -7
- package/src/muiLicense.ts +9 -0
- package/src/vite-env.d.ts +9 -0
- package/test/TrackSelect.test.tsx +435 -0
- package/test/main.tsx +37 -353
- package/test/mocks/logo-test.tsx +11 -0
- package/test/mohdDisplay.test.tsx +45 -0
- package/test/startup.test.ts +206 -0
- package/test/trackSelectState.test.ts +176 -0
- package/vite.config.ts +1 -0
- package/vitest.config.ts +20 -0
- package/dist/TrackSelect/Custom/TF-ChIP-Canonical-Motifs-w-Trimmed.json.d.ts +0 -42210
- package/dist/TrackSelect/Folders/biosamples/shared/AssayToggle.d.ts +0 -18
- package/dist/TrackSelect/Folders/biosamples/shared/treeBuilder.d.ts +0 -28
- package/dist/TrackSelect/Folders/genes/shared/treeBuilder.d.ts +0 -13
- package/dist/TrackSelect/Folders/other-tracks/shared/treeBuilder.d.ts +0 -4
- package/dist/TrackSelect/store.d.ts +0 -4
- package/src/TrackSelect/Folders/NEW.md +0 -929
- package/src/TrackSelect/Folders/biosamples/shared/AssayToggle.tsx +0 -78
- package/src/TrackSelect/Folders/biosamples/shared/treeBuilder.ts +0 -224
- package/src/TrackSelect/Folders/genes/shared/treeBuilder.ts +0 -45
- package/src/TrackSelect/Folders/other-tracks/shared/treeBuilder.ts +0 -34
- package/src/TrackSelect/store.ts +0 -117
package/dist/lib.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { default as TrackSelect, TrackSelectProps } from './TrackSelect/TrackSelect';
|
|
2
|
-
import { createSelectionStore, SelectionStoreInstance } from './TrackSelect/store.ts';
|
|
1
|
+
import { default as TrackSelect, InitialSelectedIdsByAssembly, TrackSelectProps } from './TrackSelect/TrackSelect';
|
|
3
2
|
import { foldersByAssembly } from './TrackSelect/Folders/index.ts';
|
|
4
3
|
import { tfPeaksTrack } from './TrackSelect/Custom/TfPeaks.tsx';
|
|
5
4
|
export { TrackSelect, TrackSelectProps };
|
|
6
|
-
export {
|
|
5
|
+
export type { TrackSelectTrackContext } from './TrackSelect/trackContext';
|
|
6
|
+
export type { InitialSelectedIdsByAssembly };
|
|
7
7
|
export { foldersByAssembly };
|
|
8
|
-
export type { BiosampleRowInfo, GeneRowInfo, OtherTrackInfo, } from './TrackSelect/Folders';
|
|
8
|
+
export type { BiosampleRowInfo, BiosampleTrackContext, GeneRowInfo, GeneTrackContext, MohdRowInfo, MohdTrackContext, OtherTrackInfo, OtherTracksTrackContext, } from './TrackSelect/Folders';
|
|
9
9
|
export { tfPeaksTrack };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weng-lab/genomebrowser-ui",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0-beta.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"publishConfig": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@mui/x-data-grid-premium": "^8.19.0",
|
|
24
24
|
"react": "^19.0.0",
|
|
25
25
|
"react-dom": "^19.0.0",
|
|
26
|
-
"@weng-lab/genomebrowser": "1.8.
|
|
26
|
+
"@weng-lab/genomebrowser": "1.8.5-beta.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@eslint/js": "^9.34.0",
|
|
@@ -34,15 +34,18 @@
|
|
|
34
34
|
"eslint": "^9.34.0",
|
|
35
35
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
36
36
|
"eslint-plugin-react-refresh": "^0.4.20",
|
|
37
|
+
"jsdom": "^26.1.0",
|
|
37
38
|
"typescript": "^5.7.3",
|
|
38
39
|
"typescript-eslint": "^8.42.0",
|
|
39
40
|
"vite": "^6.3.5",
|
|
40
|
-
"vite-plugin-dts": "^4.5.4"
|
|
41
|
+
"vite-plugin-dts": "^4.5.4",
|
|
42
|
+
"vitest": "^3.2.4"
|
|
41
43
|
},
|
|
42
44
|
"scripts": {
|
|
43
45
|
"dev": "vite",
|
|
44
46
|
"build": "tsc -b && vite build",
|
|
45
47
|
"lint": "eslint .",
|
|
48
|
+
"test": "vitest run",
|
|
46
49
|
"preview": "vite preview",
|
|
47
50
|
"format": "prettier --write ."
|
|
48
51
|
}
|
|
@@ -172,6 +172,10 @@ function TooltipRow({
|
|
|
172
172
|
function TfPeaksTooltip(rect: OverlayInteractionRect) {
|
|
173
173
|
const pwm = rect.pwm;
|
|
174
174
|
const label = tfDisplayName(rect.name);
|
|
175
|
+
const totalWidth = 340;
|
|
176
|
+
const pad = 8;
|
|
177
|
+
const lineH = 14;
|
|
178
|
+
const titleH = 18;
|
|
175
179
|
|
|
176
180
|
// Build metadata rows (single-value rows)
|
|
177
181
|
const metaRows: { label: string; value: string }[] = [];
|
|
@@ -181,33 +185,47 @@ function TfPeaksTooltip(rect: OverlayInteractionRect) {
|
|
|
181
185
|
label: "Position",
|
|
182
186
|
value: `${rect.chr ? rect.chr + ":" : ""}${rect.start.toLocaleString()}-${rect.end.toLocaleString()}`,
|
|
183
187
|
});
|
|
184
|
-
if (rect.expRatio) metaRows.push({ label: "Exps", value: rect.expRatio });
|
|
185
188
|
|
|
186
|
-
// Multi-value rows: split comma-separated cCREs, group
|
|
187
|
-
const
|
|
189
|
+
// Multi-value rows: split comma-separated cCREs, group 4 per row, cap at 5
|
|
190
|
+
const allCCREItems = rect.cCREId
|
|
188
191
|
? rect.cCREId
|
|
189
192
|
.split(",")
|
|
190
193
|
.map((s) => s.trim())
|
|
191
194
|
.filter(Boolean)
|
|
192
195
|
: [];
|
|
196
|
+
const maxCCREs = 5;
|
|
197
|
+
const cCREItems = allCCREItems.slice(0, maxCCREs);
|
|
198
|
+
const hiddenCCREs = Math.max(0, allCCREItems.length - maxCCREs);
|
|
193
199
|
const cCRERows: string[][] = [];
|
|
194
200
|
for (let i = 0; i < cCREItems.length; i += 4) {
|
|
195
201
|
cCRERows.push(cCREItems.slice(i, i + 4));
|
|
196
202
|
}
|
|
197
203
|
|
|
198
|
-
//
|
|
199
|
-
const
|
|
204
|
+
// Extract unique sorted biosamples from expSupport
|
|
205
|
+
const allBiosamples: string[] = [];
|
|
200
206
|
if (rect.expSupport) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
207
|
+
const seen = new Set<string>();
|
|
208
|
+
for (const cellLine of Object.keys(rect.expSupport)) {
|
|
209
|
+
if (!seen.has(cellLine)) {
|
|
210
|
+
seen.add(cellLine);
|
|
211
|
+
allBiosamples.push(cellLine);
|
|
204
212
|
}
|
|
205
213
|
}
|
|
214
|
+
allBiosamples.sort((a, b) => a.localeCompare(b));
|
|
206
215
|
}
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
const
|
|
216
|
+
const maxBiosamples = 12;
|
|
217
|
+
const biosampleItems = allBiosamples.slice(0, maxBiosamples);
|
|
218
|
+
const hiddenBiosamples = Math.max(0, allBiosamples.length - maxBiosamples);
|
|
219
|
+
const biosampleText = biosampleItems.join(", ");
|
|
220
|
+
const biosampleMoreText =
|
|
221
|
+
hiddenBiosamples > 0 ? ` ...and ${hiddenBiosamples} more` : "";
|
|
222
|
+
const biosampleCharsPerLine = Math.floor((totalWidth - 2 * pad) / 5.2);
|
|
223
|
+
const biosampleContentLines = Math.max(
|
|
224
|
+
1,
|
|
225
|
+
Math.ceil(
|
|
226
|
+
(biosampleText.length + biosampleMoreText.length) / biosampleCharsPerLine,
|
|
227
|
+
),
|
|
228
|
+
);
|
|
211
229
|
|
|
212
230
|
// Layout: compute y offsets upfront
|
|
213
231
|
const hasLogo = pwm && pwm.length > 0;
|
|
@@ -216,26 +234,25 @@ function TfPeaksTooltip(rect: OverlayInteractionRect) {
|
|
|
216
234
|
const logoSectionH = hasLogo ? logoHeight + 4 : 0;
|
|
217
235
|
const metaSectionH = metaRows.length * lineH;
|
|
218
236
|
|
|
219
|
-
// cCRE section: label row + one row per group of
|
|
237
|
+
// cCRE section: label row + one row per group of 4 + optional "+N more" row
|
|
220
238
|
const cCREGap = cCRERows.length > 0 ? 8 : 0;
|
|
221
239
|
const cCREHeaderH = cCRERows.length > 0 ? lineH : 0;
|
|
222
|
-
const
|
|
240
|
+
const cCREMoreH = hiddenCCREs > 0 ? lineH : 0;
|
|
241
|
+
const cCRESectionH = cCRERows.length * lineH + cCREMoreH;
|
|
223
242
|
|
|
224
|
-
//
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
const
|
|
243
|
+
// Biosamples section
|
|
244
|
+
const biosampleGap = biosampleContentLines > 0 ? 8 : 0;
|
|
245
|
+
const biosampleHeaderH = biosampleContentLines > 0 ? lineH : 0;
|
|
246
|
+
const biosampleSectionH = biosampleContentLines * lineH;
|
|
228
247
|
|
|
229
248
|
const titleY = pad;
|
|
230
249
|
const logoY = titleY + titleH;
|
|
231
250
|
const metaY = logoY + logoSectionH;
|
|
232
251
|
const cCREY = metaY + metaSectionH + cCREGap;
|
|
233
252
|
const cCREDataY = cCREY + cCREHeaderH;
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
const totalHeight =
|
|
237
|
-
|
|
238
|
-
const totalWidth = 340;
|
|
253
|
+
const biosampleY = cCREDataY + cCRESectionH + biosampleGap;
|
|
254
|
+
const biosampleDataY = biosampleY + biosampleHeaderH;
|
|
255
|
+
const totalHeight = biosampleDataY + biosampleSectionH + pad;
|
|
239
256
|
|
|
240
257
|
return (
|
|
241
258
|
<g>
|
|
@@ -306,60 +323,64 @@ function TfPeaksTooltip(rect: OverlayInteractionRect) {
|
|
|
306
323
|
{group.join(", ")}
|
|
307
324
|
</text>
|
|
308
325
|
))}
|
|
326
|
+
{hiddenCCREs > 0 && (
|
|
327
|
+
<text
|
|
328
|
+
x={pad}
|
|
329
|
+
y={cCREDataY + cCRERows.length * lineH + 2}
|
|
330
|
+
fontSize={9}
|
|
331
|
+
fill="#aaa"
|
|
332
|
+
dominantBaseline="hanging"
|
|
333
|
+
>
|
|
334
|
+
+{hiddenCCREs} more...
|
|
335
|
+
</text>
|
|
336
|
+
)}
|
|
309
337
|
</g>
|
|
310
338
|
)}
|
|
311
339
|
|
|
312
|
-
{/*
|
|
313
|
-
{
|
|
340
|
+
{/* Biosamples */}
|
|
341
|
+
{biosampleContentLines > 0 && (
|
|
314
342
|
<g>
|
|
315
343
|
<line
|
|
316
344
|
x1={pad}
|
|
317
345
|
x2={totalWidth - pad}
|
|
318
|
-
y1={
|
|
319
|
-
y2={
|
|
346
|
+
y1={biosampleY - 4}
|
|
347
|
+
y2={biosampleY - 4}
|
|
320
348
|
stroke="#ddd"
|
|
321
349
|
/>
|
|
322
350
|
<text
|
|
323
351
|
x={pad}
|
|
324
|
-
y={
|
|
352
|
+
y={biosampleY + 2}
|
|
325
353
|
fontSize={9}
|
|
326
354
|
fontWeight="bold"
|
|
327
355
|
fill="#666"
|
|
328
356
|
dominantBaseline="hanging"
|
|
329
357
|
>
|
|
330
|
-
|
|
358
|
+
Biosamples
|
|
331
359
|
</text>
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
fill="#666"
|
|
357
|
-
dominantBaseline="hanging"
|
|
358
|
-
>
|
|
359
|
-
{row.fileId}
|
|
360
|
-
</text>
|
|
361
|
-
</g>
|
|
362
|
-
))}
|
|
360
|
+
<foreignObject
|
|
361
|
+
x={pad}
|
|
362
|
+
y={biosampleDataY}
|
|
363
|
+
width={totalWidth - 2 * pad}
|
|
364
|
+
height={biosampleSectionH}
|
|
365
|
+
>
|
|
366
|
+
<div
|
|
367
|
+
style={{
|
|
368
|
+
color: "#333",
|
|
369
|
+
fontSize: "9px",
|
|
370
|
+
lineHeight: `${lineH}px`,
|
|
371
|
+
margin: 0,
|
|
372
|
+
padding: 0,
|
|
373
|
+
overflow: "hidden",
|
|
374
|
+
whiteSpace: "normal",
|
|
375
|
+
wordBreak: "break-word",
|
|
376
|
+
}}
|
|
377
|
+
>
|
|
378
|
+
{biosampleText}
|
|
379
|
+
{hiddenBiosamples > 0 && (
|
|
380
|
+
<span style={{ color: "#aaa" }}>{biosampleMoreText}</span>
|
|
381
|
+
)}
|
|
382
|
+
</div>
|
|
383
|
+
</foreignObject>
|
|
363
384
|
</g>
|
|
364
385
|
)}
|
|
365
386
|
</g>
|
|
@@ -23,11 +23,12 @@ export function ResetDialog({ open, onClose, onConfirm }: ResetDialogProps) {
|
|
|
23
23
|
fontWeight: "bold",
|
|
24
24
|
}}
|
|
25
25
|
>
|
|
26
|
-
Reset to
|
|
26
|
+
Reset to Browser State
|
|
27
27
|
</DialogTitle>
|
|
28
28
|
<DialogContent sx={{ mt: 2 }}>
|
|
29
29
|
<DialogContentText>
|
|
30
|
-
Are you sure you want to reset all selections to the
|
|
30
|
+
Are you sure you want to reset all selections to the current browser
|
|
31
|
+
state?
|
|
31
32
|
</DialogContentText>
|
|
32
33
|
</DialogContent>
|
|
33
34
|
<DialogActions sx={{ justifyContent: "center", gap: 2, pb: 2 }}>
|
|
@@ -44,7 +44,7 @@ export function FolderCard({ folder, onClick }: FolderCardProps) {
|
|
|
44
44
|
</Typography>
|
|
45
45
|
)}
|
|
46
46
|
<Typography variant="caption" color="text.secondary">
|
|
47
|
-
{folder.
|
|
47
|
+
{folder.rows.length.toLocaleString()} tracks available
|
|
48
48
|
</Typography>
|
|
49
49
|
</Paper>
|
|
50
50
|
);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ToggleButton, ToggleButtonGroup } from "@mui/material";
|
|
2
|
+
import { FolderView } from "../../types";
|
|
3
|
+
|
|
4
|
+
export interface BiosampleViewSelectorProps {
|
|
5
|
+
views: FolderView[];
|
|
6
|
+
activeViewId: string;
|
|
7
|
+
onChange: (viewId: string) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function BiosampleViewSelector({
|
|
11
|
+
views,
|
|
12
|
+
activeViewId,
|
|
13
|
+
onChange,
|
|
14
|
+
}: BiosampleViewSelectorProps) {
|
|
15
|
+
return (
|
|
16
|
+
<ToggleButtonGroup
|
|
17
|
+
exclusive
|
|
18
|
+
value={activeViewId}
|
|
19
|
+
size="small"
|
|
20
|
+
onChange={(_event, viewId: string | null) => {
|
|
21
|
+
if (viewId) {
|
|
22
|
+
onChange(viewId);
|
|
23
|
+
}
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
{views.map((view) => (
|
|
27
|
+
<ToggleButton key={view.id} value={view.id}>
|
|
28
|
+
{view.label}
|
|
29
|
+
</ToggleButton>
|
|
30
|
+
))}
|
|
31
|
+
</ToggleButtonGroup>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -9,24 +9,24 @@ import {
|
|
|
9
9
|
defaultColumns,
|
|
10
10
|
defaultGroupingModel,
|
|
11
11
|
defaultLeafField,
|
|
12
|
+
sortedByAssayColumns,
|
|
13
|
+
sortedByAssayGroupingModel,
|
|
14
|
+
sortedByAssayLeafField,
|
|
12
15
|
} from "./columns";
|
|
13
|
-
import { buildTreeView } from "./treeBuilder";
|
|
14
16
|
import { formatAssayType } from "./constants";
|
|
15
|
-
import {
|
|
17
|
+
import { BiosampleViewSelector } from "./BiosampleViewSelector";
|
|
16
18
|
import BiosampleGroupingCell from "./BiosampleGroupingCell";
|
|
17
19
|
import { BiosampleTreeItem } from "./BiosampleTreeItem";
|
|
20
|
+
import { createBiosampleTrack } from "./toTrack";
|
|
18
21
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* @returns Array of flattened BiosampleRowInfo objects, one per assay
|
|
25
|
-
*/
|
|
26
|
-
function flattenTrackIntoRows(track: BiosampleTrackInfo): BiosampleRowInfo[] {
|
|
22
|
+
/** Flatten a biosample track into one row per assay. */
|
|
23
|
+
function flattenTrackIntoRows(
|
|
24
|
+
folderId: string,
|
|
25
|
+
track: BiosampleTrackInfo,
|
|
26
|
+
): BiosampleRowInfo[] {
|
|
27
27
|
const { ontology, lifeStage, sampleType, displayName, collection } = track;
|
|
28
28
|
|
|
29
|
-
//
|
|
29
|
+
// Keep cCRE rows first so aggregate selections stay prominent in the UI.
|
|
30
30
|
const sortedAssays = [...track.assays].sort((a, b) => {
|
|
31
31
|
const aIsCcre = a.assay.toLowerCase() === "ccre";
|
|
32
32
|
const bIsCcre = b.assay.toLowerCase() === "ccre";
|
|
@@ -46,7 +46,7 @@ function flattenTrackIntoRows(track: BiosampleTrackInfo): BiosampleRowInfo[] {
|
|
|
46
46
|
cpgMinus,
|
|
47
47
|
coverage,
|
|
48
48
|
}) => ({
|
|
49
|
-
id
|
|
49
|
+
id: `${folderId}/${id}`,
|
|
50
50
|
ontology: capitalize(ontology),
|
|
51
51
|
lifeStage: capitalize(lifeStage),
|
|
52
52
|
sampleType: capitalize(sampleType),
|
|
@@ -63,75 +63,56 @@ function flattenTrackIntoRows(track: BiosampleTrackInfo): BiosampleRowInfo[] {
|
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
* @param folderId - Folder ID to prefix row IDs with
|
|
72
|
-
* @returns Object containing rows array and rowById map
|
|
73
|
-
*/
|
|
74
|
-
function transformData(data: BiosampleDataFile): {
|
|
75
|
-
rowById: Map<string, BiosampleRowInfo>;
|
|
76
|
-
} {
|
|
77
|
-
const rows = data.tracks.flatMap(flattenTrackIntoRows).map((row) => ({
|
|
78
|
-
...row,
|
|
79
|
-
id: row.id,
|
|
80
|
-
}));
|
|
81
|
-
const rowById = new Map<string, BiosampleRowInfo>(
|
|
82
|
-
rows.map((row) => [row.id, row]),
|
|
83
|
-
);
|
|
84
|
-
return { rowById };
|
|
66
|
+
function transformData(
|
|
67
|
+
folderId: string,
|
|
68
|
+
data: BiosampleDataFile,
|
|
69
|
+
): BiosampleRowInfo[] {
|
|
70
|
+
return data.tracks.flatMap((track) => flattenTrackIntoRows(folderId, track));
|
|
85
71
|
}
|
|
86
72
|
|
|
87
73
|
export interface CreateBiosampleFolderOptions {
|
|
88
|
-
/** Unique identifier for this folder */
|
|
89
74
|
id: string;
|
|
90
|
-
/** Display label shown in the UI */
|
|
91
75
|
label: string;
|
|
92
|
-
/** Optional description shown in folder cards */
|
|
93
76
|
description?: string;
|
|
94
|
-
/** Raw biosample data from JSON file */
|
|
95
77
|
data: BiosampleDataFile;
|
|
96
78
|
}
|
|
97
79
|
|
|
98
80
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* This handles all the common setup for biosample folders:
|
|
102
|
-
* - Transforms JSON data into flattened rows
|
|
103
|
-
* - Creates the rowById lookup map
|
|
104
|
-
* - Configures columns, grouping, and tree building
|
|
105
|
-
*
|
|
106
|
-
* @param options - Configuration options for the folder
|
|
107
|
-
* @returns A complete FolderDefinition for the biosample data
|
|
81
|
+
* Build a biosample folder with its data, tree builder, and track factory.
|
|
108
82
|
*/
|
|
109
83
|
export function createBiosampleFolder(
|
|
110
84
|
options: CreateBiosampleFolderOptions,
|
|
111
85
|
): FolderDefinition<BiosampleRowInfo> {
|
|
112
86
|
const { id, label, description, data } = options;
|
|
113
|
-
const
|
|
87
|
+
const rows = transformData(id, data);
|
|
88
|
+
const views = [
|
|
89
|
+
{
|
|
90
|
+
id: "default",
|
|
91
|
+
label: "Tissue",
|
|
92
|
+
columns: defaultColumns,
|
|
93
|
+
groupingModel: defaultGroupingModel,
|
|
94
|
+
leafField: defaultLeafField,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "by-assay",
|
|
98
|
+
label: "Assay",
|
|
99
|
+
columns: sortedByAssayColumns,
|
|
100
|
+
groupingModel: sortedByAssayGroupingModel,
|
|
101
|
+
leafField: sortedByAssayLeafField,
|
|
102
|
+
},
|
|
103
|
+
];
|
|
114
104
|
|
|
115
105
|
return {
|
|
116
106
|
id,
|
|
117
107
|
label,
|
|
118
108
|
description,
|
|
119
|
-
|
|
120
|
-
getRowId: (row) => row.id,
|
|
121
|
-
|
|
122
|
-
// Default view: ontology-based grouping
|
|
109
|
+
rows,
|
|
123
110
|
columns: defaultColumns,
|
|
124
111
|
groupingModel: defaultGroupingModel,
|
|
125
112
|
leafField: defaultLeafField,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
buildTreeView(selectedIds, rowById, label, id),
|
|
130
|
-
|
|
131
|
-
// Biosample-specific toolbar: toggle between sample-grouped and assay-grouped views
|
|
132
|
-
ToolbarExtras: AssayToggle,
|
|
133
|
-
|
|
134
|
-
// Biosample-specific custom components
|
|
113
|
+
createTrack: createBiosampleTrack,
|
|
114
|
+
views,
|
|
115
|
+
ViewSelector: BiosampleViewSelector,
|
|
135
116
|
GroupingCellComponent: BiosampleGroupingCell,
|
|
136
117
|
TreeItemComponent: BiosampleTreeItem,
|
|
137
118
|
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BigBedConfig,
|
|
3
|
+
BigWigConfig,
|
|
4
|
+
DisplayMode,
|
|
5
|
+
MethylCConfig,
|
|
6
|
+
Rect,
|
|
7
|
+
Track,
|
|
8
|
+
TrackType,
|
|
9
|
+
ValuedPoint,
|
|
10
|
+
} from "@weng-lab/genomebrowser";
|
|
11
|
+
import type { FC } from "react";
|
|
12
|
+
import { CreateTrackOptions } from "../../types";
|
|
13
|
+
import { BiosampleRowInfo } from "./types";
|
|
14
|
+
|
|
15
|
+
export type BiosampleTrackContext = {
|
|
16
|
+
onBiosampleFeatureClick?: (args: {
|
|
17
|
+
trackId: string;
|
|
18
|
+
row: BiosampleRowInfo;
|
|
19
|
+
rect: Rect;
|
|
20
|
+
}) => void;
|
|
21
|
+
onBiosampleFeatureHover?: (args: {
|
|
22
|
+
trackId: string;
|
|
23
|
+
row: BiosampleRowInfo;
|
|
24
|
+
rect: Rect;
|
|
25
|
+
}) => void;
|
|
26
|
+
biosampleFeatureTooltip?: FC<Rect>;
|
|
27
|
+
biosampleSignalTooltip?: FC<ValuedPoint[]>;
|
|
28
|
+
biosampleMethylTooltip?: FC<ValuedPoint[]>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const assayColors: Record<string, string> = {
|
|
32
|
+
dnase: "#06da93",
|
|
33
|
+
h3k4me3: "#ff0000",
|
|
34
|
+
h3k27ac: "#ffcd00",
|
|
35
|
+
ctcf: "#00b0d0",
|
|
36
|
+
atac: "#02c7b9",
|
|
37
|
+
rnaseq: "#00aa00",
|
|
38
|
+
chromhmm: "#00ff00",
|
|
39
|
+
ccre: "#000000",
|
|
40
|
+
wgbs: "#648bd8",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const defaultBigWig: Omit<BigWigConfig, "id" | "title" | "url"> = {
|
|
44
|
+
trackType: TrackType.BigWig,
|
|
45
|
+
height: 50,
|
|
46
|
+
displayMode: DisplayMode.Full,
|
|
47
|
+
titleSize: 12,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const defaultBigBed: Omit<BigBedConfig, "id" | "title" | "url"> = {
|
|
51
|
+
trackType: TrackType.BigBed,
|
|
52
|
+
height: 20,
|
|
53
|
+
displayMode: DisplayMode.Dense,
|
|
54
|
+
titleSize: 12,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const defaultMethylC: Omit<MethylCConfig, "id" | "title" | "urls"> = {
|
|
58
|
+
trackType: TrackType.MethylC,
|
|
59
|
+
height: 100,
|
|
60
|
+
displayMode: DisplayMode.Split,
|
|
61
|
+
titleSize: 12,
|
|
62
|
+
color: "#648bd8",
|
|
63
|
+
colors: {
|
|
64
|
+
cpg: "#648bd8",
|
|
65
|
+
chg: "#ff944d",
|
|
66
|
+
chh: "#ff00ff",
|
|
67
|
+
depth: "#525252",
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function createBiosampleTrack(
|
|
72
|
+
row: BiosampleRowInfo,
|
|
73
|
+
options: CreateTrackOptions,
|
|
74
|
+
): Track {
|
|
75
|
+
const assay = row.assay.toLowerCase();
|
|
76
|
+
const color = assayColors[assay] ?? "#000000";
|
|
77
|
+
const trackContext = options.trackContext;
|
|
78
|
+
|
|
79
|
+
switch (assay) {
|
|
80
|
+
case "chromhmm":
|
|
81
|
+
case "ccre":
|
|
82
|
+
return {
|
|
83
|
+
...defaultBigBed,
|
|
84
|
+
id: row.id,
|
|
85
|
+
url: row.url ?? "",
|
|
86
|
+
title: row.displayName,
|
|
87
|
+
color,
|
|
88
|
+
onClick: trackContext?.onBiosampleFeatureClick
|
|
89
|
+
? (rect: Rect) =>
|
|
90
|
+
trackContext.onBiosampleFeatureClick?.({
|
|
91
|
+
trackId: row.id,
|
|
92
|
+
row,
|
|
93
|
+
rect,
|
|
94
|
+
})
|
|
95
|
+
: undefined,
|
|
96
|
+
onHover: trackContext?.onBiosampleFeatureHover
|
|
97
|
+
? (rect: Rect) =>
|
|
98
|
+
trackContext.onBiosampleFeatureHover?.({
|
|
99
|
+
trackId: row.id,
|
|
100
|
+
row,
|
|
101
|
+
rect,
|
|
102
|
+
})
|
|
103
|
+
: undefined,
|
|
104
|
+
tooltip: trackContext?.biosampleFeatureTooltip,
|
|
105
|
+
};
|
|
106
|
+
case "wgbs":
|
|
107
|
+
return {
|
|
108
|
+
...defaultMethylC,
|
|
109
|
+
id: row.id,
|
|
110
|
+
title: row.displayName,
|
|
111
|
+
maskCpgByCoverage: true,
|
|
112
|
+
tooltip: trackContext?.biosampleMethylTooltip,
|
|
113
|
+
urls: {
|
|
114
|
+
plusStrand: {
|
|
115
|
+
cpg: { url: row.cpgPlus ?? "" },
|
|
116
|
+
chg: { url: "" },
|
|
117
|
+
chh: { url: "" },
|
|
118
|
+
depth: { url: row.coverage ?? "" },
|
|
119
|
+
},
|
|
120
|
+
minusStrand: {
|
|
121
|
+
cpg: { url: row.cpgMinus ?? "" },
|
|
122
|
+
chg: { url: "" },
|
|
123
|
+
chh: { url: "" },
|
|
124
|
+
depth: { url: row.coverage ?? "" },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
default:
|
|
129
|
+
return {
|
|
130
|
+
...defaultBigWig,
|
|
131
|
+
id: row.id,
|
|
132
|
+
url: row.url ?? "",
|
|
133
|
+
title: row.displayName,
|
|
134
|
+
color,
|
|
135
|
+
tooltip: trackContext?.biosampleSignalTooltip,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Types for biosample folder data
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
export type CollectionType = "Core" | "Ancillary" | "Partial";
|
|
6
2
|
|
|
7
3
|
/**
|
|
8
|
-
*
|
|
9
|
-
* Standard assays have a single `url`, while WGBS assays have `cpgPlus`, `cpgMinus`, `coverage`.
|
|
4
|
+
* One assay entry from the source data. WGBS rows carry strand-specific URLs.
|
|
10
5
|
*/
|
|
11
6
|
export type BiosampleAssayInfo = {
|
|
12
7
|
id: string;
|
|
@@ -14,15 +9,12 @@ export type BiosampleAssayInfo = {
|
|
|
14
9
|
url?: string;
|
|
15
10
|
experimentAccession: string;
|
|
16
11
|
fileAccession?: string;
|
|
17
|
-
// WGBS-specific fields
|
|
18
12
|
cpgPlus?: string;
|
|
19
13
|
cpgMinus?: string;
|
|
20
14
|
coverage?: string;
|
|
21
15
|
};
|
|
22
16
|
|
|
23
|
-
/**
|
|
24
|
-
* Track information from the JSON data
|
|
25
|
-
*/
|
|
17
|
+
/** One biosample entry from the source data. */
|
|
26
18
|
export type BiosampleTrackInfo = {
|
|
27
19
|
name: string;
|
|
28
20
|
ontology: string;
|
|
@@ -34,8 +26,7 @@ export type BiosampleTrackInfo = {
|
|
|
34
26
|
};
|
|
35
27
|
|
|
36
28
|
/**
|
|
37
|
-
*
|
|
38
|
-
* Standard assays have a single `url`, while WGBS assays have `cpgPlus`, `cpgMinus`, `coverage`.
|
|
29
|
+
* Flattened table row used by TrackSelect and track creation.
|
|
39
30
|
*/
|
|
40
31
|
export type BiosampleRowInfo = {
|
|
41
32
|
id: string;
|
|
@@ -48,15 +39,12 @@ export type BiosampleRowInfo = {
|
|
|
48
39
|
fileAccession?: string;
|
|
49
40
|
url?: string;
|
|
50
41
|
collection: CollectionType;
|
|
51
|
-
// WGBS-specific fields
|
|
52
42
|
cpgPlus?: string;
|
|
53
43
|
cpgMinus?: string;
|
|
54
44
|
coverage?: string;
|
|
55
45
|
};
|
|
56
46
|
|
|
57
|
-
/**
|
|
58
|
-
* Structure of the biosample JSON data files
|
|
59
|
-
*/
|
|
47
|
+
/** Root shape for biosample JSON files. */
|
|
60
48
|
export type BiosampleDataFile = {
|
|
61
49
|
tracks: BiosampleTrackInfo[];
|
|
62
50
|
};
|