@jbrowse/plugin-breakpoint-split-view 2.6.1

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 (115) hide show
  1. package/LICENSE +201 -0
  2. package/dist/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.d.ts +5 -0
  3. package/dist/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js +21 -0
  4. package/dist/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js.map +1 -0
  5. package/dist/BreakpointAlignmentsFeatureDetail/index.d.ts +3 -0
  6. package/dist/BreakpointAlignmentsFeatureDetail/index.js +57 -0
  7. package/dist/BreakpointAlignmentsFeatureDetail/index.js.map +1 -0
  8. package/dist/BreakpointSplitView/BreakpointSplitView.d.ts +23 -0
  9. package/dist/BreakpointSplitView/BreakpointSplitView.js +92 -0
  10. package/dist/BreakpointSplitView/BreakpointSplitView.js.map +1 -0
  11. package/dist/BreakpointSplitView/components/AlignmentConnections.d.ts +9 -0
  12. package/dist/BreakpointSplitView/components/AlignmentConnections.js +133 -0
  13. package/dist/BreakpointSplitView/components/AlignmentConnections.js.map +1 -0
  14. package/dist/BreakpointSplitView/components/Breakends.d.ts +9 -0
  15. package/dist/BreakpointSplitView/components/Breakends.js +117 -0
  16. package/dist/BreakpointSplitView/components/Breakends.js.map +1 -0
  17. package/dist/BreakpointSplitView/components/BreakpointSplitView.d.ts +6 -0
  18. package/dist/BreakpointSplitView/components/BreakpointSplitView.js +89 -0
  19. package/dist/BreakpointSplitView/components/BreakpointSplitView.js.map +1 -0
  20. package/dist/BreakpointSplitView/components/ExportSvgDialog.d.ts +8 -0
  21. package/dist/BreakpointSplitView/components/ExportSvgDialog.js +84 -0
  22. package/dist/BreakpointSplitView/components/ExportSvgDialog.js.map +1 -0
  23. package/dist/BreakpointSplitView/components/Overlay.d.ts +9 -0
  24. package/dist/BreakpointSplitView/components/Overlay.js +23 -0
  25. package/dist/BreakpointSplitView/components/Overlay.js.map +1 -0
  26. package/dist/BreakpointSplitView/components/Translocations.d.ts +9 -0
  27. package/dist/BreakpointSplitView/components/Translocations.js +113 -0
  28. package/dist/BreakpointSplitView/components/Translocations.js.map +1 -0
  29. package/dist/BreakpointSplitView/components/util.d.ts +7 -0
  30. package/dist/BreakpointSplitView/components/util.js +116 -0
  31. package/dist/BreakpointSplitView/components/util.js.map +1 -0
  32. package/dist/BreakpointSplitView/index.d.ts +3 -0
  33. package/dist/BreakpointSplitView/index.js +43 -0
  34. package/dist/BreakpointSplitView/index.js.map +1 -0
  35. package/dist/BreakpointSplitView/model.d.ts +299 -0
  36. package/dist/BreakpointSplitView/model.js +245 -0
  37. package/dist/BreakpointSplitView/model.js.map +1 -0
  38. package/dist/BreakpointSplitView/svgcomponents/SVGBackground.d.ts +6 -0
  39. package/dist/BreakpointSplitView/svgcomponents/SVGBackground.js +13 -0
  40. package/dist/BreakpointSplitView/svgcomponents/SVGBackground.js.map +1 -0
  41. package/dist/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.d.ts +4 -0
  42. package/dist/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js +83 -0
  43. package/dist/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js.map +1 -0
  44. package/dist/BreakpointSplitView/util.d.ts +18 -0
  45. package/dist/BreakpointSplitView/util.js +56 -0
  46. package/dist/BreakpointSplitView/util.js.map +1 -0
  47. package/dist/index.d.ts +7 -0
  48. package/dist/index.js +21 -0
  49. package/dist/index.js.map +1 -0
  50. package/esm/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.d.ts +5 -0
  51. package/esm/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js +16 -0
  52. package/esm/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js.map +1 -0
  53. package/esm/BreakpointAlignmentsFeatureDetail/index.d.ts +3 -0
  54. package/esm/BreakpointAlignmentsFeatureDetail/index.js +32 -0
  55. package/esm/BreakpointAlignmentsFeatureDetail/index.js.map +1 -0
  56. package/esm/BreakpointSplitView/BreakpointSplitView.d.ts +23 -0
  57. package/esm/BreakpointSplitView/BreakpointSplitView.js +86 -0
  58. package/esm/BreakpointSplitView/BreakpointSplitView.js.map +1 -0
  59. package/esm/BreakpointSplitView/components/AlignmentConnections.d.ts +9 -0
  60. package/esm/BreakpointSplitView/components/AlignmentConnections.js +108 -0
  61. package/esm/BreakpointSplitView/components/AlignmentConnections.js.map +1 -0
  62. package/esm/BreakpointSplitView/components/Breakends.d.ts +9 -0
  63. package/esm/BreakpointSplitView/components/Breakends.js +92 -0
  64. package/esm/BreakpointSplitView/components/Breakends.js.map +1 -0
  65. package/esm/BreakpointSplitView/components/BreakpointSplitView.d.ts +6 -0
  66. package/esm/BreakpointSplitView/components/BreakpointSplitView.js +61 -0
  67. package/esm/BreakpointSplitView/components/BreakpointSplitView.js.map +1 -0
  68. package/esm/BreakpointSplitView/components/ExportSvgDialog.d.ts +8 -0
  69. package/esm/BreakpointSplitView/components/ExportSvgDialog.js +58 -0
  70. package/esm/BreakpointSplitView/components/ExportSvgDialog.js.map +1 -0
  71. package/esm/BreakpointSplitView/components/Overlay.d.ts +9 -0
  72. package/esm/BreakpointSplitView/components/Overlay.js +18 -0
  73. package/esm/BreakpointSplitView/components/Overlay.js.map +1 -0
  74. package/esm/BreakpointSplitView/components/Translocations.d.ts +9 -0
  75. package/esm/BreakpointSplitView/components/Translocations.js +88 -0
  76. package/esm/BreakpointSplitView/components/Translocations.js.map +1 -0
  77. package/esm/BreakpointSplitView/components/util.d.ts +7 -0
  78. package/esm/BreakpointSplitView/components/util.js +107 -0
  79. package/esm/BreakpointSplitView/components/util.js.map +1 -0
  80. package/esm/BreakpointSplitView/index.d.ts +3 -0
  81. package/esm/BreakpointSplitView/index.js +15 -0
  82. package/esm/BreakpointSplitView/index.js.map +1 -0
  83. package/esm/BreakpointSplitView/model.d.ts +299 -0
  84. package/esm/BreakpointSplitView/model.js +216 -0
  85. package/esm/BreakpointSplitView/model.js.map +1 -0
  86. package/esm/BreakpointSplitView/svgcomponents/SVGBackground.d.ts +6 -0
  87. package/esm/BreakpointSplitView/svgcomponents/SVGBackground.js +7 -0
  88. package/esm/BreakpointSplitView/svgcomponents/SVGBackground.js.map +1 -0
  89. package/esm/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.d.ts +4 -0
  90. package/esm/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js +76 -0
  91. package/esm/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js.map +1 -0
  92. package/esm/BreakpointSplitView/util.d.ts +18 -0
  93. package/esm/BreakpointSplitView/util.js +49 -0
  94. package/esm/BreakpointSplitView/util.js.map +1 -0
  95. package/esm/index.d.ts +7 -0
  96. package/esm/index.js +15 -0
  97. package/esm/index.js.map +1 -0
  98. package/package.json +63 -0
  99. package/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx +24 -0
  100. package/src/BreakpointAlignmentsFeatureDetail/index.ts +35 -0
  101. package/src/BreakpointSplitView/BreakpointSplitView.ts +100 -0
  102. package/src/BreakpointSplitView/components/AlignmentConnections.tsx +166 -0
  103. package/src/BreakpointSplitView/components/Breakends.tsx +141 -0
  104. package/src/BreakpointSplitView/components/BreakpointSplitView.tsx +95 -0
  105. package/src/BreakpointSplitView/components/ExportSvgDialog.tsx +149 -0
  106. package/src/BreakpointSplitView/components/Overlay.tsx +29 -0
  107. package/src/BreakpointSplitView/components/Translocations.tsx +147 -0
  108. package/src/BreakpointSplitView/components/util.ts +127 -0
  109. package/src/BreakpointSplitView/index.ts +17 -0
  110. package/src/BreakpointSplitView/model.ts +333 -0
  111. package/src/BreakpointSplitView/svgcomponents/SVGBackground.tsx +21 -0
  112. package/src/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.tsx +179 -0
  113. package/src/BreakpointSplitView/util.ts +89 -0
  114. package/src/index.test.ts +3 -0
  115. package/src/index.ts +15 -0
