@jbrowse/plugin-breakpoint-split-view 3.7.0 → 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.
Files changed (102) hide show
  1. package/esm/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js +1 -1
  2. package/esm/BreakpointAlignmentsFeatureDetail/index.js +2 -2
  3. package/esm/BreakpointGetFeatures/BreakpointGetFeatures.d.ts +51 -0
  4. package/esm/BreakpointGetFeatures/BreakpointGetFeatures.js +57 -0
  5. package/esm/BreakpointGetFeatures/index.d.ts +2 -0
  6. package/esm/BreakpointGetFeatures/index.js +4 -0
  7. package/esm/BreakpointSplitView/BreakpointSplitView.js +33 -47
  8. package/esm/BreakpointSplitView/components/AlignmentConnections.d.ts +2 -7
  9. package/esm/BreakpointSplitView/components/AlignmentConnections.js +20 -25
  10. package/esm/BreakpointSplitView/components/Breakends.d.ts +2 -7
  11. package/esm/BreakpointSplitView/components/Breakends.js +28 -59
  12. package/esm/BreakpointSplitView/components/BreakpointSplitView.d.ts +1 -1
  13. package/esm/BreakpointSplitView/components/BreakpointSplitView.js +19 -6
  14. package/esm/BreakpointSplitView/components/BreakpointSplitViewOverlay.d.ts +1 -1
  15. package/esm/BreakpointSplitView/components/BreakpointSplitViewOverlay.js +3 -3
  16. package/esm/BreakpointSplitView/components/ExportSvgDialog.d.ts +1 -1
  17. package/esm/BreakpointSplitView/components/ExportSvgDialog.js +5 -1
  18. package/esm/BreakpointSplitView/components/Header.d.ts +2 -2
  19. package/esm/BreakpointSplitView/components/Header.js +4 -4
  20. package/esm/BreakpointSplitView/components/HeaderSearchBoxes.js +2 -2
  21. package/esm/BreakpointSplitView/components/Overlay.d.ts +1 -1
  22. package/esm/BreakpointSplitView/components/Overlay.js +8 -11
  23. package/esm/BreakpointSplitView/components/PairedFeatures.d.ts +2 -7
  24. package/esm/BreakpointSplitView/components/PairedFeatures.js +22 -47
  25. package/esm/BreakpointSplitView/components/Rubberband.d.ts +6 -0
  26. package/esm/BreakpointSplitView/components/Rubberband.js +27 -0
  27. package/esm/BreakpointSplitView/components/RubberbandSpan.d.ts +15 -0
  28. package/esm/BreakpointSplitView/components/RubberbandSpan.js +30 -0
  29. package/esm/BreakpointSplitView/components/RubberbandTooltip.d.ts +5 -0
  30. package/esm/BreakpointSplitView/components/RubberbandTooltip.js +17 -0
  31. package/esm/BreakpointSplitView/components/Translocations.d.ts +2 -7
  32. package/esm/BreakpointSplitView/components/Translocations.js +23 -58
  33. package/esm/BreakpointSplitView/components/VerticalGuide.d.ts +6 -0
  34. package/esm/BreakpointSplitView/components/VerticalGuide.js +24 -0
  35. package/esm/BreakpointSplitView/components/overlayUtils.d.ts +24 -0
  36. package/esm/BreakpointSplitView/components/overlayUtils.js +47 -0
  37. package/esm/BreakpointSplitView/components/rubberbandUtil.d.ts +4 -0
  38. package/esm/BreakpointSplitView/components/rubberbandUtil.js +3 -0
  39. package/esm/BreakpointSplitView/components/useRangeSelect.d.ts +59 -0
  40. package/esm/BreakpointSplitView/components/useRangeSelect.js +121 -0
  41. package/esm/BreakpointSplitView/components/util.js +1 -2
  42. package/esm/BreakpointSplitView/getClip.js +4 -4
  43. package/esm/BreakpointSplitView/index.js +2 -2
  44. package/esm/BreakpointSplitView/model.d.ts +402 -117
  45. package/esm/BreakpointSplitView/model.js +105 -41
  46. package/esm/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.d.ts +2 -2
  47. package/esm/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js +8 -8
  48. package/esm/BreakpointSplitView/types.d.ts +8 -0
  49. package/esm/BreakpointSplitView/util.d.ts +1 -1
  50. package/esm/BreakpointSplitView/util.js +8 -17
  51. package/esm/LaunchBreakpointSplitView/index.d.ts +2 -0
  52. package/esm/LaunchBreakpointSplitView/index.js +12 -0
  53. package/esm/index.js +7 -6
  54. package/package.json +28 -34
  55. package/dist/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.d.ts +0 -10
  56. package/dist/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js +0 -12
  57. package/dist/BreakpointAlignmentsFeatureDetail/index.d.ts +0 -2
  58. package/dist/BreakpointAlignmentsFeatureDetail/index.js +0 -67
  59. package/dist/BreakpointSplitView/BreakpointSplitView.d.ts +0 -34
  60. package/dist/BreakpointSplitView/BreakpointSplitView.js +0 -84
  61. package/dist/BreakpointSplitView/components/AlignmentConnections.d.ts +0 -8
  62. package/dist/BreakpointSplitView/components/AlignmentConnections.js +0 -133
  63. package/dist/BreakpointSplitView/components/Breakends.d.ts +0 -8
  64. package/dist/BreakpointSplitView/components/Breakends.js +0 -95
  65. package/dist/BreakpointSplitView/components/BreakpointSplitView.d.ts +0 -5
  66. package/dist/BreakpointSplitView/components/BreakpointSplitView.js +0 -46
  67. package/dist/BreakpointSplitView/components/BreakpointSplitViewOverlay.d.ts +0 -5
  68. package/dist/BreakpointSplitView/components/BreakpointSplitViewOverlay.js +0 -33
  69. package/dist/BreakpointSplitView/components/ExportSvgDialog.d.ts +0 -7
  70. package/dist/BreakpointSplitView/components/ExportSvgDialog.js +0 -57
  71. package/dist/BreakpointSplitView/components/Header.d.ts +0 -5
  72. package/dist/BreakpointSplitView/components/Header.js +0 -52
  73. package/dist/BreakpointSplitView/components/HeaderSearchBoxes.d.ts +0 -5
  74. package/dist/BreakpointSplitView/components/HeaderSearchBoxes.js +0 -24
  75. package/dist/BreakpointSplitView/components/Overlay.d.ts +0 -8
  76. package/dist/BreakpointSplitView/components/Overlay.js +0 -27
  77. package/dist/BreakpointSplitView/components/PairedFeatures.d.ts +0 -8
  78. package/dist/BreakpointSplitView/components/PairedFeatures.js +0 -75
  79. package/dist/BreakpointSplitView/components/Translocations.d.ts +0 -8
  80. package/dist/BreakpointSplitView/components/Translocations.js +0 -99
  81. package/dist/BreakpointSplitView/components/getOrientationColor.d.ts +0 -41
  82. package/dist/BreakpointSplitView/components/getOrientationColor.js +0 -103
  83. package/dist/BreakpointSplitView/components/util.d.ts +0 -8
  84. package/dist/BreakpointSplitView/components/util.js +0 -142
  85. package/dist/BreakpointSplitView/getClip.d.ts +0 -1
  86. package/dist/BreakpointSplitView/getClip.js +0 -10
  87. package/dist/BreakpointSplitView/index.d.ts +0 -2
  88. package/dist/BreakpointSplitView/index.js +0 -52
  89. package/dist/BreakpointSplitView/model.d.ts +0 -344
  90. package/dist/BreakpointSplitView/model.js +0 -256
  91. package/dist/BreakpointSplitView/svgcomponents/SVGBackground.d.ts +0 -5
  92. package/dist/BreakpointSplitView/svgcomponents/SVGBackground.js +0 -10
  93. package/dist/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.d.ts +0 -5
  94. package/dist/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js +0 -41
  95. package/dist/BreakpointSplitView/svgcomponents/util.d.ts +0 -4
  96. package/dist/BreakpointSplitView/svgcomponents/util.js +0 -18
  97. package/dist/BreakpointSplitView/types.d.ts +0 -22
  98. package/dist/BreakpointSplitView/types.js +0 -2
  99. package/dist/BreakpointSplitView/util.d.ts +0 -28
  100. package/dist/BreakpointSplitView/util.js +0 -67
  101. package/dist/index.d.ts +0 -7
  102. 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('./BreakpointAlignmentsFeatureDetail')),
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
+ }
@@ -0,0 +1,2 @@
1
+ import type PluginManager from '@jbrowse/core/PluginManager';
2
+ export default function BreakpointGetFeaturesF(pluginManager: PluginManager): void;
@@ -0,0 +1,4 @@
1
+ import BreakpointGetFeatures from "./BreakpointGetFeatures.js";
2
+ export default function BreakpointGetFeaturesF(pluginManager) {
3
+ pluginManager.addRpcMethod(() => new BreakpointGetFeatures(pluginManager));
4
+ }
@@ -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
- var _a;
7
- const alt = (_a = feature.get('ALT')) === null || _a === void 0 ? void 0 : _a[0];
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 f = (ref) => assembly.getCanonicalRefName(ref) || ref;
12
- if (alt === '<TRA>') {
13
- const INFO = feature.get('INFO');
14
- return {
15
- pos: startPos,
16
- refName: f(refName),
17
- mateRefName: f(INFO.CHR2[0]),
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(f => f.refName === refName);
63
- const bottomRegion = assembly.regions.find(f => f.refName === mateRefName);
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: `${feature.get('name') || feature.get('id') || 'breakend'} split detail`,
60
+ displayName: `${featureName} split detail`,
75
61
  },
76
62
  };
