@jbrowse/plugin-breakpoint-split-view 3.6.5 → 4.0.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/esm/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js +1 -1
- package/esm/BreakpointAlignmentsFeatureDetail/index.js +2 -2
- package/esm/BreakpointGetFeatures/BreakpointGetFeatures.d.ts +51 -0
- package/esm/BreakpointGetFeatures/BreakpointGetFeatures.js +57 -0
- package/esm/BreakpointGetFeatures/index.d.ts +2 -0
- package/esm/BreakpointGetFeatures/index.js +4 -0
- package/esm/BreakpointSplitView/BreakpointSplitView.js +33 -47
- package/esm/BreakpointSplitView/components/AlignmentConnections.d.ts +2 -7
- package/esm/BreakpointSplitView/components/AlignmentConnections.js +20 -25
- package/esm/BreakpointSplitView/components/Breakends.d.ts +2 -7
- package/esm/BreakpointSplitView/components/Breakends.js +28 -59
- package/esm/BreakpointSplitView/components/BreakpointSplitView.d.ts +1 -1
- package/esm/BreakpointSplitView/components/BreakpointSplitView.js +19 -5
- package/esm/BreakpointSplitView/components/BreakpointSplitViewOverlay.d.ts +1 -1
- package/esm/BreakpointSplitView/components/BreakpointSplitViewOverlay.js +3 -3
- package/esm/BreakpointSplitView/components/ExportSvgDialog.d.ts +1 -1
- package/esm/BreakpointSplitView/components/ExportSvgDialog.js +5 -1
- package/esm/BreakpointSplitView/components/Header.d.ts +5 -0
- package/esm/BreakpointSplitView/components/Header.js +47 -0
- package/esm/BreakpointSplitView/components/HeaderSearchBoxes.d.ts +5 -0
- package/esm/BreakpointSplitView/components/HeaderSearchBoxes.js +22 -0
- package/esm/BreakpointSplitView/components/Overlay.d.ts +1 -1
- package/esm/BreakpointSplitView/components/Overlay.js +8 -11
- package/esm/BreakpointSplitView/components/PairedFeatures.d.ts +2 -7
- package/esm/BreakpointSplitView/components/PairedFeatures.js +22 -47
- package/esm/BreakpointSplitView/components/Rubberband.d.ts +6 -0
- package/esm/BreakpointSplitView/components/Rubberband.js +27 -0
- package/esm/BreakpointSplitView/components/RubberbandSpan.d.ts +15 -0
- package/esm/BreakpointSplitView/components/RubberbandSpan.js +30 -0
- package/esm/BreakpointSplitView/components/RubberbandTooltip.d.ts +5 -0
- package/esm/BreakpointSplitView/components/RubberbandTooltip.js +17 -0
- package/esm/BreakpointSplitView/components/Translocations.d.ts +2 -7
- package/esm/BreakpointSplitView/components/Translocations.js +23 -58
- package/esm/BreakpointSplitView/components/VerticalGuide.d.ts +6 -0
- package/esm/BreakpointSplitView/components/VerticalGuide.js +24 -0
- package/esm/BreakpointSplitView/components/overlayUtils.d.ts +24 -0
- package/esm/BreakpointSplitView/components/overlayUtils.js +47 -0
- package/esm/BreakpointSplitView/components/rubberbandUtil.d.ts +4 -0
- package/esm/BreakpointSplitView/components/rubberbandUtil.js +3 -0
- package/esm/BreakpointSplitView/components/useRangeSelect.d.ts +59 -0
- package/esm/BreakpointSplitView/components/useRangeSelect.js +121 -0
- package/esm/BreakpointSplitView/components/util.js +1 -2
- package/esm/BreakpointSplitView/getClip.js +4 -4
- package/esm/BreakpointSplitView/index.js +2 -2
- package/esm/BreakpointSplitView/model.d.ts +403 -116
- package/esm/BreakpointSplitView/model.js +117 -41
- package/esm/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.d.ts +2 -2
- package/esm/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js +8 -8
- package/esm/BreakpointSplitView/types.d.ts +8 -0
- package/esm/BreakpointSplitView/util.d.ts +1 -1
- package/esm/BreakpointSplitView/util.js +8 -17
- package/esm/LaunchBreakpointSplitView/index.d.ts +2 -0
- package/esm/LaunchBreakpointSplitView/index.js +12 -0
- package/esm/index.js +7 -6
- package/package.json +28 -34
- package/dist/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.d.ts +0 -10
- package/dist/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js +0 -12
- package/dist/BreakpointAlignmentsFeatureDetail/index.d.ts +0 -2
- package/dist/BreakpointAlignmentsFeatureDetail/index.js +0 -67
- package/dist/BreakpointSplitView/BreakpointSplitView.d.ts +0 -34
- package/dist/BreakpointSplitView/BreakpointSplitView.js +0 -84
- package/dist/BreakpointSplitView/components/AlignmentConnections.d.ts +0 -8
- package/dist/BreakpointSplitView/components/AlignmentConnections.js +0 -133
- package/dist/BreakpointSplitView/components/Breakends.d.ts +0 -8
- package/dist/BreakpointSplitView/components/Breakends.js +0 -95
- package/dist/BreakpointSplitView/components/BreakpointSplitView.d.ts +0 -5
- package/dist/BreakpointSplitView/components/BreakpointSplitView.js +0 -45
- package/dist/BreakpointSplitView/components/BreakpointSplitViewOverlay.d.ts +0 -5
- package/dist/BreakpointSplitView/components/BreakpointSplitViewOverlay.js +0 -33
- package/dist/BreakpointSplitView/components/ExportSvgDialog.d.ts +0 -7
- package/dist/BreakpointSplitView/components/ExportSvgDialog.js +0 -57
- package/dist/BreakpointSplitView/components/Overlay.d.ts +0 -8
- package/dist/BreakpointSplitView/components/Overlay.js +0 -27
- package/dist/BreakpointSplitView/components/PairedFeatures.d.ts +0 -8
- package/dist/BreakpointSplitView/components/PairedFeatures.js +0 -75
- package/dist/BreakpointSplitView/components/Translocations.d.ts +0 -8
- package/dist/BreakpointSplitView/components/Translocations.js +0 -99
- package/dist/BreakpointSplitView/components/getOrientationColor.d.ts +0 -41
- package/dist/BreakpointSplitView/components/getOrientationColor.js +0 -103
- package/dist/BreakpointSplitView/components/util.d.ts +0 -8
- package/dist/BreakpointSplitView/components/util.js +0 -142
- package/dist/BreakpointSplitView/getClip.d.ts +0 -1
- package/dist/BreakpointSplitView/getClip.js +0 -10
- package/dist/BreakpointSplitView/index.d.ts +0 -2
- package/dist/BreakpointSplitView/index.js +0 -52
- package/dist/BreakpointSplitView/model.d.ts +0 -342
- package/dist/BreakpointSplitView/model.js +0 -244
- package/dist/BreakpointSplitView/svgcomponents/SVGBackground.d.ts +0 -5
- package/dist/BreakpointSplitView/svgcomponents/SVGBackground.js +0 -10
- package/dist/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.d.ts +0 -5
- package/dist/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js +0 -41
- package/dist/BreakpointSplitView/svgcomponents/util.d.ts +0 -4
- package/dist/BreakpointSplitView/svgcomponents/util.js +0 -18
- package/dist/BreakpointSplitView/types.d.ts +0 -22
- package/dist/BreakpointSplitView/types.js +0 -2
- package/dist/BreakpointSplitView/util.d.ts +0 -28
- package/dist/BreakpointSplitView/util.js +0 -67
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -20
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { BaseAttributes, BaseCoreDetails, } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail';
|
|
3
3
|
import { Paper } from '@mui/material';
|
|
4
4
|
import { observer } from 'mobx-react';
|
|
5
|
-
const BreakpointAlignmentsFeatureDetail = observer(function ({ model, }) {
|
|
5
|
+
const BreakpointAlignmentsFeatureDetail = observer(function BreakpointAlignmentsFeatureDetail({ model, }) {
|
|
6
6
|
const { featureData } = model;
|
|
7
7
|
const { feature1, feature2 } = structuredClone(featureData);
|
|
8
8
|
return (_jsxs(Paper, { children: [_jsx(BaseCoreDetails, { title: "Feature 1", feature: feature1 }), _jsx(BaseCoreDetails, { title: "Feature 2", feature: feature2 }), _jsx(BaseAttributes, { title: "Feature 1 attributes", feature: feature1 }), _jsx(BaseAttributes, { title: "Feature 2 attributes", feature: feature2 })] }));
|
|
@@ -2,7 +2,7 @@ import { lazy } from 'react';
|
|
|
2
2
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
3
3
|
import { WidgetType } from '@jbrowse/core/pluggableElementTypes';
|
|
4
4
|
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
5
|
-
import { types } from 'mobx-state-tree';
|
|
5
|
+
import { types } from '@jbrowse/mobx-state-tree';
|
|
6
6
|
const configSchema = ConfigurationSchema('BreakpointAlignmentsWidget', {});
|
|
7
7
|
const stateModel = types
|
|
8
8
|
.model('BreakpointAlignmentsWidget', {
|
|
@@ -25,7 +25,7 @@ export default function BreakpointAlignmentsFeatureDetailF(pluginManager) {
|
|
|
25
25
|
heading: 'Breakpoint feature details',
|
|
26
26
|
configSchema,
|
|
27
27
|
stateModel,
|
|
28
|
-
ReactComponent: lazy(() => import(
|
|
28
|
+
ReactComponent: lazy(() => import("./BreakpointAlignmentsFeatureDetail.js")),
|
|
29
29
|
});
|
|
30
30
|
});
|
|
31
31
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import RpcMethodType from '@jbrowse/core/pluggableElementTypes/RpcMethodType';
|
|
2
|
+
import SimpleFeature from '@jbrowse/core/util/simpleFeature';
|
|
3
|
+
import type { RenderArgs } from '@jbrowse/core/rpc/methods/util';
|
|
4
|
+
import type { Region } from '@jbrowse/core/util';
|
|
5
|
+
interface MinimalFeature {
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
uniqueId: string;
|
|
8
|
+
refName: string;
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
strand: number;
|
|
12
|
+
flags: number;
|
|
13
|
+
name?: string;
|
|
14
|
+
id?: string;
|
|
15
|
+
tags?: Record<string, unknown>;
|
|
16
|
+
pair_orientation?: string;
|
|
17
|
+
clipLengthAtStartOfRead?: number;
|
|
18
|
+
INFO?: Record<string, unknown>;
|
|
19
|
+
ALT?: unknown[];
|
|
20
|
+
mate?: Record<string, unknown>;
|
|
21
|
+
type?: string;
|
|
22
|
+
}
|
|
23
|
+
export default class BreakpointGetFeatures extends RpcMethodType {
|
|
24
|
+
name: string;
|
|
25
|
+
deserializeReturn(feats: MinimalFeature[], args: unknown, rpcDriver: string): Promise<SimpleFeature[]>;
|
|
26
|
+
serializeArguments(args: RenderArgs, rpcDriver: string): Promise<RenderArgs>;
|
|
27
|
+
execute(args: {
|
|
28
|
+
sessionId: string;
|
|
29
|
+
regions: Region[];
|
|
30
|
+
adapterConfig: Record<string, unknown>;
|
|
31
|
+
statusCallback: (arg: string) => void;
|
|
32
|
+
stopToken?: string;
|
|
33
|
+
opts?: Record<string, unknown>;
|
|
34
|
+
}, rpcDriver: string): Promise<{
|
|
35
|
+
uniqueId: string;
|
|
36
|
+
start: number;
|
|
37
|
+
end: number;
|
|
38
|
+
refName: string;
|
|
39
|
+
strand: any;
|
|
40
|
+
flags: any;
|
|
41
|
+
name: any;
|
|
42
|
+
id: any;
|
|
43
|
+
tags: any;
|
|
44
|
+
pair_orientation: any;
|
|
45
|
+
type: any;
|
|
46
|
+
ALT: any;
|
|
47
|
+
INFO: any;
|
|
48
|
+
clipLengthAtStartOfRead: number | undefined;
|
|
49
|
+
}[]>;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache';
|
|
2
|
+
import RpcMethodType from '@jbrowse/core/pluggableElementTypes/RpcMethodType';
|
|
3
|
+
import { renameRegionsIfNeeded } from '@jbrowse/core/util';
|
|
4
|
+
import SimpleFeature from '@jbrowse/core/util/simpleFeature';
|
|
5
|
+
import { firstValueFrom } from 'rxjs';
|
|
6
|
+
import { toArray } from 'rxjs/operators';
|
|
7
|
+
const startClip = /(\d+)[SH]$/;
|
|
8
|
+
const endClip = /^(\d+)([SH])/;
|
|
9
|
+
function getClip(cigar, strand) {
|
|
10
|
+
return strand === -1
|
|
11
|
+
? +(startClip.exec(cigar)?.[1] ?? 0)
|
|
12
|
+
: +(endClip.exec(cigar)?.[1] ?? 0);
|
|
13
|
+
}
|
|
14
|
+
export default class BreakpointGetFeatures extends RpcMethodType {
|
|
15
|
+
name = 'BreakpointGetFeatures';
|
|
16
|
+
async deserializeReturn(feats, args, rpcDriver) {
|
|
17
|
+
const superDeserialized = (await super.deserializeReturn(feats, args, rpcDriver));
|
|
18
|
+
return superDeserialized.map(feat => new SimpleFeature(feat));
|
|
19
|
+
}
|
|
20
|
+
async serializeArguments(args, rpcDriver) {
|
|
21
|
+
const { rootModel } = this.pluginManager;
|
|
22
|
+
const assemblyManager = rootModel.session.assemblyManager;
|
|
23
|
+
const renamedArgs = await renameRegionsIfNeeded(assemblyManager, args);
|
|
24
|
+
return super.serializeArguments(renamedArgs, rpcDriver);
|
|
25
|
+
}
|
|
26
|
+
async execute(args, rpcDriver) {
|
|
27
|
+
const { stopToken, statusCallback, sessionId, adapterConfig, regions, opts, } = await this.deserializeArguments(args, rpcDriver);
|
|
28
|
+
const dataAdapter = (await getAdapter(this.pluginManager, sessionId, adapterConfig)).dataAdapter;
|
|
29
|
+
const features = await firstValueFrom(dataAdapter
|
|
30
|
+
.getFeaturesInMultipleRegions(regions, {
|
|
31
|
+
...opts,
|
|
32
|
+
statusCallback,
|
|
33
|
+
stopToken,
|
|
34
|
+
})
|
|
35
|
+
.pipe(toArray()));
|
|
36
|
+
return features.map(feature => {
|
|
37
|
+
const cigar = feature.get('CIGAR');
|
|
38
|
+
const strand = feature.get('strand');
|
|
39
|
+
return {
|
|
40
|
+
uniqueId: feature.id(),
|
|
41
|
+
start: feature.get('start'),
|
|
42
|
+
end: feature.get('end'),
|
|
43
|
+
refName: feature.get('refName'),
|
|
44
|
+
strand,
|
|
45
|
+
flags: feature.get('flags'),
|
|
46
|
+
name: feature.get('name'),
|
|
47
|
+
id: feature.get('id'),
|
|
48
|
+
tags: feature.get('tags'),
|
|
49
|
+
pair_orientation: feature.get('pair_orientation'),
|
|
50
|
+
type: feature.get('type'),
|
|
51
|
+
ALT: feature.get('ALT'),
|
|
52
|
+
INFO: feature.get('INFO'),
|
|
53
|
+
clipLengthAtStartOfRead: cigar && strand !== undefined ? getClip(cigar, strand) : undefined,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,49 +1,34 @@
|
|
|
1
1
|
import { parseBreakend } from '@gmod/vcf';
|
|
2
2
|
import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType';
|
|
3
3
|
import { gatherOverlaps, } from '@jbrowse/core/util';
|
|
4
|
+
function getMateInfo(feature, alt) {
|
|
5
|
+
const bnd = alt ? parseBreakend(alt) : undefined;
|
|
6
|
+
if (alt === '<TRA>') {
|
|
7
|
+
const info = feature.get('INFO');
|
|
8
|
+
return { mateRefName: info.CHR2[0], matePos: info.END[0] - 1 };
|
|
9
|
+
}
|
|
10
|
+
if (bnd?.MatePosition) {
|
|
11
|
+
const [ref, pos] = bnd.MatePosition.split(':');
|
|
12
|
+
return { mateRefName: ref, matePos: +pos - 1 };
|
|
13
|
+
}
|
|
14
|
+
const mate = feature.get('mate');
|
|
15
|
+
if (mate) {
|
|
16
|
+
return { mateRefName: mate.refName, matePos: mate.start };
|
|
17
|
+
}
|
|
18
|
+
return { mateRefName: feature.get('refName'), matePos: feature.get('end') };
|
|
19
|
+
}
|
|
4
20
|
export default class BreakpointSplitViewType extends ViewType {
|
|
5
21
|
getBreakendCoveringRegions({ feature, assembly, }) {
|
|
6
|
-
|
|
7
|
-
const alt =
|
|
8
|
-
const bnd = alt ? parseBreakend(alt) : undefined;
|
|
9
|
-
const startPos = feature.get('start');
|
|
22
|
+
const canonicalize = (ref) => assembly.getCanonicalRefName(ref) || ref;
|
|
23
|
+
const alt = feature.get('ALT')?.[0];
|
|
10
24
|
const refName = feature.get('refName');
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
matePos: INFO.END[0] - 1,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
else if (bnd === null || bnd === void 0 ? void 0 : bnd.MatePosition) {
|
|
22
|
-
const matePosition = bnd.MatePosition.split(':');
|
|
23
|
-
return {
|
|
24
|
-
pos: startPos,
|
|
25
|
-
refName: f(refName),
|
|
26
|
-
mateRefName: f(matePosition[0]),
|
|
27
|
-
matePos: +matePosition[1] - 1,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
else if (feature.get('mate')) {
|
|
31
|
-
const mate = feature.get('mate');
|
|
32
|
-
return {
|
|
33
|
-
pos: startPos,
|
|
34
|
-
refName: f(refName),
|
|
35
|
-
mateRefName: f(mate.refName),
|
|
36
|
-
matePos: mate.start,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
return {
|
|
41
|
-
pos: startPos,
|
|
42
|
-
refName: f(refName),
|
|
43
|
-
mateRefName: f(refName),
|
|
44
|
-
matePos: feature.get('end'),
|
|
45
|
-
};
|
|
46
|
-
}
|
|
25
|
+
const { mateRefName, matePos } = getMateInfo(feature, alt);
|
|
26
|
+
return {
|
|
27
|
+
pos: feature.get('start'),
|
|
28
|
+
refName: canonicalize(refName),
|
|
29
|
+
mateRefName: canonicalize(mateRefName),
|
|
30
|
+
matePos,
|
|
31
|
+
};
|
|
47
32
|
}
|
|
48
33
|
singleLevelSnapshotFromBreakendFeature({ feature, session, assemblyName, }) {
|
|
49
34
|
const { assemblyManager } = session;
|
|
@@ -54,13 +39,14 @@ export default class BreakpointSplitViewType extends ViewType {
|
|
|
54
39
|
if (!assembly.regions) {
|
|
55
40
|
throw new Error(`assembly ${assemblyName} regions not loaded`);
|
|
56
41
|
}
|
|
57
|
-
const coverage = this.getBreakendCoveringRegions({
|
|
58
|
-
feature,
|
|
59
|
-
assembly,
|
|
60
|
-
});
|
|
42
|
+
const coverage = this.getBreakendCoveringRegions({ feature, assembly });
|
|
61
43
|
const { refName, mateRefName } = coverage;
|
|
62
|
-
const topRegion = assembly.regions.find(
|
|
63
|
-
const bottomRegion = assembly.regions.find(
|
|
44
|
+
const topRegion = assembly.regions.find(r => r.refName === refName);
|
|
45
|
+
const bottomRegion = assembly.regions.find(r => r.refName === mateRefName);
|
|
46
|
+
if (!topRegion || !bottomRegion) {
|
|
47
|
+
throw new Error(`could not find regions for ${refName} or ${mateRefName}`);
|
|
48
|
+
}
|
|
49
|
+
const featureName = feature.get('name') || feature.get('id') || 'breakend';
|
|
64
50
|
return {
|
|
65
51
|
coverage,
|
|
66
52
|
snap: {
|
|
@@ -71,7 +57,7 @@ export default class BreakpointSplitViewType extends ViewType {
|
|
|
71
57
|
displayedRegions: gatherOverlaps([topRegion, bottomRegion]),
|
|
72
58
|
},
|
|
73
59
|
],
|
|
74
|
-
displayName: `${
|
|
60
|
+
displayName: `${featureName} split detail`,
|
|
75
61
|
},
|
|
76
62
|
};
|
|
77
63
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
declare const AlignmentConnections: ({ model, trackId, parentRef, getTrackYPosOverride, }:
|
|
3
|
-
model: BreakpointViewModel;
|
|
4
|
-
trackId: string;
|
|
5
|
-
parentRef: React.RefObject<SVGSVGElement | null>;
|
|
6
|
-
getTrackYPosOverride?: (trackId: string, level: number) => number;
|
|
7
|
-
}) => import("react/jsx-runtime").JSX.Element | null;
|
|
1
|
+
import type { OverlayProps } from './overlayUtils.tsx';
|
|
2
|
+
declare const AlignmentConnections: ({ model, trackId, parentRef, getTrackYPosOverride, }: OverlayProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
8
3
|
export default AlignmentConnections;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useState } from 'react';
|
|
3
3
|
import { getSession, getStrokeProps } from '@jbrowse/core/util';
|
|
4
|
+
import { getSnapshot } from '@jbrowse/mobx-state-tree';
|
|
4
5
|
import { useTheme } from '@mui/material';
|
|
5
6
|
import { observer } from 'mobx-react';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
const
|
|
11
|
-
const AlignmentConnections = observer(function ({ model, trackId, parentRef, getTrackYPosOverride, }) {
|
|
7
|
+
import { getLongReadOrientationAbnormal, getLongReadOrientationColorOrDefault, getPairedOrientationColor, isAbnormalOrientation, } from "./getOrientationColor.js";
|
|
8
|
+
import { LEFT, RIGHT, getTestId, getYOffset } from "./overlayUtils.js";
|
|
9
|
+
import { getBadlyPairedAlignments, getMatchedAlignmentFeatures, hasPairedReads, } from "./util.js";
|
|
10
|
+
import { getPxFromCoordinate, heightFromSpecificLevel, useNextFrame, yPos, } from "../util.js";
|
|
11
|
+
const AlignmentConnections = observer(function AlignmentConnections({ model, trackId, parentRef, getTrackYPosOverride, }) {
|
|
12
12
|
const { interactiveOverlay, views, showIntraviewLinks } = model;
|
|
13
13
|
const theme = useTheme();
|
|
14
14
|
const session = getSession(model);
|
|
@@ -20,31 +20,28 @@ const AlignmentConnections = observer(function ({ model, trackId, parentRef, get
|
|
|
20
20
|
const allFeatures = model.getTrackFeatures(trackId);
|
|
21
21
|
const hasPaired = useMemo(() => hasPairedReads(allFeatures), [allFeatures]);
|
|
22
22
|
const layoutMatches = useMemo(() => {
|
|
23
|
-
const
|
|
23
|
+
const matched = hasPaired
|
|
24
24
|
? getBadlyPairedAlignments(allFeatures)
|
|
25
|
-
: getMatchedAlignmentFeatures(allFeatures)
|
|
25
|
+
: getMatchedAlignmentFeatures(allFeatures);
|
|
26
|
+
const layoutMatches = model.getMatchedFeaturesInLayout(trackId, matched);
|
|
26
27
|
if (!hasPaired) {
|
|
27
28
|
for (const m of layoutMatches) {
|
|
28
|
-
m.sort((a, b) => a.
|
|
29
|
+
m.sort((a, b) => a.clipLengthAtStartOfRead - b.clipLengthAtStartOfRead);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
return layoutMatches;
|
|
32
33
|
}, [allFeatures, trackId, hasPaired, model]);
|
|
33
34
|
const [mouseoverElt, setMouseoverElt] = useState();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const yOffset = getYOffset(parentRef);
|
|
36
|
+
const tracks = views.map(v => v.getTrack(trackId));
|
|
37
|
+
if (!assembly) {
|
|
38
|
+
return null;
|
|
38
39
|
}
|
|
39
|
-
return
|
|
40
|
+
return (_jsx("g", { fill: "none", "data-testid": getTestId(trackId, layoutMatches.length > 0), children: layoutMatches.map(chunk => {
|
|
40
41
|
const ret = [];
|
|
41
42
|
for (let i = 0; i < chunk.length - 1; i++) {
|
|
42
43
|
const { layout: c1, feature: f1, level: level1 } = chunk[i];
|
|
43
44
|
const { layout: c2, feature: f2, level: level2 } = chunk[i + 1];
|
|
44
|
-
if (!c1 || !c2) {
|
|
45
|
-
console.warn('received null layout for a overlay feature');
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
45
|
if (!showIntraviewLinks && level1 === level2) {
|
|
49
46
|
return null;
|
|
50
47
|
}
|
|
@@ -81,7 +78,6 @@ const AlignmentConnections = observer(function ({ model, trackId, parentRef, get
|
|
|
81
78
|
const reversed2 = views[level2].pxToBp(x2).reversed;
|
|
82
79
|
const rf1 = reversed1 ? -1 : 1;
|
|
83
80
|
const rf2 = reversed2 ? -1 : 1;
|
|
84
|
-
const tracks = views.map(v => v.getTrack(trackId));
|
|
85
81
|
const y1 = yPos(trackId, level1, views, tracks, c1, getTrackYPosOverride) -
|
|
86
82
|
yOffset;
|
|
87
83
|
const y2 = yPos(trackId, level2, views, tracks, c2, getTrackYPosOverride) -
|
|
@@ -111,14 +107,13 @@ const AlignmentConnections = observer(function ({ model, trackId, parentRef, get
|
|
|
111
107
|
].join(' ');
|
|
112
108
|
const id = `${f1.id()}-${f2.id()}`;
|
|
113
109
|
ret.push(_jsx("path", { d: path, "data-testid": "r1", pointerEvents: interactiveOverlay ? 'auto' : undefined, strokeWidth: mouseoverElt === id ? 5 : 1, ...getStrokeProps(orientationColor || theme.palette.text.disabled), onClick: () => {
|
|
114
|
-
|
|
115
|
-
const featureWidget = (_a = session.addWidget) === null || _a === void 0 ? void 0 : _a.call(session, 'BreakpointAlignmentsWidget', 'breakpointAlignments', {
|
|
110
|
+
const featureWidget = session.addWidget?.('BreakpointAlignmentsWidget', 'breakpointAlignments', {
|
|
116
111
|
featureData: {
|
|
117
|
-
feature1: (allFeatures.get(f1.id()) || { toJSON: () => {
|
|
118
|
-
feature2: (allFeatures.get(f2.id()) || { toJSON: () => {
|
|
112
|
+
feature1: (allFeatures.get(f1.id()) || { toJSON: () => ({}) }).toJSON(),
|
|
113
|
+
feature2: (allFeatures.get(f2.id()) || { toJSON: () => ({}) }).toJSON(),
|
|
119
114
|
},
|
|
120
115
|
});
|
|
121
|
-
|
|
116
|
+
session.showWidget?.(featureWidget);
|
|
122
117
|
}, onMouseOver: () => {
|
|
123
118
|
setMouseoverElt(id);
|
|
124
119
|
}, onMouseOut: () => {
|
|
@@ -126,6 +121,6 @@ const AlignmentConnections = observer(function ({ model, trackId, parentRef, get
|
|
|
126
121
|
} }, id));
|
|
127
122
|
}
|
|
128
123
|
return ret;
|
|
129
|
-
}) }))
|
|
124
|
+
}) }));
|
|
130
125
|
});
|
|
131
126
|
export default AlignmentConnections;
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
declare const Breakends: ({ model, trackId, parentRef
|
|
3
|
-
model: BreakpointViewModel;
|
|
4
|
-
trackId: string;
|
|
5
|
-
parentRef: React.RefObject<SVGSVGElement | null>;
|
|
6
|
-
getTrackYPosOverride?: (trackId: string, level: number) => number;
|
|
7
|
-
}) => import("react/jsx-runtime").JSX.Element | null;
|
|
1
|
+
import type { OverlayProps } from './overlayUtils.tsx';
|
|
2
|
+
declare const Breakends: ({ model, trackId, parentRef, getTrackYPosOverride, }: OverlayProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
8
3
|
export default Breakends;
|
|
@@ -1,90 +1,59 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useState } from 'react';
|
|
3
3
|
import { getSession } from '@jbrowse/core/util';
|
|
4
|
+
import { getSnapshot } from '@jbrowse/mobx-state-tree';
|
|
4
5
|
import { observer } from 'mobx-react';
|
|
5
|
-
import {
|
|
6
|
-
import { findMatchingAlt, getMatchedBreakendFeatures } from
|
|
7
|
-
import { getPxFromCoordinate, useNextFrame, yPos } from
|
|
8
|
-
const
|
|
9
|
-
const Breakends = observer(function ({ model, trackId, parentRef: ref, getTrackYPosOverride, }) {
|
|
6
|
+
import { LEFT, buildBreakpointPath, createMouseHandlers, getCanonicalRefs, getTestId, getYOffset, } from "./overlayUtils.js";
|
|
7
|
+
import { findMatchingAlt, getMatchedBreakendFeatures } from "./util.js";
|
|
8
|
+
import { getPxFromCoordinate, useNextFrame, yPos } from "../util.js";
|
|
9
|
+
const Breakends = observer(function Breakends({ model, trackId, parentRef, getTrackYPosOverride, }) {
|
|
10
10
|
const { interactiveOverlay, views } = model;
|
|
11
11
|
const session = getSession(model);
|
|
12
12
|
const { assemblyManager } = session;
|
|
13
|
-
const totalFeatures = model.getTrackFeatures(trackId);
|
|
14
|
-
const layoutMatches = useMemo(() => model.getMatchedFeaturesInLayout(trackId, getMatchedBreakendFeatures(totalFeatures)), [totalFeatures, trackId, model]);
|
|
15
|
-
const [mouseoverElt, setMouseoverElt] = useState();
|
|
16
13
|
const snap = getSnapshot(model);
|
|
14
|
+
const v0 = views[0];
|
|
15
|
+
const assembly = v0 ? assemblyManager.get(v0.assemblyNames[0]) : undefined;
|
|
17
16
|
useNextFrame(snap);
|
|
18
|
-
const
|
|
17
|
+
const totalFeatures = model.getTrackFeatures(trackId);
|
|
18
|
+
const layoutMatches = useMemo(() => {
|
|
19
|
+
const matchedFeatures = getMatchedBreakendFeatures(totalFeatures);
|
|
20
|
+
return model.getMatchedFeaturesInLayout(trackId, matchedFeatures);
|
|
21
|
+
}, [totalFeatures, trackId, model]);
|
|
22
|
+
const [mouseoverElt, setMouseoverElt] = useState();
|
|
23
|
+
const yOffset = getYOffset(parentRef);
|
|
24
|
+
const tracks = views.map(v => v.getTrack(trackId));
|
|
19
25
|
if (!assembly) {
|
|
20
26
|
return null;
|
|
21
27
|
}
|
|
22
|
-
|
|
23
|
-
if (ref.current) {
|
|
24
|
-
const rect = ref.current.getBoundingClientRect();
|
|
25
|
-
yoff = rect.top;
|
|
26
|
-
}
|
|
27
|
-
return (_jsx("g", { stroke: "green", strokeWidth: 5, fill: "none", "data-testid": layoutMatches.length ? `${trackId}-loaded` : trackId, children: layoutMatches.map(chunk => {
|
|
28
|
+
return (_jsx("g", { stroke: "green", strokeWidth: 5, fill: "none", "data-testid": getTestId(trackId, layoutMatches.length > 0), children: layoutMatches.map(chunk => {
|
|
28
29
|
const ret = [];
|
|
29
30
|
for (let i = 0; i < chunk.length - 1; i += 1) {
|
|
30
31
|
const { layout: c1, feature: f1, level: level1 } = chunk[i];
|
|
31
32
|
const { layout: c2, feature: f2, level: level2 } = chunk[i + 1];
|
|
32
33
|
const id = f1.id();
|
|
33
34
|
const relevantAlt = findMatchingAlt(f1, f2);
|
|
34
|
-
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
const f1origref = f1.get('refName');
|
|
38
|
-
const f2origref = f2.get('refName');
|
|
39
|
-
const f1ref = assembly.getCanonicalRefName(f1origref);
|
|
40
|
-
const f2ref = assembly.getCanonicalRefName(f2origref);
|
|
41
|
-
if (!f1ref || !f2ref) {
|
|
42
|
-
throw new Error(`unable to find ref for ${f1ref || f2ref}`);
|
|
43
|
-
}
|
|
35
|
+
const { f1ref, f2ref } = getCanonicalRefs(assembly, f1.get('refName'), f2.get('refName'));
|
|
44
36
|
const x1 = getPxFromCoordinate(views[level1], f1ref, c1[LEFT]);
|
|
45
37
|
const x2 = getPxFromCoordinate(views[level2], f2ref, c2[LEFT]);
|
|
46
38
|
const reversed1 = views[level1].pxToBp(x1).reversed;
|
|
47
39
|
const reversed2 = views[level2].pxToBp(x2).reversed;
|
|
48
|
-
const tracks = views.map(v => v.getTrack(trackId));
|
|
49
40
|
const y1 = yPos(trackId, level1, views, tracks, c1, getTrackYPosOverride) -
|
|
50
|
-
|
|
41
|
+
yOffset;
|
|
51
42
|
const y2 = yPos(trackId, level2, views, tracks, c2, getTrackYPosOverride) -
|
|
52
|
-
|
|
43
|
+
yOffset;
|
|
53
44
|
if (!relevantAlt) {
|
|
54
45
|
console.warn('the relevant ALT allele was not found, cannot render');
|
|
55
46
|
}
|
|
56
47
|
else {
|
|
57
|
-
const
|
|
58
|
-
'
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
y1,
|
|
67
|
-
'L',
|
|
68
|
-
x2,
|
|
69
|
-
y2,
|
|
70
|
-
'L',
|
|
71
|
-
x2 -
|
|
72
|
-
20 *
|
|
73
|
-
(relevantAlt.MateDirection === 'left' ? 1 : -1) *
|
|
74
|
-
(reversed2 ? -1 : 1),
|
|
75
|
-
y2,
|
|
76
|
-
].join(' ');
|
|
77
|
-
ret.push(_jsx("path", { d: path, "data-testid": "r2", pointerEvents: interactiveOverlay ? 'auto' : undefined, strokeWidth: id === mouseoverElt ? 10 : 5, onClick: () => {
|
|
78
|
-
var _a, _b, _c;
|
|
79
|
-
const featureWidget = (_a = session.addWidget) === null || _a === void 0 ? void 0 : _a.call(session, 'VariantFeatureWidget', 'variantFeature', {
|
|
80
|
-
featureData: (_b = totalFeatures.get(id)) === null || _b === void 0 ? void 0 : _b.toJSON(),
|
|
81
|
-
});
|
|
82
|
-
(_c = session.showWidget) === null || _c === void 0 ? void 0 : _c.call(session, featureWidget);
|
|
83
|
-
}, onMouseOver: () => {
|
|
84
|
-
setMouseoverElt(id);
|
|
85
|
-
}, onMouseOut: () => {
|
|
86
|
-
setMouseoverElt(undefined);
|
|
87
|
-
} }, JSON.stringify(path)));
|
|
48
|
+
const x1Tick = x1 -
|
|
49
|
+
20 * (relevantAlt.Join === 'left' ? -1 : 1) * (reversed1 ? -1 : 1);
|
|
50
|
+
const x2Tick = x2 -
|
|
51
|
+
20 *
|
|
52
|
+
(relevantAlt.MateDirection === 'left' ? 1 : -1) *
|
|
53
|
+
(reversed2 ? -1 : 1);
|
|
54
|
+
const path = buildBreakpointPath(x1, y1, x2, y2, x1Tick, x2Tick);
|
|
55
|
+
const mouseHandlers = createMouseHandlers(id, setMouseoverElt, session, 'VariantFeatureWidget', 'variantFeature', totalFeatures.get(id)?.toJSON());
|
|
56
|
+
ret.push(_jsx("path", { d: path, "data-testid": "r2", pointerEvents: interactiveOverlay ? 'auto' : undefined, strokeWidth: id === mouseoverElt ? 10 : 5, ...mouseHandlers }, JSON.stringify(path)));
|
|
88
57
|
}
|
|
89
58
|
}
|
|
90
59
|
return ret;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { getEnv } from '@jbrowse/core/util';
|
|
3
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
3
4
|
import { observer } from 'mobx-react';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
5
|
+
import BreakpointSplitViewOverlay from "./BreakpointSplitViewOverlay.js";
|
|
6
|
+
import Header from "./Header.js";
|
|
7
|
+
import Rubberband from "./Rubberband.js";
|
|
6
8
|
const useStyles = makeStyles()(theme => ({
|
|
7
9
|
viewDivider: {
|
|
8
10
|
background: theme.palette.secondary.main,
|
|
@@ -17,8 +19,20 @@ const useStyles = makeStyles()(theme => ({
|
|
|
17
19
|
rel: {
|
|
18
20
|
position: 'relative',
|
|
19
21
|
},
|
|
22
|
+
rubberbandContainer: {
|
|
23
|
+
position: 'relative',
|
|
24
|
+
overflow: 'hidden',
|
|
25
|
+
},
|
|
26
|
+
rubberbandDiv: {
|
|
27
|
+
width: '100%',
|
|
28
|
+
background: theme.palette.action.disabledBackground,
|
|
29
|
+
height: 15,
|
|
30
|
+
'&:hover': {
|
|
31
|
+
background: theme.palette.action.selected,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
20
34
|
}));
|
|
21
|
-
const BreakpointSplitViewLevels = observer(function ({ model, }) {
|
|
35
|
+
const BreakpointSplitViewLevels = observer(function BreakpointSplitViewLevels({ model, }) {
|
|
22
36
|
const { classes } = useStyles();
|
|
23
37
|
const { views } = model;
|
|
24
38
|
const { pluginManager } = getEnv(model);
|
|
@@ -33,8 +47,8 @@ const BreakpointSplitViewLevels = observer(function ({ model, }) {
|
|
|
33
47
|
];
|
|
34
48
|
}) }) }));
|
|
35
49
|
});
|
|
36
|
-
const BreakpointSplitView = observer(function ({ model, }) {
|
|
50
|
+
const BreakpointSplitView = observer(function BreakpointSplitView({ model, }) {
|
|
37
51
|
const { classes } = useStyles();
|
|
38
|
-
return (
|
|
52
|
+
return (_jsxs("div", { className: classes.rubberbandContainer, children: [model.showHeader ? _jsx(Header, { model: model }) : null, _jsx(Rubberband, { model: model, ControlComponent: _jsx("div", { className: classes.rubberbandDiv }) }), _jsxs("div", { className: classes.container, children: [_jsx(BreakpointSplitViewLevels, { model: model }), _jsx(BreakpointSplitViewOverlay, { model: model })] })] }));
|
|
39
53
|
});
|
|
40
54
|
export default BreakpointSplitView;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useRef } from 'react';
|
|
3
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
3
4
|
import { observer } from 'mobx-react';
|
|
4
|
-
import
|
|
5
|
-
import Overlay from './Overlay';
|
|
5
|
+
import Overlay from "./Overlay.js";
|
|
6
6
|
const useStyles = makeStyles()({
|
|
7
7
|
overlay: {
|
|
8
8
|
display: 'flex',
|
|
@@ -19,7 +19,7 @@ const useStyles = makeStyles()({
|
|
|
19
19
|
zIndex: 100,
|
|
20
20
|
},
|
|
21
21
|
});
|
|
22
|
-
const BreakpointSplitViewOverlay = observer(function ({ model, }) {
|
|
22
|
+
const BreakpointSplitViewOverlay = observer(function BreakpointSplitViewOverlay({ model, }) {
|
|
23
23
|
const { classes } = useStyles();
|
|
24
24
|
const { matchedTracks } = model;
|
|
25
25
|
const ref = useRef(null);
|
|
@@ -16,6 +16,7 @@ export default function ExportSvgDialog({ model, handleClose, }) {
|
|
|
16
16
|
const session = getSession(model);
|
|
17
17
|
const offscreenCanvas = typeof OffscreenCanvas !== 'undefined';
|
|
18
18
|
const [rasterizeLayers, setRasterizeLayers] = useState(offscreenCanvas);
|
|
19
|
+
const [showGridlines, setShowGridlines] = useSvgLocal('gridlines', false);
|
|
19
20
|
const [loading, setLoading] = useState(false);
|
|
20
21
|
const [error, setError] = useState();
|
|
21
22
|
const [filename, setFilename] = useSvgLocal('file', 'jbrowse.svg');
|
|
@@ -27,7 +28,9 @@ export default function ExportSvgDialog({ model, handleClose, }) {
|
|
|
27
28
|
setTrackLabels(event.target.value);
|
|
28
29
|
}, children: [_jsx(MenuItem, { value: "offset", children: "Offset" }), _jsx(MenuItem, { value: "overlay", children: "Overlay" }), _jsx(MenuItem, { value: "left", children: "Left" }), _jsx(MenuItem, { value: "none", children: "None" })] }), _jsx("br", {}), session.allThemes ? (_jsx(TextField2, { select: true, label: "Theme", variant: "outlined", value: themeName, onChange: event => {
|
|
29
30
|
setThemeName(event.target.value);
|
|
30
|
-
}, children: Object.entries(session.allThemes()).map(([key, val]) => (_jsx(MenuItem, { value: key, children: val.name || '(Unknown name)' }, key))) })) : null,
|
|
31
|
+
}, children: Object.entries(session.allThemes()).map(([key, val]) => (_jsx(MenuItem, { value: key, children: val.name || '(Unknown name)' }, key))) })) : null, _jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: showGridlines, onChange: () => {
|
|
32
|
+
setShowGridlines(val => !val);
|
|
33
|
+
} }), label: "Show gridlines" }), offscreenCanvas ? (_jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: rasterizeLayers, onChange: () => {
|
|
31
34
|
setRasterizeLayers(val => !val);
|
|
32
35
|
} }), label: "Rasterize canvas based tracks? File may be much larger if this is turned off" })) : (_jsx(Typography, { children: "Note: rasterizing layers not yet supported in this browser, so SVG size may be large" }))] }), _jsxs(DialogActions, { children: [_jsx(Button, { variant: "contained", color: "secondary", onClick: () => {
|
|
33
36
|
handleClose();
|
|
@@ -40,6 +43,7 @@ export default function ExportSvgDialog({ model, handleClose, }) {
|
|
|
40
43
|
filename,
|
|
41
44
|
trackLabels,
|
|
42
45
|
themeName,
|
|
46
|
+
showGridlines,
|
|
43
47
|
});
|
|
44
48
|
handleClose();
|
|
45
49
|
}
|