@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.
@@ -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 { GridColDef } from "@mui/x-data-grid-premium";
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 { AssayIcon, ontologyTypes, assayTypes } from "./constants";
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: ["Adult", "Embryonic", "N/A"],
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,
@@ -14,6 +14,8 @@ export const assayTypes = [
14
14
  "ChromHMM",
15
15
  ];
16
16
 
17
+ export const lifeStages = ["Adult", "Embryonic", "N/A"];
18
+
17
19
  export const ontologyTypes = [
18
20
  "Aggregate",
19
21
  "Adipose",
@@ -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
- return track.assays.map(
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 = "mm10";
82
+ const currentAssembly: Assembly = "GRCh38";
83
83
 
84
84
  const browserStore = createBrowserStoreMemo({
85
85
  // chr7:19,695,494-19,699,803