@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.
Files changed (91) hide show
  1. package/.env.local +1 -1
  2. package/dist/TrackSelect/Folders/biosamples/shared/BiosampleViewSelector.d.ts +7 -0
  3. package/dist/TrackSelect/Folders/biosamples/shared/createFolder.d.ts +1 -13
  4. package/dist/TrackSelect/Folders/biosamples/shared/toTrack.d.ts +20 -0
  5. package/dist/TrackSelect/Folders/biosamples/shared/types.d.ts +4 -13
  6. package/dist/TrackSelect/Folders/genes/shared/columns.d.ts +2 -2
  7. package/dist/TrackSelect/Folders/genes/shared/createFolder.d.ts +1 -3
  8. package/dist/TrackSelect/Folders/genes/shared/toTrack.d.ts +18 -0
  9. package/dist/TrackSelect/Folders/genes/shared/types.d.ts +2 -0
  10. package/dist/TrackSelect/Folders/index.d.ts +6 -12
  11. package/dist/TrackSelect/Folders/mohd/data/human.json.d.ts +2948 -0
  12. package/dist/TrackSelect/Folders/mohd/human.d.ts +1 -0
  13. package/dist/TrackSelect/Folders/mohd/shared/MohdGroupingCell.d.ts +2 -0
  14. package/dist/TrackSelect/Folders/mohd/shared/MohdTreeItem.d.ts +3 -0
  15. package/dist/TrackSelect/Folders/mohd/shared/MohdViewSelector.d.ts +7 -0
  16. package/dist/TrackSelect/Folders/mohd/shared/columns.d.ts +5 -0
  17. package/dist/TrackSelect/Folders/mohd/shared/config.d.ts +42 -0
  18. package/dist/TrackSelect/Folders/mohd/shared/createFolder.d.ts +9 -0
  19. package/dist/TrackSelect/Folders/mohd/shared/toTrack.d.ts +9 -0
  20. package/dist/TrackSelect/Folders/mohd/shared/types.d.ts +40 -0
  21. package/dist/TrackSelect/Folders/other-tracks/shared/toTrack.d.ts +5 -0
  22. package/dist/TrackSelect/Folders/other-tracks/shared/types.d.ts +1 -0
  23. package/dist/TrackSelect/Folders/types.d.ts +23 -55
  24. package/dist/TrackSelect/TrackSelect.d.ts +10 -7
  25. package/dist/TrackSelect/TreeView/TreeViewWrapper.d.ts +1 -1
  26. package/dist/TrackSelect/buildSelectedTree.d.ts +15 -0
  27. package/dist/TrackSelect/managedTracks.d.ts +13 -0
  28. package/dist/TrackSelect/resolveFolderView.d.ts +2 -0
  29. package/dist/TrackSelect/trackContext.d.ts +5 -0
  30. package/dist/TrackSelect/types.d.ts +12 -33
  31. package/dist/genomebrowser-ui.es.js +2231 -1732
  32. package/dist/genomebrowser-ui.es.js.map +1 -1
  33. package/dist/lib.d.ts +4 -4
  34. package/dist/muiLicense.d.ts +1 -0
  35. package/package.json +6 -3
  36. package/src/TrackSelect/Dialogs/ClearDialog.tsx +3 -8
  37. package/src/TrackSelect/Dialogs/ResetDialog.tsx +5 -4
  38. package/src/TrackSelect/FolderList/FolderCard.tsx +1 -1
  39. package/src/TrackSelect/Folders/biosamples/shared/BiosampleViewSelector.tsx +33 -0
  40. package/src/TrackSelect/Folders/biosamples/shared/createFolder.ts +39 -58
  41. package/src/TrackSelect/Folders/biosamples/shared/toTrack.ts +138 -0
  42. package/src/TrackSelect/Folders/biosamples/shared/types.ts +4 -16
  43. package/src/TrackSelect/Folders/genes/shared/columns.tsx +2 -2
  44. package/src/TrackSelect/Folders/genes/shared/createFolder.ts +11 -31
  45. package/src/TrackSelect/Folders/genes/shared/toTrack.ts +59 -0
  46. package/src/TrackSelect/Folders/genes/shared/types.ts +2 -0
  47. package/src/TrackSelect/Folders/index.ts +14 -17
  48. package/src/TrackSelect/Folders/mohd/data/human.json +2945 -0
  49. package/src/TrackSelect/Folders/mohd/human.ts +10 -0
  50. package/src/TrackSelect/Folders/mohd/shared/MohdGroupingCell.tsx +68 -0
  51. package/src/TrackSelect/Folders/mohd/shared/MohdTreeItem.tsx +17 -0
  52. package/src/TrackSelect/Folders/mohd/shared/MohdViewSelector.tsx +33 -0
  53. package/src/TrackSelect/Folders/mohd/shared/columns.tsx +79 -0
  54. package/src/TrackSelect/Folders/mohd/shared/config.tsx +71 -0
  55. package/src/TrackSelect/Folders/mohd/shared/createFolder.ts +144 -0
  56. package/src/TrackSelect/Folders/mohd/shared/toTrack.ts +164 -0
  57. package/src/TrackSelect/Folders/mohd/shared/types.ts +46 -0
  58. package/src/TrackSelect/Folders/other-tracks/shared/createFolder.ts +13 -14
  59. package/src/TrackSelect/Folders/other-tracks/shared/toTrack.ts +17 -0
  60. package/src/TrackSelect/Folders/other-tracks/shared/types.ts +1 -0
  61. package/src/TrackSelect/Folders/types.ts +26 -69
  62. package/src/TrackSelect/TrackSelect.tsx +301 -257
  63. package/src/TrackSelect/TreeView/CustomTreeItem.tsx +9 -9
  64. package/src/TrackSelect/TreeView/TreeViewWrapper.tsx +84 -6
  65. package/src/TrackSelect/buildSelectedTree.ts +145 -0
  66. package/src/TrackSelect/managedTracks.ts +92 -0
  67. package/src/TrackSelect/resolveFolderView.ts +20 -0
  68. package/src/TrackSelect/trackContext.ts +9 -0
  69. package/src/TrackSelect/types.ts +14 -39
  70. package/src/lib.ts +13 -7
  71. package/src/muiLicense.ts +9 -0
  72. package/src/vite-env.d.ts +9 -0
  73. package/test/TrackSelect.test.tsx +435 -0
  74. package/test/main.tsx +36 -352
  75. package/test/mocks/logo-test.tsx +11 -0
  76. package/test/mohdDisplay.test.tsx +45 -0
  77. package/test/startup.test.ts +206 -0
  78. package/test/trackSelectState.test.ts +176 -0
  79. package/vite.config.ts +1 -0
  80. package/vitest.config.ts +20 -0
  81. package/dist/TrackSelect/Folders/biosamples/shared/AssayToggle.d.ts +0 -18
  82. package/dist/TrackSelect/Folders/biosamples/shared/treeBuilder.d.ts +0 -28
  83. package/dist/TrackSelect/Folders/genes/shared/treeBuilder.d.ts +0 -13
  84. package/dist/TrackSelect/Folders/other-tracks/shared/treeBuilder.d.ts +0 -4
  85. package/dist/TrackSelect/store.d.ts +0 -4
  86. package/src/TrackSelect/Folders/NEW.md +0 -929
  87. package/src/TrackSelect/Folders/biosamples/shared/AssayToggle.tsx +0 -78
  88. package/src/TrackSelect/Folders/biosamples/shared/treeBuilder.ts +0 -224
  89. package/src/TrackSelect/Folders/genes/shared/treeBuilder.ts +0 -45
  90. package/src/TrackSelect/Folders/other-tracks/shared/treeBuilder.ts +0 -34
  91. 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 { createSelectionStore, SelectionStoreInstance };
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.3.6",
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.3"
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: "#0c184a",
30
- color: "white",
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: "#0c184a",
22
- color: "white",
21
+ bgcolor: "primary.main",
22
+ color: "primary.contrastText",
23
23
  fontWeight: "bold",