77
63
  }
@@ -1,8 +1,3 @@
1
- import type { BreakpointViewModel } from '../model';
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 { getSnapshot } from 'mobx-state-tree';
7
- import { getBadlyPairedAlignments, getMatchedAlignmentFeatures, hasPairedReads, } from './util';
8
- import { getPxFromCoordinate, heightFromSpecificLevel, useNextFrame, yPos, } from '../util';
9
- import { getLongReadOrientationAbnormal, getLongReadOrientationColorOrDefault, getPairedOrientationColor, isAbnormalOrientation, } from './getOrientationColor';
10
- const [LEFT, , RIGHT] = [0, 1, 2, 3];
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 layoutMatches = model.getMatchedFeaturesInLayout(trackId, hasPaired
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.clipPos - b.clipPos);
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
- let yOffset = 0;
35
- if (parentRef.current) {
36
- const rect = parentRef.current.getBoundingClientRect();
37
- yOffset = rect.top;
35
+ const yOffset = getYOffset(parentRef);
36
+ const tracks = views.map(v => v.getTrack(trackId));
37
+ if (!assembly) {
38
+ return null;
38
39
  }
39
- return assembly ? (_jsx("g", { fill: "none", "data-testid": layoutMatches.length ? `${trackId}-loaded` : trackId, children: layoutMatches.map(chunk => {
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
- var _a, _b;
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: () => { } }).toJSON(),
118
- feature2: (allFeatures.get(f2.id()) || { toJSON: () => { } }).toJSON(),
112
+ feature1: (allFeatures.get(f1.id()) || { toJSON: () => ({}) }).toJSON(),
113
+ feature2: (allFeatures.get(f2.id()) || { toJSON: () => ({}) }).toJSON(),
119
114
  },
120
115
  });
121
- (_b = session.showWidget) === null || _b === void 0 ? void 0 : _b.call(session, featureWidget);
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
- }) })) : null;
124
+ }) }));
130
125
  });
