@jbrowse/plugin-breakpoint-split-view 2.15.0 → 2.15.2
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/BreakpointSplitView/BreakpointSplitView.d.ts +29 -1
- package/dist/BreakpointSplitView/BreakpointSplitView.js +71 -32
- package/dist/BreakpointSplitView/components/AlignmentConnections.js +43 -12
- package/dist/BreakpointSplitView/components/BreakpointSplitView.js +12 -10
- package/dist/BreakpointSplitView/components/BreakpointSplitViewOverlay.js +6 -5
- package/dist/BreakpointSplitView/components/Overlay.js +8 -3
- package/dist/BreakpointSplitView/components/PairedFeatures.d.ts +9 -0
- package/dist/BreakpointSplitView/components/PairedFeatures.js +100 -0
- package/dist/BreakpointSplitView/components/getOrientationColor.d.ts +41 -0
- package/dist/BreakpointSplitView/components/getOrientationColor.js +111 -0
- package/dist/BreakpointSplitView/components/util.d.ts +1 -0
- package/dist/BreakpointSplitView/components/util.js +26 -0
- package/dist/BreakpointSplitView/model.d.ts +24 -5
- package/dist/BreakpointSplitView/model.js +35 -7
- package/dist/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js +5 -8
- package/dist/BreakpointSplitView/util.d.ts +1 -0
- package/dist/BreakpointSplitView/util.js +1 -0
- package/esm/BreakpointSplitView/BreakpointSplitView.d.ts +29 -1
- package/esm/BreakpointSplitView/BreakpointSplitView.js +71 -32
- package/esm/BreakpointSplitView/components/AlignmentConnections.js +44 -13
- package/esm/BreakpointSplitView/components/BreakpointSplitView.js +11 -9
- package/esm/BreakpointSplitView/components/BreakpointSplitViewOverlay.js +6 -5
- package/esm/BreakpointSplitView/components/Overlay.js +8 -3
- package/esm/BreakpointSplitView/components/PairedFeatures.d.ts +9 -0
- package/esm/BreakpointSplitView/components/PairedFeatures.js +75 -0
- package/esm/BreakpointSplitView/components/getOrientationColor.d.ts +41 -0
- package/esm/BreakpointSplitView/components/getOrientationColor.js +103 -0
- package/esm/BreakpointSplitView/components/util.d.ts +1 -0
- package/esm/BreakpointSplitView/components/util.js +25 -0
- package/esm/BreakpointSplitView/model.d.ts +24 -5
- package/esm/BreakpointSplitView/model.js +35 -7
- package/esm/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js +5 -8
- package/esm/BreakpointSplitView/util.d.ts +1 -0
- package/esm/BreakpointSplitView/util.js +1 -1
- package/package.json +2 -2
|
@@ -6,6 +6,7 @@ exports.hasPairedReads = hasPairedReads;
|
|
|
6
6
|
exports.findMatchingAlt = findMatchingAlt;
|
|
7
7
|
exports.getMatchedBreakendFeatures = getMatchedBreakendFeatures;
|
|
8
8
|
exports.getMatchedTranslocationFeatures = getMatchedTranslocationFeatures;
|
|
9
|
+
exports.getMatchedPairedFeatures = getMatchedPairedFeatures;
|
|
9
10
|
const util_1 = require("@jbrowse/core/util");
|
|
10
11
|
const vcf_1 = require("@gmod/vcf");
|
|
11
12
|
// this finds candidate alignment features, aimed at plotting split reads from
|
|
@@ -115,3 +116,28 @@ function getMatchedTranslocationFeatures(feats) {
|
|
|
115
116
|
}
|
|
116
117
|
return ret;
|
|
117
118
|
}
|
|
119
|
+
// Getting "matched" TRA means just return all TRA
|
|
120
|
+
function getMatchedPairedFeatures(feats) {
|
|
121
|
+
const candidates = new Map();
|
|
122
|
+
const alreadySeen = new Set();
|
|
123
|
+
for (const f of feats.values()) {
|
|
124
|
+
if (!alreadySeen.has(f.id()) && f.get('type') === 'paired_feature') {
|
|
125
|
+
const r1 = f.id().replace('-r1', '');
|
|
126
|
+
const r2 = f.id().replace('-r2', '');
|
|
127
|
+
if (f.id().endsWith('-r1')) {
|
|
128
|
+
if (!candidates.get(r1)) {
|
|
129
|
+
candidates.set(r1, []);
|
|
130
|
+
}
|
|
131
|
+
candidates.get(r1).push(f);
|
|
132
|
+
}
|
|
133
|
+
else if (f.id().endsWith('-r2')) {
|
|
134
|
+
if (!candidates.get(r2)) {
|
|
135
|
+
candidates.set(r2, []);
|
|
136
|
+
}
|
|
137
|
+
candidates.get(r2).push(f);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
alreadySeen.add(f.id());
|
|
141
|
+
}
|
|
142
|
+
return [...candidates.values()].filter(v => v.length > 1);
|
|
143
|
+
}
|
|
@@ -282,9 +282,14 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
282
282
|
} & {
|
|
283
283
|
/**
|
|
284
284
|
* #getter
|
|
285
|
-
* Find all track ids that match across multiple views
|
|
285
|
+
* Find all track ids that match across multiple views, or return just
|
|
286
|
+
* the single view's track if only a single row is used
|
|
286
287
|
*/
|
|
287
|
-
readonly matchedTracks:
|
|
288
|
+
readonly matchedTracks: (import("mobx-state-tree").IMSTArray<import("mobx-state-tree").IAnyType> & import("mobx-state-tree").IStateTreeNode<import("mobx-state-tree").IArrayType<import("mobx-state-tree").IAnyType>>) | {
|
|
289
|
+
configuration: {
|
|
290
|
+
trackId: string;
|
|
291
|
+
};
|
|
292
|
+
}[];
|
|
288
293
|
/**
|
|
289
294
|
* #method
|
|
290
295
|
* Get tracks with a given trackId across multiple views
|
|
@@ -292,11 +297,15 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
292
297
|
getMatchedTracks(trackConfigId: string): any[];
|
|
293
298
|
/**
|
|
294
299
|
* #method
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
* since they do not have a mate e.g. they are one sided
|
|
300
|
+
* Translocation features are handled differently since they do not have
|
|
301
|
+
* a mate e.g. they are one sided
|
|
298
302
|
*/
|
|
299
303
|
hasTranslocations(trackConfigId: string): Feature | undefined;
|
|
304
|
+
/**
|
|
305
|
+
* #method
|
|
306
|
+
* Paired features similar to breakends, but simpler, like BEDPE
|
|
307
|
+
*/
|
|
308
|
+
hasPairedFeatures(trackConfigId: string): Feature | undefined;
|
|
300
309
|
/**
|
|
301
310
|
* #method
|
|
302
311
|
* Get a composite map of featureId-\>feature map for a track across
|
|
@@ -338,6 +347,10 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
338
347
|
* #action
|
|
339
348
|
*/
|
|
340
349
|
setMatchedTrackFeatures(obj: Record<string, Feature[][]>): void;
|
|
350
|
+
/**
|
|
351
|
+
* #action
|
|
352
|
+
*/
|
|
353
|
+
reverseViewOrder(): void;
|
|
341
354
|
} & {
|
|
342
355
|
afterAttach(): void;
|
|
343
356
|
/**
|
|
@@ -346,6 +359,12 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
346
359
|
menuItems(): ({
|
|
347
360
|
label: string;
|
|
348
361
|
subMenu: import("@jbrowse/core/ui").MenuItem[];
|
|
362
|
+
} | {
|
|
363
|
+
label: string;
|
|
364
|
+
onClick: () => void;
|
|
365
|
+
type?: undefined;
|
|
366
|
+
checked?: undefined;
|
|
367
|
+
icon?: undefined;
|
|
349
368
|
} | {
|
|
350
369
|
label: string;
|
|
351
370
|
type: string;
|
|
@@ -46,9 +46,13 @@ function calc(track, f) {
|
|
|
46
46
|
return (_b = (_a = track.displays[0]).searchFeatureByID) === null || _b === void 0 ? void 0 : _b.call(_a, f.id());
|
|
47
47
|
}
|
|
48
48
|
async function getBlockFeatures(model, track) {
|
|
49
|
+
var _a;
|
|
49
50
|
const { views } = model;
|
|
50
51
|
const { rpcManager, assemblyManager } = (0, util_1.getSession)(model);
|
|
51
|
-
const assemblyName = model.views[0].assemblyNames[0];
|
|
52
|
+
const assemblyName = (_a = model.views[0]) === null || _a === void 0 ? void 0 : _a.assemblyNames[0];
|
|
53
|
+
if (!assemblyName) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
52
56
|
const assembly = await assemblyManager.waitForAssembly(assemblyName);
|
|
53
57
|
if (!assembly) {
|
|
54
58
|
return undefined; // throw new Error(`assembly not found: "${assemblyName}"`)
|
|
@@ -119,10 +123,13 @@ function stateModelFactory(pluginManager) {
|
|
|
119
123
|
.views(self => ({
|
|
120
124
|
/**
|
|
121
125
|
* #getter
|
|
122
|
-
* Find all track ids that match across multiple views
|
|
126
|
+
* Find all track ids that match across multiple views, or return just
|
|
127
|
+
* the single view's track if only a single row is used
|
|
123
128
|
*/
|
|
124
129
|
get matchedTracks() {
|
|
125
|
-
return
|
|
130
|
+
return self.views.length === 1
|
|
131
|
+
? self.views[0].tracks
|
|
132
|
+
: (0, util_2.intersect)(elt => elt.configuration.trackId, ...self.views.map(view => view.tracks));
|
|
126
133
|
},
|
|
127
134
|
/**
|
|
128
135
|
* #method
|
|
@@ -135,13 +142,19 @@ function stateModelFactory(pluginManager) {
|
|
|
135
142
|
},
|
|
136
143
|
/**
|
|
137
144
|
* #method
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* since they do not have a mate e.g. they are one sided
|
|
145
|
+
* Translocation features are handled differently since they do not have
|
|
146
|
+
* a mate e.g. they are one sided
|
|
141
147
|
*/
|
|
142
148
|
hasTranslocations(trackConfigId) {
|
|
143
149
|
return [...this.getTrackFeatures(trackConfigId).values()].find(f => f.get('type') === 'translocation');
|
|
144
150
|
},
|
|
151
|
+
/**
|
|
152
|
+
* #method
|
|
153
|
+
* Paired features similar to breakends, but simpler, like BEDPE
|
|
154
|
+
*/
|
|
155
|
+
hasPairedFeatures(trackConfigId) {
|
|
156
|
+
return [...this.getTrackFeatures(trackConfigId).values()].find(f => f.get('type') === 'paired_feature');
|
|
157
|
+
},
|
|
145
158
|
/**
|
|
146
159
|
* #method
|
|
147
160
|
* Get a composite map of featureId-\>feature map for a track across
|
|
@@ -239,6 +252,12 @@ function stateModelFactory(pluginManager) {
|
|
|
239
252
|
setMatchedTrackFeatures(obj) {
|
|
240
253
|
self.matchedTrackFeatures = obj;
|
|
241
254
|
},
|
|
255
|
+
/**
|
|
256
|
+
* #action
|
|
257
|
+
*/
|
|
258
|
+
reverseViewOrder() {
|
|
259
|
+
self.views.reverse();
|
|
260
|
+
},
|
|
242
261
|
}))
|
|
243
262
|
.actions(self => ({
|
|
244
263
|
afterAttach() {
|
|
@@ -265,7 +284,16 @@ function stateModelFactory(pluginManager) {
|
|
|
265
284
|
return [
|
|
266
285
|
...self.views
|
|
267
286
|
.map((view, idx) => [idx, view.menuItems()])
|
|
268
|
-
.map(f => ({
|
|
287
|
+
.map(f => ({
|
|
288
|
+
label: `Row ${f[0] + 1} view menu`,
|
|
289
|
+
subMenu: f[1],
|
|
290
|
+
})),
|
|
291
|
+
{
|
|
292
|
+
label: 'Reverse view order',
|
|
293
|
+
onClick: () => {
|
|
294
|
+
self.reverseViewOrder();
|
|
295
|
+
},
|
|
296
|
+
},
|
|
269
297
|
{
|
|
270
298
|
label: 'Show intra-view links',
|
|
271
299
|
type: 'checkbox',
|
|
@@ -38,10 +38,7 @@ async function renderToSvg(model, opts) {
|
|
|
38
38
|
const trackLabelMaxLen = (0, util_2.getTrackNameMaxLen)(views, fontSize, session) + 40;
|
|
39
39
|
const trackLabelOffset = trackLabels === 'left' ? trackLabelMaxLen : 0;
|
|
40
40
|
const textOffset = trackLabels === 'offset' ? textHeight : 0;
|
|
41
|
-
const trackOffsets = [
|
|
42
|
-
(0, util_2.getTrackOffsets)(views[0], textOffset, fontSize + offset),
|
|
43
|
-
(0, util_2.getTrackOffsets)(views[1], textOffset, fontSize + heights[0] + offset),
|
|
44
|
-
];
|
|
41
|
+
const trackOffsets = views.map((view, idx) => (0, util_2.getTrackOffsets)(view, textOffset, fontSize + (idx > 0 ? heights[idx - 1] : 0) + offset));
|
|
45
42
|
const w = width + trackLabelOffset;
|
|
46
43
|
const t = (0, ui_1.createJBrowseTheme)(theme);
|
|
47
44
|
// the xlink namespace is used for rendering <image> tag
|
|
@@ -49,16 +46,16 @@ async function renderToSvg(model, opts) {
|
|
|
49
46
|
react_1.default.createElement(Wrapper, null,
|
|
50
47
|
react_1.default.createElement("svg", { width: width, height: totalHeightSvg, xmlns: "http://www.w3.org/2000/svg", xmlnsXlink: "http://www.w3.org/1999/xlink", viewBox: [0, 0, w + shift * 2, totalHeightSvg].toString() },
|
|
51
48
|
react_1.default.createElement(SVGBackground_1.default, { width: w, height: totalHeightSvg, shift: shift }),
|
|
52
|
-
react_1.default.createElement("g", { transform: `translate(${shift} ${fontSize})` },
|
|
49
|
+
views[0] ? (react_1.default.createElement("g", { transform: `translate(${shift} ${fontSize})` },
|
|
53
50
|
react_1.default.createElement("g", { transform: `translate(${trackLabelOffset})` },
|
|
54
51
|
react_1.default.createElement("text", { x: 0, fontSize: fontSize, fill: t.palette.text.primary }, views[0].assemblyNames.join(', ')),
|
|
55
52
|
react_1.default.createElement(plugin_linear_genome_view_1.SVGRuler, { model: displayResults[0].view, fontSize: fontSize })),
|
|
56
|
-
react_1.default.createElement(plugin_linear_genome_view_1.SVGTracks, { textHeight: textHeight, trackLabels: trackLabels, fontSize: fontSize, model: displayResults[0].view, displayResults: displayResults[0].data, offset: offset, trackLabelOffset: trackLabelOffset })),
|
|
57
|
-
react_1.default.createElement("g", { transform: `translate(${shift} ${fontSize + heights[0]})` },
|
|
53
|
+
react_1.default.createElement(plugin_linear_genome_view_1.SVGTracks, { textHeight: textHeight, trackLabels: trackLabels, fontSize: fontSize, model: displayResults[0].view, displayResults: displayResults[0].data, offset: offset, trackLabelOffset: trackLabelOffset }))) : null,
|
|
54
|
+
views[1] ? (react_1.default.createElement("g", { transform: `translate(${shift} ${fontSize + heights[0]})` },
|
|
58
55
|
react_1.default.createElement("g", { transform: `translate(${trackLabelOffset})` },
|
|
59
56
|
react_1.default.createElement("text", { x: 0, fontSize: fontSize, fill: t.palette.text.primary }, views[1].assemblyNames.join(', ')),
|
|
60
57
|
react_1.default.createElement(plugin_linear_genome_view_1.SVGRuler, { model: displayResults[1].view, fontSize: fontSize })),
|
|
61
|
-
react_1.default.createElement(plugin_linear_genome_view_1.SVGTracks, { textHeight: textHeight, trackLabels: trackLabels, fontSize: fontSize, model: displayResults[1].view, displayResults: displayResults[1].data, offset: offset, trackLabelOffset: trackLabelOffset })),
|
|
58
|
+
react_1.default.createElement(plugin_linear_genome_view_1.SVGTracks, { textHeight: textHeight, trackLabels: trackLabels, fontSize: fontSize, model: displayResults[1].view, displayResults: displayResults[1].data, offset: offset, trackLabelOffset: trackLabelOffset }))) : null,
|
|
62
59
|
react_1.default.createElement("defs", null,
|
|
63
60
|
react_1.default.createElement("clipPath", { id: "clip-bsv" },
|
|
64
61
|
react_1.default.createElement("rect", { x: 0, y: 0, width: width, height: totalHeightSvg }))),
|
|
@@ -11,6 +11,7 @@ interface Display {
|
|
|
11
11
|
interface Track {
|
|
12
12
|
displays: Display[];
|
|
13
13
|
}
|
|
14
|
+
export declare function heightFromSpecificLevel(views: LGV[], trackId: string, level: number, getYPosOverride?: (trackId: string, level: number) => number): number;
|
|
14
15
|
export declare function getPxFromCoordinate(view: LGV, refName: string, coord: number): number;
|
|
15
16
|
export declare function yPos(trackId: string, level: number, views: LGV[], tracks: Track[], c: LayoutRecord, getYPosOverride?: (trackId: string, level: number) => number): number;
|
|
16
17
|
export declare const useNextFrame: (variable: unknown) => void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useNextFrame = void 0;
|
|
4
|
+
exports.heightFromSpecificLevel = heightFromSpecificLevel;
|
|
4
5
|
exports.getPxFromCoordinate = getPxFromCoordinate;
|
|
5
6
|
exports.yPos = yPos;
|
|
6
7
|
exports.intersect = intersect;
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { Feature, Region } from '@jbrowse/core/util';
|
|
2
2
|
import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType';
|
|
3
3
|
import { IStateTreeNode } from 'mobx-state-tree';
|
|
4
|
+
import { Assembly } from '@jbrowse/core/assemblyManager/assembly';
|
|
4
5
|
export default class BreakpointSplitViewType extends ViewType {
|
|
5
|
-
|
|
6
|
+
getBreakendCoveringRegions({ feature, assembly, }: {
|
|
7
|
+
feature: Feature;
|
|
8
|
+
assembly: Assembly;
|
|
9
|
+
}): {
|
|
10
|
+
pos: number;
|
|
11
|
+
refName: string | undefined;
|
|
12
|
+
mateRefName: string | undefined;
|
|
13
|
+
matePos: number;
|
|
14
|
+
};
|
|
15
|
+
singleLevelSnapshotFromBreakendFeature(feature: Feature, view: {
|
|
6
16
|
displayedRegions: Region[];
|
|
7
17
|
} & IStateTreeNode): {
|
|
8
18
|
type: string;
|
|
@@ -21,4 +31,22 @@ export default class BreakpointSplitViewType extends ViewType {
|
|
|
21
31
|
displayName: string;
|
|
22
32
|
featureData: unknown;
|
|
23
33
|
};
|
|
34
|
+
snapshotFromBreakendFeature(feature: Feature, view: {
|
|
35
|
+
displayedRegions: Region[];
|
|
36
|
+
} & IStateTreeNode): {
|
|
37
|
+
type: string;
|
|
38
|
+
views: {
|
|
39
|
+
type: string;
|
|
40
|
+
displayedRegions: {
|
|
41
|
+
start: number;
|
|
42
|
+
end: number;
|
|
43
|
+
refName: string;
|
|
44
|
+
assemblyName: string;
|
|
45
|
+
}[];
|
|
46
|
+
hideHeader: boolean;
|
|
47
|
+
bpPerPx: number;
|
|
48
|
+
offsetPx: number;
|
|
49
|
+
}[];
|
|
50
|
+
displayName: string;
|
|
51
|
+
};
|
|
24
52
|
}
|
|
@@ -2,49 +2,29 @@ import { getSession } from '@jbrowse/core/util';
|
|
|
2
2
|
import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType';
|
|
3
3
|
import { parseBreakend } from '@gmod/vcf';
|
|
4
4
|
export default class BreakpointSplitViewType extends ViewType {
|
|
5
|
-
|
|
5
|
+
getBreakendCoveringRegions({ feature, assembly, }) {
|
|
6
6
|
var _a;
|
|
7
7
|
const alt = (_a = feature.get('ALT')) === null || _a === void 0 ? void 0 : _a[0];
|
|
8
8
|
const bnd = alt ? parseBreakend(alt) : undefined;
|
|
9
9
|
const startPos = feature.get('start');
|
|
10
|
+
const refName = feature.get('refName');
|
|
10
11
|
let endPos;
|
|
11
|
-
const bpPerPx = 10;
|
|
12
|
-
// TODO: Figure this out for multiple assembly names
|
|
13
|
-
const { assemblyName } = view.displayedRegions[0];
|
|
14
|
-
const { assemblyManager } = getSession(view);
|
|
15
|
-
const assembly = assemblyManager.get(assemblyName);
|
|
16
|
-
if (!assembly) {
|
|
17
|
-
throw new Error(`assembly ${assemblyName} not found`);
|
|
18
|
-
}
|
|
19
|
-
if (!assembly.regions) {
|
|
20
|
-
throw new Error(`assembly ${assemblyName} regions not loaded`);
|
|
21
|
-
}
|
|
22
|
-
const featureRefName = assembly.getCanonicalRefName(feature.get('refName'));
|
|
23
|
-
const topRegion = assembly.regions.find(f => f.refName === featureRefName);
|
|
24
12
|
let mateRefName;
|
|
25
|
-
let startMod = 0;
|
|
26
|
-
let endMod = 0;
|
|
27
13
|
// a VCF breakend feature
|
|
28
14
|
if (alt === '<TRA>') {
|
|
29
15
|
const INFO = feature.get('INFO');
|
|
30
16
|
endPos = INFO.END[0] - 1;
|
|
31
|
-
mateRefName =
|
|
17
|
+
mateRefName = INFO.CHR2[0];
|
|
32
18
|
}
|
|
33
19
|
else if (bnd === null || bnd === void 0 ? void 0 : bnd.MatePosition) {
|
|
34
20
|
const matePosition = bnd.MatePosition.split(':');
|
|
35
21
|
endPos = +matePosition[1] - 1;
|
|
36
|
-
mateRefName =
|
|
37
|
-
if (bnd.Join === 'left') {
|
|
38
|
-
startMod = -1;
|
|
39
|
-
}
|
|
40
|
-
if (bnd.MateDirection === 'left') {
|
|
41
|
-
endMod = -1;
|
|
42
|
-
}
|
|
22
|
+
mateRefName = matePosition[0];
|
|
43
23
|
}
|
|
44
24
|
else if (feature.get('mate')) {
|
|
45
25
|
// a generic 'mate' feature
|
|
46
26
|
const mate = feature.get('mate');
|
|
47
|
-
mateRefName =
|
|
27
|
+
mateRefName = mate.refName;
|
|
48
28
|
endPos = mate.start;
|
|
49
29
|
}
|
|
50
30
|
else {
|
|
@@ -53,16 +33,76 @@ export default class BreakpointSplitViewType extends ViewType {
|
|
|
53
33
|
if (!mateRefName) {
|
|
54
34
|
throw new Error(`unable to resolve mate refName ${mateRefName} in reference genome`);
|
|
55
35
|
}
|
|
36
|
+
return {
|
|
37
|
+
pos: startPos,
|
|
38
|
+
refName: assembly.getCanonicalRefName(refName),
|
|
39
|
+
mateRefName: assembly.getCanonicalRefName(mateRefName),
|
|
40
|
+
matePos: endPos,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
singleLevelSnapshotFromBreakendFeature(feature, view) {
|
|
44
|
+
const session = getSession(view);
|
|
45
|
+
const bpPerPx = 10;
|
|
46
|
+
const { assemblyName } = view.displayedRegions[0];
|
|
47
|
+
const { assemblyManager } = session;
|
|
48
|
+
const assembly = assemblyManager.get(assemblyName);
|
|
49
|
+
if (!assembly) {
|
|
50
|
+
throw new Error(`assembly ${assemblyName} not found`);
|
|
51
|
+
}
|
|
52
|
+
if (!assembly.regions) {
|
|
53
|
+
throw new Error(`assembly ${assemblyName} regions not loaded`);
|
|
54
|
+
}
|
|
55
|
+
const { refName, pos: startPos, mateRefName, matePos: endPos, } = this.getBreakendCoveringRegions({
|
|
56
|
+
feature,
|
|
57
|
+
assembly,
|
|
58
|
+
});
|
|
59
|
+
const topRegion = assembly.regions.find(f => f.refName === refName);
|
|
56
60
|
const bottomRegion = assembly.regions.find(f => f.refName === mateRefName);
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
const topMarkedRegion = [{ ...topRegion }, { ...topRegion }];
|
|
62
|
+
const bottomMarkedRegion = [{ ...bottomRegion }, { ...bottomRegion }];
|
|
63
|
+
topMarkedRegion[0].end = startPos;
|
|
64
|
+
topMarkedRegion[1].start = startPos;
|
|
65
|
+
bottomMarkedRegion[0].end = endPos;
|
|
66
|
+
bottomMarkedRegion[1].start = endPos;
|
|
67
|
+
return {
|
|
68
|
+
type: 'BreakpointSplitView',
|
|
69
|
+
views: [
|
|
70
|
+
{
|
|
71
|
+
type: 'LinearGenomeView',
|
|
72
|
+
displayedRegions: topMarkedRegion,
|
|
73
|
+
hideHeader: true,
|
|
74
|
+
bpPerPx,
|
|
75
|
+
offsetPx: (topRegion.start + feature.get('start')) / bpPerPx,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
displayName: `${feature.get('name') || feature.get('id') || 'breakend'} split detail`,
|
|
79
|
+
featureData: undefined,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
snapshotFromBreakendFeature(feature, view) {
|
|
83
|
+
const session = getSession(view);
|
|
84
|
+
const bpPerPx = 10;
|
|
85
|
+
const { assemblyName } = view.displayedRegions[0];
|
|
86
|
+
const { assemblyManager } = session;
|
|
87
|
+
const assembly = assemblyManager.get(assemblyName);
|
|
88
|
+
if (!assembly) {
|
|
89
|
+
throw new Error(`assembly ${assemblyName} not found`);
|
|
90
|
+
}
|
|
91
|
+
if (!assembly.regions) {
|
|
92
|
+
throw new Error(`assembly ${assemblyName} regions not loaded`);
|
|
59
93
|
}
|
|
94
|
+
const { refName, pos: startPos, mateRefName, matePos: endPos, } = this.getBreakendCoveringRegions({
|
|
95
|
+
feature,
|
|
96
|
+
assembly,
|
|
97
|
+
});
|
|
98
|
+
const topRegion = assembly.regions.find(f => f.refName === refName);
|
|
99
|
+
const bottomRegion = assembly.regions.find(f => f.refName === mateRefName);
|
|
60
100
|
const topMarkedRegion = [{ ...topRegion }, { ...topRegion }];
|
|
61
101
|
const bottomMarkedRegion = [{ ...bottomRegion }, { ...bottomRegion }];
|
|
62
|
-
topMarkedRegion[0].end = startPos
|
|
63
|
-
topMarkedRegion[1].start = startPos
|
|
64
|
-
bottomMarkedRegion[0].end = endPos
|
|
65
|
-
bottomMarkedRegion[1].start = endPos
|
|
102
|
+
topMarkedRegion[0].end = startPos;
|
|
103
|
+
topMarkedRegion[1].start = startPos;
|
|
104
|
+
bottomMarkedRegion[0].end = endPos;
|
|
105
|
+
bottomMarkedRegion[1].start = endPos;
|
|
66
106
|
return {
|
|
67
107
|
type: 'BreakpointSplitView',
|
|
68
108
|
views: [
|
|
@@ -82,7 +122,6 @@ export default class BreakpointSplitViewType extends ViewType {
|
|
|
82
122
|
},
|
|
83
123
|
],
|
|
84
124
|
displayName: `${feature.get('name') || feature.get('id') || 'breakend'} split detail`,
|
|
85
|
-
featureData: undefined,
|
|
86
125
|
};
|
|
87
126
|
}
|
|
88
127
|
}
|
|
@@ -5,7 +5,8 @@ import { useTheme } from '@mui/material';
|
|
|
5
5
|
import { getSession, getStrokeProps } from '@jbrowse/core/util';
|
|
6
6
|
// locals
|
|
7
7
|
import { getBadlyPairedAlignments, getMatchedAlignmentFeatures, hasPairedReads, } from './util';
|
|
8
|
-
import { yPos, useNextFrame, getPxFromCoordinate } from '../util';
|
|
8
|
+
import { yPos, useNextFrame, getPxFromCoordinate, heightFromSpecificLevel, } from '../util';
|
|
9
|
+
import { getLongReadOrientationAbnormal, getLongReadOrientationColorOrDefault, getPairedOrientationColor, isAbnormalOrientation, } from './getOrientationColor';
|
|
9
10
|
const [LEFT, , RIGHT] = [0, 1, 2, 3];
|
|
10
11
|
const AlignmentConnections = observer(function ({ model, trackId, parentRef, getTrackYPosOverride, }) {
|
|
11
12
|
const { views, showIntraviewLinks } = model;
|
|
@@ -13,7 +14,8 @@ const AlignmentConnections = observer(function ({ model, trackId, parentRef, get
|
|
|
13
14
|
const session = getSession(model);
|
|
14
15
|
const snap = getSnapshot(model);
|
|
15
16
|
const { assemblyManager } = session;
|
|
16
|
-
const
|
|
17
|
+
const v0 = views[0];
|
|
18
|
+
const assembly = v0 ? assemblyManager.get(v0.assemblyNames[0]) : undefined;
|
|
17
19
|
useNextFrame(snap);
|
|
18
20
|
const allFeatures = model.getTrackFeatures(trackId);
|
|
19
21
|
const hasPaired = useMemo(() => hasPairedReads(allFeatures), [allFeatures]);
|
|
@@ -34,7 +36,7 @@ const AlignmentConnections = observer(function ({ model, trackId, parentRef, get
|
|
|
34
36
|
const rect = parentRef.current.getBoundingClientRect();
|
|
35
37
|
yOffset = rect.top;
|
|
36
38
|
}
|
|
37
|
-
return assembly ? (React.createElement("g", { fill: "none",
|
|
39
|
+
return assembly ? (React.createElement("g", { fill: "none", "data-testid": layoutMatches.length ? `${trackId}-loaded` : trackId }, layoutMatches.map(chunk => {
|
|
38
40
|
const ret = [];
|
|
39
41
|
// we follow a path in the list of chunks, not from top to bottom, just in series
|
|
40
42
|
// following x1,y1 -> x2,y2
|
|
@@ -45,7 +47,7 @@ const AlignmentConnections = observer(function ({ model, trackId, parentRef, get
|
|
|
45
47
|
console.warn('received null layout for a overlay feature');
|
|
46
48
|
return null;
|
|
47
49
|
}
|
|
48
|
-
// disable rendering connections in a single
|
|
50
|
+
// disable rendering connections in a single row
|
|
49
51
|
if (!showIntraviewLinks && level1 === level2) {
|
|
50
52
|
return null;
|
|
51
53
|
}
|
|
@@ -54,8 +56,25 @@ const AlignmentConnections = observer(function ({ model, trackId, parentRef, get
|
|
|
54
56
|
if (!f1ref || !f2ref) {
|
|
55
57
|
throw new Error(`unable to find ref for ${f1ref || f2ref}`);
|
|
56
58
|
}
|
|
59
|
+
const r = {
|
|
60
|
+
pair_orientation: f1.get('pair_orientation'),
|
|
61
|
+
};
|
|
57
62
|
const s1 = f1.get('strand');
|
|
58
63
|
const s2 = f2.get('strand');
|
|
64
|
+
const sameRef = f1ref === f2ref;
|
|
65
|
+
const checkOrientation = sameRef;
|
|
66
|
+
let orientationColor = '';
|
|
67
|
+
let isAbnormal = false;
|
|
68
|
+
if (checkOrientation) {
|
|
69
|
+
if (hasPaired) {
|
|
70
|
+
orientationColor = getPairedOrientationColor(r);
|
|
71
|
+
isAbnormal = isAbnormalOrientation(r);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
orientationColor = getLongReadOrientationColorOrDefault(s1, s2);
|
|
75
|
+
isAbnormal = getLongReadOrientationAbnormal(s1, s2);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
59
78
|
const p1 = c1[s1 === -1 ? LEFT : RIGHT];
|
|
60
79
|
const sn1 = s2 === -1;
|
|
61
80
|
const p2 = hasPaired ? c2[sn1 ? LEFT : RIGHT] : c2[sn1 ? RIGHT : LEFT];
|
|
@@ -63,11 +82,20 @@ const AlignmentConnections = observer(function ({ model, trackId, parentRef, get
|
|
|
63
82
|
const x2 = getPxFromCoordinate(views[level2], f2ref, p2);
|
|
64
83
|
const reversed1 = views[level1].pxToBp(x1).reversed;
|
|
65
84
|
const reversed2 = views[level2].pxToBp(x2).reversed;
|
|
85
|
+
const rf1 = reversed1 ? -1 : 1;
|
|
86
|
+
const rf2 = reversed2 ? -1 : 1;
|
|
66
87
|
const tracks = views.map(v => v.getTrack(trackId));
|
|
67
88
|
const y1 = yPos(trackId, level1, views, tracks, c1, getTrackYPosOverride) -
|
|
68
89
|
yOffset;
|
|
69
90
|
const y2 = yPos(trackId, level2, views, tracks, c2, getTrackYPosOverride) -
|
|
70
91
|
yOffset;
|
|
92
|
+
const sameLevel = level1 === level2;
|
|
93
|
+
const abnormalSpecialRenderFlag = sameLevel && isAbnormal;
|
|
94
|
+
const trackHeight = abnormalSpecialRenderFlag
|
|
95
|
+
? tracks[level1].displays[0].height
|
|
96
|
+
: 0;
|
|
97
|
+
const pf1 = hasPaired ? -1 : 1;
|
|
98
|
+
const y0 = heightFromSpecificLevel(views, trackId, level1, getTrackYPosOverride);
|
|
71
99
|
// possible todo: use totalCurveHeight to possibly make alternative
|
|
72
100
|
// squiggle if the S is too small
|
|
73
101
|
const path = [
|
|
@@ -75,19 +103,22 @@ const AlignmentConnections = observer(function ({ model, trackId, parentRef, get
|
|
|
75
103
|
x1,
|
|
76
104
|
y1,
|
|
77
105
|
'C',
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
106
|
+
// first bezier x,y
|
|
107
|
+
x1 + 200 * f1.get('strand') * rf1,
|
|
108
|
+
abnormalSpecialRenderFlag
|
|
109
|
+
? Math.min(y0 - yOffset + trackHeight, y1 + trackHeight)
|
|
110
|
+
: y1,
|
|
111
|
+
// second bezier x,y
|
|
112
|
+
x2 - 200 * f2.get('strand') * rf2 * pf1,
|
|
113
|
+
abnormalSpecialRenderFlag
|
|
114
|
+
? Math.min(y0 - yOffset + trackHeight, y2 + trackHeight)
|
|
115
|
+
: y2,
|
|
116
|
+
// third bezier x,y
|
|
86
117
|
x2,
|
|
87
118
|
y2,
|
|
88
119
|
].join(' ');
|
|
89
120
|
const id = `${f1.id()}-${f2.id()}`;
|
|
90
|
-
ret.push(React.createElement("path", { d: path, key: id, "data-testid": "r1", strokeWidth: mouseoverElt === id ? 5 : 1, onClick: () => {
|
|
121
|
+
ret.push(React.createElement("path", { d: path, key: id, "data-testid": "r1", strokeWidth: mouseoverElt === id ? 5 : 1, ...getStrokeProps(orientationColor || theme.palette.text.disabled), onClick: () => {
|
|
91
122
|
var _a, _b;
|
|
92
123
|
const featureWidget = (_a = session.addWidget) === null || _a === void 0 ? void 0 : _a.call(session, 'BreakpointAlignmentsWidget', 'breakpointAlignments', {
|
|
93
124
|
featureData: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { observer } from 'mobx-react';
|
|
3
|
-
import { getEnv } from '
|
|
3
|
+
import { getEnv } from '@jbrowse/core/util';
|
|
4
4
|
import { makeStyles } from 'tss-react/mui';
|
|
5
5
|
import BreakpointSplitViewOverlay from './BreakpointSplitViewOverlay';
|
|
6
6
|
const useStyles = makeStyles()(theme => ({
|
|
@@ -14,22 +14,24 @@ const useStyles = makeStyles()(theme => ({
|
|
|
14
14
|
content: {
|
|
15
15
|
gridArea: '1/1',
|
|
16
16
|
},
|
|
17
|
+
rel: {
|
|
18
|
+
position: 'relative',
|
|
19
|
+
},
|
|
17
20
|
}));
|
|
18
21
|
const BreakpointSplitViewLevels = observer(function ({ model, }) {
|
|
19
22
|
const { classes } = useStyles();
|
|
20
23
|
const { views } = model;
|
|
21
24
|
const { pluginManager } = getEnv(model);
|
|
22
25
|
return (React.createElement("div", { className: classes.content },
|
|
23
|
-
React.createElement("div", {
|
|
26
|
+
React.createElement("div", { className: classes.rel }, views.map((view, idx) => {
|
|
24
27
|
const { ReactComponent } = pluginManager.getViewType(view.type);
|
|
25
28
|
const viewComponent = React.createElement(ReactComponent, { key: view.id, model: view });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
];
|
|
29
|
+
return idx === views.length - 1
|
|
30
|
+
? viewComponent
|
|
31
|
+
: [
|
|
32
|
+
viewComponent,
|
|
33
|
+
React.createElement("div", { key: `${view.id}-divider`, className: classes.viewDivider }),
|
|
34
|
+
];
|
|
33
35
|
}))));
|
|
34
36
|
});
|
|
35
37
|
const BreakpointSplitView = observer(function ({ model, }) {
|
|
@@ -23,11 +23,12 @@ const BreakpointSplitViewOverlay = observer(function ({ model, }) {
|
|
|
23
23
|
zIndex: 10,
|
|
24
24
|
pointerEvents: interactToggled ? undefined : 'none',
|
|
25
25
|
} }, matchedTracks.map(track => (
|
|
26
|
-
// note: we must pass ref down, because
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
26
|
+
// note: we must pass ref down, because:
|
|
27
|
+
// - the child component needs to getBoundingClientRect on the this
|
|
28
|
+
// components SVG, and...
|
|
29
|
+
// - we cannot rely on using getBoundingClientRect in this component
|
|
30
|
+
// to make sure this works because if it gets shifted around by
|
|
31
|
+
// another element, this will not re-render necessarily
|
|
31
32
|
React.createElement(Overlay, { parentRef: ref, key: track.configuration.trackId, model: model, trackId: track.configuration.trackId }))))));
|
|
32
33
|
});
|
|
33
34
|
export default BreakpointSplitViewOverlay;
|
|
@@ -3,16 +3,21 @@ import { observer } from 'mobx-react';
|
|
|
3
3
|
import AlignmentConnections from './AlignmentConnections';
|
|
4
4
|
import Breakends from './Breakends';
|
|
5
5
|
import Translocations from './Translocations';
|
|
6
|
+
import PairedFeatures from './PairedFeatures';
|
|
6
7
|
const Overlay = observer(function (props) {
|
|
7
8
|
var _a, _b;
|
|
8
9
|
const { model, trackId } = props;
|
|
9
10
|
const tracks = model.getMatchedTracks(trackId);
|
|
11
|
+
// curvy line type arcs
|
|
10
12
|
if (((_a = tracks[0]) === null || _a === void 0 ? void 0 : _a.type) === 'AlignmentsTrack') {
|
|
11
13
|
return React.createElement(AlignmentConnections, { ...props });
|
|
12
14
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
// translocation type arcs
|
|
16
|
+
else if (((_b = tracks[0]) === null || _b === void 0 ? void 0 : _b.type) === 'VariantTrack') {
|
|
17
|
+
return model.hasTranslocations(trackId) ? (React.createElement(Translocations, { ...props })) : model.hasPairedFeatures(trackId) ? (React.createElement(PairedFeatures, { ...props })) : (React.createElement(Breakends, { ...props }));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
return null;
|
|
15
21
|
}
|
|
16
|
-
return null;
|
|
17
22
|
});
|
|
18
23
|
export default Overlay;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { BreakpointViewModel } from '../model';
|
|
3
|
+
declare const PairedFeatures: ({ model, trackId, parentRef: ref, getTrackYPosOverride, }: {
|
|
4
|
+
model: BreakpointViewModel;
|
|
5
|
+
trackId: string;
|
|
6
|
+
parentRef: React.RefObject<SVGSVGElement>;
|
|
7
|
+
getTrackYPosOverride?: (trackId: string, level: number) => number;
|
|
8
|
+
}) => React.JSX.Element | null;
|
|
9
|
+
export default PairedFeatures;
|