@weng-lab/genomebrowser-ui 0.1.9 → 0.1.10

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 (35) hide show
  1. package/dist/TrackSelect/Data/{modifiedHumanTracks.json.d.ts → humanBiosamples.json.d.ts} +40705 -20804
  2. package/dist/TrackSelect/Data/mouseBiosamples.json.d.ts +10346 -0
  3. package/dist/TrackSelect/DataGrid/GroupingCell.d.ts +2 -0
  4. package/dist/TrackSelect/DataGrid/dataGridHelpers.d.ts +25 -6
  5. package/dist/TrackSelect/TrackSelect.d.ts +4 -1
  6. package/dist/TrackSelect/TreeView/treeViewHelpers.d.ts +1 -1
  7. package/dist/TrackSelect/consts.d.ts +6 -17
  8. package/dist/TrackSelect/store.d.ts +2 -1
  9. package/dist/TrackSelect/types.d.ts +5 -0
  10. package/dist/genomebrowser-ui.es.js +1173 -951
  11. package/dist/genomebrowser-ui.es.js.map +1 -1
  12. package/dist/lib.d.ts +0 -2
  13. package/package.json +2 -2
  14. package/src/TrackSelect/Data/formatBiosamples.go +254 -0
  15. package/src/TrackSelect/Data/{modifiedHumanTracks.json → humanBiosamples.json} +40704 -20804
  16. package/src/TrackSelect/Data/mouseBiosamples.json +10343 -0
  17. package/src/TrackSelect/DataGrid/DataGridWrapper.tsx +13 -6
  18. package/src/TrackSelect/DataGrid/GroupingCell.tsx +144 -0
  19. package/src/TrackSelect/DataGrid/columns.tsx +7 -0
  20. package/src/TrackSelect/DataGrid/dataGridHelpers.tsx +64 -19
  21. package/src/TrackSelect/TrackSelect.tsx +86 -27
  22. package/src/TrackSelect/TreeView/TreeViewWrapper.tsx +1 -1
  23. package/src/TrackSelect/TreeView/treeViewHelpers.tsx +65 -17
  24. package/src/TrackSelect/consts.ts +30 -30
  25. package/src/TrackSelect/issues.md +404 -0
  26. package/src/TrackSelect/store.ts +16 -6
  27. package/src/TrackSelect/types.ts +8 -0
  28. package/src/lib.ts +0 -3
  29. package/test/main.tsx +399 -17
  30. package/dist/TrackSelect/treeViewHelpers.d.ts +0 -1
  31. package/src/TrackSelect/.claude/settings.local.json +0 -7
  32. package/src/TrackSelect/Data/humanTracks.json +0 -35711
  33. package/src/TrackSelect/Data/human_chromhmm_biosamples_with_all_urls.json +0 -35716
  34. package/src/TrackSelect/bug.md +0 -4
  35. package/src/TrackSelect/treeViewHelpers.tsx +0 -0
package/src/lib.ts CHANGED
@@ -7,8 +7,5 @@ import {
7
7
  } from "./TrackSelect/store.ts";
8
8
  export { createSelectionStore, SelectionStoreInstance };
9
9
 
10
- import { rowById } from "./TrackSelect/consts.ts";
11
- export { rowById };
12
-
13
10
  import type { RowInfo } from "./TrackSelect/types.ts";
14
11
  export { RowInfo };
package/test/main.tsx CHANGED
@@ -1,9 +1,84 @@
1
- import { useEffect, useMemo } from "react";
2
- import { createSelectionStore } from "../src/TrackSelect/store";
3
- import TrackSelect from "../src/TrackSelect/TrackSelect";
1
+ // react
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
4
3
  import { createRoot } from "react-dom/client";
5
4
 
