@weng-lab/genomebrowser-ui 0.2.4 → 0.2.6
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/Folders/biosamples/data/human.json.d.ts +109 -5
- package/dist/TrackSelect/Folders/biosamples/shared/constants.d.ts +1 -0
- package/dist/TrackSelect/Folders/biosamples/shared/types.d.ts +2 -0
- package/dist/genomebrowser-ui.es.js +396 -375
- package/dist/genomebrowser-ui.es.js.map +1 -1
- package/package.json +2 -2
- package/src/TrackSelect/Folders/biosamples/data/human.json +109 -4
- package/src/TrackSelect/Folders/biosamples/data/mark_core.go +158 -0
- package/src/TrackSelect/Folders/biosamples/shared/columns.tsx +48 -11
- package/src/TrackSelect/Folders/biosamples/shared/constants.tsx +2 -0
- package/src/TrackSelect/Folders/biosamples/shared/createFolder.ts +12 -2
- package/src/TrackSelect/Folders/biosamples/shared/types.ts +2 -0
- package/test/main.tsx +1 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
//go:build ignore
|
|
2
|
+
|
|
3
|
+
package main
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"bufio"
|
|
7
|
+
"encoding/json"
|
|
8
|
+
"fmt"
|
|
9
|
+
"os"
|
|
10
|
+
"regexp"
|
|
11
|
+
"strings"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
type Assay struct {
|
|
15
|
+
ID string `json:"id"`
|
|
16
|
+
Assay string `json:"assay"`
|
|
17
|
+
URL string `json:"url"`
|
|
18
|
+
ExperimentAccession string `json:"experimentAccession"`
|
|
19
|
+
FileAccession string `json:"fileAccession"`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Track struct {
|
|
23
|
+
Name string `json:"name"`
|
|
24
|
+
Core bool `json:"core,omitempty"`
|
|
25
|
+
Ontology string `json:"ontology"`
|
|
26
|
+
LifeStage string `json:"lifeStage"`
|
|
27
|
+
SampleType string `json:"sampleType"`
|
|
28
|
+
DisplayName string `json:"displayName"`
|
|
29
|
+
Assays []Assay `json:"assays"`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type BiosampleData struct {
|
|
33
|
+
Tracks []Track `json:"tracks"`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// CoreSample represents a sample to be marked as core, with donor ID and display name
|
|
37
|
+
type CoreSample struct {
|
|
38
|
+
DonorID string
|
|
39
|
+
DisplayName string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func main() {
|
|
43
|
+
// Extract core samples from test.txt (donor ID + display name pairs)
|
|
44
|
+
coreSamples, err := extractCoreSamples("test.txt")
|
|
45
|
+
if err != nil {
|
|
46
|
+
fmt.Fprintf(os.Stderr, "Error reading test.txt: %v\n", err)
|
|
47
|
+
os.Exit(1)
|
|
48
|
+
}
|
|
49
|
+
fmt.Printf("Found %d core samples to mark\n", len(coreSamples))
|
|
50
|
+
|
|
51
|
+
// Load human.json
|
|
52
|
+
data, err := os.ReadFile("human.json")
|
|
53
|
+
if err != nil {
|
|
54
|
+
fmt.Fprintf(os.Stderr, "Error reading human.json: %v\n", err)
|
|
55
|
+
os.Exit(1)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
var biosampleData BiosampleData
|
|
59
|
+
if err := json.Unmarshal(data, &biosampleData); err != nil {
|
|
60
|
+
fmt.Fprintf(os.Stderr, "Error parsing human.json: %v\n", err)
|
|
61
|
+
os.Exit(1)
|
|
62
|
+
}
|
|
63
|
+
fmt.Printf("Loaded %d tracks\n", len(biosampleData.Tracks))
|
|
64
|
+
|
|
65
|
+
// Mark tracks as core if they match both donor ID and display name
|
|
66
|
+
markedCount := 0
|
|
67
|
+
alreadyCore := 0
|
|
68
|
+
for i := range biosampleData.Tracks {
|
|
69
|
+
track := &biosampleData.Tracks[i]
|
|
70
|
+
for _, sample := range coreSamples {
|
|
71
|
+
// Check if track name ends with the donor ID AND display name matches (case-insensitive)
|
|
72
|
+
if strings.HasSuffix(track.Name, "_"+sample.DonorID) &&
|
|
73
|
+
strings.EqualFold(track.DisplayName, sample.DisplayName) {
|
|
74
|
+
if track.Core {
|
|
75
|
+
alreadyCore++
|
|
76
|
+
} else {
|
|
77
|
+
track.Core = true
|
|
78
|
+
markedCount++
|
|
79
|
+
}
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
fmt.Printf("Marked %d tracks as core (already core: %d)\n", markedCount, alreadyCore)
|
|
85
|
+
|
|
86
|
+
// Write to temp file
|
|
87
|
+
output, err := json.MarshalIndent(biosampleData, "", " ")
|
|
88
|
+
if err != nil {
|
|
89
|
+
fmt.Fprintf(os.Stderr, "Error marshaling JSON: %v\n", err)
|
|
90
|
+
os.Exit(1)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if err := os.WriteFile("human_updated.json", output, 0644); err != nil {
|
|
94
|
+
fmt.Fprintf(os.Stderr, "Error writing human_updated.json: %v\n", err)
|
|
95
|
+
os.Exit(1)
|
|
96
|
+
}
|
|
97
|
+
fmt.Println("Wrote updated data to human_updated.json")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// extractCoreSamples parses test.txt and extracts donor ID + display name pairs
|
|
101
|
+
// Format: "Organ \t Type \t ENCDO... \t DisplayName: (1) cCREs \t Data format"
|
|
102
|
+
func extractCoreSamples(filename string) ([]CoreSample, error) {
|
|
103
|
+
file, err := os.Open(filename)
|
|
104
|
+
if err != nil {
|
|
105
|
+
return nil, err
|
|
106
|
+
}
|
|
107
|
+
defer file.Close()
|
|
108
|
+
|
|
109
|
+
var samples []CoreSample
|
|
110
|
+
donorRe := regexp.MustCompile(`ENCDO\w+`)
|
|
111
|
+
|
|
112
|
+
scanner := bufio.NewScanner(file)
|
|
113
|
+
for scanner.Scan() {
|
|
114
|
+
line := scanner.Text()
|
|
115
|
+
if strings.TrimSpace(line) == "" {
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Find the donor ID
|
|
120
|
+
donorMatch := donorRe.FindString(line)
|
|
121
|
+
if donorMatch == "" {
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Extract display name: it's after the donor ID, before ": (1)"
|
|
126
|
+
// Split by donor ID to get the part after it
|
|
127
|
+
parts := strings.SplitN(line, donorMatch, 2)
|
|
128
|
+
if len(parts) < 2 {
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
afterDonor := parts[1]
|
|
133
|
+
// Remove leading tab/whitespace
|
|
134
|
+
afterDonor = strings.TrimLeft(afterDonor, " \t")
|
|
135
|
+
|
|
136
|
+
// Extract the display name (before ": (1)" or ":(1)")
|
|
137
|
+
displayName := afterDonor
|
|
138
|
+
if idx := strings.Index(afterDonor, ": ("); idx != -1 {
|
|
139
|
+
displayName = afterDonor[:idx]
|
|
140
|
+
} else if idx := strings.Index(afterDonor, ":("); idx != -1 {
|
|
141
|
+
displayName = afterDonor[:idx]
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
displayName = strings.TrimSpace(displayName)
|
|
145
|
+
if displayName != "" {
|
|
146
|
+
samples = append(samples, CoreSample{
|
|
147
|
+
DonorID: donorMatch,
|
|
148
|
+
DisplayName: displayName,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if err := scanner.Err(); err != nil {
|
|
154
|
+
return nil, err
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return samples, nil
|
|
158
|
+
}
|
|
@@ -1,8 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GridColDef,
|
|
3
|
+
GridRenderCellParams,
|
|
4
|
+
GridGroupNode,
|
|
5
|
+
useGridApiContext,
|
|
6
|
+
} from "@mui/x-data-grid-premium";
|
|
2
7
|
import { Stack, capitalize } from "@mui/material";
|
|
3
|
-
import
|
|
8
|
+
import Check from "@mui/icons-material/Check";
|
|
9
|
+
import { AssayIcon, ontologyTypes, assayTypes, lifeStages } from "./constants";
|
|
4
10
|
import { BiosampleRowInfo } from "./types";
|
|
5
11
|
|
|
12
|
+
function CoreCollectionCell(params: GridRenderCellParams<BiosampleRowInfo>) {
|
|
13
|
+
const apiRef = useGridApiContext();
|
|
14
|
+
|
|
15
|
+
if (params.rowNode.type !== "group") {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const groupNode = params.rowNode as GridGroupNode;
|
|
20
|
+
if (groupNode.groupingField !== "displayName") {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const childIds = groupNode.children;
|
|
25
|
+
if (!childIds || childIds.length === 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const firstChildRow = apiRef.current.getRow(
|
|
30
|
+
childIds[0],
|
|
31
|
+
) as BiosampleRowInfo | null;
|
|
32
|
+
|
|
33
|
+
if (firstChildRow?.coreCollection) {
|
|
34
|
+
return <Check color="primary" />;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
6
40
|
const displayNameCol: GridColDef<BiosampleRowInfo> = {
|
|
7
41
|
field: "displayName",
|
|
8
42
|
headerName: "Name",
|
|
@@ -94,14 +128,7 @@ const sampleTypeCol: GridColDef<BiosampleRowInfo> = {
|
|
|
94
128
|
field: "sampleType",
|
|
95
129
|
headerName: "Sample Type",
|
|
96
130
|
type: "singleSelect",
|
|
97
|
-
valueOptions:
|
|
98
|
-
"Aggregate",
|
|
99
|
-
"Tissue",
|
|
100
|
-
"Primary cell",
|
|
101
|
-
"Cell line",
|
|
102
|
-
"In vitro differentiated cells",
|
|
103
|
-
"Organoid",
|
|
104
|
-
],
|
|
131
|
+
valueOptions: ontologyTypes,
|
|
105
132
|
valueFormatter: (value) => value && capitalize(value),
|
|
106
133
|
};
|
|
107
134
|
|
|
@@ -109,10 +136,18 @@ const lifeStageCol: GridColDef<BiosampleRowInfo> = {
|
|
|
109
136
|
field: "lifeStage",
|
|
110
137
|
headerName: "Life Stage",
|
|
111
138
|
type: "singleSelect",
|
|
112
|
-
valueOptions:
|
|
139
|
+
valueOptions: lifeStages,
|
|
113
140
|
valueFormatter: (value) => value && capitalize(value),
|
|
114
141
|
};
|
|
115
142
|
|
|
143
|
+
const coreCollectionCol: GridColDef<BiosampleRowInfo> = {
|
|
144
|
+
field: "coreCollection",
|
|
145
|
+
headerName: "Core Collection",
|
|
146
|
+
type: "boolean",
|
|
147
|
+
width: 120,
|
|
148
|
+
renderCell: (params) => <CoreCollectionCell {...params} />,
|
|
149
|
+
};
|
|
150
|
+
|
|
116
151
|
const experimentCol: GridColDef<BiosampleRowInfo> = {
|
|
117
152
|
field: "experimentAccession",
|
|
118
153
|
headerName: "Experiment Accession",
|
|
@@ -132,6 +167,7 @@ const idCol: GridColDef<BiosampleRowInfo> = {
|
|
|
132
167
|
export const sortedByAssayColumns: GridColDef<BiosampleRowInfo>[] = [
|
|
133
168
|
displayNameCol,
|
|
134
169
|
sortedByAssayOntologyCol,
|
|
170
|
+
coreCollectionCol,
|
|
135
171
|
sampleTypeCol,
|
|
136
172
|
lifeStageCol,
|
|
137
173
|
sortedByAssayAssayCol,
|
|
@@ -143,6 +179,7 @@ export const sortedByAssayColumns: GridColDef<BiosampleRowInfo>[] = [
|
|
|
143
179
|
/** Default columns (ontology as top-level grouping) */
|
|
144
180
|
export const defaultColumns: GridColDef<BiosampleRowInfo>[] = [
|
|
145
181
|
defaultAssayCol,
|
|
182
|
+
coreCollectionCol,
|
|
146
183
|
sampleTypeCol,
|
|
147
184
|
lifeStageCol,
|
|
148
185
|
defaultOntologyCol,
|
|
@@ -24,9 +24,18 @@ import { BiosampleTreeItem } from "./BiosampleTreeItem";
|
|
|
24
24
|
* @returns Array of flattened BiosampleRowInfo objects, one per assay
|
|
25
25
|
*/
|
|
26
26
|
function flattenTrackIntoRows(track: BiosampleTrackInfo): BiosampleRowInfo[] {
|
|
27
|
-
const { ontology, lifeStage, sampleType, displayName } = track;
|
|
27
|
+
const { ontology, lifeStage, sampleType, displayName, core } = track;
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// Sort assays so cCRE comes first, then maintain original order for the rest
|
|
30
|
+
const sortedAssays = [...track.assays].sort((a, b) => {
|
|
31
|
+
const aIsCcre = a.assay.toLowerCase() === "ccre";
|
|
32
|
+
const bIsCcre = b.assay.toLowerCase() === "ccre";
|
|
33
|
+
if (aIsCcre && !bIsCcre) return -1;
|
|
34
|
+
if (!aIsCcre && bIsCcre) return 1;
|
|
35
|
+
return 0;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return sortedAssays.map(
|
|
30
39
|
({ id, assay, experimentAccession, fileAccession, url }) => ({
|
|
31
40
|
id,
|
|
32
41
|
ontology: capitalize(ontology),
|
|
@@ -37,6 +46,7 @@ function flattenTrackIntoRows(track: BiosampleTrackInfo): BiosampleRowInfo[] {
|
|
|
37
46
|
experimentAccession,
|
|
38
47
|
fileAccession,
|
|
39
48
|
url,
|
|
49
|
+
coreCollection: core ?? false,
|
|
40
50
|
}),
|
|
41
51
|
);
|
|
42
52
|
}
|
|
@@ -23,6 +23,7 @@ export type BiosampleTrackInfo = {
|
|
|
23
23
|
sampleType: string;
|
|
24
24
|
displayName: string;
|
|
25
25
|
assays: BiosampleAssayInfo[];
|
|
26
|
+
core?: boolean;
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -38,6 +39,7 @@ export type BiosampleRowInfo = {
|
|
|
38
39
|
experimentAccession: string;
|
|
39
40
|
fileAccession: string;
|
|
40
41
|
url: string;
|
|
42
|
+
coreCollection: boolean;
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
/**
|
package/test/main.tsx
CHANGED
|
@@ -79,7 +79,7 @@ function injectCallbacks(track: Track, callbacks: TrackCallbacks): Track {
|
|
|
79
79
|
|
|
80
80
|
function Main() {
|
|
81
81
|
const [open, setOpen] = useState(false);
|
|
82
|
-
const currentAssembly: Assembly = "
|
|
82
|
+
const currentAssembly: Assembly = "GRCh38";
|
|
83
83
|
|
|
84
84
|
const browserStore = createBrowserStoreMemo({
|
|
85
85
|
// chr7:19,695,494-19,699,803
|