@weng-lab/genomebrowser-ui 0.3.6 → 0.4.0-beta.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/.env.local +1 -1
- 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/columns.d.ts +2 -2
- 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/genes/shared/types.d.ts +2 -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 +2231 -1732
- 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/Dialogs/ClearDialog.tsx +3 -8
- package/src/TrackSelect/Dialogs/ResetDialog.tsx +5 -4
- 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/columns.tsx +2 -2
- package/src/TrackSelect/Folders/genes/shared/createFolder.ts +11 -31
- package/src/TrackSelect/Folders/genes/shared/toTrack.ts +59 -0
- package/src/TrackSelect/Folders/genes/shared/types.ts +2 -0
- package/src/TrackSelect/Folders/index.ts +14 -17
- 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 +301 -257
- package/src/TrackSelect/TreeView/CustomTreeItem.tsx +9 -9
- 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 +36 -352
- 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/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.1",
|
|
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
|
}
|
|
@@ -26,8 +26,8 @@ export function ClearDialog({
|
|
|
26
26
|
<Dialog open={open} onClose={onClose}>
|
|
27
27
|
<DialogTitle
|
|
28
28
|
sx={{
|
|
29
|
-
bgcolor: "
|
|
30
|
-
color: "
|
|
29
|
+
bgcolor: "primary.main",
|
|
30
|
+
color: "primary.contrastText",
|
|
31
31
|
fontWeight: "bold",
|
|
32
32
|
}}
|
|
33
33
|
>
|
|
@@ -46,12 +46,7 @@ export function ClearDialog({
|
|
|
46
46
|
</DialogContentText>
|
|
47
47
|
</DialogContent>
|
|
48
48
|
<DialogActions sx={{ justifyContent: "center", gap: 2, pb: 2 }}>
|
|
49
|
-
<Button
|
|
50
|
-
variant="contained"
|
|
51
|
-
color="primary"
|
|
52
|
-
onClick={onClose}
|
|
53
|
-
autoFocus
|
|
54
|
-
>
|
|
49
|
+
<Button variant="contained" color="primary" onClick={onClose} autoFocus>
|
|
55
50
|
Cancel
|
|
56
51
|
</Button>
|
|
57
52
|
<Button variant="outlined" color="secondary" onClick={onConfirm}>
|
|
@@ -18,16 +18,17 @@ export function ResetDialog({ open, onClose, onConfirm }: ResetDialogProps) {
|
|
|
18
18
|
<Dialog open={open} onClose={onClose}>
|
|
19
19
|
<DialogTitle
|
|
20
20
|
sx={{
|
|
21
|
-
bgcolor: "
|
|
22
|
-
color: "
|
|
21
|
+
bgcolor: "primary.main",
|
|
22
|
+
color: "primary.contrastText",
|
|
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
|
};
|
|
@@ -37,6 +37,6 @@ export const defaultColumns: GridColDef<GeneRowInfo>[] = [
|
|
|
37
37
|
export const defaultGroupingModel: string[] = [];
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Leaf field - the
|
|
40
|
+
* Leaf field - the raw track ID (without folder prefix) used as the tree view leaf label
|
|
41
41
|
*/
|
|
42
|
-
export const defaultLeafField = "
|
|
42
|
+
export const defaultLeafField = "trackId";
|
|
@@ -5,32 +5,20 @@ import {
|
|
|
5
5
|
defaultGroupingModel,
|
|
6
6
|
defaultLeafField,
|
|
7
7
|
} from "./columns";
|
|
8
|
-
import {
|
|
8
|
+
import { createGeneTrack } from "./toTrack";
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
* For genes, this is a 1:1 mapping (no flattening needed)
|
|
13
|
-
*/
|
|
14
|
-
function trackToRow(track: GeneTrackInfo): GeneRowInfo {
|
|
10
|
+
/** Genes map 1:1 from JSON track entries to table rows. */
|
|
11
|
+
function trackToRow(folderId: string, track: GeneTrackInfo): GeneRowInfo {
|
|
15
12
|
return {
|
|
16
|
-
id: track.id
|
|
13
|
+
id: `${folderId}/${track.id}`,
|
|
14
|
+
trackId: track.id,
|
|
17
15
|
displayName: track.displayName,
|
|
18
16
|
versions: track.versions,
|
|
19
17
|
};
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*/
|
|
25
|
-
function transformData(data: GeneDataFile): {
|
|
26
|
-
rows: GeneRowInfo[];
|
|
27
|
-
rowById: Map<string, GeneRowInfo>;
|
|
28
|
-
} {
|
|
29
|
-
const rows = data.map(trackToRow);
|
|
30
|
-
const rowById = new Map<string, GeneRowInfo>(
|
|
31
|
-
rows.map((row) => [row.id, row]),
|
|
32
|
-
);
|
|
33
|
-
return { rows, rowById };
|
|
20
|
+
function transformData(folderId: string, data: GeneDataFile): GeneRowInfo[] {
|
|
21
|
+
return data.map((track) => trackToRow(folderId, track));
|
|
34
22
|
}
|
|
35
23
|
|
|
36
24
|
export interface CreateGeneFolderOptions {
|
|
@@ -40,29 +28,21 @@ export interface CreateGeneFolderOptions {
|
|
|
40
28
|
data: GeneDataFile;
|
|
41
29
|
}
|
|
42
30
|
|
|
43
|
-
/**
|
|
44
|
-
* Factory function that creates a FolderDefinition for genes
|
|
45
|
-
*/
|
|
31
|
+
/** Build a gene folder with its row lookup, tree, and track factory. */
|
|
46
32
|
export function createGeneFolder(
|
|
47
33
|
options: CreateGeneFolderOptions,
|
|
48
34
|
): FolderDefinition<GeneRowInfo> {
|
|
49
35
|
const { id, label, description, data } = options;
|
|
50
|
-
const
|
|
36
|
+
const rows = transformData(id, data);
|
|
51
37
|
|
|
52
38
|
return {
|
|
53
39
|
id,
|
|
54
40
|
label,
|
|
55
41
|
description,
|
|
56
|
-
|
|
57
|
-
getRowId: (row) => row.id,
|
|
58
|
-
|
|
59
|
-
// Default view configuration
|
|
42
|
+
rows,
|
|
60
43
|
columns: defaultColumns,
|
|
61
44
|
groupingModel: defaultGroupingModel,
|
|
62
45
|
leafField: defaultLeafField,
|
|
63
|
-
|
|
64
|
-
// Tree builder for selected items panel
|
|
65
|
-
buildTree: (selectedIds, rowById) =>
|
|
66
|
-
buildTreeView(selectedIds, rowById, label),
|
|
46
|
+
createTrack: createGeneTrack,
|
|
67
47
|
};
|
|
68
48
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DisplayMode,
|
|
3
|
+
Track,
|
|
4
|
+
TrackType,
|
|
5
|
+
Transcript,
|
|
6
|
+
TranscriptConfig,
|
|
7
|
+
} from "@weng-lab/genomebrowser";
|
|
8
|
+
import type { FC } from "react";
|
|
9
|
+
import { CreateTrackOptions } from "../../types";
|
|
10
|
+
import { GeneRowInfo } from "./types";
|
|
11
|
+
|
|
12
|
+
export type GeneTrackContext = {
|
|
13
|
+
onGeneClick?: (args: {
|
|
14
|
+
trackId: string;
|
|
15
|
+
row: GeneRowInfo;
|
|
16
|
+
transcript: Transcript;
|
|
17
|
+
}) => void;
|
|
18
|
+
onGeneHover?: (args: {
|
|
19
|
+
trackId: string;
|
|
20
|
+
row: GeneRowInfo;
|
|
21
|
+
transcript: Transcript;
|
|
22
|
+
}) => void;
|
|
23
|
+
geneTooltip?: FC<Transcript>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const defaultTranscript: Omit<TranscriptConfig, "id" | "assembly" | "version"> =
|
|
27
|
+
{
|
|
28
|
+
title: "GENCODE Genes",
|
|
29
|
+
trackType: TrackType.Transcript,
|
|
30
|
+
displayMode: DisplayMode.Squish,
|
|
31
|
+
height: 100,
|
|
32
|
+
color: "#0c184a",
|
|
33
|
+
canonicalColor: "#100e98",
|
|
34
|
+
highlightColor: "#3c69e8",
|
|
35
|
+
titleSize: 12,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function createGeneTrack(
|
|
39
|
+
row: GeneRowInfo,
|
|
40
|
+
options: CreateTrackOptions,
|
|
41
|
+
): Track {
|
|
42
|
+
const trackContext = options.trackContext;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
...defaultTranscript,
|
|
46
|
+
id: row.id,
|
|
47
|
+
assembly: options.assembly,
|
|
48
|
+
version: row.versions[row.versions.length - 1],
|
|
49
|
+
onClick: trackContext?.onGeneClick
|
|
50
|
+
? (transcript: Transcript) =>
|
|
51
|
+
trackContext.onGeneClick?.({ trackId: row.id, row, transcript })
|
|
52
|
+
: undefined,
|
|
53
|
+
onHover: trackContext?.onGeneHover
|
|
54
|
+
? (transcript: Transcript) =>
|
|
55
|
+
trackContext.onGeneHover?.({ trackId: row.id, row, transcript })
|
|
56
|
+
: undefined,
|
|
57
|
+
tooltip: trackContext?.geneTooltip,
|
|
58
|
+
};
|
|
59
|
+
}
|