131
126
  export default AlignmentConnections;
@@ -1,8 +1,3 @@
1
- import type { BreakpointViewModel } from '../model';
2
- declare const Breakends: ({ model, trackId, parentRef: ref, 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 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 { getSnapshot } from 'mobx-state-tree';
6
- import { findMatchingAlt, getMatchedBreakendFeatures } from './util';
7
- import { getPxFromCoordinate, useNextFrame, yPos } from '../util';
8
- const [LEFT] = [0, 1, 2, 3];
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 assembly = assemblyManager.get(views[0].assemblyNames[0]);
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
- let yoff = 0;
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
- if (!c1 || !c2) {
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
- yoff;
41
+ yOffset;
51
42
  const y2 = yPos(trackId, level2, views, tracks, c2, getTrackYPosOverride) -
52
- yoff;
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 path = [
58
- 'M',
59
- x1 -
60
- 20 *
61
- (relevantAlt.Join === 'left' ? -1 : 1) *
62
- (reversed1 ? -1 : 1),
63
- y1,
64
- 'L',
65
- x1,
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,4 +1,4 @@
1
- import type { BreakpointViewModel } from '../model';
1
+ import type { BreakpointViewModel } from '../model.ts';
2
2
  declare const BreakpointSplitView: ({ model, }: {
3
3
  model: BreakpointViewModel;
4
4
  }) => import("react/jsx-runtime").JSX.Element;
@@ -1,9 +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 { makeStyles } from 'tss-react/mui';
5
- import BreakpointSplitViewOverlay from './BreakpointSplitViewOverlay';
6
- import Header from './Header';
5
+ import BreakpointSplitViewOverlay from "./BreakpointSplitViewOverlay.js";
6
+ import Header from "./Header.js";
7
+ import Rubberband from "./Rubberband.js";
7
8
  const useStyles = makeStyles()(theme => ({
8
9
  viewDivider: {
9
10
  background: theme.palette.secondary.main,
@@ -18,8 +19,20 @@ const useStyles = makeStyles()(theme => ({
18
19
  rel: {
19
20
  position: 'relative',
20
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
+ },
21
34
  }));
22
- const BreakpointSplitViewLevels = observer(function ({ model, }) {
35
+ const BreakpointSplitViewLevels = observer(function BreakpointSplitViewLevels({ model, }) {
23
36
  const { classes } = useStyles();
24
37
  const { views } = model;
25
38
  const { pluginManager } = getEnv(model);
@@ -34,8 +47,8 @@ const BreakpointSplitViewLevels = observer(function ({ model, }) {
34
47
  ];
35
48
  }) }) }));
36
49
  });
37
- const BreakpointSplitView = observer(function ({ model, }) {
50
+ const BreakpointSplitView = observer(function BreakpointSplitView({ model, }) {
38
51
  const { classes } = useStyles();
39
- return (_jsxs("div", { children: [model.showHeader ? _jsx(Header, { model: model }) : null, _jsxs("div", { className: classes.container, children: [_jsx(BreakpointSplitViewLevels, { model: model }), _jsx(BreakpointSplitViewOverlay, { model: model })] })] }));
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 })] })] }));
40
53
  });
