@weng-lab/genomebrowser-ui 0.3.2 → 0.3.4
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/TrackSelect/{CustomTracks → Custom}/TfPeaks.d.ts +2 -0
- package/dist/genomebrowser-ui.es.js +764 -914
- package/dist/genomebrowser-ui.es.js.map +1 -1
- package/dist/lib.d.ts +0 -1
- package/package.json +3 -2
- package/src/TrackSelect/Custom/TF-ChIP-Canonical-Motifs-w-Trimmed.json +42207 -0
- package/src/TrackSelect/{CustomTracks → Custom}/TfPeaks.tsx +133 -3
- package/src/lib.ts +0 -2
- package/test/main.tsx +1 -1
|
@@ -12,8 +12,11 @@ import {
|
|
|
12
12
|
useRowHeight,
|
|
13
13
|
useXTransform,
|
|
14
14
|
Vibrant,
|
|
15
|
+
useTrackStore,
|
|
15
16
|
} from "@weng-lab/genomebrowser";
|
|
16
|
-
import {
|
|
17
|
+
import { DNALogo } from "logo-test";
|
|
18
|
+
import { useMemo, useState } from "react";
|
|
19
|
+
import motifData from "./TF-ChIP-Canonical-Motifs-w-Trimmed.json";
|
|
17
20
|
|
|
18
21
|
// --- Types ---
|
|
19
22
|
|
|
@@ -25,6 +28,7 @@ type OverlayData = {
|
|
|
25
28
|
type OverlayInteractionRect = Rect & {
|
|
26
29
|
source: "base" | "overlay";
|
|
27
30
|
matchedName?: string;
|
|
31
|
+
pwm?: number[][];
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
// The config type — extra fields go directly on the config
|
|
@@ -33,6 +37,7 @@ type OverlayBigBedConfig = CustomTrackConfig<OverlayInteractionRect> & {
|
|
|
33
37
|
overlayUrl: string;
|
|
34
38
|
baseColor?: string;
|
|
35
39
|
overlayColor?: string;
|
|
40
|
+
filter?: string[];
|
|
36
41
|
};
|
|
37
42
|
|
|
38
43
|
// --- Helpers ---
|
|
@@ -70,6 +75,113 @@ function darkenHexColor(color: string, score?: number) {
|
|
|
70
75
|
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
// --- Motif data lookup ---
|
|
79
|
+
|
|
80
|
+
type MotifEntry = { trimmed_ppm: number[][]; ppm: number[][] };
|
|
81
|
+
const motifLookup = motifData as Record<string, MotifEntry>;
|
|
82
|
+
|
|
83
|
+
function lookupPwm(name?: string): number[][] | undefined {
|
|
84
|
+
const key = nameKey(name)?.toUpperCase();
|
|
85
|
+
if (!key) return undefined;
|
|
86
|
+
const entry = motifLookup[key];
|
|
87
|
+
return entry?.trimmed_ppm ?? entry?.ppm;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// --- Tooltip ---
|
|
91
|
+
|
|
92
|
+
function tfDisplayName(name?: string): string {
|
|
93
|
+
return nameKey(name)?.toUpperCase() || name || "Unknown";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function TfPeaksTooltip(rect: OverlayInteractionRect) {
|
|
97
|
+
const pwm = rect.pwm;
|
|
98
|
+
const label = tfDisplayName(rect.name);
|
|
99
|
+
if (!pwm || pwm.length === 0) {
|
|
100
|
+
return (
|
|
101
|
+
<g>
|
|
102
|
+
<rect
|
|
103
|
+
width={120}
|
|
104
|
+
height={24}
|
|
105
|
+
fill="white"
|
|
106
|
+
rx={2}
|
|
107
|
+
style={{ filter: "drop-shadow(0px 0px 4px rgba(0,0,0,0.25))" }}
|
|
108
|
+
/>
|
|
109
|
+
<text x={6} y={16} fontSize={12} fill="#333">
|
|
110
|
+
{label}
|
|
111
|
+
</text>
|
|
112
|
+
</g>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const logoWidth = pwm.length * 15;
|
|
116
|
+
const logoHeight = 130;
|
|
117
|
+
const totalHeight = logoHeight - 5;
|
|
118
|
+
return (
|
|
119
|
+
<g transform={`translate(0, ${-totalHeight})`}>
|
|
120
|
+
<rect
|
|
121
|
+
width={logoWidth + 10}
|
|
122
|
+
height={totalHeight}
|
|
123
|
+
fill="white"
|
|
124
|
+
rx={3}
|
|
125
|
+
style={{ filter: "drop-shadow(0px 0px 5px rgba(0,0,0,0.3))" }}
|
|
126
|
+
/>
|
|
127
|
+
<text x={5} y={16} fontSize={12} fontWeight="bold" fill="#333">
|
|
128
|
+
{label}
|
|
129
|
+
</text>
|
|
130
|
+
<g transform="translate(5, 5)">
|
|
131
|
+
<DNALogo
|
|
132
|
+
ppm={pwm}
|
|
133
|
+
mode="INFORMATION_CONTENT"
|
|
134
|
+
width={logoWidth}
|
|
135
|
+
height={logoHeight}
|
|
136
|
+
/>
|
|
137
|
+
</g>
|
|
138
|
+
</g>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- Settings Panel ---
|
|
143
|
+
|
|
144
|
+
function TfPeaksSettings({ id }: { id: string }) {
|
|
145
|
+
const track = useTrackStore((state) => state.getTrack(id)) as
|
|
146
|
+
| OverlayBigBedConfig
|
|
147
|
+
| undefined;
|
|
148
|
+
const editTrack = useTrackStore((state) => state.editTrack);
|
|
149
|
+
const [input, setInput] = useState((track?.filter || []).join(", "));
|
|
150
|
+
|
|
151
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
152
|
+
const value = e.target.value;
|
|
153
|
+
setInput(value);
|
|
154
|
+
const names = value
|
|
155
|
+
.split(",")
|
|
156
|
+
.map((s) => s.trim().toUpperCase())
|
|
157
|
+
.filter(Boolean);
|
|
158
|
+
editTrack<OverlayBigBedConfig>(id, {
|
|
159
|
+
filter: names.length > 0 ? names : undefined,
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div
|
|
165
|
+
style={{
|
|
166
|
+
display: "flex",
|
|
167
|
+
flexDirection: "column",
|
|
168
|
+
alignItems: "flex-start",
|
|
169
|
+
paddingBlock: "5px",
|
|
170
|
+
paddingInline: "10px",
|
|
171
|
+
gap: "3px",
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
<div style={{ fontWeight: "bold" }}>Filter TFs</div>
|
|
175
|
+
<input
|
|
176
|
+
value={input}
|
|
177
|
+
onChange={handleChange}
|
|
178
|
+
placeholder="e.g. CTCF, TP53, GATA1"
|
|
179
|
+
style={{ width: "100%" }}
|
|
180
|
+
/>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
73
185
|
// --- Renderer ---
|
|
74
186
|
|
|
75
187
|
function OverlayBigBedRenderer(
|
|
@@ -83,18 +195,32 @@ function OverlayBigBedRenderer(
|
|
|
83
195
|
color,
|
|
84
196
|
baseColor: baseColorProp,
|
|
85
197
|
overlayColor: overlayColorProp,
|
|
198
|
+
filter,
|
|
86
199
|
...rest
|
|
87
200
|
} = props;
|
|
88
201
|
const domain = useBrowserStore((state) => state.domain);
|
|
89
202
|
const { totalWidth, sideWidth } = dimensions;
|
|
90
203
|
const { x, reverseX } = useXTransform(totalWidth);
|
|
91
204
|
|
|
205
|
+
const filterSet = useMemo(
|
|
206
|
+
() =>
|
|
207
|
+
filter && filter.length > 0
|
|
208
|
+
? new Set(filter.map((f) => f.toUpperCase()))
|
|
209
|
+
: null,
|
|
210
|
+
[filter],
|
|
211
|
+
);
|
|
212
|
+
|
|
92
213
|
const rows = useMemo(() => {
|
|
93
|
-
|
|
214
|
+
let visible = (data?.primary || []).filter(
|
|
94
215
|
(r) => r.end >= domain.start && r.start <= domain.end,
|
|
95
216
|
);
|
|
217
|
+
if (filterSet) {
|
|
218
|
+
visible = visible.filter((r) =>
|
|
219
|
+
filterSet.has(nameKey(r.name)?.toUpperCase()),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
96
222
|
return renderSquishBigBedData(visible, x);
|
|
97
|
-
}, [data, domain.end, domain.start, x]);
|
|
223
|
+
}, [data, domain.end, domain.start, x, filterSet]);
|
|
98
224
|
|
|
99
225
|
const rowHeight = useRowHeight(rows.length, id);
|
|
100
226
|
const height = Math.max(trackHeight, rowHeight * Math.max(rows.length, 1));
|
|
@@ -151,6 +277,7 @@ function OverlayBigBedRenderer(
|
|
|
151
277
|
name: baseName,
|
|
152
278
|
color: fill,
|
|
153
279
|
score: rect.score,
|
|
280
|
+
pwm: lookupPwm(baseName),
|
|
154
281
|
};
|
|
155
282
|
|
|
156
283
|
return (
|
|
@@ -180,6 +307,7 @@ function OverlayBigBedRenderer(
|
|
|
180
307
|
color: a.color || overlayColor,
|
|
181
308
|
score: a.score,
|
|
182
309
|
matchedName: baseName,
|
|
310
|
+
pwm: lookupPwm(a.name),
|
|
183
311
|
};
|
|
184
312
|
return (
|
|
185
313
|
<rect
|
|
@@ -228,6 +356,8 @@ export const tfPeaksTrack: OverlayBigBedConfig = {
|
|
|
228
356
|
overlayUrl: DECORATOR_BIGBED_URL,
|
|
229
357
|
baseColor: "#d1d5db",
|
|
230
358
|
overlayColor: "#1e3a8a",
|
|
359
|
+
tooltip: TfPeaksTooltip,
|
|
360
|
+
settingsPanel: TfPeaksSettings,
|
|
231
361
|
renderers: {
|
|
232
362
|
[DisplayMode.Full]: OverlayBigBedRenderer,
|
|
233
363
|
},
|
package/src/lib.ts
CHANGED
package/test/main.tsx
CHANGED
|
@@ -37,8 +37,8 @@ import { foldersByAssembly, TrackSelect } from "../src/lib";
|
|
|
37
37
|
import type { BiosampleRowInfo } from "../src/TrackSelect/Folders/biosamples/shared/types";
|
|
38
38
|
import type { GeneRowInfo } from "../src/TrackSelect/Folders/genes/shared/types";
|
|
39
39
|
import type { OtherTrackInfo } from "../src/TrackSelect/Folders/other-tracks/shared/types";
|
|
40
|
-
import { tfPeaksTrack } from "../src/TrackSelect/CustomTracks/TfPeaks";
|
|
41
40
|
import { Exon } from "@weng-lab/genomebrowser/dist/components/tracks/transcript/types";
|
|
41
|
+
import { tfPeaksTrack } from "@weng-lab/genomebrowser/test/TfPeaks";
|
|
42
42
|
|
|
43
43
|
interface Transcript {
|
|
44
44
|
id: string;
|