@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
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
# LLM Implementation Guide: Adding a New Data Folder to TrackSelect
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This guide is optimized for LLM assistants to implement a new data folder in the TrackSelect component. When the user provides JSON data files, follow this guide step-by-step to create a fully functional folder.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
**Before starting, obtain from the user:**
|
|
12
|
+
|
|
13
|
+
1. **JSON data file(s)** - The track data for the folder
|
|
14
|
+
2. **Folder name** - What to call this folder (e.g., "Genes", "Variants", "Regulatory Elements")
|
|
15
|
+
3. **Assembly(ies)** - Which genome assemblies to support (GRCh38, mm10, or both)
|
|
16
|
+
4. **Grouping hierarchy** - How should data be organized in the UI (e.g., Category → Subcategory → Item)
|
|
17
|
+
|
|
18
|
+
**Analyze the JSON data to determine:**
|
|
19
|
+
|
|
20
|
+
- Available fields for grouping
|
|
21
|
+
- Unique identifier field
|
|
22
|
+
- Display name field
|
|
23
|
+
- URL field for track files
|
|
24
|
+
- Any fields that need special formatting or icons
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Implementation Steps
|
|
29
|
+
|
|
30
|
+
### Step 1: Analyze the Data Structure
|
|
31
|
+
|
|
32
|
+
**ACTION:** Examine the provided JSON file to understand:
|
|
33
|
+
|
|
34
|
+
1. Top-level structure (array of tracks? nested objects?)
|
|
35
|
+
2. Fields available for grouping (2-3 levels recommended)
|
|
36
|
+
3. Leaf-level items (individual tracks)
|
|
37
|
+
4. Any fields that need transformation/formatting
|
|
38
|
+
|
|
39
|
+
**Example JSON structure to look for:**
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"tracks": [
|
|
44
|
+
{
|
|
45
|
+
"id": "track-001",
|
|
46
|
+
"name": "...",
|
|
47
|
+
"category": "...",
|
|
48
|
+
"subcategory": "...",
|
|
49
|
+
"url": "https://...",
|
|
50
|
+
"metadata": {...}
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Step 2: Create Directory Structure
|
|
57
|
+
|
|
58
|
+
**PATH:** `packages/ui/src/TrackSelect/folders/{folder-name}/`
|
|
59
|
+
|
|
60
|
+
**CREATE these directories and files:**
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
{folder-name}/
|
|
64
|
+
├── data/
|
|
65
|
+
│ └── human.json # (and/or mouse.json)
|
|
66
|
+
├── shared/
|
|
67
|
+
│ ├── types.ts # REQUIRED
|
|
68
|
+
│ ├── columns.tsx # REQUIRED
|
|
69
|
+
│ ├── treeBuilder.ts # REQUIRED
|
|
70
|
+
│ ├── createFolder.ts # REQUIRED
|
|
71
|
+
│ ├── constants.tsx # OPTIONAL (if you have icons/colors/mappings)
|
|
72
|
+
│ ├── GroupingCell.tsx # OPTIONAL (for custom rendering)
|
|
73
|
+
│ ├── TreeItem.tsx # OPTIONAL (for custom tree items)
|
|
74
|
+
│ └── Toolbar.tsx # OPTIONAL (for view toggles/filters)
|
|
75
|
+
└── human.ts # REQUIRED (and/or mouse.ts)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Step 3: Define TypeScript Types
|
|
79
|
+
|
|
80
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/types.ts`
|
|
81
|
+
|
|
82
|
+
**TEMPLATE:**
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
/**
|
|
86
|
+
* Types for {folder-name} folder data
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Track information from the JSON data (raw structure)
|
|
91
|
+
*/
|
|
92
|
+
export type {YourName}TrackInfo = {
|
|
93
|
+
// Map exactly to your JSON structure
|
|
94
|
+
id: string;
|
|
95
|
+
name: string;
|
|
96
|
+
// ... all other fields from JSON
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Row format for DataGrid (flattened structure)
|
|
101
|
+
* - One row per selectable item
|
|
102
|
+
* - Include all fields needed for display and grouping
|
|
103
|
+
*/
|
|
104
|
+
export type {YourName}RowInfo = {
|
|
105
|
+
id: string; // Unique identifier (REQUIRED)
|
|
106
|
+
groupField1: string; // Top-level grouping field
|
|
107
|
+
groupField2?: string; // Second-level grouping field (optional)
|
|
108
|
+
displayName: string; // Display name
|
|
109
|
+
url: string; // Track URL
|
|
110
|
+
// ... other display fields
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Structure of the JSON data file
|
|
115
|
+
*/
|
|
116
|
+
export type {YourName}DataFile = {
|
|
117
|
+
tracks: {YourName}TrackInfo[];
|
|
118
|
+
};
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**RULES:**
|
|
122
|
+
|
|
123
|
+
- RowInfo must have an `id` field
|
|
124
|
+
- Include all fields you want to display or group by
|
|
125
|
+
- Keep field names camelCase
|
|
126
|
+
- Add JSDoc comments for clarity
|
|
127
|
+
|
|
128
|
+
**REFERENCE:** See `biosamples/shared/types.ts` for a real example with nested assay data.
|
|
129
|
+
|
|
130
|
+
### Step 4: Create Column Definitions
|
|
131
|
+
|
|
132
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/columns.tsx`
|
|
133
|
+
|
|
134
|
+
**TEMPLATE:**
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { GridColDef } from "@mui/x-data-grid-premium";
|
|
138
|
+
import { Stack, capitalize } from "@mui/material";
|
|
139
|
+
import { {YourName}RowInfo } from "./types";
|
|
140
|
+
|
|
141
|
+
// Define each column
|
|
142
|
+
const groupField1Col: GridColDef<{YourName}RowInfo> = {
|
|
143
|
+
field: "groupField1",
|
|
144
|
+
headerName: "Group 1",
|
|
145
|
+
type: "singleSelect", // Optional: for filtering
|
|
146
|
+
valueOptions: [...], // Optional: list of possible values
|
|
147
|
+
renderCell: (params) => {
|
|
148
|
+
// Custom rendering for group rows
|
|
149
|
+
if (params.rowNode.type === "group") {
|
|
150
|
+
return <div><b>{params.value}</b></div>;
|
|
151
|
+
}
|
|
152
|
+
// Rendering for leaf rows
|
|
153
|
+
return <div>{params.value}</div>;
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const displayNameCol: GridColDef<{YourName}RowInfo> = {
|
|
158
|
+
field: "displayName",
|
|
159
|
+
headerName: "Name",
|
|
160
|
+
valueFormatter: (value) => value && capitalize(value),
|
|
161
|
+
maxWidth: 300,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// ... define all columns
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Default columns for DataGrid
|
|
168
|
+
* Order matters: left to right display order
|
|
169
|
+
*/
|
|
170
|
+
export const defaultColumns: GridColDef<{YourName}RowInfo>[] = [
|
|
171
|
+
groupField1Col,
|
|
172
|
+
groupField2Col,
|
|
173
|
+
displayNameCol,
|
|
174
|
+
// ... other columns
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Default grouping model - defines hierarchy
|
|
179
|
+
* - First field = top-level groups
|
|
180
|
+
* - Last field = bottom-level groups (before leaf)
|
|
181
|
+
*/
|
|
182
|
+
export const defaultGroupingModel = ["groupField1", "groupField2"];
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Leaf field - the lowest level field that makes each row unique
|
|
186
|
+
* This is typically NOT in the groupingModel
|
|
187
|
+
*/
|
|
188
|
+
export const defaultLeafField = "id";
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**RULES:**
|
|
192
|
+
|
|
193
|
+
- Column order in array = display order in UI
|
|
194
|
+
- `groupingModel` should match your desired hierarchy (2-3 levels recommended)
|
|
195
|
+
- `leafField` should uniquely identify each row
|
|
196
|
+
- Use `renderCell` for custom rendering in groups vs leaf rows
|
|
197
|
+
- Always check `params.rowNode.type === "group"` before custom rendering
|
|
198
|
+
|
|
199
|
+
**REFERENCE:** See `biosamples/shared/columns.tsx` for examples of:
|
|
200
|
+
|
|
201
|
+
- Custom icon rendering (`AssayIcon`)
|
|
202
|
+
- Multiple column sets for view toggling
|
|
203
|
+
- Value formatters and type definitions
|
|
204
|
+
|
|
205
|
+
**OPTIONAL - Multiple View Modes:**
|
|
206
|
+
If you want a toggle (like the biosamples "Sort by assay"), create additional column sets:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
export const alternateColumns: GridColDef<{YourName}RowInfo>[] = [...];
|
|
210
|
+
export const alternateGroupingModel = ["differentField", "groupField1"];
|
|
211
|
+
export const alternateLeafField = "id";
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Step 5: Create Tree Builder
|
|
215
|
+
|
|
216
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/treeBuilder.ts`
|
|
217
|
+
|
|
218
|
+
**TEMPLATE:**
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { TreeViewBaseItem } from "@mui/x-tree-view";
|
|
222
|
+
import { ExtendedTreeItemProps } from "../../../types";
|
|
223
|
+
import { {YourName}RowInfo } from "./types";
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Builds hierarchical tree for the TreeView panel (selected items)
|
|
227
|
+
* Hierarchy should match your groupingModel
|
|
228
|
+
*
|
|
229
|
+
* @param selectedIds - Array of selected row IDs
|
|
230
|
+
* @param rowById - Map of row ID to row data
|
|
231
|
+
* @param rootLabel - Label for the root node
|
|
232
|
+
* @returns Tree structure for RichTreeView
|
|
233
|
+
*/
|
|
234
|
+
export function buildTreeView(
|
|
235
|
+
selectedIds: string[],
|
|
236
|
+
rowById: Map<string, {YourName}RowInfo>,
|
|
237
|
+
rootLabel: string = "{Your Folder Name}"
|
|
238
|
+
): TreeViewBaseItem<ExtendedTreeItemProps>[] {
|
|
239
|
+
// Root node
|
|
240
|
+
const root: TreeViewBaseItem<ExtendedTreeItemProps> = {
|
|
241
|
+
id: "root",
|
|
242
|
+
label: rootLabel,
|
|
243
|
+
icon: "folder",
|
|
244
|
+
children: [],
|
|
245
|
+
allExpAccessions: [],
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// Maps to track nodes at each level
|
|
249
|
+
const level1Map = new Map<string, TreeViewBaseItem<ExtendedTreeItemProps>>();
|
|
250
|
+
const level2Map = new Map<string, TreeViewBaseItem<ExtendedTreeItemProps>>();
|
|
251
|
+
|
|
252
|
+
// Get selected rows
|
|
253
|
+
const selectedRows = selectedIds.reduce<{YourName}RowInfo[]>((acc, id) => {
|
|
254
|
+
const row = rowById.get(id);
|
|
255
|
+
if (row) acc.push(row);
|
|
256
|
+
return acc;
|
|
257
|
+
}, []);
|
|
258
|
+
|
|
259
|
+
// Build tree hierarchy
|
|
260
|
+
selectedRows.forEach((row) => {
|
|
261
|
+
// Level 1: groupField1
|
|
262
|
+
const level1Key = row.groupField1;
|
|
263
|
+
let level1Node = level1Map.get(level1Key);
|
|
264
|
+
if (!level1Node) {
|
|
265
|
+
level1Node = {
|
|
266
|
+
id: level1Key,
|
|
267
|
+
label: row.groupField1,
|
|
268
|
+
icon: "removeable",
|
|
269
|
+
children: [],
|
|
270
|
+
allExpAccessions: [],
|
|
271
|
+
};
|
|
272
|
+
level1Map.set(level1Key, level1Node);
|
|
273
|
+
root.children!.push(level1Node);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Level 2: groupField2 (if applicable)
|
|
277
|
+
const level2Key = `${row.groupField1}-${row.groupField2}`;
|
|
278
|
+
let level2Node = level2Map.get(level2Key);
|
|
279
|
+
if (!level2Node) {
|
|
280
|
+
level2Node = {
|
|
281
|
+
id: level2Key,
|
|
282
|
+
label: row.groupField2,
|
|
283
|
+
icon: "removeable",
|
|
284
|
+
children: [],
|
|
285
|
+
allExpAccessions: [],
|
|
286
|
+
};
|
|
287
|
+
level1Node.children!.push(level2Node);
|
|
288
|
+
level2Map.set(level2Key, level2Node);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Leaf: individual track
|
|
292
|
+
const leafNode: TreeViewBaseItem<ExtendedTreeItemProps> = {
|
|
293
|
+
id: row.id,
|
|
294
|
+
label: row.displayName,
|
|
295
|
+
icon: "removeable",
|
|
296
|
+
children: [],
|
|
297
|
+
allExpAccessions: [row.id],
|
|
298
|
+
};
|
|
299
|
+
level2Node.children!.push(leafNode);
|
|
300
|
+
|
|
301
|
+
// Propagate IDs up the tree
|
|
302
|
+
level1Node.allExpAccessions!.push(row.id);
|
|
303
|
+
level2Node.allExpAccessions!.push(row.id);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return [root];
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**RULES:**
|
|
311
|
+
|
|
312
|
+
- Tree structure must match your `groupingModel` hierarchy
|
|
313
|
+
- Each node needs: `id`, `label`, `icon`, `children`, `allExpAccessions`
|
|
314
|
+
- `icon: "removeable"` enables the remove button on tree items
|
|
315
|
+
- `allExpAccessions` tracks all descendant leaf IDs (for bulk removal)
|
|
316
|
+
- Leaf nodes should have `allExpAccessions: [row.id]`
|
|
317
|
+
|
|
318
|
+
**REFERENCE:** See `biosamples/shared/treeBuilder.ts` for:
|
|
319
|
+
|
|
320
|
+
- `buildTreeView()` - 2-level hierarchy (Ontology → DisplayName)
|
|
321
|
+
- `buildSortedAssayTreeView()` - 3-level hierarchy (Assay → Ontology → DisplayName)
|
|
322
|
+
- Optional sorting using predefined type arrays
|
|
323
|
+
|
|
324
|
+
### Step 6: Create Folder Factory
|
|
325
|
+
|
|
326
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/createFolder.ts`
|
|
327
|
+
|
|
328
|
+
**TEMPLATE:**
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import { capitalize } from "@mui/material";
|
|
332
|
+
import { FolderDefinition } from "../../types";
|
|
333
|
+
import { {YourName}DataFile, {YourName}RowInfo, {YourName}TrackInfo } from "./types";
|
|
334
|
+
import { defaultColumns, defaultGroupingModel, defaultLeafField } from "./columns";
|
|
335
|
+
import { buildTreeView } from "./treeBuilder";
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Transforms a single track from JSON into row(s) for DataGrid
|
|
339
|
+
* Flatten any nested structures here
|
|
340
|
+
*/
|
|
341
|
+
function flattenTrackIntoRows(track: {YourName}TrackInfo): {YourName}RowInfo[] {
|
|
342
|
+
// If your data is already flat (one track = one row):
|
|
343
|
+
return [{
|
|
344
|
+
id: track.id,
|
|
345
|
+
groupField1: capitalize(track.groupField1),
|
|
346
|
+
groupField2: capitalize(track.groupField2),
|
|
347
|
+
displayName: track.name,
|
|
348
|
+
url: track.url,
|
|
349
|
+
// ... map all other fields
|
|
350
|
+
}];
|
|
351
|
+
|
|
352
|
+
// If your data is nested (one track = multiple rows):
|
|
353
|
+
// return track.items.map(item => ({
|
|
354
|
+
// id: item.id,
|
|
355
|
+
// groupField1: capitalize(track.category),
|
|
356
|
+
// displayName: item.name,
|
|
357
|
+
// url: item.url,
|
|
358
|
+
// // ...
|
|
359
|
+
// }));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Transforms raw JSON data into flattened rows and lookup map
|
|
364
|
+
*/
|
|
365
|
+
function transformData(data: {YourName}DataFile): {
|
|
366
|
+
rows: {YourName}RowInfo[];
|
|
367
|
+
rowById: Map<string, {YourName}RowInfo>;
|
|
368
|
+
} {
|
|
369
|
+
const rows = data.tracks.flatMap(flattenTrackIntoRows);
|
|
370
|
+
const rowById = new Map<string, {YourName}RowInfo>(
|
|
371
|
+
rows.map((row) => [row.id, row])
|
|
372
|
+
);
|
|
373
|
+
return { rows, rowById };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export interface Create{YourName}FolderOptions {
|
|
377
|
+
id: string;
|
|
378
|
+
label: string;
|
|
379
|
+
data: {YourName}DataFile;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Factory function that creates a FolderDefinition
|
|
384
|
+
*/
|
|
385
|
+
export function create{YourName}Folder(
|
|
386
|
+
options: Create{YourName}FolderOptions
|
|
387
|
+
): FolderDefinition<{YourName}RowInfo> {
|
|
388
|
+
const { id, label, data } = options;
|
|
389
|
+
const { rowById } = transformData(data);
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
id,
|
|
393
|
+
label,
|
|
394
|
+
rowById,
|
|
395
|
+
getRowId: (row) => row.id,
|
|
396
|
+
|
|
397
|
+
// Default view configuration
|
|
398
|
+
columns: defaultColumns,
|
|
399
|
+
groupingModel: defaultGroupingModel,
|
|
400
|
+
leafField: defaultLeafField,
|
|
401
|
+
|
|
402
|
+
// Tree builder for selected items panel
|
|
403
|
+
buildTree: (selectedIds, rowById) =>
|
|
404
|
+
buildTreeView(selectedIds, rowById, label),
|
|
405
|
+
|
|
406
|
+
// Optional: Custom components (uncomment if created)
|
|
407
|
+
// ToolbarExtras: {YourName}Toolbar,
|
|
408
|
+
// GroupingCellComponent: {YourName}GroupingCell,
|
|
409
|
+
// TreeItemComponent: {YourName}TreeItem,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**RULES:**
|
|
415
|
+
|
|
416
|
+
- `flattenTrackIntoRows` must return an array (even if just one item)
|
|
417
|
+
- Every row must have a unique `id`
|
|
418
|
+
- Use `capitalize()` for consistent formatting
|
|
419
|
+
- `rowById` Map is the single source of truth
|
|
420
|
+
|
|
421
|
+
**REFERENCE:** See `biosamples/shared/createFolder.ts` for:
|
|
422
|
+
|
|
423
|
+
- One-to-many flattening (one track with multiple assays)
|
|
424
|
+
- Using `formatAssayType()` for data normalization
|
|
425
|
+
|
|
426
|
+
### Step 7: Create Assembly-Specific Export
|
|
427
|
+
|
|
428
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/human.ts`
|
|
429
|
+
|
|
430
|
+
**TEMPLATE:**
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import { create{YourName}Folder } from "./shared/createFolder";
|
|
434
|
+
import humanData from "./data/human.json";
|
|
435
|
+
import { {YourName}DataFile } from "./shared/types";
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* {Description of what this folder contains}
|
|
439
|
+
*
|
|
440
|
+
* For GRCh38 assembly.
|
|
441
|
+
*/
|
|
442
|
+
export const human{YourName}Folder = create{YourName}Folder({
|
|
443
|
+
id: "human-{folder-name}",
|
|
444
|
+
label: "{Display Label}",
|
|
445
|
+
data: humanData as {YourName}DataFile,
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**For mouse (if applicable):**
|
|
450
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/mouse.ts`
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import { create{YourName}Folder } from "./shared/createFolder";
|
|
454
|
+
import mouseData from "./data/mouse.json";
|
|
455
|
+
import { {YourName}DataFile } from "./shared/types";
|
|
456
|
+
|
|
457
|
+
export const mouse{YourName}Folder = create{YourName}Folder({
|
|
458
|
+
id: "mouse-{folder-name}",
|
|
459
|
+
label: "{Display Label}",
|
|
460
|
+
data: mouseData as {YourName}DataFile,
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**REFERENCE:** See `biosamples/human.ts` and `biosamples/mouse.ts` for minimal assembly-specific exports.
|
|
465
|
+
|
|
466
|
+
### Step 8: Register the Folder
|
|
467
|
+
|
|
468
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/index.ts`
|
|
469
|
+
|
|
470
|
+
**ACTION:** Update the imports and foldersByAssembly export:
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
import { Assembly, FolderDefinition } from "./types";
|
|
474
|
+
import { humanBiosamplesFolder } from "./biosamples/human";
|
|
475
|
+
import { mouseBiosamplesFolder } from "./biosamples/mouse";
|
|
476
|
+
import { human{YourName}Folder } from "./{folder-name}/human"; // ADD THIS
|
|
477
|
+
|
|
478
|
+
export {
|
|
479
|
+
type Assembly,
|
|
480
|
+
type FolderDefinition,
|
|
481
|
+
type FolderRuntimeConfig,
|
|
482
|
+
} from "./types";
|
|
483
|
+
|
|
484
|
+
export const foldersByAssembly: Record<Assembly, FolderDefinition[]> = {
|
|
485
|
+
GRCh38: [humanBiosamplesFolder, human{YourName}Folder], // ADD YOUR FOLDER
|
|
486
|
+
mm10: [mouseBiosamplesFolder],
|
|
487
|
+
};
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Optional: Custom Components
|
|
493
|
+
|
|
494
|
+
### Optional 1: Constants and Icons
|
|
495
|
+
|
|
496
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/constants.tsx`
|
|
497
|
+
|
|
498
|
+
**USE WHEN:** You have categories that need color-coding or special icons
|
|
499
|
+
|
|
500
|
+
**TEMPLATE:**
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
import { Box } from "@mui/material";
|
|
504
|
+
|
|
505
|
+
// List of all category values (for ordering and validation)
|
|
506
|
+
export const categoryTypes = ["Category1", "Category2", "Category3"];
|
|
507
|
+
|
|
508
|
+
// Color mapping for categories
|
|
509
|
+
export const categoryColorMap: { [key: string]: string } = {
|
|
510
|
+
Category1: "#ff0000",
|
|
511
|
+
Category2: "#00ff00",
|
|
512
|
+
Category3: "#0000ff",
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Creates icon for a category
|
|
517
|
+
*/
|
|
518
|
+
export function CategoryIcon(type: string) {
|
|
519
|
+
const color = categoryColorMap[type];
|
|
520
|
+
return (
|
|
521
|
+
<Box
|
|
522
|
+
sx={{
|
|
523
|
+
width: 12,
|
|
524
|
+
height: 12,
|
|
525
|
+
borderRadius: "20%",
|
|
526
|
+
bgcolor: color,
|
|
527
|
+
}}
|
|
528
|
+
/>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Format category names from JSON to display format
|
|
534
|
+
*/
|
|
535
|
+
const categoryJsonToDisplay: Record<string, string> = {
|
|
536
|
+
cat1: "Category1",
|
|
537
|
+
cat2: "Category2",
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
export function formatCategoryType(jsonKey: string): string {
|
|
541
|
+
return categoryJsonToDisplay[jsonKey.toLowerCase()] ?? jsonKey;
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**REFERENCE:** See `biosamples/shared/constants.tsx` for:
|
|
546
|
+
|
|
547
|
+
- Color mapping for assay types
|
|
548
|
+
- `AssayIcon()` component
|
|
549
|
+
- `formatAssayType()` normalization function
|
|
550
|
+
|
|
551
|
+
### Optional 2: Custom Grouping Cell
|
|
552
|
+
|
|
553
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/GroupingCell.tsx`
|
|
554
|
+
|
|
555
|
+
**USE WHEN:** You want custom rendering in the DataGrid (icons, special formatting, etc.)
|
|
556
|
+
|
|
557
|
+
**TEMPLATE:**
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
import { Stack, Tooltip, Box, IconButton } from "@mui/material";
|
|
561
|
+
import { GridRenderCellParams, useGridApiContext, GridGroupNode } from "@mui/x-data-grid-premium";
|
|
562
|
+
import { CategoryIcon } from "./constants"; // If you have icons
|
|
563
|
+
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
|
564
|
+
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
565
|
+
|
|
566
|
+
export default function {YourName}GroupingCell(params: GridRenderCellParams) {
|
|
567
|
+
const apiRef = useGridApiContext();
|
|
568
|
+
const isGroup = params.rowNode.type === "group";
|
|
569
|
+
const groupNode = params.rowNode as GridGroupNode;
|
|
570
|
+
const isExpanded = isGroup ? groupNode.childrenExpanded : false;
|
|
571
|
+
const groupingField = isGroup ? groupNode.groupingField : null;
|
|
572
|
+
const depth = params.rowNode.depth ?? 0;
|
|
573
|
+
|
|
574
|
+
const handleExpandClick = (e: React.MouseEvent) => {
|
|
575
|
+
e.stopPropagation();
|
|
576
|
+
apiRef.current.setRowChildrenExpansion(params.id, !isExpanded);
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
const renderContent = () => {
|
|
580
|
+
const value = String(params.value ?? "");
|
|
581
|
+
|
|
582
|
+
// Custom rendering for specific grouping field with icons
|
|
583
|
+
if (isGroup && groupingField === "categoryField") {
|
|
584
|
+
return (
|
|
585
|
+
<Stack direction="row" spacing={1} alignItems="center" sx={{ flex: 1, overflow: "hidden" }}>
|
|
586
|
+
<CategoryIcon value={value} />
|
|
587
|
+
<Tooltip title={value} placement="top-start" enterDelay={500}>
|
|
588
|
+
<Box sx={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontWeight: "bold" }}>
|
|
589
|
+
{params.formattedValue}
|
|
590
|
+
</Box>
|
|
591
|
+
</Tooltip>
|
|
592
|
+
</Stack>
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Default rendering for other groups
|
|
597
|
+
if (isGroup) {
|
|
598
|
+
return (
|
|
599
|
+
<Tooltip title={value} placement="top-start" enterDelay={500}>
|
|
600
|
+
<Box sx={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1, fontWeight: "bold" }}>
|
|
601
|
+
{params.formattedValue}
|
|
602
|
+
</Box>
|
|
603
|
+
</Tooltip>
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Leaf row rendering
|
|
608
|
+
return (
|
|
609
|
+
<Tooltip title={value} placement="top-start" enterDelay={500}>
|
|
610
|
+
<Box sx={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1 }}>
|
|
611
|
+
{params.formattedValue}
|
|
612
|
+
</Box>
|
|
613
|
+
</Tooltip>
|
|
614
|
+
);
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
const indentLevel = depth * 2;
|
|
618
|
+
|
|
619
|
+
return (
|
|
620
|
+
<Box sx={{ display: "flex", alignItems: "center", width: "100%", ml: indentLevel }}>
|
|
621
|
+
{isGroup && (
|
|
622
|
+
<IconButton size="small" onClick={handleExpandClick} sx={{ mr: 0.5 }}>
|
|
623
|
+
{isExpanded ? <ExpandMoreIcon fontSize="small" /> : <ChevronRightIcon fontSize="small" />}
|
|
624
|
+
</IconButton>
|
|
625
|
+
)}
|
|
626
|
+
{renderContent()}
|
|
627
|
+
</Box>
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**Then update createFolder.ts:**
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
import {YourName}GroupingCell from "./GroupingCell";
|
|
636
|
+
|
|
637
|
+
export function create{YourName}Folder(...) {
|
|
638
|
+
return {
|
|
639
|
+
// ...
|
|
640
|
+
GroupingCellComponent: {YourName}GroupingCell, // ADD THIS
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**REFERENCE:** See `biosamples/shared/BiosampleGroupingCell.tsx` for:
|
|
646
|
+
|
|
647
|
+
- Expand/collapse functionality
|
|
648
|
+
- Icon rendering based on grouping field
|
|
649
|
+
- Tooltip support with text truncation
|
|
650
|
+
- Proper indentation based on depth
|
|
651
|
+
|
|
652
|
+
### Optional 3: Custom Tree Item
|
|
653
|
+
|
|
654
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/TreeItem.tsx`
|
|
655
|
+
|
|
656
|
+
**USE WHEN:** You want custom icons or rendering in the TreeView
|
|
657
|
+
|
|
658
|
+
**TEMPLATE:**
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
import React from "react";
|
|
662
|
+
import { CustomTreeItem } from "../../../TreeView/CustomTreeItem";
|
|
663
|
+
import { CustomTreeItemProps } from "../../../types";
|
|
664
|
+
import { CategoryIcon } from "./constants";
|
|
665
|
+
|
|
666
|
+
export const {YourName}TreeItem = React.forwardRef<HTMLLIElement, CustomTreeItemProps>(
|
|
667
|
+
function {YourName}TreeItem(props, ref) {
|
|
668
|
+
return <CustomTreeItem {...props} ref={ref} renderIcon={CategoryIcon} />;
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
**Then update createFolder.ts:**
|
|
674
|
+
|
|
675
|
+
```typescript
|
|
676
|
+
import { {YourName}TreeItem } from "./TreeItem";
|
|
677
|
+
|
|
678
|
+
export function create{YourName}Folder(...) {
|
|
679
|
+
return {
|
|
680
|
+
// ...
|
|
681
|
+
TreeItemComponent: {YourName}TreeItem, // ADD THIS
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
**REFERENCE:** See `biosamples/shared/BiosampleTreeItem.tsx` - a simple wrapper that passes `AssayIcon` as the icon renderer.
|
|
687
|
+
|
|
688
|
+
### Optional 4: Toolbar with View Toggle
|
|
689
|
+
|
|
690
|
+
**FILE:** `packages/ui/src/TrackSelect/folders/{folder-name}/shared/Toolbar.tsx`
|
|
691
|
+
|
|
692
|
+
**USE WHEN:** You want multiple view modes (like "Sort by assay")
|
|
693
|
+
|
|
694
|
+
**TEMPLATE:**
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
697
|
+
import { FormControlLabel, Switch } from "@mui/material";
|
|
698
|
+
import { useState } from "react";
|
|
699
|
+
import { FolderRuntimeConfig } from "../../types";
|
|
700
|
+
import {
|
|
701
|
+
defaultColumns,
|
|
702
|
+
defaultGroupingModel,
|
|
703
|
+
defaultLeafField,
|
|
704
|
+
alternateColumns,
|
|
705
|
+
alternateGroupingModel,
|
|
706
|
+
alternateLeafField,
|
|
707
|
+
} from "./columns";
|
|
708
|
+
|
|
709
|
+
export interface {YourName}ToolbarProps {
|
|
710
|
+
updateConfig: (partial: Partial<FolderRuntimeConfig>) => void;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export function {YourName}Toolbar({ updateConfig }: {YourName}ToolbarProps) {
|
|
714
|
+
const [alternateView, setAlternateView] = useState(false);
|
|
715
|
+
|
|
716
|
+
const handleToggle = () => {
|
|
717
|
+
const newValue = !alternateView;
|
|
718
|
+
setAlternateView(newValue);
|
|
719
|
+
|
|
720
|
+
if (newValue) {
|
|
721
|
+
updateConfig({
|
|
722
|
+
columns: alternateColumns,
|
|
723
|
+
groupingModel: alternateGroupingModel,
|
|
724
|
+
leafField: alternateLeafField,
|
|
725
|
+
});
|
|
726
|
+
} else {
|
|
727
|
+
updateConfig({
|
|
728
|
+
columns: defaultColumns,
|
|
729
|
+
groupingModel: defaultGroupingModel,
|
|
730
|
+
leafField: defaultLeafField,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
return (
|
|
736
|
+
<FormControlLabel
|
|
737
|
+
sx={{ display: "flex", justifyContent: "flex-end" }}
|
|
738
|
+
value="Alternate View"
|
|
739
|
+
control={<Switch color="primary" checked={alternateView} onChange={handleToggle} />}
|
|
740
|
+
label="Alternate View"
|
|
741
|
+
labelPlacement="end"
|
|
742
|
+
/>
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
**Then update createFolder.ts:**
|
|
748
|
+
|
|
749
|
+
```typescript
|
|
750
|
+
import { {YourName}Toolbar } from "./Toolbar";
|
|
751
|
+
|
|
752
|
+
export function create{YourName}Folder(...) {
|
|
753
|
+
return {
|
|
754
|
+
// ...
|
|
755
|
+
ToolbarExtras: {YourName}Toolbar, // ADD THIS
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
**REFERENCE:** See `biosamples/shared/AssayToggle.tsx` for:
|
|
761
|
+
|
|
762
|
+
- Toggle between two view modes
|
|
763
|
+
- Dynamic config updates with `updateConfig()`
|
|
764
|
+
- Different column sets and grouping models per view
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
## Validation Checklist
|
|
769
|
+
|
|
770
|
+
After implementation, verify:
|
|
771
|
+
|
|
772
|
+
- [ ] JSON data files are in `data/` directory
|
|
773
|
+
- [ ] All TypeScript files compile without errors
|
|
774
|
+
- [ ] Types match the JSON structure exactly
|
|
775
|
+
- [ ] Column definitions include all fields you want to display
|
|
776
|
+
- [ ] Grouping model creates a logical hierarchy (2-3 levels)
|
|
777
|
+
- [ ] Tree builder creates the same hierarchy as grouping model
|
|
778
|
+
- [ ] Folder is registered in `folders/index.ts`
|
|
779
|
+
- [ ] Folder appears in TrackSelect UI
|
|
780
|
+
- [ ] Items can be selected/deselected
|
|
781
|
+
- [ ] Selected items appear in the TreeView panel
|
|
782
|
+
- [ ] Remove buttons work in TreeView
|
|
783
|
+
- [ ] Selection persists on page refresh (sessionStorage)
|
|
784
|
+
- [ ] No console errors
|
|
785
|
+
|
|
786
|
+
---
|
|
787
|
+
|
|
788
|
+
## Common Patterns Reference
|
|
789
|
+
|
|
790
|
+
### Pattern: One-to-Many Data
|
|
791
|
+
|
|
792
|
+
When one track has multiple items (like biosamples has multiple assays):
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
function flattenTrackIntoRows(track: TrackInfo): RowInfo[] {
|
|
796
|
+
return track.items.map((item) => ({
|
|
797
|
+
id: item.id,
|
|
798
|
+
parentName: track.name,
|
|
799
|
+
itemName: item.name,
|
|
800
|
+
// ...
|
|
801
|
+
}));
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
**REFERENCE:** `biosamples/shared/createFolder.ts` - each biosample track has multiple assays, creating one row per assay.
|
|
806
|
+
|
|
807
|
+
### Pattern: Nested Grouping
|
|
808
|
+
|
|
809
|
+
For 3-level hierarchy (Category → Subcategory → Item):
|
|
810
|
+
|
|
811
|
+
```typescript
|
|
812
|
+
export const defaultGroupingModel = ["category", "subcategory"];
|
|
813
|
+
export const defaultLeafField = "name"; // or "id"
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
**REFERENCE:** `biosamples/shared/columns.tsx` - see `sortedByAssayGroupingModel` for a 3-level example.
|
|
817
|
+
|
|
818
|
+
### Pattern: Custom Formatting
|
|
819
|
+
|
|
820
|
+
Use `valueFormatter` for simple formatting:
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
{
|
|
824
|
+
field: "lifeStage",
|
|
825
|
+
valueFormatter: (value) => value && capitalize(value),
|
|
826
|
+
}
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
Use `renderCell` for complex rendering:
|
|
830
|
+
|
|
831
|
+
```typescript
|
|
832
|
+
{
|
|
833
|
+
field: "category",
|
|
834
|
+
renderCell: (params) => {
|
|
835
|
+
if (params.rowNode.type === "group") {
|
|
836
|
+
return <b>{params.value}</b>;
|
|
837
|
+
}
|
|
838
|
+
return params.value;
|
|
839
|
+
},
|
|
840
|
+
}
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## Troubleshooting
|
|
846
|
+
|
|
847
|
+
### Issue: Items not appearing in DataGrid
|
|
848
|
+
|
|
849
|
+
- Check `rowById` Map is populated correctly
|
|
850
|
+
- Verify `getRowId` returns unique values
|
|
851
|
+
- Check JSON data file path is correct
|
|
852
|
+
|
|
853
|
+
### Issue: Grouping not working
|
|
854
|
+
|
|
855
|
+
- Verify `groupingModel` fields exist in your row data
|
|
856
|
+
- Check field names are exact matches (case-sensitive)
|
|
857
|
+
- Ensure `leafField` is NOT in `groupingModel`
|
|
858
|
+
|
|
859
|
+
### Issue: Tree not building correctly
|
|
860
|
+
|
|
861
|
+
- Tree hierarchy must match `groupingModel` exactly
|
|
862
|
+
- Ensure `allExpAccessions` is populated at all levels
|
|
863
|
+
- Check node IDs are unique
|
|
864
|
+
|
|
865
|
+
### Issue: TypeScript errors
|
|
866
|
+
|
|
867
|
+
- Ensure types match JSON structure exactly
|
|
868
|
+
- Check import paths are correct
|
|
869
|
+
- Verify all required fields are present in types
|
|
870
|
+
|
|
871
|
+
---
|
|
872
|
+
|
|
873
|
+
## Reference: Biosamples Implementation
|
|
874
|
+
|
|
875
|
+
The biosamples folder is the complete reference implementation. Study these files:
|
|
876
|
+
|
|
877
|
+
**Core Files:**
|
|
878
|
+
|
|
879
|
+
- `biosamples/shared/types.ts` - Type definitions for nested data (tracks with multiple assays)
|
|
880
|
+
- `biosamples/shared/createFolder.ts` - Factory with one-to-many flattening
|
|
881
|
+
- `biosamples/shared/columns.tsx` - Two column sets for view toggling
|
|
882
|
+
- `biosamples/shared/treeBuilder.ts` - Two tree builders for different hierarchies
|
|
883
|
+
|
|
884
|
+
**Custom Components:**
|
|
885
|
+
|
|
886
|
+
- `biosamples/shared/constants.tsx` - Icons, colors, and formatters
|
|
887
|
+
- `biosamples/shared/BiosampleGroupingCell.tsx` - Custom cell with icons and expand/collapse
|
|
888
|
+
- `biosamples/shared/BiosampleTreeItem.tsx` - Custom tree item with icon renderer
|
|
889
|
+
- `biosamples/shared/AssayToggle.tsx` - View toggle toolbar component
|
|
890
|
+
|
|
891
|
+
**Assembly Files:**
|
|
892
|
+
|
|
893
|
+
- `biosamples/human.ts` - GRCh38 assembly export
|
|
894
|
+
- `biosamples/mouse.ts` - mm10 assembly export
|
|
895
|
+
|
|
896
|
+
**Data:**
|
|
897
|
+
|
|
898
|
+
- `biosamples/data/human.json` - Example of nested track structure
|
|
899
|
+
- `biosamples/data/mouse.json` - Mouse genome data
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
## Final Notes for LLMs
|
|
904
|
+
|
|
905
|
+
**When implementing:**
|
|
906
|
+
|
|
907
|
+
1. Always ask for clarification if the data structure is ambiguous
|
|
908
|
+
2. Show the user your understanding of the hierarchy before coding
|
|
909
|
+
3. Create the simplest version first (no custom components)
|
|
910
|
+
4. Add custom components only if needed
|
|
911
|
+
5. Test each step before moving to the next
|
|
912
|
+
6. Use the biosamples folder as a reference for patterns
|
|
913
|
+
|
|
914
|
+
**Communication:**
|
|
915
|
+
|
|
916
|
+
- Explain your decisions (especially for grouping hierarchy)
|
|
917
|
+
- Show example data transformations
|
|
918
|
+
- Summarize what you created after each major step
|
|
919
|
+
- Point out where you deviated from the template and why
|
|
920
|
+
|
|
921
|
+
**Key Concepts:**
|
|
922
|
+
|
|
923
|
+
- **RowInfo** = Flattened data for DataGrid (one row per selectable item)
|
|
924
|
+
- **TrackInfo** = Raw JSON structure (may be nested)
|
|
925
|
+
- **groupingModel** = Defines the hierarchy in both DataGrid and TreeView
|
|
926
|
+
- **leafField** = The unique identifier field (not in groupingModel)
|
|
927
|
+
- **rowById** = Single source of truth Map for all row data
|
|
928
|
+
- **buildTree** = Converts selected IDs into hierarchical tree matching groupingModel
|
|
929
|
+
- **allExpAccessions** = Tracks all descendant IDs for bulk operations
|