41
54
  export default BreakpointSplitView;
@@ -1,4 +1,4 @@
1
- import type { BreakpointViewModel } from '../model';
1
+ import type { BreakpointViewModel } from '../model.ts';
2
2
  declare const BreakpointSplitViewOverlay: ({ model, }: {
3
3
  model: BreakpointViewModel;
4
4
  }) => import("react/jsx-runtime").JSX.Element;
@@ -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 { makeStyles } from 'tss-react/mui';
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);
@@ -1,4 +1,4 @@
1
- import type { ExportSvgOptions } from '../types';
1
+ import type { ExportSvgOptions } from '../types.ts';
2
2
  export default function ExportSvgDialog({ model, handleClose, }: {
3
3
  model: {
4
4
  exportSvg(opts: ExportSvgOptions): Promise<void>;
@@ -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, offscreenCanvas ? (_jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: rasterizeLayers, onChange: () => {
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
  }
@@ -1,5 +1,5 @@
1
- import type { BreakpointViewModel } from '../model';
2
- declare const Header: ({ model }: {
1
+ import type { BreakpointViewModel } from '../model.ts';
2
+ declare const Header: ({ model, }: {
3
3
  model: BreakpointViewModel;
4
4
  }) => import("react/jsx-runtime").JSX.Element;
5
5
  export default Header;