@weng-lab/genomebrowser-ui 0.1.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/dist/TrackSelect/Data/modifiedHumanTracks.json.d.ts +37222 -0
- package/dist/TrackSelect/DataGrid/CustomToolbar.d.ts +12 -0
- package/dist/TrackSelect/DataGrid/DataGridWrapper.d.ts +2 -0
- package/dist/TrackSelect/DataGrid/columns.d.ts +4 -0
- package/dist/TrackSelect/DataGrid/dataGridHelpers.d.ts +30 -0
- package/dist/TrackSelect/TrackSelect.d.ts +5 -0
- package/dist/TrackSelect/TreeView/TreeViewWrapper.d.ts +2 -0
- package/dist/TrackSelect/TreeView/treeViewHelpers.d.ts +49 -0
- package/dist/TrackSelect/consts.d.ts +21 -0
- package/dist/TrackSelect/store.d.ts +4 -0
- package/dist/TrackSelect/types.d.ts +123 -0
- package/dist/genomebrowser-ui.es.js +2299 -0
- package/dist/genomebrowser-ui.es.js.map +1 -0
- package/dist/lib.d.ts +4 -0
- package/eslint.config.js +30 -0
- package/index.html +14 -0
- package/package.json +47 -0
- package/src/TrackSelect/Data/humanTracks.json +35711 -0
- package/src/TrackSelect/Data/human_chromhmm_biosamples_with_all_urls.json +35716 -0
- package/src/TrackSelect/Data/modifiedHumanTracks.json +37220 -0
- package/src/TrackSelect/DataGrid/CustomToolbar.tsx +160 -0
- package/src/TrackSelect/DataGrid/DataGridWrapper.tsx +119 -0
- package/src/TrackSelect/DataGrid/columns.tsx +134 -0
- package/src/TrackSelect/DataGrid/dataGridHelpers.tsx +114 -0
- package/src/TrackSelect/TrackSelect.tsx +258 -0
- package/src/TrackSelect/TreeView/TreeViewWrapper.tsx +115 -0
- package/src/TrackSelect/TreeView/treeViewHelpers.tsx +428 -0
- package/src/TrackSelect/bug.md +4 -0
- package/src/TrackSelect/consts.ts +92 -0
- package/src/TrackSelect/store.ts +26 -0
- package/src/TrackSelect/types.ts +139 -0
- package/src/lib.ts +8 -0
- package/test/main.tsx +13 -0
- package/tsconfig.app.json +25 -0
- package/tsconfig.json +4 -0
- package/tsconfig.node.json +25 -0
- package/vite.config.ts +66 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Toolbar,
|
|
4
|
+
ToolbarButton,
|
|
5
|
+
ColumnsPanelTrigger,
|
|
6
|
+
FilterPanelTrigger,
|
|
7
|
+
ExportCsv,
|
|
8
|
+
ExportPrint,
|
|
9
|
+
GridToolbarProps,
|
|
10
|
+
ToolbarPropsOverrides,
|
|
11
|
+
ExportExcel,
|
|
12
|
+
} from "@mui/x-data-grid-premium";
|
|
13
|
+
import Tooltip from "@mui/material/Tooltip";
|
|
14
|
+
import Menu from "@mui/material/Menu";
|
|
15
|
+
import Badge from "@mui/material/Badge";
|
|
16
|
+
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
|
17
|
+
import FilterListIcon from "@mui/icons-material/FilterList";
|
|
18
|
+
import FileDownloadIcon from "@mui/icons-material/FileDownload";
|
|
19
|
+
import MenuItem from "@mui/material/MenuItem";
|
|
20
|
+
import Divider from "@mui/material/Divider";
|
|
21
|
+
import Typography from "@mui/material/Typography";
|
|
22
|
+
import { DataGridProps } from "../types";
|
|
23
|
+
import { InfoOutline } from "@mui/icons-material";
|
|
24
|
+
|
|
25
|
+
type CustomToolbarProps = {
|
|
26
|
+
label: DataGridProps["label"];
|
|
27
|
+
downloadFileName: DataGridProps["downloadFileName"];
|
|
28
|
+
labelTooltip: DataGridProps["labelTooltip"];
|
|
29
|
+
toolbarSlot?: DataGridProps["toolbarSlot"];
|
|
30
|
+
toolbarStyle?: DataGridProps["toolbarStyle"];
|
|
31
|
+
toolbarIconColor?: DataGridProps["toolbarIconColor"];
|
|
32
|
+
} & GridToolbarProps &
|
|
33
|
+
ToolbarPropsOverrides;
|
|
34
|
+
|
|
35
|
+
export function CustomToolbar({
|
|
36
|
+
label,
|
|
37
|
+
downloadFileName,
|
|
38
|
+
labelTooltip,
|
|
39
|
+
toolbarSlot,
|
|
40
|
+
toolbarStyle,
|
|
41
|
+
toolbarIconColor,
|
|
42
|
+
...restToolbarProps
|
|
43
|
+
}: CustomToolbarProps) {
|
|
44
|
+
const [exportMenuOpen, setExportMenuOpen] = React.useState(false);
|
|
45
|
+
const exportMenuTriggerRef = React.useRef<HTMLButtonElement>(null);
|
|
46
|
+
|
|
47
|
+
const iconColor = toolbarIconColor ?? "inherit";
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Toolbar style={{ ...toolbarStyle }}>
|
|
51
|
+
{typeof label !== "string" && label}
|
|
52
|
+
<Typography
|
|
53
|
+
fontWeight="medium"
|
|
54
|
+
sx={{ flex: 1, mx: 0.5, display: "flex", alignItems: "center", gap: 1 }}
|
|
55
|
+
>
|
|
56
|
+
{typeof label === "string" && label}
|
|
57
|
+
{/* ReactNode can be more than just an element, string, or number but not accounting for that for simplicity */}
|
|
58
|
+
{labelTooltip &&
|
|
59
|
+
(typeof labelTooltip === "string" ||
|
|
60
|
+
typeof labelTooltip === "number") ? (
|
|
61
|
+
<Tooltip title={labelTooltip}>
|
|
62
|
+
<InfoOutline fontSize="inherit" color="primary" />
|
|
63
|
+
</Tooltip>
|
|
64
|
+
) : (
|
|
65
|
+
labelTooltip
|
|
66
|
+
)}
|
|
67
|
+
</Typography>
|
|
68
|
+
{toolbarSlot && (
|
|
69
|
+
<>
|
|
70
|
+
{toolbarSlot}
|
|
71
|
+
<Divider
|
|
72
|
+
orientation="vertical"
|
|
73
|
+
variant="middle"
|
|
74
|
+
flexItem
|
|
75
|
+
sx={{ mx: 0.5 }}
|
|
76
|
+
/>
|
|
77
|
+
</>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
<Tooltip title="Columns">
|
|
81
|
+
<ColumnsPanelTrigger render={<ToolbarButton />}>
|
|
82
|
+
<ViewColumnIcon fontSize="small" htmlColor={iconColor} />
|
|
83
|
+
</ColumnsPanelTrigger>
|
|
84
|
+
</Tooltip>
|
|
85
|
+
|
|
86
|
+
<Tooltip title="Filters">
|
|
87
|
+
<FilterPanelTrigger
|
|
88
|
+
render={(props, state) => (
|
|
89
|
+
<ToolbarButton {...props} color="default">
|
|
90
|
+
<Badge
|
|
91
|
+
badgeContent={state.filterCount}
|
|
92
|
+
color="primary"
|
|
93
|
+
variant="dot"
|
|
94
|
+
>
|
|
95
|
+
<FilterListIcon fontSize="small" htmlColor={iconColor} />
|
|
96
|
+
</Badge>
|
|
97
|
+
</ToolbarButton>
|
|
98
|
+
)}
|
|
99
|
+
/>
|
|
100
|
+
</Tooltip>
|
|
101
|
+
<Divider
|
|
102
|
+
orientation="vertical"
|
|
103
|
+
variant="middle"
|
|
104
|
+
flexItem
|
|
105
|
+
sx={{ mx: 0.5 }}
|
|
106
|
+
/>
|
|
107
|
+
<Tooltip title="Export">
|
|
108
|
+
<ToolbarButton
|
|
109
|
+
ref={exportMenuTriggerRef}
|
|
110
|
+
id="export-menu-trigger"
|
|
111
|
+
aria-controls="export-menu"
|
|
112
|
+
aria-haspopup="true"
|
|
113
|
+
aria-expanded={exportMenuOpen ? "true" : undefined}
|
|
114
|
+
onClick={() => setExportMenuOpen(true)}
|
|
115
|
+
>
|
|
116
|
+
<FileDownloadIcon fontSize="small" htmlColor={iconColor} />
|
|
117
|
+
</ToolbarButton>
|
|
118
|
+
</Tooltip>
|
|
119
|
+
|
|
120
|
+
<Menu
|
|
121
|
+
id="export-menu"
|
|
122
|
+
anchorEl={exportMenuTriggerRef.current}
|
|
123
|
+
open={exportMenuOpen}
|
|
124
|
+
onClose={() => setExportMenuOpen(false)}
|
|
125
|
+
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
|
126
|
+
transformOrigin={{ vertical: "top", horizontal: "right" }}
|
|
127
|
+
slotProps={{
|
|
128
|
+
list: {
|
|
129
|
+
"aria-labelledby": "export-menu-trigger",
|
|
130
|
+
},
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<ExportPrint
|
|
134
|
+
options={{ ...restToolbarProps.printOptions }}
|
|
135
|
+
render={<MenuItem />}
|
|
136
|
+
onClick={() => setExportMenuOpen(false)}
|
|
137
|
+
>
|
|
138
|
+
Print
|
|
139
|
+
</ExportPrint>
|
|
140
|
+
<ExportCsv
|
|
141
|
+
options={{
|
|
142
|
+
fileName: typeof label === "string" ? label : downloadFileName,
|
|
143
|
+
...restToolbarProps.csvOptions,
|
|
144
|
+
}}
|
|
145
|
+
render={<MenuItem />}
|
|
146
|
+
onClick={() => setExportMenuOpen(false)}
|
|
147
|
+
>
|
|
148
|
+
Download as CSV
|
|
149
|
+
</ExportCsv>
|
|
150
|
+
<ExportExcel
|
|
151
|
+
options={{ ...restToolbarProps.excelOptions }}
|
|
152
|
+
render={<MenuItem />}
|
|
153
|
+
onClick={() => setExportMenuOpen(false)}
|
|
154
|
+
>
|
|
155
|
+
Download as Excel
|
|
156
|
+
</ExportExcel>
|
|
157
|
+
</Menu>
|
|
158
|
+
</Toolbar>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Box, Paper } from "@mui/material";
|
|
2
|
+
import {
|
|
3
|
+
DataGridPremium,
|
|
4
|
+
GridToolbarProps,
|
|
5
|
+
ToolbarPropsOverrides,
|
|
6
|
+
GridAutosizeOptions,
|
|
7
|
+
useGridApiRef,
|
|
8
|
+
GridColDef,
|
|
9
|
+
FilterColumnsArgs
|
|
10
|
+
} from "@mui/x-data-grid-premium";
|
|
11
|
+
import { DataGridProps } from "../types";
|
|
12
|
+
import { CustomToolbar } from "./CustomToolbar";
|
|
13
|
+
import { useEffect, useMemo } from "react";
|
|
14
|
+
import { sortedByAssayColumns, defaultColumns } from "./columns";
|
|
15
|
+
|
|
16
|
+
const autosizeOptions: GridAutosizeOptions = {
|
|
17
|
+
expand: true,
|
|
18
|
+
includeHeaders: true,
|
|
19
|
+
outliersFactor: 1.5,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// TODO: figure out where mui stores the number of rows in a row grouping so that can be bolded too
|
|
23
|
+
export function DataGridWrapper(props: DataGridProps) {
|
|
24
|
+
const {
|
|
25
|
+
label,
|
|
26
|
+
labelTooltip,
|
|
27
|
+
downloadFileName,
|
|
28
|
+
toolbarSlot,
|
|
29
|
+
toolbarStyle,
|
|
30
|
+
toolbarIconColor,
|
|
31
|
+
sortedAssay,
|
|
32
|
+
handleSelection,
|
|
33
|
+
rows,
|
|
34
|
+
selectedIds
|
|
35
|
+
} = props;
|
|
36
|
+
|
|
37
|
+
const CustomToolbarWrapper = useMemo(() => {
|
|
38
|
+
const customToolbarProps = {
|
|
39
|
+
label,
|
|
40
|
+
downloadFileName,
|
|
41
|
+
labelTooltip,
|
|
42
|
+
toolbarSlot,
|
|
43
|
+
toolbarStyle,
|
|
44
|
+
toolbarIconColor,
|
|
45
|
+
};
|
|
46
|
+
return (props: GridToolbarProps & ToolbarPropsOverrides) => <CustomToolbar {...props} {...customToolbarProps} />;
|
|
47
|
+
}, [label, labelTooltip, toolbarSlot]);
|
|
48
|
+
|
|
49
|
+
const apiRef = useGridApiRef();
|
|
50
|
+
const groupingModel = sortedAssay ? ["assay", "ontology"] : ["ontology", "assay"];
|
|
51
|
+
const columnModel = sortedAssay ? sortedByAssayColumns : defaultColumns;
|
|
52
|
+
|
|
53
|
+
// functions to customize the column and filter panel in the toolbar
|
|
54
|
+
const filterColumns = ({ columns }: FilterColumnsArgs) => {
|
|
55
|
+
return columns.filter((column) => column.type !== 'custom').map((column) => column.field);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const getTogglableColumns = (columns: GridColDef[]) => {
|
|
59
|
+
return columns.filter((column) => column.type !== 'custom').map((column) => column.field);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const handleResizeCols = () => {
|
|
63
|
+
// need to check .autosizeColumns since the current was being set with an empty object
|
|
64
|
+
if (!apiRef.current?.autosizeColumns) return;
|
|
65
|
+
apiRef.current.autosizeColumns(autosizeOptions);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// trigger resize when rows or columns change so that rows/columns don't need to be memoized outisde of this component
|
|
69
|
+
// otherwise sometimes would snap back to default widths when rows/columns change
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
handleResizeCols();
|
|
72
|
+
}, [rows, defaultColumns, sortedByAssayColumns, handleResizeCols]);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Paper sx={{ width: "100%" }}>
|
|
76
|
+
<Box sx={{
|
|
77
|
+
height: 500,
|
|
78
|
+
width: "100%",
|
|
79
|
+
overflow: "auto",
|
|
80
|
+
}}>
|
|
81
|
+
<DataGridPremium
|
|
82
|
+
apiRef={apiRef}
|
|
83
|
+
rows={rows}
|
|
84
|
+
columns={columnModel}
|
|
85
|
+
getRowId={(row) => row.experimentAccession}
|
|
86
|
+
autosizeOptions={autosizeOptions}
|
|
87
|
+
rowGroupingModel={groupingModel}
|
|
88
|
+
groupingColDef={{ leafField: "displayname", display: "flex" }}
|
|
89
|
+
columnVisibilityModel={{ displayname: false }} // so you don't see a second name column
|
|
90
|
+
onRowSelectionModelChange={handleSelection}
|
|
91
|
+
rowSelectionPropagation={{ descendants: true }}
|
|
92
|
+
rowSelectionModel={{ type: "include", ids: new Set(selectedIds) }}
|
|
93
|
+
slots={{
|
|
94
|
+
toolbar: CustomToolbarWrapper,
|
|
95
|
+
}}
|
|
96
|
+
slotProps={{
|
|
97
|
+
filterPanel: {
|
|
98
|
+
filterFormProps: {
|
|
99
|
+
filterColumns,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
columnsManagement: {
|
|
103
|
+
getTogglableColumns,
|
|
104
|
+
},
|
|
105
|
+
}}
|
|
106
|
+
keepNonExistentRowsSelected
|
|
107
|
+
showToolbar
|
|
108
|
+
disableAggregation
|
|
109
|
+
disableRowSelectionExcludeModel
|
|
110
|
+
disablePivoting
|
|
111
|
+
checkboxSelection
|
|
112
|
+
autosizeOnMount
|
|
113
|
+
pagination
|
|
114
|
+
hideFooterSelectedRowCount
|
|
115
|
+
/>
|
|
116
|
+
</Box>
|
|
117
|
+
</Paper>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { GridColDef } from "@mui/x-data-grid-premium";
|
|
2
|
+
import { RowInfo } from "../types";
|
|
3
|
+
import { Stack, capitalize } from "@mui/material";
|
|
4
|
+
import { AssayIcon } from "../TreeView/treeViewHelpers";
|
|
5
|
+
import { ontologyTypes, assayTypes } from "../consts";
|
|
6
|
+
|
|
7
|
+
const displayNameCol: GridColDef<RowInfo> = {
|
|
8
|
+
field: "displayname",
|
|
9
|
+
headerName: "Name",
|
|
10
|
+
valueFormatter: (value) => value && capitalize(value),
|
|
11
|
+
maxWidth: 300,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const sortedByAssayOntologyCol: GridColDef<RowInfo> = {
|
|
15
|
+
field: "ontology",
|
|
16
|
+
headerName: "Ontology",
|
|
17
|
+
type: "singleSelect",
|
|
18
|
+
valueOptions: ontologyTypes,
|
|
19
|
+
renderCell: (params) => {
|
|
20
|
+
if (params.rowNode.type === "group") {
|
|
21
|
+
if (params.value === undefined) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const val = params.value;
|
|
25
|
+
return (
|
|
26
|
+
<div><b>{val}</b></div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const sortedByAssayAssayCol : GridColDef<RowInfo> = {
|
|
33
|
+
field: "assay",
|
|
34
|
+
headerName: "Assay",
|
|
35
|
+
valueOptions: assayTypes,
|
|
36
|
+
renderCell: (params) => {
|
|
37
|
+
if (params.rowNode.type === "group") {
|
|
38
|
+
if (params.value === undefined) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const val = params.value;
|
|
42
|
+
return (
|
|
43
|
+
<Stack direction="row" spacing={2} alignItems="center">
|
|
44
|
+
{AssayIcon(val)}
|
|
45
|
+
<div><b>{val}</b></div>
|
|
46
|
+
</Stack>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const defaultOntologyCol: GridColDef<RowInfo> = {
|
|
53
|
+
field: "ontology",
|
|
54
|
+
headerName: "Ontology",
|
|
55
|
+
type: "singleSelect",
|
|
56
|
+
valueOptions: ontologyTypes,
|
|
57
|
+
renderCell: (params) => {
|
|
58
|
+
if (params.rowNode.type === "group") {
|
|
59
|
+
if (params.value === undefined) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const val = params.value;
|
|
63
|
+
return (
|
|
64
|
+
<div><b>{val}</b></div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const defaultAssayCol: GridColDef<RowInfo> = {
|
|
71
|
+
field: "assay",
|
|
72
|
+
headerName: "Assay",
|
|
73
|
+
valueOptions: assayTypes,
|
|
74
|
+
renderCell: (params) => {
|
|
75
|
+
if (params.rowNode.type === "group") {
|
|
76
|
+
if (params.value === undefined) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const val = params.value;
|
|
80
|
+
return (
|
|
81
|
+
<Stack direction="row" spacing={2} alignItems="center">
|
|
82
|
+
{AssayIcon(val)}
|
|
83
|
+
<div>{val}</div>
|
|
84
|
+
</Stack>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const sampleTypeCol: GridColDef<RowInfo> = {
|
|
91
|
+
field: "sampleType",
|
|
92
|
+
headerName: "Sample Type",
|
|
93
|
+
type: "singleSelect",
|
|
94
|
+
valueOptions: ["tissue", "primary cell", "cell line", "in vitro differentiated cells", "organoid"],
|
|
95
|
+
valueFormatter: (value) => value && capitalize(value),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const lifeStageCol: GridColDef<RowInfo> = {
|
|
99
|
+
field: "lifeStage",
|
|
100
|
+
headerName: "Life Stage",
|
|
101
|
+
type: "singleSelect",
|
|
102
|
+
valueOptions: ["adult", "embryonic"],
|
|
103
|
+
valueFormatter: (value) => value && capitalize(value),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const experimentCol: GridColDef<RowInfo> = {
|
|
107
|
+
field: "experimentAccession",
|
|
108
|
+
headerName: "Experiment Accession"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const fileCol: GridColDef<RowInfo> = {
|
|
112
|
+
field: "fileAccession",
|
|
113
|
+
headerName: "File Accession"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const sortedByAssayColumns: GridColDef<RowInfo>[] = [
|
|
117
|
+
displayNameCol,
|
|
118
|
+
sortedByAssayOntologyCol,
|
|
119
|
+
sampleTypeCol,
|
|
120
|
+
lifeStageCol,
|
|
121
|
+
sortedByAssayAssayCol,
|
|
122
|
+
experimentCol,
|
|
123
|
+
fileCol
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
export const defaultColumns: GridColDef<RowInfo>[] = [
|
|
127
|
+
displayNameCol,
|
|
128
|
+
sampleTypeCol,
|
|
129
|
+
lifeStageCol,
|
|
130
|
+
defaultOntologyCol,
|
|
131
|
+
defaultAssayCol,
|
|
132
|
+
experimentCol,
|
|
133
|
+
fileCol
|
|
134
|
+
]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { capitalize } from "@mui/material";
|
|
2
|
+
import Fuse, { FuseResult } from "fuse.js";
|
|
3
|
+
import tracksData from "../Data/modifiedHumanTracks.json";
|
|
4
|
+
import {
|
|
5
|
+
AssayInfo,
|
|
6
|
+
RowInfo,
|
|
7
|
+
SearchTracksProps,
|
|
8
|
+
TrackInfo
|
|
9
|
+
} from "../types";
|
|
10
|
+
|
|
11
|
+
function formatAssayType(assay: string): string {
|
|
12
|
+
switch (assay) {
|
|
13
|
+
case "dnase":
|
|
14
|
+
return "DNase";
|
|
15
|
+
case "atac":
|
|
16
|
+
return "ATAC";
|
|
17
|
+
case "h3k4me3":
|
|
18
|
+
return "H3K4me3";
|
|
19
|
+
case "h3k27ac":
|
|
20
|
+
return "H3K27ac";
|
|
21
|
+
case "ctcf":
|
|
22
|
+
return "CTCF";
|
|
23
|
+
case "chromhmm":
|
|
24
|
+
return "ChromHMM";
|
|
25
|
+
default:
|
|
26
|
+
return assay;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// use to get nested data in JSON file
|
|
31
|
+
function getNestedValue(obj: any, path: string): any {
|
|
32
|
+
return path.split(".").reduce((acc, key) => acc && acc[key], obj);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getTracksByAssayAndOntology(
|
|
36
|
+
assay: string,
|
|
37
|
+
ontology: string,
|
|
38
|
+
): TrackInfo[] {
|
|
39
|
+
let res: TrackInfo[] = [];
|
|
40
|
+
const data = getNestedValue(tracksData, "tracks");
|
|
41
|
+
|
|
42
|
+
data.forEach((track: TrackInfo) => {
|
|
43
|
+
const filteredAssays =
|
|
44
|
+
track.assays?.filter((e: AssayInfo) => e.assay === assay.toLowerCase()) ||
|
|
45
|
+
[];
|
|
46
|
+
if (
|
|
47
|
+
filteredAssays.length > 0 &&
|
|
48
|
+
track.ontology === ontology.toLowerCase()
|
|
49
|
+
) {
|
|
50
|
+
res.push({
|
|
51
|
+
...track,
|
|
52
|
+
assays: filteredAssays,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return res;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Flatten TrackInfo or FuseResult into RowInfo for DataGrid display.
|
|
60
|
+
* @param track TrackInfo object or FuseResult containing information from JSON file
|
|
61
|
+
* @returns Flattened RowInfo object
|
|
62
|
+
*/
|
|
63
|
+
export function flattenIntoRow(track: TrackInfo): RowInfo {
|
|
64
|
+
const { ontology, lifeStage, sampleType, displayname } = track;
|
|
65
|
+
const { assay, experimentAccession, fileAccession } = track.assays[0];
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
ontology: capitalize(ontology),
|
|
69
|
+
lifeStage: capitalize(lifeStage),
|
|
70
|
+
sampleType: capitalize(sampleType),
|
|
71
|
+
displayname: capitalize(displayname),
|
|
72
|
+
assay: formatAssayType(assay),
|
|
73
|
+
experimentAccession,
|
|
74
|
+
fileAccession,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Fuzzy search in tracks stored in a JSON file.
|
|
80
|
+
*
|
|
81
|
+
* @param jsonStructure - Dot-separated path to the data array in the JSON structure.
|
|
82
|
+
* @param query - The search query string.
|
|
83
|
+
* @param keyWeightMap - Array of keys to search within each track object.
|
|
84
|
+
* Can look like ["name", "author"] or if weighted, [
|
|
85
|
+
{
|
|
86
|
+
name: 'title',
|
|
87
|
+
weight: 0.3
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'author',
|
|
91
|
+
weight: 0.7
|
|
92
|
+
}
|
|
93
|
+
].
|
|
94
|
+
* @param threshold - (Optional) Threshold for the fuzzy search (default is 0.5).
|
|
95
|
+
* Smaller = stricter match, larger = fuzzier since 0 is perfect match and 1 is worst match.
|
|
96
|
+
* @param limit - (Optional) Maximum number of results to return (default is 10).
|
|
97
|
+
* @returns FuseResult object containing the search results.
|
|
98
|
+
*/
|
|
99
|
+
export function searchTracks({
|
|
100
|
+
jsonStructure,
|
|
101
|
+
query,
|
|
102
|
+
keyWeightMap,
|
|
103
|
+
threshold = 0.75,
|
|
104
|
+
}: SearchTracksProps): FuseResult<TrackInfo>[] {
|
|
105
|
+
const data = getNestedValue(tracksData, jsonStructure ?? "");
|
|
106
|
+
|
|
107
|
+
const fuse = new Fuse(data, {
|
|
108
|
+
includeScore: true,
|
|
109
|
+
shouldSort: true,
|
|
110
|
+
threshold: threshold,
|
|
111
|
+
keys: keyWeightMap,
|
|
112
|
+
});
|
|
113
|
+
return fuse.search(query);
|
|
114
|
+
}
|