@@ -0,0 +1,18 @@
1
+ import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
2
+ import { LayoutRecord } from './model';
3
+ type LGV = LinearGenomeViewModel;
4
+ interface Display {
5
+ height: number;
6
+ scrollTop: number;
7
+ SNPCoverageDisplay?: {
8
+ height: number;
9
+ };
10
+ }
11
+ interface Track {
12
+ displays: Display[];
13
+ }
14
+ export declare function getPxFromCoordinate(view: LGV, refName: string, coord: number): number;
15
+ export declare function yPos(trackId: string, level: number, views: LGV[], tracks: Track[], c: LayoutRecord, getYPosOverride?: (trackId: string, level: number) => number): number;
16
+ export declare const useNextFrame: (variable: unknown) => void;
17
+ export declare function intersect<T>(cb: (l: T) => string, a1?: T[], a2?: T[], ...rest: T[][]): T[];
18
+ export {};
@@ -0,0 +1,49 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { clamp } from '@jbrowse/core/util';
3
+ const [, TOP, , BOTTOM] = [0, 1, 2, 3];
4
+ function cheight(chunk) {
5
+ return chunk[BOTTOM] - chunk[TOP];
6
+ }
7
+ function heightFromSpecificLevel(views, trackId, level, getYPosOverride) {
8
+ var _a;
9
+ return getYPosOverride
10
+ ? getYPosOverride(trackId, level)
11
+ : ((_a = views[level].trackRefs[trackId]) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().top) || 0;
12
+ }
13
+ export function getPxFromCoordinate(view, refName, coord) {
14
+ return ((view.bpToPx({ refName, coord }) || {}).offsetPx || 0) - view.offsetPx;
15
+ }
16
+ // get's the yposition of a layout record in a track
17
+ export function yPos(trackId, level, views, tracks, c, getYPosOverride) {
18
+ const display = tracks[level].displays[0];
19
+ const min = 0;
20
+ const max = display.height;
21
+ let offset = 0;
22
+ const { SNPCoverageDisplay } = display;
23
+ if (SNPCoverageDisplay) {
24
+ offset = SNPCoverageDisplay.height;
25
+ }
26
+ const yPos = getYPosOverride ? 0 : display.scrollTop;
27
+ return (clamp(c[TOP] - yPos + cheight(c) / 2 + offset, min, max) +
28
+ heightFromSpecificLevel(views, trackId, level, getYPosOverride) +
29
+ display.scrollTop);
30
+ }
31
+ // we combo a useEffect and useState combo to force rerender on snap changing.
32
+ // the setup of this being a useEffect+useState makes it re-render once the
33
+ // useEffect is called, which is generally the "next frame". If we removed the
34
+ // below use
35
+ export const useNextFrame = (variable) => {
36
+ const [, setNextFrameState] = useState();
37
+ useEffect(() => {
38
+ setNextFrameState(variable);
39
+ }, [variable]);
40
+ };
41
+ // https://stackoverflow.com/a/49186706/2129219 the array-intersection package
42
+ // on npm has a large kb size, and we are just intersecting open track ids so
43
+ // simple is better
44
+ export function intersect(cb, a1 = [], a2 = [], ...rest) {
45
+ const ids = new Set(a2.map(elt => cb(elt)));
46
+ const a12 = a1.filter(value => ids.has(cb(value)));
47
+ return rest.length === 0 ? a12 : intersect(cb, a12, ...rest);
48
+ }
49
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/BreakpointSplitView/util.ts"],"names":[],"mappings":"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"}
package/esm/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import PluginManager from '@jbrowse/core/PluginManager';
2
+ import Plugin from '@jbrowse/core/Plugin';
3
+ export default class BreakpointSplitViewPlugin extends Plugin {
4
+ name: string;
5
+ install(pluginManager: PluginManager): void;
6
+ configure(): void;
7
+ }
package/esm/index.js ADDED
@@ -0,0 +1,15 @@
1
+ import Plugin from '@jbrowse/core/Plugin';
2
+ import BreakpointAlignmentsWidgetF from './BreakpointAlignmentsFeatureDetail';
3
+ import BreakpointSplitViewF from './BreakpointSplitView';
4
+ export default class BreakpointSplitViewPlugin extends Plugin {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.name = 'BreakpointSplitViewPlugin';
8
+ }
9
+ install(pluginManager) {
10
+ BreakpointSplitViewF(pluginManager);
11
+ BreakpointAlignmentsWidgetF(pluginManager);
12
+ }
13
+ configure() { }
14
+ }
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,sBAAsB,CAAA;AACzC,OAAO,2BAA2B,MAAM,qCAAqC,CAAA;AAC7E,OAAO,oBAAoB,MAAM,uBAAuB,CAAA;AAExD,MAAM,CAAC,OAAO,OAAO,yBAA0B,SAAQ,MAAM;IAA7D;;QACE,SAAI,GAAG,2BAA2B,CAAA;IAQpC,CAAC;IANC,OAAO,CAAC,aAA4B;QAClC,oBAAoB,CAAC,aAAa,CAAC,CAAA;QACnC,2BAA2B,CAAC,aAAa,CAAC,CAAA;IAC5C,CAAC;IAED,SAAS,KAAI,CAAC;CACf"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@jbrowse/plugin-breakpoint-split-view",
3
+ "version": "2.6.1",
4
+ "description": "JBrowse 2 breakpoint detail split view",
5
+ "keywords": [
6
+ "jbrowse",
7
+ "jbrowse2"
8
+ ],
9
+ "license": "Apache-2.0",
10
+ "homepage": "https://jbrowse.org",
11
+ "bugs": "https://github.com/GMOD/jbrowse-components/issues",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/GMOD/jbrowse-components.git",
15
+ "directory": "plugins/breakpoint-split-view"
16
+ },
17
+ "author": "JBrowse Team",
18
+ "distMain": "dist/index.js",
19
+ "srcMain": "src/index.ts",
20
+ "main": "dist/index.js",
21
+ "files": [
22
+ "dist",
23
+ "src",
24
+ "esm"
25
+ ],
26
+ "scripts": {
27
+ "build": "npm-run-all build:*",
28
+ "test": "cd ../..; jest plugins/breakpoint-split-view",
29
+ "prepublishOnly": "yarn test",
30
+ "prepack": "yarn build && yarn useDist",
31
+ "postpack": "yarn useSrc",
32
+ "useDist": "node ../../scripts/useDist.js",
33
+ "useSrc": "node ../../scripts/useSrc.js",
34
+ "prebuild": "npm run clean",
35
+ "build:esm": "tsc --build tsconfig.build.esm.json",
36
+ "build:es5": "tsc --build tsconfig.build.es5.json",
37
+ "clean": "rimraf dist esm *.tsbuildinfo"
38
+ },
39
+ "dependencies": {
40
+ "@gmod/vcf": "^5.0.9",
41
+ "@mui/icons-material": "^5.0.1",
42
+ "@types/file-saver": "^2.0.1",
43
+ "file-saver": "^2.0.0"
44
+ },
45
+ "peerDependencies": {
46
+ "@jbrowse/core": "^2.0.0",
47
+ "@jbrowse/plugin-linear-genome-view": "^2.0.0",
48
+ "@mui/material": "^5.0.0",
49
+ "mobx": "^6.0.0",
50
+ "mobx-react": "^7.0.0",
51
+ "mobx-state-tree": "^5.0.0",
52
+ "react": ">=16.8.4",
53
+ "react-dom": ">=16.8.4",
54
+ "tss-react": "^4.0.0"
55
+ },
56
+ "distModule": "esm/index.js",
57
+ "srcModule": "src/index.ts",
58
+ "module": "esm/index.js",
59
+ "publishConfig": {
60
+ "access": "public"
61
+ },
62
+ "gitHead": "1cbe7ba097fb2d2763c776e5e429e4670cdd583c"
63
+ }
@@ -0,0 +1,24 @@
1
+ import Paper from '@mui/material/Paper'
2
+ import { observer } from 'mobx-react'
3
+ import React from 'react'
4
+ import {
5
+ BaseCoreDetails,
6
+ BaseAttributes,
7
+ } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail'
8
+
9
+ const BreakpointAlignmentsFeatureDetail = observer(
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ ({ model }: { model: any }) => {
12
+ const { feature1, feature2 } = JSON.parse(JSON.stringify(model.featureData))
13
+ return (
14
+ <Paper data-testid="alignment-side-drawer">
15
+ <BaseCoreDetails title="Feature 1" feature={feature1} />
16
+ <BaseCoreDetails title="Feature 2" feature={feature2} />
17
+ <BaseAttributes title="Feature 1 attributes" feature={feature1} />
18
+ <BaseAttributes title="Feature 2 attributes" feature={feature2} />
19
+ </Paper>
20
+ )
21
+ },
22
+ )
23
+
24
+ export default BreakpointAlignmentsFeatureDetail
@@ -0,0 +1,35 @@
1
+ import { lazy } from 'react'
2
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
3
+ import PluginManager from '@jbrowse/core/PluginManager'
4
+ import { ElementId } from '@jbrowse/core/util/types/mst'
5
+ import { types } from 'mobx-state-tree'
6
+ import { WidgetType } from '@jbrowse/core/pluggableElementTypes'
7
+
8
+ const configSchema = ConfigurationSchema('BreakpointAlignmentsWidget', {})
9
+
10
+ const stateModel = types
11
+ .model('BreakpointAlignmentsWidget', {
12
+ id: ElementId,
13
+ type: types.literal('BreakpointAlignmentsWidget'),
14
+ featureData: types.frozen(),
15
+ })
16
+ .actions(self => ({
17
+ setFeatureData(data: unknown) {
18
+ self.featureData = data
19
+ },
20
+ clearFeatureData() {
21
+ self.featureData = undefined
22
+ },
23
+ }))
24
+
25
+ export default (pluginManager: PluginManager) => {
26
+ pluginManager.addWidgetType(() => {
27
+ return new WidgetType({
28
+ name: 'BreakpointAlignmentsWidget',
29
+ heading: 'Breakpoint feature details',
30
+ configSchema,
31
+ stateModel,
32
+ ReactComponent: lazy(() => import('./BreakpointAlignmentsFeatureDetail')),
33
+ })
34
+ })
35
+ }
@@ -0,0 +1,100 @@
1
+ import { getSession, Feature } from '@jbrowse/core/util'
2
+ import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType'
3
+ import { parseBreakend } from '@gmod/vcf'
4
+ import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
5
+
6
+ type LGV = LinearGenomeViewModel
7
+
8
+ export default class BreakpointSplitViewType extends ViewType {
9
+ snapshotFromBreakendFeature(feature: Feature, view: LGV) {
10
+ const alt = feature.get('ALT')?.[0]
11
+ const bnd = alt ? parseBreakend(alt) : undefined
12
+ const startPos = feature.get('start')
13
+ let endPos
14
+ const bpPerPx = 10
15
+
16
+ // TODO: Figure this out for multiple assembly names
17
+ const { assemblyName } = view.displayedRegions[0]
18
+ const { assemblyManager } = getSession(view)
19
+ const assembly = assemblyManager.get(assemblyName)
20
+
21
+ if (!assembly) {
22
+ throw new Error(`assembly ${assemblyName} not found`)
23
+ }
24
+ if (!assembly.regions) {
25
+ throw new Error(`assembly ${assemblyName} regions not loaded`)
26
+ }
27
+ const { getCanonicalRefName } = assembly
28
+ const featureRefName = getCanonicalRefName(feature.get('refName'))
29
+ const topRegion = assembly.regions.find(f => f.refName === featureRefName)
30
+
31
+ let mateRefName: string | undefined
32
+ let startMod = 0
33
+ let endMod = 0
34
+
35
+ // a VCF breakend feature
36
+ if (alt === '<TRA>') {
37
+ const INFO = feature.get('INFO')
38
+ endPos = INFO.END[0] - 1
39
+ mateRefName = getCanonicalRefName(INFO.CHR2[0])
40
+ } else if (bnd?.MatePosition) {
41
+ const matePosition = bnd.MatePosition.split(':')
42
+ endPos = +matePosition[1] - 1
43
+ mateRefName = getCanonicalRefName(matePosition[0])
44
+ if (bnd.Join === 'left') {
45
+ startMod = -1
46
+ }
47
+ if (bnd.MateDirection === 'left') {
48
+ endMod = -1
49
+ }
50
+ } else if (feature.get('mate')) {
51
+ // a generic 'mate' feature
52
+ const mate = feature.get('mate')
53
+ mateRefName = getCanonicalRefName(mate.refName)
54
+ endPos = mate.start
55
+ }
56
+
57
+ if (!mateRefName) {
58
+ throw new Error(
59
+ `unable to resolve mate refName ${mateRefName} in reference genome`,
60
+ )
61
+ }
62
+
63
+ const bottomRegion = assembly.regions.find(f => f.refName === mateRefName)
64
+
65
+ if (!topRegion || !bottomRegion) {
66
+ throw new Error(
67
+ `unable to find the refName for the top or bottom of the breakpoint view`,
68
+ )
69
+ }
70
+
71
+ const topMarkedRegion = [{ ...topRegion }, { ...topRegion }]
72
+ const bottomMarkedRegion = [{ ...bottomRegion }, { ...bottomRegion }]
73
+ topMarkedRegion[0].end = startPos + startMod
74
+ topMarkedRegion[1].start = startPos + startMod
75
+ bottomMarkedRegion[0].end = endPos + endMod
76
+ bottomMarkedRegion[1].start = endPos + endMod
77
+ return {
78
+ type: 'BreakpointSplitView',
79
+ views: [
80
+ {
81
+ type: 'LinearGenomeView',
82
+ displayedRegions: topMarkedRegion,
83
+ hideHeader: true,
84
+ bpPerPx,
85
+ offsetPx: (topRegion.start + feature.get('start')) / bpPerPx,
86
+ },
87
+ {
88
+ type: 'LinearGenomeView',
89
+ displayedRegions: bottomMarkedRegion,
90
+ hideHeader: true,
91
+ bpPerPx,
92
+ offsetPx: (bottomRegion.start + endPos) / bpPerPx,
93
+ },
94
+ ],
95
+ displayName: `${
96
+ feature.get('name') || feature.get('id') || 'breakend'
97
+ } split detail`,
98
+ }
99
+ }
100
+ }
@@ -0,0 +1,166 @@
1
+ import React, { useMemo, useState } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { getSnapshot } from 'mobx-state-tree'
4
+ import { useTheme } from '@mui/material'
5
+ import { getSession } from '@jbrowse/core/util'
6
+
7
+ // locals
8
+ import {
9
+ getBadlyPairedAlignments,
10
+ getMatchedAlignmentFeatures,
11
+ hasPairedReads,
12
+ } from './util'
13
+ import { yPos, useNextFrame, getPxFromCoordinate } from '../util'
14
+ import { BreakpointViewModel } from '../model'
15
+
16
+ const [LEFT, , RIGHT] = [0, 1, 2, 3]
17
+
18
+ const AlignmentConnections = observer(function ({
19
+ model,
20
+ trackId,
21
+ parentRef,
22
+ getTrackYPosOverride,
23
+ }: {
24
+ model: BreakpointViewModel
25
+ trackId: string
26
+ parentRef: React.RefObject<SVGSVGElement>
27
+ getTrackYPosOverride?: (trackId: string, level: number) => number
28
+ }) {
29
+ const { views, showIntraviewLinks } = model
30
+ const theme = useTheme()
31
+ const session = getSession(model)
32
+ const snap = getSnapshot(model)
33
+ const { assemblyManager } = session
34
+ const assembly = assemblyManager.get(views[0].assemblyNames[0])
35
+ useNextFrame(snap)
36
+ const allFeatures = model.getTrackFeatures(trackId)
37
+ const hasPaired = useMemo(() => hasPairedReads(allFeatures), [allFeatures])
38
+
39
+ const layoutMatches = useMemo(() => {
40
+ const layoutMatches = model.getMatchedFeaturesInLayout(
41
+ trackId,
42
+ hasPaired
43
+ ? getBadlyPairedAlignments(allFeatures)
44
+ : getMatchedAlignmentFeatures(allFeatures),
45
+ )
46
+ if (!hasPaired) {
47
+ layoutMatches.forEach(m => {
48
+ m.sort((a, b) => a.feature.get('clipPos') - b.feature.get('clipPos'))
49
+ })
50
+ }
51
+ return layoutMatches
52
+ }, [allFeatures, trackId, hasPaired, model])
53
+
54
+ const [mouseoverElt, setMouseoverElt] = useState<string>()
55
+
56
+ let yOffset = 0
57
+ if (parentRef.current) {
58
+ const rect = parentRef.current.getBoundingClientRect()
59
+ yOffset = rect.top
60
+ }
61
+
62
+ if (!assembly) {
63
+ return null
64
+ }
65
+
66
+ return (
67
+ <g
68
+ stroke={theme.palette.text.disabled}
69
+ fill="none"
70
+ data-testid={layoutMatches.length ? `${trackId}-loaded` : trackId}
71
+ >
72
+ {layoutMatches.map(chunk => {
73
+ const ret = []
74
+ // we follow a path in the list of chunks, not from top to bottom, just in series
75
+ // following x1,y1 -> x2,y2
76
+ for (let i = 0; i < chunk.length - 1; i++) {
77
+ const { layout: c1, feature: f1, level: level1 } = chunk[i]
78
+ const { layout: c2, feature: f2, level: level2 } = chunk[i + 1]
79
+
80
+ if (!c1 || !c2) {
81
+ console.warn('received null layout for a overlay feature')
82
+ return null
83
+ }
84
+
85
+ // disable rendering connections in a single level
86
+ if (!showIntraviewLinks && level1 === level2) {
87
+ return null
88
+ }
89
+ const f1ref = assembly.getCanonicalRefName(f1.get('refName'))
90
+ const f2ref = assembly.getCanonicalRefName(f2.get('refName'))
91
+
92
+ if (!f1ref || !f2ref) {
93
+ throw new Error(`unable to find ref for ${f1ref || f2ref}`)
94
+ }
95
+
96
+ const s1 = f1.get('strand')
97
+ const s2 = f2.get('strand')
98
+ const p1 = c1[s1 === -1 ? LEFT : RIGHT]
99
+ const sn1 = s2 === -1
100
+ const p2 = hasPaired ? c2[sn1 ? LEFT : RIGHT] : c2[sn1 ? RIGHT : LEFT]
101
+ const x1 = getPxFromCoordinate(views[level1], f1ref, p1)
102
+ const x2 = getPxFromCoordinate(views[level2], f2ref, p2)
103
+ const reversed1 = views[level1].pxToBp(x1).reversed
104
+ const reversed2 = views[level2].pxToBp(x2).reversed
105
+ const tracks = views.map(v => v.getTrack(trackId))
106
+ const y1 =
107
+ yPos(trackId, level1, views, tracks, c1, getTrackYPosOverride) -
108
+ yOffset
109
+ const y2 =
110
+ yPos(trackId, level2, views, tracks, c2, getTrackYPosOverride) -
111
+ yOffset
112
+
113
+ // possible todo: use totalCurveHeight to possibly make alternative
114
+ // squiggle if the S is too small
115
+ const path = [
116
+ 'M',
117
+ x1,
118
+ y1,
119
+ 'C',
120
+ x1 + 200 * f1.get('strand') * (reversed1 ? -1 : 1),
121
+ y1,
122
+ x2 -
123
+ 200 *
124
+ f2.get('strand') *
125
+ (reversed2 ? -1 : 1) *
126
+ (hasPaired ? -1 : 1),
127
+ y2,
128
+ x2,
129
+ y2,
130
+ ].join(' ')
131
+ const id = `${f1.id()}-${f2.id()}`
132
+ ret.push(
133
+ <path
134
+ d={path}
135
+ key={id}
136
+ data-testid="r1"
137
+ strokeWidth={mouseoverElt === id ? 5 : 1}
138
+ onClick={() => {
139
+ const featureWidget = session.addWidget?.(
140
+ 'BreakpointAlignmentsWidget',
141
+ 'breakpointAlignments',
142
+ {
143
+ featureData: {
144
+ feature1: (
145
+ allFeatures.get(f1.id()) || { toJSON: () => {} }
146
+ ).toJSON(),
147
+ feature2: (
148
+ allFeatures.get(f2.id()) || { toJSON: () => {} }
149
+ ).toJSON(),
150
+ },
151
+ },
152
+ )
153
+ session.showWidget?.(featureWidget)
154
+ }}
155
+ onMouseOver={() => setMouseoverElt(id)}
156
+ onMouseOut={() => setMouseoverElt(undefined)}
157
+ />,
158
+ )
159
+ }
160
+ return ret
161
+ })}
162
+ </g>
163
+ )
164
+ })
165
+
166
+ export default AlignmentConnections
@@ -0,0 +1,141 @@
1
+ import React, { useState, useMemo } from 'react'
2
+ import { getSession } from '@jbrowse/core/util'
3
+ import { observer } from 'mobx-react'
4
+ import { getSnapshot } from 'mobx-state-tree'
5
+
6
+ // locals
7
+ import { findMatchingAlt, getMatchedBreakendFeatures } from './util'
8
+ import { yPos, getPxFromCoordinate, useNextFrame } from '../util'
9
+ import { BreakpointViewModel } from '../model'
10
+
11
+ const [LEFT] = [0, 1, 2, 3]
12
+
13
+ const Breakends = observer(function ({
14
+ model,
15
+ trackId,
16
+ parentRef: ref,
17
+ getTrackYPosOverride,
18
+ }: {
19
+ model: BreakpointViewModel
20
+ trackId: string
21
+ parentRef: React.RefObject<SVGSVGElement>
22
+ getTrackYPosOverride?: (trackId: string, level: number) => number
23
+ }) {
24
+ const { views } = model
25
+ const session = getSession(model)
26
+ const { assemblyManager } = session
27
+ const totalFeatures = model.getTrackFeatures(trackId)
28
+ const layoutMatches = useMemo(
29
+ () =>
30
+ model.getMatchedFeaturesInLayout(
31
+ trackId,
32
+ getMatchedBreakendFeatures(totalFeatures),
33
+ ),
34
+ [totalFeatures, trackId, model],
35
+ )
36
+
37
+ const [mouseoverElt, setMouseoverElt] = useState<string>()
38
+ const snap = getSnapshot(model)
39
+ useNextFrame(snap)
40
+ const assembly = assemblyManager.get(views[0].assemblyNames[0])
41
+
42
+ if (!assembly) {
43
+ return null
44
+ }
45
+
46
+ let yoff = 0
47
+ if (ref.current) {
48
+ const rect = ref.current.getBoundingClientRect()
49
+ yoff = rect.top
50
+ }
51
+
52
+ return (
53
+ <g
54
+ stroke="green"
55
+ strokeWidth={5}
56
+ fill="none"
57
+ data-testid={layoutMatches.length ? `${trackId}-loaded` : trackId}
58
+ >
59
+ {layoutMatches.map(chunk => {
60
+ const ret = []
61
+ // we follow a path in the list of chunks, not from top to bottom, just
62
+ // in series following x1,y1 -> x2,y2
63
+ for (let i = 0; i < chunk.length - 1; i += 1) {
64
+ const { layout: c1, feature: f1, level: level1 } = chunk[i]
65
+ const { layout: c2, feature: f2, level: level2 } = chunk[i + 1]
66
+ const id = f1.id()
67
+
68
+ const relevantAlt = findMatchingAlt(f1, f2)
69
+ if (!c1 || !c2) {
70
+ return null
71
+ }
72
+ const f1origref = f1.get('refName')
73
+ const f2origref = f2.get('refName')
74
+ const f1ref = assembly.getCanonicalRefName(f1origref)
75
+ const f2ref = assembly.getCanonicalRefName(f2origref)
76
+ if (!f1ref || !f2ref) {
77
+ throw new Error(`unable to find ref for ${f1ref || f2ref}`)
78
+ }
79
+ const x1 = getPxFromCoordinate(views[level1], f1ref, c1[LEFT])
80
+ const x2 = getPxFromCoordinate(views[level2], f2ref, c2[LEFT])
81
+ const reversed1 = views[level1].pxToBp(x1).reversed
82
+ const reversed2 = views[level2].pxToBp(x2).reversed
83
+
84
+ const tracks = views.map(v => v.getTrack(trackId))
85
+ const y1 =
86
+ yPos(trackId, level1, views, tracks, c1, getTrackYPosOverride) -
87
+ yoff
88
+ const y2 =
89
+ yPos(trackId, level2, views, tracks, c2, getTrackYPosOverride) -
90
+ yoff
91
+ if (!relevantAlt) {
92
+ console.warn('the relevant ALT allele was not found, cannot render')
93
+ } else {
94
+ const path = [
95
+ 'M', // move to
96
+ x1 -
97
+ 20 *
98
+ (relevantAlt.Join === 'left' ? -1 : 1) *
99
+ (reversed1 ? -1 : 1),
100
+ y1,
101
+ 'L', // line to
102
+ x1,
103
+ y1,
104
+ 'L', // line to
105
+ x2,
106
+ y2,
107
+ 'L', // line to
108
+ x2 -
109
+ 20 *
110
+ (relevantAlt.MateDirection === 'left' ? 1 : -1) *
111
+ (reversed2 ? -1 : 1),
112
+ y2,
113
+ ].join(' ')
114
+ ret.push(
115
+ <path
116
+ d={path}
117
+ key={JSON.stringify(path)}
118
+ strokeWidth={id === mouseoverElt ? 10 : 5}
119
+ onClick={() => {
120
+ const featureWidget = session.addWidget?.(
121
+ 'VariantFeatureWidget',
122
+ 'variantFeature',
123
+ {
124
+ featureData: totalFeatures.get(id)?.toJSON(),
125
+ },
126
+ )
127
+ session.showWidget?.(featureWidget)
128
+ }}
129
+ onMouseOver={() => setMouseoverElt(id)}
130
+ onMouseOut={() => setMouseoverElt(undefined)}
131
+ />,
132
+ )
133
+ }
134
+ }
135
+ return ret
136
+ })}
137
+ </g>
138
+ )
139
+ })
140
+
141
+ export default Breakends