6
- function getLocalStorage(assembly: string): Set<string> | null {
5
+ // mui
6
+ import EditIcon from "@mui/icons-material/Edit";
7
+ import CloseIcon from "@mui/icons-material/Close";
8
+ import {
9
+ Box,
10
+ Button,
11
+ Dialog,
12
+ DialogContent,
13
+ DialogTitle,
14
+ IconButton,
15
+ Typography,
16
+ } from "@mui/material";
17
+
18
+ // weng lab
19
+ import {
20
+ BigBedConfig,
21
+ BigWigConfig,
22
+ Browser,
23
+ createBrowserStoreMemo,
24
+ createTrackStoreMemo,
25
+ DisplayMode,
26
+ Domain,
27
+ GQLWrapper,
28
+ Rect,
29
+ Track,
30
+ TrackType,
31
+ TranscriptConfig,
32
+ } from "@weng-lab/genomebrowser";
33
+
34
+ // local
35
+ import { createSelectionStore, TrackSelect, RowInfo } from "../src/lib";
36
+ import { Exon } from "@weng-lab/genomebrowser/dist/components/tracks/transcript/types";
37
+
38
+ interface Transcript {
39
+ id: string;
40
+ name: string;
41
+ coordinates: Domain;
42
+ strand: string;
43
+ exons?: Exon[];
44
+ color?: string;
45
+ }
46
+
47
+ const enum Assembly {
48
+ human = "GRCh38",
49
+ mouse = "mm10",
50
+ }
51
+
52
+ // Callback types for track interactions (using any to avoid type conflicts with library types)
53
+ interface TrackCallbacks {
54
+ onHover: (item: any) => void;
55
+ onLeave: () => void;
56
+ onCCREClick: (item: any) => void;
57
+ onGeneClick: (item: any) => void;
58
+ }
59
+
60
+ // Helper to inject callbacks based on track type
61
+ function injectCallbacks(track: Track, callbacks: TrackCallbacks): Track {
62
+ if (track.trackType === TrackType.Transcript) {
63
+ return {
64
+ ...track,
65
+ onHover: callbacks.onHover,
66
+ onLeave: callbacks.onLeave,
67
+ onClick: callbacks.onGeneClick,
68
+ };
69
+ }
70
+ if (track.trackType === TrackType.BigBed) {
71
+ return {
72
+ ...track,
73
+ onHover: callbacks.onHover,
74
+ onLeave: callbacks.onLeave,
75
+ onClick: callbacks.onCCREClick,
76
+ };
77
+ }
78
+ return track;
79
+ }
80
+
81
+ function getLocalStorage(assembly: Assembly): Set<string> | null {
7
82
  if (typeof window === "undefined" || !window.sessionStorage) return null;
8
83
 
9
84
  const selectedIds = sessionStorage.getItem(assembly + "-selected-tracks");
@@ -12,32 +87,339 @@ function getLocalStorage(assembly: string): Set<string> | null {
12
87
  return new Set(idsArray);
13
88
  }
14
89
 
15
- function setLocalStorage(trackIds: Set<string>, assembly: string) {
90
+ function setLocalStorage(trackIds: Set<string>, assembly: Assembly) {
16
91
  sessionStorage.setItem(
17
92
  assembly + "-selected-tracks",
18
93
  JSON.stringify([...trackIds]),
19
94
  );
20
95
  }
21
96
 
22
- const assembly = "grch38";
23
97
  function Main() {
98
+ const [open, setOpen] = useState(false);
99
+ const currentAssembly = Assembly.mouse;
100
+
101
+ const browserStore = createBrowserStoreMemo({
102
+ // chr12:53,380,176-53,416,446
103
+ domain: { chromosome: "chr12", start: 53380176, end: 53416446 },
104
+ marginWidth: 100,
105
+ trackWidth: 1400,
106
+ multiplier: 3,
107
+ });
108
+
109
+ const addHighlight = browserStore((s) => s.addHighlight);
110
+ const removeHighlight = browserStore((s) => s.removeHighlight);
111
+ const onHover = useCallback(
112
+ (item: Rect | Transcript) => {
113
+ const domain =
114
+ "start" in item
115
+ ? { start: item.start, end: item.end }
116
+ : { start: item.coordinates.start, end: item.coordinates.end };
117
+
118
+ addHighlight({
119
+ id: "hover-highlight",
120
+ domain,
121
+ color: item.color || "blue",
122
+ });
123
+ },
124
+ [addHighlight],
125
+ );
126
+ const onLeave = useCallback(() => {
127
+ removeHighlight("hover-highlight");
128
+ }, [removeHighlight]);
129
+
130
+ const onCCREClick = useCallback((item: Rect) => {
131
+ console.log(item);
132
+ }, []);
133
+ const onGeneClick = useCallback((item: Transcript) => {
134
+ console.log(item);
135
+ }, []);
136
+
137
+ // Bundle callbacks for track injection
138
+ const callbacks = useMemo<TrackCallbacks>(
139
+ () => ({
140
+ onHover,
141
+ onLeave,
142
+ onCCREClick,
143
+ onGeneClick,
144
+ }),
145
+ [onHover, onLeave, onCCREClick, onGeneClick],
146
+ );
147
+
148
+ const trackStore = useLocalTracks(currentAssembly, callbacks);
149
+
150
+ const tracks = trackStore((s) => s.tracks);
151
+ const insertTrack = trackStore((s) => s.insertTrack);
152
+ const removeTrack = trackStore((s) => s.removeTrack);
153
+
24
154
  const selectionStore = useMemo(() => {
25
- const localIds = getLocalStorage(assembly);
155
+ const localIds = getLocalStorage(currentAssembly);
26
156
  const ids = localIds != null ? localIds : new Set<string>();
27
- return createSelectionStore(ids);
28
- }, [assembly]);
157
+ return createSelectionStore(currentAssembly, ids);
158
+ }, [currentAssembly]);
29
159
 
30
- const selectedIds = selectionStore((s) => s.selectedIds);
31
- const getTrackIds = selectionStore((s) => s.getTrackIds);
160
+ const rowById = selectionStore((s) => s.rowById);
32
161
 
33
- // Get only real track IDs (no auto-generated group IDs)
34
- const trackIds = useMemo(() => getTrackIds(), [selectedIds, getTrackIds]);
162
+ // Handle submit: sync tracks to browser and save to localStorage
163
+ const handleSubmit = useCallback(
164
+ (newTrackIds: Set<string>) => {
165
+ const currentIds = new Set(tracks.map((t) => t.id));
35
166
 
36
- useEffect(() => {
37
- setLocalStorage(trackIds, assembly);
38
- }, [assembly, trackIds]);
167
+ // Build tracks to add from newTrackIds + rowById lookup
168
+ const tracksToAdd = Array.from(newTrackIds)
169
+ .filter((id) => !currentIds.has(id)) // not in current track list
170
+ .map((id) => rowById.get(id)) // get RowInfo object
171
+ .filter((track): track is RowInfo => track !== undefined); // filter out undefined
172
+
173
+ const tracksToRemove = tracks.filter((t) => {
174
+ return !t.id.includes("ignore") && !newTrackIds.has(t.id);
175
+ });
176
+
177
+ console.log("removing", tracksToRemove);
178
+ for (const t of tracksToRemove) {
179
+ removeTrack(t.id);
180
+ }
181
+
182
+ for (const s of tracksToAdd) {
183
+ const track = generateTrack(s, callbacks);
184
+ if (track === null) continue;
185
+ insertTrack(track);
186
+ }
187
+
188
+ // Save the track IDs (not the auto-generated group IDs)
189
+ setLocalStorage(newTrackIds, currentAssembly);
190
+ // Close the dialog
191
+ setOpen(false);
192
+ },
193
+ [tracks, removeTrack, insertTrack, callbacks],
194
+ );
195
+
196
+ const handleCancel = () => {
197
+ setOpen(false);
198
+ };
199
+
200
+ // Handle reset: clear selections and remove non-default tracks
201
+ const handleReset = () => {
202
+ // Clear the selection store
203
+ selectionStore.getState().clear();
39
204
 
40
- return <TrackSelect store={selectionStore} />;
205
+ // Remove all non-default tracks from the browser
206
+ const tracksToRemove = tracks.filter((t) => !t.id.includes("ignore"));
207
+ for (const t of tracksToRemove) {
208
+ removeTrack(t.id);
209
+ }
210
+
211
+ // Clear localStorage for selected tracks
212
+ setLocalStorage(new Set(), currentAssembly);
213
+ };
214
+
215
+ return (
216
+ <>
217
+ <Button
218
+ variant="contained"
219
+ startIcon={<EditIcon />}
220
+ size="small"
221
+ onClick={() => setOpen(true)}
222
+ >
223
+ Select Tracks
224
+ </Button>
225
+ <Dialog
226
+ open={open}
227
+ onClose={() => setOpen(false)}
228
+ maxWidth="lg"
229
+ fullWidth
230
+ >
231
+ <DialogTitle
232
+ bgcolor="#0c184a"
233
+ color="white"
234
+ display={"flex"}
235
+ justifyContent={"space-between"}
236
+ alignItems={"center"}
237
+ fontWeight={"bold"}
238
+ >
239
+ Biosample Tracks
240
+ <IconButton
241
+ size="large"
242
+ onClick={() => setOpen(false)}
243
+ sx={{ color: "white", padding: 0 }}
244
+ >
245
+ <CloseIcon fontSize="large" />
246
+ </IconButton>
247
+ </DialogTitle>
248
+ <DialogContent sx={{ marginTop: "5px" }}>
249
+ <TrackSelect
250
+ store={selectionStore}
251
+ onSubmit={handleSubmit}
252
+ onCancel={handleCancel}
253
+ onReset={handleReset}
254
+ />
255
+ </DialogContent>
256
+ </Dialog>
257
+ <GQLWrapper>
258
+ <Browser browserStore={browserStore} trackStore={trackStore} />
259
+ </GQLWrapper>
260
+ </>
261
+ );
41
262
  }
42
263
 
43
264
  createRoot(document.getElementById("root")!).render(<Main />);
265
+
266
+ const ASSAY_COLORS: Record<string, string> = {
267
+ dnase: "#06da93",
268
+ h3k4me3: "#ff0000",
269
+ h3k27ac: "#ffcd00",
270
+ ctcf: "#00b0d0",
271
+ atac: "#02c7b9",
272
+ rnaseq: "#00aa00",
273
+ chromhmm: "#00ff00",
274
+ ccre: "#0c184a",
275
+ };
276
+
277
+ function generateTrack(sel: RowInfo, callbacks?: TrackCallbacks): Track {
278
+ const color = ASSAY_COLORS[sel.assay.toLowerCase()] || "#000000";
279
+ let track: Track;
280
+
281
+ switch (sel.assay.toLowerCase()) {
282
+ case "chromhmm":
283
+ track = {
284
+ ...defaultBigBed,
285
+ id: sel.id,
286
+ url: sel.url,
287
+ title: sel.displayname,
288
+ color,
289
+ };
290
+ break;
291
+ case "ccre":
292
+ track = {
293
+ ...defaultBigBed,
294
+ id: sel.id,
295
+ url: sel.url,
296
+ title: sel.displayname,
297
+ color,
298
+ };
299
+ break;
300
+ default:
301
+ track = {
302
+ ...defaultBigWig,
303
+ id: sel.id,
304
+ url: sel.url,
305
+ title: sel.displayname,
306
+ color,
307
+ };
308
+ }
309
+
310
+ return callbacks ? injectCallbacks(track, callbacks) : track;
311
+ }
312
+
313
+ export const defaultBigWig: Omit<BigWigConfig, "id" | "title" | "url"> = {
314
+ trackType: TrackType.BigWig,
315
+ height: 50,
316
+ displayMode: DisplayMode.Full,
317
+ titleSize: 12,
318
+ };
319
+
320
+ export const defaultBigBed: Omit<BigBedConfig, "id" | "title" | "url"> = {
321
+ trackType: TrackType.BigBed,
322
+ height: 20,
323
+ displayMode: DisplayMode.Dense,
324
+ titleSize: 12,
325
+ };
326
+
327
+ export const defaultTranscript: Omit<
328
+ TranscriptConfig,
329
+ "id" | "assembly" | "version"
330
+ > = {
331
+ title: "GENCODE Genes",
332
+ trackType: TrackType.Transcript,
333
+ displayMode: DisplayMode.Squish,
334
+ height: 100,
335
+ color: "#0c184a", // screen theme default
336
+ canonicalColor: "#100e98", // screen theme light
337
+ highlightColor: "#3c69e8", // bright blue
338
+ titleSize: 12,
339
+ };
340
+
341
+ export function useLocalTracks(assembly: string, callbacks?: TrackCallbacks) {
342
+ const localTracks = getLocalTracks(assembly);
343
+
344
+ const defaultTracks =
345
+ assembly === "GRCh38" ? defaultHumanTracks : defaultMouseTracks;
346
+
347
+ // Get base tracks (from storage or defaults)
348
+ let initialTracks = localTracks || defaultTracks;
349
+
350
+ // Inject callbacks if provided (callbacks are lost on JSON serialization)
351
+ if (callbacks) {
352
+ initialTracks = initialTracks.map((t) => injectCallbacks(t, callbacks));
353
+ }
354
+
355
+ const trackStore = createTrackStoreMemo(initialTracks, []);
356
+ const tracks = trackStore((state) => state.tracks);
357
+
358
+ // any time the track list changes, update local storage
359
+ useEffect(() => {
360
+ setLocalTracks(tracks, assembly);
361
+ }, [tracks, assembly]);
362
+
363
+ return trackStore;
364
+ }
365
+
366
+ export function getLocalTracks(assembly: string): Track[] | null {
367
+ if (typeof window === "undefined" || !window.sessionStorage) return null;
368
+
369
+ const localTracks = sessionStorage.getItem(assembly + "-" + "tracks");
370
+ if (!localTracks) return null;
371
+ const localTracksJson = JSON.parse(localTracks) as Track[];
372
+ return localTracksJson;
373
+ }
374
+
375
+ export function setLocalTracks(tracks: Track[], assembly: string) {
376
+ sessionStorage.setItem(assembly + "-tracks", JSON.stringify(tracks));
377
+ }
378
+
379
+ const defaultHumanTracks = [
380
+ {
381
+ ...defaultTranscript,
382
+ color: ASSAY_COLORS.ccre,
383
+ id: "human-genes-ignore",
384
+ assembly: "GRCh38",
385
+ version: 40,
386
+ },
387
+ {
388
+ ...defaultBigBed,
389
+ color: ASSAY_COLORS.ccre,
390
+ id: "human-ccre-ignore",
391
+ title: "All cCREs colored by group",
392
+ url: "https://downloads.wenglab.org/GRCh38-cCREs.DCC.bigBed",
393
+ },
394
+ {
395
+ ...defaultBigWig,
396
+ color: ASSAY_COLORS.dnase,
397
+ id: "human-dnase-aggregate-ignore",
398
+ title: "Aggregated DNase signal, all ENCODE biosamples",
399
+ url: "https://downloads.wenglab.org/DNAse_All_ENCODE_MAR20_2024_merged.bw",
400
+ },
401
+ ];
402
+
403
+ const defaultMouseTracks = [
404
+ {
405
+ ...defaultTranscript,
406
+ color: ASSAY_COLORS.ccre,
407
+ id: "mouse-genes-ignore",
408
+ assembly: "mm10",
409
+ version: 21,
410
+ },
411
+ {
412
+ ...defaultBigBed,
413
+ color: ASSAY_COLORS.ccre,
414
+ id: "mouse-ccre-ignore",
415
+ title: "All cCREs colored by group",
416
+ url: "https://downloads.wenglab.org/mm10-cCREs.DCC.bigBed",
417
+ },
418
+ {
419
+ ...defaultBigWig,
420
+ color: ASSAY_COLORS.dnase,
421
+ id: "mouse-dnase-aggregate-ignore",
422
+ title: "Aggregated DNase signal, all ENCODE biosamples",
423
+ url: "https://downloads.wenglab.org/DNase_MM10_ENCODE_DEC2024_merged_nanrm.bigWig",
424
+ },
425
+ ];
@@ -1 +0,0 @@
1
- export {};
@@ -1,7 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "mcp__Ref__ref_read_url"
5
- ]
6
- }
7
- }