@weng-lab/genomebrowser-ui 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib.d.ts CHANGED
@@ -4,4 +4,4 @@ import { foldersByAssembly } from './TrackSelect/Folders/index.ts';
4
4
  export { TrackSelect, TrackSelectProps };
5
5
  export { createSelectionStore, SelectionStoreInstance };
6
6
  export { foldersByAssembly };
7
- export type { BiosampleRowInfo, GeneRowInfo } from './TrackSelect/Folders';
7
+ export type { BiosampleRowInfo, GeneRowInfo, OtherTrackInfo, } from './TrackSelect/Folders';
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.1",
4
+ "version": "0.3.3",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "publishConfig": {
@@ -22,7 +22,7 @@
22
22
  "@mui/x-data-grid-premium": "^8.19.0",
23
23
  "react": "^19.0.0",
24
24
  "react-dom": "^19.0.0",
25
- "@weng-lab/genomebrowser": "1.8.0"
25
+ "@weng-lab/genomebrowser": "1.8.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@eslint/js": "^9.34.0",
package/src/lib.ts CHANGED
@@ -15,5 +15,3 @@ export type {
15
15
  GeneRowInfo,
16
16
  OtherTrackInfo,
17
17
  } from "./TrackSelect/Folders";
18
-
19
- export { tfPeaksTrack } from "./TrackSelect/CustomTracks/TfPeaks";
package/test/main.tsx CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  Track,
31
31
  TrackType,
32
32
  TranscriptConfig,
33
+ tfPeaksTrack,
33
34
  } from "@weng-lab/genomebrowser";
34
35
 
35
36
  // local
@@ -37,7 +38,6 @@ import { foldersByAssembly, TrackSelect } from "../src/lib";
37
38
  import type { BiosampleRowInfo } from "../src/TrackSelect/Folders/biosamples/shared/types";
38
39
  import type { GeneRowInfo } from "../src/TrackSelect/Folders/genes/shared/types";
39
40
  import type { OtherTrackInfo } from "../src/TrackSelect/Folders/other-tracks/shared/types";
40
- import { tfPeaksTrack } from "../src/TrackSelect/CustomTracks/TfPeaks";
41
41
  import { Exon } from "@weng-lab/genomebrowser/dist/components/tracks/transcript/types";
42
42
 