24
24
  }}
25
25
  >
26
- Reset to Default
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 default?
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.rowById.size.toLocaleString()} tracks available
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 { AssayToggle } from "./AssayToggle";
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
- * Flattens TrackInfo into RowInfo objects for DataGrid display.
21
- * Each track can have multiple assays, so this creates one row per assay.
22
- *
23
- * @param track - TrackInfo object from JSON data
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
- // Sort assays so cCRE comes first, then maintain original order for the rest
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
- * Transforms raw JSON data into flattened rows and a lookup map.
68
- * Prefixes each row ID with the folder ID to ensure uniqueness across folders.
69
- *
70
- * @param data - Raw biosample data from JSON file
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
- * Factory function that creates a FolderDefinition for biosample data.
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 { rowById } = transformData(data);
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
- rowById,
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
- // Build tree for selected items panel
128
- buildTree: (selectedIds, rowById) =>
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
- * Assay information from the JSON data.
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
- * Row format for DataGrid (flattened from TrackInfo).
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 unique identifier
40
+ * Leaf field - the raw track ID (without folder prefix) used as the tree view leaf label
41
41
  */
42
- export const defaultLeafField = "id";
42
+ export const defaultLeafField = "trackId";
@@ -5,32 +5,20 @@ import {
5
5
  defaultGroupingModel,
6
6
  defaultLeafField,
7
7
  } from "./columns";
8
- import { buildTreeView } from "./treeBuilder";
8
+ import { createGeneTrack } from "./toTrack";
9
9
 
10
- /**
11
- * Transforms a single track from JSON into a row for DataGrid
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
- * Transforms raw JSON data into rows and lookup map
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 { rowById } = transformData(data);
36
+ const rows = transformData(id, data);
51
37
 
52
38
  return {
53
39
  id,
54
40
  label,
55
41
  description,
56
- rowById,
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
+ }
@@ -19,6 +19,8 @@ export type GeneTrackInfo = {
19
19
  */
20
20
  export type GeneRowInfo = {
21
21
  id: string;
22
+ /** The raw track ID without the folder prefix, used as the tree view leaf label */
23
+ trackId: string;
22
24
  displayName: string;
23
25
  versions: number[];
24
26
  };