@weng-lab/genomebrowser-ui 0.1.11 → 0.2.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 -0
- package/dist/TrackSelect/DataGrid/DefaultGroupingCell.d.ts +6 -0
- package/dist/TrackSelect/FolderList/Breadcrumb.d.ts +6 -0
- package/dist/TrackSelect/FolderList/FolderCard.d.ts +6 -0
- package/dist/TrackSelect/FolderList/FolderList.d.ts +6 -0
- package/dist/TrackSelect/{Data/humanBiosamples.json.d.ts → Folders/biosamples/data/human.json.d.ts} +1940 -1919
- package/dist/TrackSelect/{Data/mouseBiosamples.json.d.ts → Folders/biosamples/data/mouse.json.d.ts} +408 -357
- package/dist/TrackSelect/Folders/biosamples/human.d.ts +7 -0
- package/dist/TrackSelect/Folders/biosamples/mouse.d.ts +7 -0
- package/dist/TrackSelect/Folders/biosamples/shared/AssayToggle.d.ts +14 -0
- package/dist/TrackSelect/Folders/biosamples/shared/BiosampleGroupingCell.d.ts +6 -0
- package/dist/TrackSelect/Folders/biosamples/shared/BiosampleTreeItem.d.ts +7 -0
- package/dist/TrackSelect/Folders/biosamples/shared/columns.d.ts +14 -0
- package/dist/TrackSelect/Folders/biosamples/shared/constants.d.ts +19 -0
- package/dist/TrackSelect/Folders/biosamples/shared/createFolder.d.ts +24 -0
- package/dist/TrackSelect/Folders/biosamples/shared/treeBuilder.d.ts +25 -0
- package/dist/TrackSelect/Folders/biosamples/shared/types.d.ts +44 -0
- package/dist/TrackSelect/Folders/genes/data/human.json.d.ts +10 -0
- package/dist/TrackSelect/Folders/genes/data/mouse.json.d.ts +10 -0
- package/dist/TrackSelect/Folders/genes/human.d.ts +7 -0
- package/dist/TrackSelect/Folders/genes/mouse.d.ts +7 -0
- package/dist/TrackSelect/Folders/genes/shared/columns.d.ts +14 -0
- package/dist/TrackSelect/Folders/genes/shared/createFolder.d.ts +12 -0
- package/dist/TrackSelect/Folders/genes/shared/treeBuilder.d.ts +13 -0
- package/dist/TrackSelect/Folders/genes/shared/types.d.ts +26 -0
- package/dist/TrackSelect/Folders/index.d.ts +14 -0
- package/dist/TrackSelect/Folders/types.d.ts +76 -0
- package/dist/TrackSelect/TrackSelect.d.ts +12 -5
- package/dist/TrackSelect/TreeView/CustomTreeItem.d.ts +3 -0
- package/dist/TrackSelect/TreeView/TreeViewWrapper.d.ts +1 -1
- package/dist/TrackSelect/store.d.ts +1 -2
- package/dist/TrackSelect/types.d.ts +24 -62
- package/dist/genomebrowser-ui.es.js +1373 -2117
- package/dist/genomebrowser-ui.es.js.map +1 -1
- package/dist/lib.d.ts +2 -2
- package/package.json +3 -3
- package/src/TrackSelect/DataGrid/DataGridWrapper.tsx +36 -20
- package/src/TrackSelect/DataGrid/DefaultGroupingCell.tsx +64 -0
- package/src/TrackSelect/FolderList/Breadcrumb.tsx +38 -0
- package/src/TrackSelect/FolderList/FolderCard.tsx +51 -0
- package/src/TrackSelect/FolderList/FolderList.tsx +47 -0
- package/src/TrackSelect/Folders/NEW.md +929 -0
- package/src/TrackSelect/{Data → Folders/biosamples/data}/formatBiosamples.go +2 -2
- package/src/TrackSelect/{Data/humanBiosamples.json → Folders/biosamples/data/human.json} +1940 -1919
- package/src/TrackSelect/{Data/mouseBiosamples.json → Folders/biosamples/data/mouse.json} +408 -357
- package/src/TrackSelect/Folders/biosamples/human.ts +17 -0
- package/src/TrackSelect/Folders/biosamples/mouse.ts +17 -0
- package/src/TrackSelect/Folders/biosamples/shared/AssayToggle.tsx +65 -0
- package/src/TrackSelect/{DataGrid/GroupingCell.tsx → Folders/biosamples/shared/BiosampleGroupingCell.tsx} +7 -5
- package/src/TrackSelect/Folders/biosamples/shared/BiosampleTreeItem.tsx +15 -0
- package/src/TrackSelect/{DataGrid → Folders/biosamples/shared}/columns.tsx +31 -17
- package/src/TrackSelect/Folders/biosamples/shared/constants.tsx +116 -0
- package/src/TrackSelect/Folders/biosamples/shared/createFolder.ts +116 -0
- package/src/TrackSelect/Folders/biosamples/shared/treeBuilder.ts +227 -0
- package/src/TrackSelect/Folders/biosamples/shared/types.ts +48 -0
- package/src/TrackSelect/Folders/genes/data/human.json +7 -0
- package/src/TrackSelect/Folders/genes/data/mouse.json +7 -0
- package/src/TrackSelect/Folders/genes/human.ts +16 -0
- package/src/TrackSelect/Folders/genes/mouse.ts +16 -0
- package/src/TrackSelect/Folders/genes/shared/columns.tsx +42 -0
- package/src/TrackSelect/Folders/genes/shared/createFolder.ts +68 -0
- package/src/TrackSelect/Folders/genes/shared/treeBuilder.ts +45 -0
- package/src/TrackSelect/Folders/genes/shared/types.ts +29 -0
- package/src/TrackSelect/Folders/index.ts +27 -0
- package/src/TrackSelect/Folders/types.ts +95 -0
- package/src/TrackSelect/TrackSelect.tsx +409 -311
- package/src/TrackSelect/TreeView/CustomTreeItem.tsx +217 -0
- package/src/TrackSelect/TreeView/TreeViewWrapper.tsx +47 -42
- package/src/TrackSelect/store.ts +103 -46
- package/src/TrackSelect/types.ts +28 -74
- package/src/lib.ts +2 -2
- package/test/main.tsx +113 -169
- package/.claude/settings.local.json +0 -7
- package/dist/TrackSelect/DataGrid/CustomToolbar.d.ts +0 -12
- package/dist/TrackSelect/DataGrid/GroupingCell.d.ts +0 -2
- package/dist/TrackSelect/DataGrid/columns.d.ts +0 -4
- package/dist/TrackSelect/DataGrid/dataGridHelpers.d.ts +0 -49
- package/dist/TrackSelect/TreeView/treeViewHelpers.d.ts +0 -49
- package/dist/TrackSelect/consts.d.ts +0 -11
- package/src/TrackSelect/DataGrid/CustomToolbar.tsx +0 -152
- package/src/TrackSelect/DataGrid/dataGridHelpers.tsx +0 -155
- package/src/TrackSelect/TreeView/treeViewHelpers.tsx +0 -475
- package/src/TrackSelect/consts.ts +0 -92
- package/src/TrackSelect/issues.md +0 -404
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { capitalize } from "@mui/material";
|
|
2
|
-
import Fuse, { FuseResult } from "fuse.js";
|
|
3
|
-
import humanTracksData from "../Data/humanBiosamples.json";
|
|
4
|
-
import mouseTracksData from "../Data/mouseBiosamples.json";
|
|
5
|
-
import { AssayInfo, RowInfo, SearchTracksProps, TrackInfo } from "../types";
|
|
6
|
-
import { Assembly } from "../consts";
|
|
7
|
-
|
|
8
|
-
const tracksDataByAssembly: Record<Assembly, typeof humanTracksData> = {
|
|
9
|
-
GRCh38: humanTracksData,
|
|
10
|
-
mm10: mouseTracksData,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export function getTracksData(assembly: Assembly) {
|
|
14
|
-
return tracksDataByAssembly[assembly];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function formatAssayType(assay: string): string {
|
|
18
|
-
switch (assay) {
|
|
19
|
-
case "dnase":
|
|
20
|
-
return "DNase";
|
|
21
|
-
case "atac":
|
|
22
|
-
return "ATAC";
|
|
23
|
-
case "h3k4me3":
|
|
24
|
-
return "H3K4me3";
|
|
25
|
-
case "h3k27ac":
|
|
26
|
-
return "H3K27ac";
|
|
27
|
-
case "ctcf":
|
|
28
|
-
return "CTCF";
|
|
29
|
-
case "chromhmm":
|
|
30
|
-
return "ChromHMM";
|
|
31
|
-
case "ccre":
|
|
32
|
-
return "cCRE";
|
|
33
|
-
case "rnaseq":
|
|
34
|
-
return "RNA-seq";
|
|
35
|
-
default:
|
|
36
|
-
return assay;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Convert display assay name to JSON format */
|
|
41
|
-
function toJsonAssayType(displayName: string): string {
|
|
42
|
-
switch (displayName.toLowerCase()) {
|
|
43
|
-
case "dnase":
|
|
44
|
-
return "dnase";
|
|
45
|
-
case "atac":
|
|
46
|
-
return "atac";
|
|
47
|
-
case "h3k4me3":
|
|
48
|
-
return "h3k4me3";
|
|
49
|
-
case "h3k27ac":
|
|
50
|
-
return "h3k27ac";
|
|
51
|
-
case "ctcf":
|
|
52
|
-
return "ctcf";
|
|
53
|
-
case "chromhmm":
|
|
54
|
-
return "chromhmm";
|
|
55
|
-
case "ccre":
|
|
56
|
-
return "ccre";
|
|
57
|
-
case "rna-seq":
|
|
58
|
-
return "rnaseq";
|
|
59
|
-
default:
|
|
60
|
-
return displayName.toLowerCase();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// use to get nested data in JSON file
|
|
65
|
-
function getNestedValue(obj: any, path: string): any {
|
|
66
|
-
return path.split(".").reduce((acc, key) => acc && acc[key], obj);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function getTracksByAssayAndOntology(
|
|
70
|
-
assay: string,
|
|
71
|
-
ontology: string,
|
|
72
|
-
tracksData: ReturnType<typeof getTracksData>,
|
|
73
|
-
): TrackInfo[] {
|
|
74
|
-
let res: TrackInfo[] = [];
|
|
75
|
-
const data = getNestedValue(tracksData, "tracks");
|
|
76
|
-
const jsonAssay = toJsonAssayType(assay);
|
|
77
|
-
|
|
78
|
-
data.forEach((track: TrackInfo) => {
|
|
79
|
-
const filteredAssays =
|
|
80
|
-
track.assays?.filter((e: AssayInfo) => e.assay === jsonAssay) || [];
|
|
81
|
-
if (
|
|
82
|
-
filteredAssays.length > 0 &&
|
|
83
|
-
track.ontology === ontology.toLowerCase()
|
|
84
|
-
) {
|
|
85
|
-
res.push({
|
|
86
|
-
...track,
|
|
87
|
-
assays: filteredAssays,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
return res;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Flatten TrackInfo into RowInfo objects for DataGrid display.
|
|
95
|
-
* @param track TrackInfo object containing information from JSON file
|
|
96
|
-
* @returns Array of flattened RowInfo objects, one per assay
|
|
97
|
-
*/
|
|
98
|
-
export function flattenIntoRows(track: TrackInfo): RowInfo[] {
|
|
99
|
-
const { ontology, lifeStage, sampleType, displayname } = track;
|
|
100
|
-
|
|
101
|
-
return track.assays.map(
|
|
102
|
-
({ id, assay, experimentAccession, fileAccession, url }) => ({
|
|
103
|
-
id,
|
|
104
|
-
ontology: capitalize(ontology),
|
|
105
|
-
lifeStage: capitalize(lifeStage),
|
|
106
|
-
sampleType: capitalize(sampleType),
|
|
107
|
-
displayname: capitalize(displayname),
|
|
108
|
-
assay: formatAssayType(assay),
|
|
109
|
-
experimentAccession,
|
|
110
|
-
fileAccession,
|
|
111
|
-
url,
|
|
112
|
-
}),
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Fuzzy search in tracks stored in a JSON file.
|
|
118
|
-
*
|
|
119
|
-
* @param jsonStructure - Dot-separated path to the data array in the JSON structure.
|
|
120
|
-
* @param query - The search query string.
|
|
121
|
-
* @param keyWeightMap - Array of keys to search within each track object.
|
|
122
|
-
* Can look like ["name", "author"] or if weighted, [
|
|
123
|
-
{
|
|
124
|
-
name: 'title',
|
|
125
|
-
weight: 0.3
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: 'author',
|
|
129
|
-
weight: 0.7
|
|
130
|
-
}
|
|
131
|
-
].
|
|
132
|
-
* @param threshold - (Optional) Threshold for the fuzzy search (default is 0.5).
|
|
133
|
-
* Smaller = stricter match, larger = fuzzier since 0 is perfect match and 1 is worst match.
|
|
134
|
-
* @param limit - (Optional) Maximum number of results to return (default is 10).
|
|
135
|
-
* @returns FuseResult object containing the search results.
|
|
136
|
-
*/
|
|
137
|
-
export function searchTracks({
|
|
138
|
-
jsonStructure,
|
|
139
|
-
query,
|
|
140
|
-
keyWeightMap,
|
|
141
|
-
threshold = 0.75,
|
|
142
|
-
tracksData,
|
|
143
|
-
}: SearchTracksProps & {
|
|
144
|
-
tracksData: ReturnType<typeof getTracksData>;
|
|
145
|
-
}): FuseResult<TrackInfo>[] {
|
|
146
|
-
const data = getNestedValue(tracksData, jsonStructure ?? "");
|
|
147
|
-
|
|
148
|
-
const fuse = new Fuse(data, {
|
|
149
|
-
includeScore: true,
|
|
150
|
-
shouldSort: true,
|
|
151
|
-
threshold: threshold,
|
|
152
|
-
keys: keyWeightMap,
|
|
153
|
-
});
|
|
154
|
-
return fuse.search(query);
|
|
155
|
-
}
|
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
import Folder from "@mui/icons-material/Folder";
|
|
2
|
-
import IndeterminateCheckBoxRoundedIcon from "@mui/icons-material/IndeterminateCheckBoxRounded";
|
|
3
|
-
import { Box, Typography, Stack } from "@mui/material";
|
|
4
|
-
import Collapse from "@mui/material/Collapse";
|
|
5
|
-
import { alpha, styled } from "@mui/material/styles";
|
|
6
|
-
import { TreeViewBaseItem } from "@mui/x-tree-view";
|
|
7
|
-
import {
|
|
8
|
-
TreeItemCheckbox,
|
|
9
|
-
TreeItemIconContainer,
|
|
10
|
-
TreeItemLabel,
|
|
11
|
-
} from "@mui/x-tree-view/TreeItem";
|
|
12
|
-
import { TreeItemIcon } from "@mui/x-tree-view/TreeItemIcon";
|
|
13
|
-
import { TreeItemProvider } from "@mui/x-tree-view/TreeItemProvider";
|
|
14
|
-
import { useTreeItemModel } from "@mui/x-tree-view/hooks";
|
|
15
|
-
import { useTreeItem } from "@mui/x-tree-view/useTreeItem";
|
|
16
|
-
import React from "react";
|
|
17
|
-
import {
|
|
18
|
-
CustomLabelProps,
|
|
19
|
-
CustomTreeItemProps,
|
|
20
|
-
ExtendedTreeItemProps,
|
|
21
|
-
RowInfo,
|
|
22
|
-
} from "../types";
|
|
23
|
-
import Fuse, { FuseResult } from "fuse.js";
|
|
24
|
-
import { SearchTracksProps } from "../types";
|
|
25
|
-
import { assayTypes, ontologyTypes } from "../consts";
|
|
26
|
-
|
|
27
|
-
/** Format an ID like "h3k27ac-ENCFF922YMQ" to "H3K27ac - ENCFF922YMQ" */
|
|
28
|
-
function formatIdLabel(id: string): string {
|
|
29
|
-
const hyphenIndex = id.indexOf("-");
|
|
30
|
-
if (hyphenIndex === -1) return id;
|
|
31
|
-
|
|
32
|
-
const assayPart = id.substring(0, hyphenIndex);
|
|
33
|
-
let accessionPart = id.substring(hyphenIndex + 1);
|
|
34
|
-
|
|
35
|
-
// Truncate accession parts to 15 characters
|
|
36
|
-
if (accessionPart.length > 25)
|
|
37
|
-
accessionPart = accessionPart.substring(0, 15) + "…";
|
|
38
|
-
|
|
39
|
-
const formattedAssay = formatAssayName(assayPart);
|
|
40
|
-
return `${formattedAssay} - ${accessionPart}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function formatAssayName(assay: string): string {
|
|
44
|
-
switch (assay.toLowerCase()) {
|
|
45
|
-
case "dnase":
|
|
46
|
-
return "DNase";
|
|
47
|
-
case "atac":
|
|
48
|
-
return "ATAC";
|
|
49
|
-
case "h3k4me3":
|
|
50
|
-
return "H3K4me3";
|
|
51
|
-
case "h3k27ac":
|
|
52
|
-
return "H3K27ac";
|
|
53
|
-
case "ctcf":
|
|
54
|
-
return "CTCF";
|
|
55
|
-
case "chromhmm":
|
|
56
|
-
return "ChromHMM";
|
|
57
|
-
case "ccre":
|
|
58
|
-
return "cCRE";
|
|
59
|
-
case "rnaseq":
|
|
60
|
-
return "RNA-seq";
|
|
61
|
-
default:
|
|
62
|
-
return assay;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Builds tree in the sorted by assay view
|
|
68
|
-
* @param selectedIds: list of ids (from useSelectionStore)
|
|
69
|
-
* @param root: Root TreeViewBaseItem
|
|
70
|
-
* @param rowById: Mapping between an id (experimentAccession) and its RowInfo object
|
|
71
|
-
* @returns all of the items for the RichTreeView in TreeViewWrapper
|
|
72
|
-
*/
|
|
73
|
-
export function buildSortedAssayTreeView(
|
|
74
|
-
selectedIds: string[],
|
|
75
|
-
root: TreeViewBaseItem<ExtendedTreeItemProps>,
|
|
76
|
-
rowById: Map<string, RowInfo>,
|
|
77
|
-
): TreeViewBaseItem<ExtendedTreeItemProps>[] {
|
|
78
|
-
const assayMap = new Map<string, TreeViewBaseItem<ExtendedTreeItemProps>>(); // keep track of top level nodes
|
|
79
|
-
const ontologyMap = new Map<
|
|
80
|
-
string,
|
|
81
|
-
TreeViewBaseItem<ExtendedTreeItemProps>
|
|
82
|
-
>();
|
|
83
|
-
const sampleAssayMap = new Map<
|
|
84
|
-
string,
|
|
85
|
-
TreeViewBaseItem<ExtendedTreeItemProps>
|
|
86
|
-
>();
|
|
87
|
-
let idx = 1;
|
|
88
|
-
|
|
89
|
-
const selectedRows = selectedIds.reduce<RowInfo[]>((acc, id) => {
|
|
90
|
-
const row = rowById.get(id);
|
|
91
|
-
if (row) acc.push(row);
|
|
92
|
-
return acc;
|
|
93
|
-
}, []);
|
|
94
|
-
|
|
95
|
-
selectedRows.forEach((row) => {
|
|
96
|
-
let assayNode = assayMap.get(row.assay);
|
|
97
|
-
if (!assayNode) {
|
|
98
|
-
assayNode = {
|
|
99
|
-
id: row.assay,
|
|
100
|
-
isAssayItem: true,
|
|
101
|
-
label: row.assay,
|
|
102
|
-
icon: "removeable",
|
|
103
|
-
children: [],
|
|
104
|
-
allExpAccessions: [],
|
|
105
|
-
};
|
|
106
|
-
assayMap.set(row.assay, assayNode);
|
|
107
|
-
root.children!.push(assayNode);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
let ontologyNode = ontologyMap.get(row.ontology + row.assay);
|
|
111
|
-
if (!ontologyNode) {
|
|
112
|
-
ontologyNode = {
|
|
113
|
-
id: row.ontology + "_" + idx++,
|
|
114
|
-
isAssayItem: false,
|
|
115
|
-
label: row.ontology,
|
|
116
|
-
icon: "removeable",
|
|
117
|
-
children: [],
|
|
118
|
-
allExpAccessions: [],
|
|
119
|
-
};
|
|
120
|
-
assayNode.children!.push(ontologyNode);
|
|
121
|
-
ontologyMap.set(row.ontology + row.assay, ontologyNode);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const displayNameNode: TreeViewBaseItem<ExtendedTreeItemProps> = {
|
|
125
|
-
id: row.displayname + "_" + idx++,
|
|
126
|
-
isAssayItem: false,
|
|
127
|
-
label: row.displayname,
|
|
128
|
-
icon: "removeable",
|
|
129
|
-
children: [],
|
|
130
|
-
allExpAccessions: [],
|
|
131
|
-
};
|
|
132
|
-
ontologyNode.children!.push(displayNameNode);
|
|
133
|
-
|
|
134
|
-
let expNode = sampleAssayMap.get(row.displayname + row.id);
|
|
135
|
-
if (!expNode) {
|
|
136
|
-
expNode = {
|
|
137
|
-
id: row.id,
|
|
138
|
-
isAssayItem: false,
|
|
139
|
-
label: formatIdLabel(row.id),
|
|
140
|
-
icon: "removeable",
|
|
141
|
-
assayName: row.assay,
|
|
142
|
-
children: [],
|
|
143
|
-
allExpAccessions: [row.id],
|
|
144
|
-
};
|
|
145
|
-
sampleAssayMap.set(row.displayname + row.assay, expNode);
|
|
146
|
-
displayNameNode.children!.push(expNode);
|
|
147
|
-
}
|
|
148
|
-
assayNode.allExpAccessions!.push(row.id);
|
|
149
|
-
ontologyNode.allExpAccessions!.push(row.id);
|
|
150
|
-
displayNameNode.allExpAccessions!.push(row.id);
|
|
151
|
-
root.allRowInfo!.push(row);
|
|
152
|
-
});
|
|
153
|
-
// standardize the order of the assay folders everytime one is added
|
|
154
|
-
root.children!.sort((a, b): number => {
|
|
155
|
-
return assayTypes.indexOf(a.id) - assayTypes.indexOf(b.id);
|
|
156
|
-
});
|
|
157
|
-
return [root];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Builds tree in the sorted by assay view
|
|
162
|
-
* @param selectedIds: list of ids (from useSelectionStore)
|
|
163
|
-
* @param root: Root TreeViewBaseItem
|
|
164
|
-
* @param rowById: Mapping between an id (experimentAccession) and its RowInfo object
|
|
165
|
-
* @returns all of the items for the RichTreeView in TreeViewWrapper
|
|
166
|
-
*/
|
|
167
|
-
export function buildTreeView(
|
|
168
|
-
selectedIds: string[],
|
|
169
|
-
root: TreeViewBaseItem<ExtendedTreeItemProps>,
|
|
170
|
-
rowById: Map<string, RowInfo>,
|
|
171
|
-
): TreeViewBaseItem<ExtendedTreeItemProps>[] {
|
|
172
|
-
const ontologyMap = new Map<
|
|
173
|
-
string,
|
|
174
|
-
TreeViewBaseItem<ExtendedTreeItemProps>
|
|
175
|
-
>(); // keep track of top level nodes
|
|
176
|
-
const displayNameMap = new Map<
|
|
177
|
-
string,
|
|
178
|
-
TreeViewBaseItem<ExtendedTreeItemProps>
|
|
179
|
-
>();
|
|
180
|
-
const sampleAssayMap = new Map<
|
|
181
|
-
string,
|
|
182
|
-
TreeViewBaseItem<ExtendedTreeItemProps>
|
|
183
|
-
>();
|
|
184
|
-
|
|
185
|
-
const selectedRows = selectedIds.reduce<RowInfo[]>((acc, id) => {
|
|
186
|
-
const row = rowById.get(id);
|
|
187
|
-
if (row) acc.push(row);
|
|
188
|
-
return acc;
|
|
189
|
-
}, []);
|
|
190
|
-
|
|
191
|
-
selectedRows.forEach((row) => {
|
|
192
|
-
if (!row) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
let ontologyNode = ontologyMap.get(row.ontology);
|
|
196
|
-
if (!ontologyNode) {
|
|
197
|
-
ontologyNode = {
|
|
198
|
-
id: row.ontology,
|
|
199
|
-
label: row.ontology,
|
|
200
|
-
icon: "removeable",
|
|
201
|
-
children: [],
|
|
202
|
-
allExpAccessions: [],
|
|
203
|
-
};
|
|
204
|
-
ontologyMap.set(row.ontology, ontologyNode);
|
|
205
|
-
root.children!.push(ontologyNode);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
let displayNameNode = displayNameMap.get(row.displayname);
|
|
209
|
-
if (!displayNameNode) {
|
|
210
|
-
displayNameNode = {
|
|
211
|
-
id: row.displayname,
|
|
212
|
-
label: row.displayname,
|
|
213
|
-
icon: "removeable",
|
|
214
|
-
children: [],
|
|
215
|
-
allExpAccessions: [],
|
|
216
|
-
};
|
|
217
|
-
ontologyNode.children!.push(displayNameNode);
|
|
218
|
-
displayNameMap.set(row.displayname, displayNameNode);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
let expNode = sampleAssayMap.get(row.displayname + row.assay);
|
|
222
|
-
if (!expNode) {
|
|
223
|
-
expNode = {
|
|
224
|
-
id: row.id,
|
|
225
|
-
label: formatIdLabel(row.id),
|
|
226
|
-
icon: "removeable",
|
|
227
|
-
assayName: row.assay,
|
|
228
|
-
children: [],
|
|
229
|
-
allExpAccessions: [row.id],
|
|
230
|
-
};
|
|
231
|
-
sampleAssayMap.set(row.displayname + row.assay, expNode);
|
|
232
|
-
displayNameNode.children!.push(expNode);
|
|
233
|
-
}
|
|
234
|
-
ontologyNode.allExpAccessions!.push(row.id);
|
|
235
|
-
displayNameNode.allExpAccessions!.push(row.id);
|
|
236
|
-
root.allRowInfo!.push(row);
|
|
237
|
-
});
|
|
238
|
-
// standardize the order of the assay folders everytime one is added
|
|
239
|
-
root.children!.sort((a, b): number => {
|
|
240
|
-
return ontologyTypes.indexOf(a.id) - ontologyTypes.indexOf(b.id);
|
|
241
|
-
});
|
|
242
|
-
return [root];
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Fuzzy search of active tracks.
|
|
247
|
-
*
|
|
248
|
-
* @param treeItems - TreeBaseViewItems from the tree.
|
|
249
|
-
* @param query - The search query string.
|
|
250
|
-
* @param keyWeightMap - Array of keys to search within each track object.
|
|
251
|
-
* Can look like ["name", "author"] or if weighted, [
|
|
252
|
-
{
|
|
253
|
-
name: 'title',
|
|
254
|
-
weight: 0.3
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
name: 'author',
|
|
258
|
-
weight: 0.7
|
|
259
|
-
}
|
|
260
|
-
].
|
|
261
|
-
* @param threshold - (Optional) Threshold for the fuzzy search (default is 0.5).
|
|
262
|
-
* Smaller = stricter match, larger = fuzzier since 0 is perfect match and 1 is worst match.
|
|
263
|
-
* @param limit - (Optional) Maximum number of results to return (default is 10).
|
|
264
|
-
* @returns FuseResult object containing the search results.
|
|
265
|
-
*/
|
|
266
|
-
export function searchTreeItems({
|
|
267
|
-
treeItems,
|
|
268
|
-
query,
|
|
269
|
-
keyWeightMap,
|
|
270
|
-
threshold,
|
|
271
|
-
limit = 10,
|
|
272
|
-
}: SearchTracksProps): FuseResult<RowInfo>[] {
|
|
273
|
-
const data = treeItems![0].allRowInfo ?? [];
|
|
274
|
-
const fuse = new Fuse(data, {
|
|
275
|
-
includeScore: true,
|
|
276
|
-
shouldSort: true,
|
|
277
|
-
threshold: threshold,
|
|
278
|
-
keys: keyWeightMap,
|
|
279
|
-
});
|
|
280
|
-
return fuse.search(query, { limit: limit });
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Creates the assay icon for DataGrid and RichTreeView
|
|
285
|
-
* @param type: assay type
|
|
286
|
-
* @returns an icon of the assay's respective color
|
|
287
|
-
*/
|
|
288
|
-
export function AssayIcon(type: string) {
|
|
289
|
-
const colorMap: { [key: string]: string } = {
|
|
290
|
-
DNase: "#06da93",
|
|
291
|
-
ATAC: "#02c7b9",
|
|
292
|
-
H3K4me3: "#ff2020",
|
|
293
|
-
ChromHMM: "#0097a7",
|
|
294
|
-
H3K27ac: "#fdc401",
|
|
295
|
-
CTCF: "#01a6f1",
|
|
296
|
-
cCRE: "#8b5cf6",
|
|
297
|
-
"RNA-seq": "#f97316",
|
|
298
|
-
};
|
|
299
|
-
const color = colorMap[type];
|
|
300
|
-
return (
|
|
301
|
-
<Box
|
|
302
|
-
sx={{
|
|
303
|
-
width: 12,
|
|
304
|
-
height: 12,
|
|
305
|
-
borderRadius: "20%",
|
|
306
|
-
bgcolor: color,
|
|
307
|
-
}}
|
|
308
|
-
/>
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Everything below is styling for the custom directory look of the tree view
|
|
313
|
-
const TreeItemRoot = styled("li")(({ theme }) => ({
|
|
314
|
-
listStyle: "none",
|
|
315
|
-
margin: 0,
|
|
316
|
-
padding: 0,
|
|
317
|
-
outline: 4,
|
|
318
|
-
color: theme.palette.grey[400],
|
|
319
|
-
...theme.applyStyles("light", {
|
|
320
|
-
color: theme.palette.grey[600], // controls colors of the MUI icons
|
|
321
|
-
}),
|
|
322
|
-
}));
|
|
323
|
-
|
|
324
|
-
const TreeItemLabelText = styled(Typography)({
|
|
325
|
-
color: "black",
|
|
326
|
-
fontFamily: "inherit",
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
function CustomLabel({
|
|
330
|
-
icon: Icon,
|
|
331
|
-
children,
|
|
332
|
-
isAssayItem,
|
|
333
|
-
assayName,
|
|
334
|
-
...other
|
|
335
|
-
}: CustomLabelProps) {
|
|
336
|
-
const variant = isAssayItem ? "subtitle2" : "body2";
|
|
337
|
-
const fontWeight = isAssayItem ? "bold" : 500;
|
|
338
|
-
return (
|
|
339
|
-
<TreeItemLabel
|
|
340
|
-
{...other}
|
|
341
|
-
sx={{
|
|
342
|
-
display: "flex",
|
|
343
|
-
alignItems: "center",
|
|
344
|
-
}}
|
|
345
|
-
>
|
|
346
|
-
{Icon && React.isValidElement(Icon) ? (
|
|
347
|
-
<Box className="labelIcon" sx={{ mr: 1 }}>
|
|
348
|
-
{Icon}
|
|
349
|
-
</Box>
|
|
350
|
-
) : (
|
|
351
|
-
<Box
|
|
352
|
-
component={Icon as React.ElementType}
|
|
353
|
-
className="labelIcon"
|
|
354
|
-
color="inherit"
|
|
355
|
-
sx={{ mr: 1, fontSize: "1.2rem" }}
|
|
356
|
-
/>
|
|
357
|
-
)}
|
|
358
|
-
<Stack direction="row" spacing={1} alignItems="center">
|
|
359
|
-
{isAssayItem && AssayIcon(other.id)}
|
|
360
|
-
{assayName && AssayIcon(assayName)}
|
|
361
|
-
<TreeItemLabelText fontWeight={fontWeight} variant={variant}>
|
|
362
|
-
{children}
|
|
363
|
-
</TreeItemLabelText>
|
|
364
|
-
</Stack>
|
|
365
|
-
</TreeItemLabel>
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const TreeItemContent = styled("div")(({ theme }) => ({
|
|
370
|
-
padding: theme.spacing(0.5),
|
|
371
|
-
paddingRight: theme.spacing(2),
|
|
372
|
-
paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`,
|
|
373
|
-
width: "100%",
|
|
374
|
-
boxSizing: "border-box", // prevent width + padding to overflow
|
|
375
|
-
position: "relative",
|
|
376
|
-
display: "flex",
|
|
377
|
-
alignItems: "center",
|
|
378
|
-
gap: theme.spacing(1),
|
|
379
|
-
cursor: "pointer",
|
|
380
|
-
WebkitTapHighlightColor: "transparent",
|
|
381
|
-
flexDirection: "row-reverse",
|
|
382
|
-
borderRadius: theme.spacing(0.7),
|
|
383
|
-
marginBottom: theme.spacing(0.5),
|
|
384
|
-
marginTop: theme.spacing(0.5),
|
|
385
|
-
fontWeight: 500,
|
|
386
|
-
"&:hover": {
|
|
387
|
-
backgroundColor: alpha(theme.palette.primary.main, 0.1),
|
|
388
|
-
color: "white",
|
|
389
|
-
...theme.applyStyles("light", {
|
|
390
|
-
color: theme.palette.primary.main,
|
|
391
|
-
}),
|
|
392
|
-
},
|
|
393
|
-
}));
|
|
394
|
-
|
|
395
|
-
const getIconFromTreeItemType = (itemType: string) => {
|
|
396
|
-
switch (itemType) {
|
|
397
|
-
case "folder":
|
|
398
|
-
return Folder;
|
|
399
|
-
case "removeable":
|
|
400
|
-
return IndeterminateCheckBoxRoundedIcon;
|
|
401
|
-
default:
|
|
402
|
-
return AssayIcon(itemType);
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
export const CustomTreeItem = React.forwardRef(function CustomTreeItem(
|
|
407
|
-
props: CustomTreeItemProps,
|
|
408
|
-
ref: React.Ref<HTMLLIElement>,
|
|
409
|
-
) {
|
|
410
|
-
const { id, itemId, label, disabled, children, onRemove, ...other } = props;
|
|
411
|
-
|
|
412
|
-
const {
|
|
413
|
-
getContextProviderProps,
|
|
414
|
-
getRootProps,
|
|
415
|
-
getContentProps,
|
|
416
|
-
getIconContainerProps,
|
|
417
|
-
getCheckboxProps,
|
|
418
|
-
getLabelProps,
|
|
419
|
-
getGroupTransitionProps,
|
|
420
|
-
status,
|
|
421
|
-
} = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });
|
|
422
|
-
|
|
423
|
-
const item = useTreeItemModel<ExtendedTreeItemProps>(itemId)!;
|
|
424
|
-
const icon = getIconFromTreeItemType(item.icon);
|
|
425
|
-
|
|
426
|
-
const handleRemoveIconClick = (e: React.MouseEvent) => {
|
|
427
|
-
e.stopPropagation(); // prevent item expand/select
|
|
428
|
-
onRemove?.(item);
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
return (
|
|
432
|
-
<TreeItemProvider {...getContextProviderProps()}>
|
|
433
|
-
<TreeItemRoot {...getRootProps(other)}>
|
|
434
|
-
<TreeItemContent {...getContentProps()}>
|
|
435
|
-
<TreeItemIconContainer {...getIconContainerProps()}>
|
|
436
|
-
<TreeItemIcon status={status} />
|
|
437
|
-
</TreeItemIconContainer>
|
|
438
|
-
<TreeItemCheckbox {...getCheckboxProps()} />
|
|
439
|
-
<CustomLabel
|
|
440
|
-
{...getLabelProps({
|
|
441
|
-
icon:
|
|
442
|
-
item.icon === "removeable" ? (
|
|
443
|
-
<Box
|
|
444
|
-
onClick={handleRemoveIconClick}
|
|
445
|
-
sx={{
|
|
446
|
-
width: 20,
|
|
447
|
-
height: 20,
|
|
448
|
-
display: "flex",
|
|
449
|
-
alignItems: "center",
|
|
450
|
-
justifyContent: "center",
|
|
451
|
-
borderRadius: "4px",
|
|
452
|
-
cursor: "pointer",
|
|
453
|
-
mr: 1,
|
|
454
|
-
"&:hover": {
|
|
455
|
-
backgroundColor: "rgba(0,0,0,0.1)",
|
|
456
|
-
},
|
|
457
|
-
}}
|
|
458
|
-
>
|
|
459
|
-
<IndeterminateCheckBoxRoundedIcon fontSize="small" />
|
|
460
|
-
</Box>
|
|
461
|
-
) : (
|
|
462
|
-
icon
|
|
463
|
-
),
|
|
464
|
-
expandable: (status.expandable && status.expanded).toString(),
|
|
465
|
-
isAssayItem: item.isAssayItem,
|
|
466
|
-
assayName: item.assayName,
|
|
467
|
-
id: item.id,
|
|
468
|
-
})}
|
|
469
|
-
/>
|
|
470
|
-
</TreeItemContent>
|
|
471
|
-
{children && <Collapse {...getGroupTransitionProps()} />}
|
|
472
|
-
</TreeItemRoot>
|
|
473
|
-
</TreeItemProvider>
|
|
474
|
-
);
|
|
475
|
-
});
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getTracksByAssayAndOntology,
|
|
3
|
-
flattenIntoRows,
|
|
4
|
-
getTracksData,
|
|
5
|
-
} from "./DataGrid/dataGridHelpers";
|
|
6
|
-
import { RowInfo, TrackInfo } from "./types";
|
|
7
|
-
|
|
8
|
-
export type Assembly = "GRCh38" | "mm10";
|
|
9
|
-
|
|
10
|
-
export const assayTypes = [
|
|
11
|
-
"cCRE",
|
|
12
|
-
"DNase",
|
|
13
|
-
"H3K4me3",
|
|
14
|
-
"H3K27ac",
|
|
15
|
-
"ATAC",
|
|
16
|
-
"CTCF",
|
|
17
|
-
"RNA-seq",
|
|
18
|
-
"ChromHMM",
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
export const ontologyTypes = [
|
|
22
|
-
"Adipose",
|
|
23
|
-
"Adrenal gland",
|
|
24
|
-
"Blood",
|
|
25
|
-
"Blood vessel",
|
|
26
|
-
"Bone",
|
|
27
|
-
"Bone marrow",
|
|
28
|
-
"Brain",
|
|
29
|
-
"Breast",
|
|
30
|
-
"Connective tissue",
|
|
31
|
-
"Embryo",
|
|
32
|
-
"Epithelium",
|
|
33
|
-
"Esophagus",
|
|
34
|
-
"Eye",
|
|
35
|
-
"Fallopian Tube",
|
|
36
|
-
"Gallbladder",
|
|
37
|
-
"Heart",
|
|
38
|
-
"Kidney",
|
|
39
|
-
"Large Intestine",
|
|
40
|
-
"Limb",
|
|
41
|
-
"Liver",
|
|
42
|
-
"Lung",
|
|
43
|
-
"Lymphoid Tissue",
|
|
44
|
-
"Muscle",
|
|
45
|
-
"Mouth",
|
|
46
|
-
"Nerve",
|
|
47
|
-
"Nose",
|
|
48
|
-
"Pancreas",
|
|
49
|
-
"Parathyroid Gland",
|
|
50
|
-
"Ovary",
|
|
51
|
-
"Penis",
|
|
52
|
-
"Placenta",
|
|
53
|
-
"Prostate",
|
|
54
|
-
"Skin",
|
|
55
|
-
"Small Intestine",
|
|
56
|
-
"Spinal Cord",
|
|
57
|
-
"Spleen",
|
|
58
|
-
"Stomach",
|
|
59
|
-
"Testis",
|
|
60
|
-
"Thymus",
|
|
61
|
-
"Thyroid",
|
|
62
|
-
"Urinary Bladder",
|
|
63
|
-
"Uterus",
|
|
64
|
-
"Vagina",
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Build rows and rowById for a specific assembly
|
|
69
|
-
*/
|
|
70
|
-
export function buildRowsForAssembly(assembly: Assembly): {
|
|
71
|
-
rows: RowInfo[];
|
|
72
|
-
rowById: Map<string, RowInfo>;
|
|
73
|
-
} {
|
|
74
|
-
const tracksData = getTracksData(assembly);
|
|
75
|
-
const rows = ontologyTypes.flatMap((ontology) =>
|
|
76
|
-
assayTypes.flatMap((assay) =>
|
|
77
|
-
getTracksByAssayAndOntology(
|
|
78
|
-
assay.toLowerCase(),
|
|
79
|
-
ontology.toLowerCase(),
|
|
80
|
-
tracksData,
|
|
81
|
-
).flatMap((r: TrackInfo) =>
|
|
82
|
-
flattenIntoRows(r).map((flat) => ({
|
|
83
|
-
...flat,
|
|
84
|
-
assay,
|
|
85
|
-
ontology,
|
|
86
|
-
})),
|
|
87
|
-
),
|
|
88
|
-
),
|
|
89
|
-
);
|
|
90
|
-
const rowById = new Map<string, RowInfo>(rows.map((r) => [r.id, r]));
|
|
91
|
-
return { rows, rowById };
|
|
92
|
-
}
|