43
43
  interface Transcript {
@@ -1,15 +0,0 @@
1
- import { CustomTrackConfig, Rect } from '@weng-lab/genomebrowser';
2
- type OverlayInteractionRect = Rect & {
3
- source: "base" | "overlay";
4
- matchedName?: string;
5
- };
6
- type OverlayBigBedConfig = CustomTrackConfig<OverlayInteractionRect> & {
7
- primaryUrl: string;
8
- overlayUrl: string;
9
- baseColor?: string;
10
- overlayColor?: string;
11
- };
12
- export declare const PEAKS_BIGBED_URL = "https://users.wenglab.org/gaomingshi/no_trim.TF_name.rPeaks.bb";
13
- export declare const DECORATOR_BIGBED_URL = "https://users.wenglab.org/gaomingshi/no_trim.TF_name.decorator.bb";
14
- export declare const tfPeaksTrack: OverlayBigBedConfig;
15
- export {};
@@ -1,247 +0,0 @@
1
- import {
2
- ClipPath,
3
- CustomTrackConfig,
4
- CustomTrackProps,
5
- DisplayMode,
6
- fetchBigBedUrl,
7
- Rect,
8
- renderSquishBigBedData,
9
- TrackType,
10
- useBrowserStore,
11
- useInteraction,
12
- useRowHeight,
13
- useXTransform,
14
- Vibrant,
15
- } from "@weng-lab/genomebrowser";
16
- import { useMemo } from "react";
17
-
18
- // --- Types ---
19
-
20
- type OverlayData = {
21
- primary: Rect[];
22
- overlay: Rect[];
23
- };
24
-
25
- type OverlayInteractionRect = Rect & {
26
- source: "base" | "overlay";
27
- matchedName?: string;
28
- };
29
-
30
- // The config type — extra fields go directly on the config
31
- type OverlayBigBedConfig = CustomTrackConfig<OverlayInteractionRect> & {
32
- primaryUrl: string;
33
- overlayUrl: string;
34
- baseColor?: string;
35
- overlayColor?: string;
36
- };
37
-
38
- // --- Helpers ---
39
-
40
- function nameKey(name?: string) {
41
- if (!name) return "";
42
- return name.split(/[-_]/)[0].trim().toLowerCase();
43
- }
44
-
45
- function buildOverlayIndex(rects: Rect[]) {
46
- return rects.reduce<Record<string, Rect[]>>((acc, rect) => {
47
- const key = nameKey(rect.name);
48
- if (!key) return acc;
49
- (acc[key] ??= []).push(rect);
50
- return acc;
51
- }, {});
52
- }
53
-
54
- function intervalsOverlap(
55
- aStart: number,
56
- aEnd: number,
57
- bStart: number,
58
- bEnd: number,
59
- ) {
60
- return aStart <= bEnd && bStart <= aEnd;
61
- }
62
-
63
- function darkenHexColor(color: string, score?: number) {
64
- if (!color.startsWith("#") || color.length !== 7) return color;
65
- const t = 1 - (0.7 * Math.min(Math.max(score ?? 0, 0), 1000)) / 1000;
66
- const r = Math.round(parseInt(color.slice(1, 3), 16) * t);
67
- const g = Math.round(parseInt(color.slice(3, 5), 16) * t);
68
- const b = Math.round(parseInt(color.slice(5, 7), 16) * t);
69
- if ([r, g, b].some(isNaN)) return color;
70
- return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
71
- }
72
-
73
- // --- Renderer ---
74
-
75
- function OverlayBigBedRenderer(
76
- props: CustomTrackProps<OverlayData> & OverlayBigBedConfig,
77
- ) {
78
- const {
79
- data,
80
- dimensions,
81
- id,
82
- height: trackHeight,
83
- color,
84
- baseColor: baseColorProp,
85
- overlayColor: overlayColorProp,
86
- ...rest
87
- } = props;
88
- const domain = useBrowserStore((state) => state.domain);
89
- const { totalWidth, sideWidth } = dimensions;
90
- const { x, reverseX } = useXTransform(totalWidth);
91
-
92
- const rows = useMemo(() => {
93
- const visible = (data?.primary || []).filter(
94
- (r) => r.end >= domain.start && r.start <= domain.end,
95
- );
96
- return renderSquishBigBedData(visible, x);
97
- }, [data, domain.end, domain.start, x]);
98
-
99
- const rowHeight = useRowHeight(rows.length, id);
100
- const height = Math.max(trackHeight, rowHeight * Math.max(rows.length, 1));
101
- const overlayByName = useMemo(
102
- () => buildOverlayIndex(data?.overlay || []),
103
- [data],
104
- );
105
-
106
- const baseColor = baseColorProp || color || Vibrant[0];
107
- const overlayColor = overlayColorProp || Vibrant[3];
108
- const cursor = rest.onClick ? "pointer" : "default";
109
-
110
- const { handleClick, handleHover, handleLeave } =
111
- useInteraction<OverlayInteractionRect>({
112
- onClick: rest.onClick,
113
- onHover: rest.onHover,
114
- onLeave: rest.onLeave,
115
- tooltip: rest.tooltip,
116
- });
117
-
118
- return (
119
- <g
120
- width={totalWidth}
121
- height={height}
122
- clipPath={`url(#${id})`}
123
- transform={`translate(-${sideWidth}, 0)`}
124
- >
125
- <rect width={totalWidth} height={height} fill="transparent" />
126
- <defs>
127
- <ClipPath id={id} width={totalWidth} height={height} />
128
- </defs>
129
- {rows.map((row, rowIndex) => {
130
- const baseY = rowHeight * 0.2;
131
- const baseH = rowHeight * 0.6;
132
- return (
133
- <g
134
- key={`row_${rowIndex}`}
135
- transform={`translate(0, ${rowIndex * rowHeight})`}
136
- >
137
- {row.map((rect, ri) => {
138
- const realStart = Math.round(reverseX(rect.start));
139
- const realEnd = Math.round(reverseX(rect.end));
140
- const baseName = rect.rectname;
141
- const attached = (
142
- overlayByName[nameKey(rect.rectname)] || []
143
- ).filter((a) =>
144
- intervalsOverlap(realStart, realEnd, a.start, a.end),
145
- );
146
- const fill = darkenHexColor(rect.color || baseColor, rect.score);
147
- const baseRect: OverlayInteractionRect = {
148
- source: "base",
149
- start: realStart,
150
- end: realEnd,
151
- name: baseName,
152
- color: fill,
153
- score: rect.score,
154
- };
155
-
156
- return (
157
- <g key={`${id}_${rowIndex}_${ri}`}>
158
- <rect
159
- style={{ cursor }}
160
- x={rect.start}
161
- y={baseY}
162
- width={Math.max(rect.end - rect.start, 1)}
163
- height={baseH}
164
- fill={fill}
165
- opacity={0.9}
166
- onClick={() => handleClick(baseRect)}
167
- onMouseOver={(e) =>
168
- handleHover(baseRect, baseRect.name || "", e)
169
- }
170
- onMouseOut={() => handleLeave(baseRect)}
171
- />
172
- {attached.map((a, ai) => {
173
- const left = x(a.start);
174
- const right = x(a.end);
175
- const overlayRect: OverlayInteractionRect = {
176
- source: "overlay",
177
- start: a.start,
178
- end: a.end,
179
- name: a.name,
180
- color: a.color || overlayColor,
181
- score: a.score,
182
- matchedName: baseName,
183
- };
184
- return (
185
- <rect
186
- style={{ cursor }}
187
- key={`${id}_${rowIndex}_${ri}_a_${ai}`}
188
- x={left}
189
- y={baseY}
190
- width={Math.max(right - left, 1)}
191
- height={baseH}
192
- fill={a.color || overlayColor}
193
- opacity={0.9}
194
- onClick={() => handleClick(overlayRect)}
195
- onMouseOver={(e) =>
196
- handleHover(overlayRect, overlayRect.name || "", e)
197
- }
198
- onMouseOut={() => handleLeave(overlayRect)}
199
- />
200
- );
201
- })}
202
- </g>
203
- );
204
- })}
205
- </g>
206
- );
207
- })}
208
- </g>
209
- );
210
- }
211
-
212
- // --- Track config ---
213
-
214
- export const PEAKS_BIGBED_URL =
215
- "https://users.wenglab.org/gaomingshi/no_trim.TF_name.rPeaks.bb";
216
- export const DECORATOR_BIGBED_URL =
217
- "https://users.wenglab.org/gaomingshi/no_trim.TF_name.decorator.bb";
218
-
219
- export const tfPeaksTrack: OverlayBigBedConfig = {
220
- id: "custom-tf-peaks",
221
- title: "TF Peaks (Overlay)",
222
- shortLabel: "TF Peaks",
223
- trackType: TrackType.Custom,
224
- displayMode: DisplayMode.Full,
225
- color: Vibrant[0],
226
- height: 80,
227
- primaryUrl: PEAKS_BIGBED_URL,
228
- overlayUrl: DECORATOR_BIGBED_URL,
229
- baseColor: "#d1d5db",
230
- overlayColor: "#1e3a8a",
231
- renderers: {
232
- [DisplayMode.Full]: OverlayBigBedRenderer,
233
- },
234
- fetcher: async (ctx) => {
235
- const track = ctx.track as OverlayBigBedConfig;
236
- const [primary, overlay] = await Promise.all([
237
- fetchBigBedUrl(track.primaryUrl, ctx),
238
- fetchBigBedUrl(track.overlayUrl, ctx),
239
- ]);
240
- const error =
241
- [primary.error, overlay.error].filter(Boolean).join("\n") || null;
242
- return {
243
- data: { primary: primary.data || [], overlay: overlay.data || [] },
244
- error,
245
- };
246
- },
247
- };