From d8ec45b9c3938f8ceddcc670b34a65aa5c127a60 Mon Sep 17 00:00:00 2001 From: Colin Date: Mon, 25 Nov 2024 18:32:26 -0500 Subject: [PATCH] Wow --- packages/core/util/index.ts | 5 +- .../src/BreakendMultiLevelOptionDialog.tsx | 18 +- .../src/BreakendSingleLevelOptionDialog.tsx | 13 +- packages/sv-core/src/util.ts | 173 +++++++++--------- ...aunchPairedEndBreakpointSplitViewPanel.tsx | 13 +- ...ntaryAlignmentBreakpointSplitViewPanel.tsx | 34 +--- .../LinkedPairedAlignments.tsx | 13 +- .../SupplementaryAlignments.tsx | 12 +- .../dialogs/HighlightSettingsDialog.tsx | 4 +- plugins/sv-inspector/package.json | 3 +- .../sv-inspector/src/SvInspectorView/index.ts | 14 +- .../LaunchBreakendPanel.tsx | 8 +- 12 files changed, 123 insertions(+), 187 deletions(-) diff --git a/packages/core/util/index.ts b/packages/core/util/index.ts index d1c0049401..4a7058d8b1 100644 --- a/packages/core/util/index.ts +++ b/packages/core/util/index.ts @@ -1342,12 +1342,11 @@ export interface BasicFeature { end: number start: number refName: string - assemblyName?: string } // returns new array non-overlapping features -export function gatherOverlaps(regions: BasicFeature[], w = 5000) { - const memo = {} as Record +export function gatherOverlaps(regions: T[], w = 5000) { + const memo = {} as Record for (const x of regions) { if (!memo[x.refName]) { memo[x.refName] = [] diff --git a/packages/sv-core/src/BreakendMultiLevelOptionDialog.tsx b/packages/sv-core/src/BreakendMultiLevelOptionDialog.tsx index 6e5877da16..02dd75a0a1 100644 --- a/packages/sv-core/src/BreakendMultiLevelOptionDialog.tsx +++ b/packages/sv-core/src/BreakendMultiLevelOptionDialog.tsx @@ -6,10 +6,10 @@ import { when } from 'mobx' import { observer } from 'mobx-react' import { getSnapshot } from 'mobx-state-tree' -// types import Checkbox2 from './Checkbox2' +import { getBreakendCoveringRegions } from './util' -import type { Assembly } from '@jbrowse/core/assemblyManager/assembly' +// types import type { AbstractSessionModel, Feature } from '@jbrowse/core/util' import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' @@ -38,7 +38,6 @@ const BreakendMultiLevelOptionDialog = observer(function ({ feature, assemblyName, stableViewId, - viewType, view, }: { session: AbstractSessionModel @@ -47,17 +46,6 @@ const BreakendMultiLevelOptionDialog = observer(function ({ view?: LinearGenomeViewModel assemblyName: string stableViewId?: string - viewType: { - getBreakendCoveringRegions: (arg: { - feature: Feature - assembly: Assembly - }) => { - pos: number - refName: string - mateRefName: string - matePos: number - } - } }) { const [copyTracks, setCopyTracks] = useState(true) const [mirror, setMirror] = useState(true) @@ -107,7 +95,7 @@ const BreakendMultiLevelOptionDialog = observer(function ({ } const { refName, pos, mateRefName, matePos } = - viewType.getBreakendCoveringRegions({ + getBreakendCoveringRegions({ feature, assembly: assembly, }) diff --git a/packages/sv-core/src/BreakendSingleLevelOptionDialog.tsx b/packages/sv-core/src/BreakendSingleLevelOptionDialog.tsx index f92b5fb3b9..88d020b2f7 100644 --- a/packages/sv-core/src/BreakendSingleLevelOptionDialog.tsx +++ b/packages/sv-core/src/BreakendSingleLevelOptionDialog.tsx @@ -12,6 +12,7 @@ import Checkbox2 from './Checkbox2' // types import type { AbstractSessionModel, Feature } from '@jbrowse/core/util' import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' +import { singleLevelSnapshotFromBreakendFeature } from './util' interface Display { id: string @@ -36,7 +37,6 @@ const BreakendSingleLevelOptionDialog = observer(function ({ feature, stableViewId, assemblyName, - viewType, view, }: { session: AbstractSessionModel @@ -45,13 +45,6 @@ const BreakendSingleLevelOptionDialog = observer(function ({ feature: Feature view?: LinearGenomeViewModel assemblyName: string - viewType: { - singleLevelSnapshotFromBreakendFeature: (arg: { - feature: Feature - assemblyName: string - session: AbstractSessionModel - }) => any - } }) { const [copyTracks, setCopyTracks] = useState(true) const [windowSize, setWindowSize] = useLocalStorage( @@ -101,7 +94,7 @@ const BreakendSingleLevelOptionDialog = observer(function ({ throw new Error('windowSize not a number') } const { snap, coverage } = - viewType.singleLevelSnapshotFromBreakendFeature({ + singleLevelSnapshotFromBreakendFeature({ feature, assemblyName, session, @@ -130,7 +123,7 @@ const BreakendSingleLevelOptionDialog = observer(function ({ }) as unknown as { views: LinearGenomeViewModel[] } } else { viewInStack.views[0]?.setDisplayedRegions( - snap.views[0].displayedRegions, + snap.views[0]!.displayedRegions, ) // @ts-expect-error viewInStack.setDisplayName(snap.displayName) diff --git a/packages/sv-core/src/util.ts b/packages/sv-core/src/util.ts index e6c9675ce0..941e1a301e 100644 --- a/packages/sv-core/src/util.ts +++ b/packages/sv-core/src/util.ts @@ -1,99 +1,96 @@ import { parseBreakend } from '@gmod/vcf' -import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType' - +import { gatherOverlaps } from '@jbrowse/core/util' +// types import type { Assembly } from '@jbrowse/core/assemblyManager/assembly' -import { - gatherOverlaps, - type AbstractSessionModel, - type Feature, -} from '@jbrowse/core/util' +import type { AbstractSessionModel, Feature } from '@jbrowse/core/util' -export default class BreakpointSplitViewType extends ViewType { - getBreakendCoveringRegions({ - feature, - assembly, - }: { - feature: Feature - assembly: Assembly - }) { - const alt = feature.get('ALT')?.[0] - const bnd = alt ? parseBreakend(alt) : undefined - const startPos = feature.get('start') - const refName = feature.get('refName') - const f = (ref: string) => assembly.getCanonicalRefName(ref) || ref +export function getBreakendCoveringRegions({ + feature, + assembly, +}: { + feature: Feature + assembly: Assembly +}) { + const alt = feature.get('ALT')?.[0] + const bnd = alt ? parseBreakend(alt) : undefined + const startPos = feature.get('start') + const refName = feature.get('refName') + const f = (ref: string) => assembly.getCanonicalRefName(ref) || ref - if (alt === '') { - const INFO = feature.get('INFO') - return { - pos: startPos, - refName: f(refName), - mateRefName: f(INFO.CHR2[0]), - matePos: INFO.END[0] - 1, - } - } else if (bnd?.MatePosition) { - const matePosition = bnd.MatePosition.split(':') - return { - pos: startPos, - refName: f(refName), - mateRefName: f(matePosition[0]!), - matePos: +matePosition[1]! - 1, - } - } else if (feature.get('mate')) { - const mate = feature.get('mate') - return { - pos: startPos, - refName: f(refName), - mateRefName: f(mate.refName), - matePos: mate.start, - } - } else { - return { - pos: startPos, - refName: f(refName), - mateRefName: f(refName), - matePos: feature.get('end'), - } + if (alt === '') { + const INFO = feature.get('INFO') + return { + pos: startPos, + refName: f(refName), + mateRefName: f(INFO.CHR2[0]), + matePos: INFO.END[0] - 1, } - } - - singleLevelSnapshotFromBreakendFeature({ - feature, - session, - assemblyName, - }: { - feature: Feature - session: AbstractSessionModel - assemblyName: string - }) { - const { assemblyManager } = session - const assembly = assemblyManager.get(assemblyName) - if (!assembly) { - throw new Error(`assembly ${assemblyName} not found`) + } else if (bnd?.MatePosition) { + const matePosition = bnd.MatePosition.split(':') + return { + pos: startPos, + refName: f(refName), + mateRefName: f(matePosition[0]!), + matePos: +matePosition[1]! - 1, } - if (!assembly.regions) { - throw new Error(`assembly ${assemblyName} regions not loaded`) + } else if (feature.get('mate')) { + const mate = feature.get('mate') + return { + pos: startPos, + refName: f(refName), + mateRefName: f(mate.refName), + matePos: mate.start, } - const coverage = this.getBreakendCoveringRegions({ - feature, - assembly, - }) - const { refName, mateRefName } = coverage - const topRegion = assembly.regions.find(f => f.refName === refName)! - const bottomRegion = assembly.regions.find(f => f.refName === mateRefName)! + } else { return { - coverage, - snap: { - type: 'BreakpointSplitView', - views: [ - { - type: 'LinearGenomeView', - displayedRegions: gatherOverlaps([topRegion, bottomRegion]), - }, - ], - displayName: `${ - feature.get('name') || feature.get('id') || 'breakend' - } split detail`, - }, + pos: startPos, + refName: f(refName), + mateRefName: f(refName), + matePos: feature.get('end'), } } } + +export function singleLevelSnapshotFromBreakendFeature({ + feature, + session, + assemblyName, +}: { + feature: Feature + session: AbstractSessionModel + assemblyName: string +}) { + const { assemblyManager } = session + const assembly = assemblyManager.get(assemblyName) + if (!assembly) { + throw new Error(`assembly ${assemblyName} not found`) + } + if (!assembly.regions) { + throw new Error(`assembly ${assemblyName} regions not loaded`) + } + const coverage = getBreakendCoveringRegions({ + feature, + assembly, + }) + const { refName, mateRefName } = coverage + const topRegion = assembly.regions.find(f => f.refName === refName)! + const bottomRegion = assembly.regions.find(f => f.refName === mateRefName)! + return { + coverage, + snap: { + type: 'BreakpointSplitView', + views: [ + { + type: 'LinearGenomeView', + displayedRegions: gatherOverlaps([ + { ...topRegion, assemblyName }, + { ...bottomRegion, assemblyName }, + ]), + }, + ], + displayName: `${ + feature.get('name') || feature.get('id') || 'breakend' + } split detail`, + }, + } +} diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/LaunchPairedEndBreakpointSplitViewPanel.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/LaunchPairedEndBreakpointSplitViewPanel.tsx index d8bb96e50e..4aa19b3d52 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/LaunchPairedEndBreakpointSplitViewPanel.tsx +++ b/plugins/alignments/src/AlignmentsFeatureDetail/LaunchPairedEndBreakpointSplitViewPanel.tsx @@ -3,12 +3,11 @@ import React, { lazy } from 'react' import { SimpleFeature, getSession, toLocale } from '@jbrowse/core/util' import { Link, Typography } from '@mui/material' +// types import type { AlignmentFeatureWidgetModel } from './stateModelFactory' import type { ViewType } from '@jbrowse/core/pluggableElementTypes' import type { SimpleFeatureSerialized } from '@jbrowse/core/util' -// locals - // lazies const BreakendMultiLevelOptionDialog = lazy( () => import('./BreakendMultiLevelOptionDialog'), @@ -20,11 +19,9 @@ const BreakendSingleLevelOptionDialog = lazy( export default function LaunchPairedEndBreakpointSplitViewPanel({ model, feature, - viewType, }: { model: AlignmentFeatureWidgetModel feature: SimpleFeatureSerialized - viewType: ViewType }) { const session = getSession(model) const f1 = { @@ -56,10 +53,8 @@ export default function LaunchPairedEndBreakpointSplitViewPanel({ BreakendMultiLevelOptionDialog, { handleClose, - model, + session, feature: new SimpleFeature({ ...f1, mate: f2 }), - // @ts-expect-error - viewType, view: model.view, assemblyName: model.view.displayedRegions[0].assemblyName, }, @@ -76,10 +71,8 @@ export default function LaunchPairedEndBreakpointSplitViewPanel({ BreakendSingleLevelOptionDialog, { handleClose, - model, + session, feature: new SimpleFeature({ ...f1, mate: f2 }), - // @ts-expect-error - viewType, view: model.view, assemblyName: model.view.displayedRegions[0].assemblyName, }, diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/LaunchSupplementaryAlignmentBreakpointSplitViewPanel.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/LaunchSupplementaryAlignmentBreakpointSplitViewPanel.tsx index f917748c90..279ae4ef2f 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/LaunchSupplementaryAlignmentBreakpointSplitViewPanel.tsx +++ b/plugins/alignments/src/AlignmentsFeatureDetail/LaunchSupplementaryAlignmentBreakpointSplitViewPanel.tsx @@ -24,11 +24,9 @@ const BreakendSingleLevelOptionDialog = lazy( export default function LaunchBreakpointSplitViewPanel({ model, feature, - viewType, }: { model: AlignmentFeatureWidgetModel feature: SimpleFeatureSerialized - viewType: ViewType }) { const { view } = model const [res, setRes] = useState() @@ -66,18 +64,8 @@ export default function LaunchBreakpointSplitViewPanel({
  • {f1.refName}:{toLocale(f1.strand === 1 ? f1.end : f1.start)} ->{' '} {f2.refName}:{toLocale(f2.strand === 1 ? f2.start : f2.end)}{' '} - {' '} - + {' '} +
  • ) })} @@ -90,26 +78,23 @@ function TopBottomSplitViewLink({ model, f1, f2, - viewType, }: { model: AlignmentFeatureWidgetModel f1: ReducedFeature f2: ReducedFeature - viewType: ViewType }) { return ( { event.preventDefault() - getSession(model).queueDialog(handleClose => [ + const session = getSession(model) + session.queueDialog(handleClose => [ BreakendMultiLevelOptionDialog, { handleClose, - model, + session, feature: new SimpleFeature({ ...f1, mate: f2 }), - // @ts-expect-error - viewType, view: model.view, assemblyName: model.view.displayedRegions[0].assemblyName, }, @@ -125,26 +110,23 @@ function SideBySideViewLink({ model, f1, f2, - viewType, }: { model: AlignmentFeatureWidgetModel f1: ReducedFeature f2: ReducedFeature - viewType: ViewType }) { return ( { event.preventDefault() - getSession(model).queueDialog(handleClose => [ + const session = getSession(model) + session.queueDialog(handleClose => [ BreakendSingleLevelOptionDialog, { handleClose, - model, + session, feature: new SimpleFeature({ ...f1, mate: f2 }), - // @ts-expect-error - viewType, view: model.view, assemblyName: model.view.displayedRegions[0].assemblyName, }, diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/LinkedPairedAlignments.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/LinkedPairedAlignments.tsx index 76c5c42066..3d97e1fd36 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/LinkedPairedAlignments.tsx +++ b/plugins/alignments/src/AlignmentsFeatureDetail/LinkedPairedAlignments.tsx @@ -5,11 +5,8 @@ import { getEnv, getSession } from '@jbrowse/core/util' import LaunchPairedEndBreakpointSplitViewPanel from './LaunchPairedEndBreakpointSplitViewPanel' +// types import type { AlignmentFeatureWidgetModel } from './stateModelFactory' -import type { ViewType } from '@jbrowse/core/pluggableElementTypes' - -// locals - import type { SimpleFeatureSerialized } from '@jbrowse/core/util' export default function SuppAlignments(props: { @@ -19,19 +16,17 @@ export default function SuppAlignments(props: { const { model, feature } = props const session = getSession(model) const { pluginManager } = getEnv(session) - let viewType: ViewType | undefined + let hasBreakendSplitView = false try { - viewType = pluginManager.getViewType('BreakpointSplitView') + hasBreakendSplitView = !!pluginManager.getViewType('BreakpointSplitView') } catch (e) { // ignore } - return ( - {viewType ? ( + {hasBreakendSplitView ? ( diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/SupplementaryAlignments.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/SupplementaryAlignments.tsx index feecb49ccd..a738e53644 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/SupplementaryAlignments.tsx +++ b/plugins/alignments/src/AlignmentsFeatureDetail/SupplementaryAlignments.tsx @@ -21,10 +21,10 @@ export default function SupplementaryAlignments(props: { const { model, tag, feature } = props const session = getSession(model) const { pluginManager } = getEnv(session) - let viewType: ViewType | undefined + let hasBreakendSplitView = false try { - viewType = pluginManager.getViewType('BreakpointSplitView') + hasBreakendSplitView = !!pluginManager.getViewType('BreakpointSplitView') } catch (e) { // ignore } @@ -32,12 +32,8 @@ export default function SupplementaryAlignments(props: { return ( - {viewType ? ( - + {hasBreakendSplitView ? ( + ) : null} ) diff --git a/plugins/grid-bookmark/src/GridBookmarkWidget/components/dialogs/HighlightSettingsDialog.tsx b/plugins/grid-bookmark/src/GridBookmarkWidget/components/dialogs/HighlightSettingsDialog.tsx index acab69de1e..a4990a20b5 100644 --- a/plugins/grid-bookmark/src/GridBookmarkWidget/components/dialogs/HighlightSettingsDialog.tsx +++ b/plugins/grid-bookmark/src/GridBookmarkWidget/components/dialogs/HighlightSettingsDialog.tsx @@ -30,7 +30,7 @@ const HighlightSettingsDialog = observer(function ({ data-testid="toggle_highlight_all_switch" checked={model.areBookmarksHighlightedOnAllOpenViews} onChange={() => { - model.setHighlightToggle( + model.setBookmarkHighlightsVisible( !model.areBookmarksHighlightedOnAllOpenViews, ) }} @@ -42,7 +42,7 @@ const HighlightSettingsDialog = observer(function ({ data-testid="toggle_highlight_label_all_switch" checked={model.areBookmarksHighlightLabelsOnAllOpenViews} onChange={() => { - model.setLabelToggle( + model.setBookmarkLabelsVisible( !model.areBookmarksHighlightLabelsOnAllOpenViews, ) }} diff --git a/plugins/sv-inspector/package.json b/plugins/sv-inspector/package.json index 8f28da159a..6516d19b7f 100644 --- a/plugins/sv-inspector/package.json +++ b/plugins/sv-inspector/package.json @@ -36,7 +36,8 @@ "clean": "rimraf dist esm *.tsbuildinfo" }, "dependencies": { - "@mui/icons-material": "^6.0.0" + "@mui/icons-material": "^6.0.0", + "@jbrowse/sv-core": "^2.0.0" }, "peerDependencies": { "@jbrowse/core": "^2.0.0", diff --git a/plugins/sv-inspector/src/SvInspectorView/index.ts b/plugins/sv-inspector/src/SvInspectorView/index.ts index 5e89a03ce6..f0b7faac1b 100644 --- a/plugins/sv-inspector/src/SvInspectorView/index.ts +++ b/plugins/sv-inspector/src/SvInspectorView/index.ts @@ -11,24 +11,20 @@ import type PluginManager from '@jbrowse/core/PluginManager' import type { Feature } from '@jbrowse/core/util' import type { CircularViewModel } from '@jbrowse/plugin-circular-view' import type { IAnyStateTreeNode } from 'mobx-state-tree' +import { singleLevelSnapshotFromBreakendFeature } from '@jbrowse/sv-core' -function defaultOnChordClick( - feature: Feature, - chordTrack: IAnyStateTreeNode, - pluginManager: PluginManager, -) { +function defaultOnChordClick(feature: Feature, chordTrack: IAnyStateTreeNode) { const session = getSession(chordTrack) session.setSelection(feature) const view = getContainingView(chordTrack) as CircularViewModel - const viewType = pluginManager.getViewType('BreakpointSplitView') as any - const viewSnapshot = viewType.singleLevelSnapshotFromBreakendFeature({ + const { coverage, snap } = singleLevelSnapshotFromBreakendFeature({ feature, view, }) // try to center the offsetPx - viewSnapshot.views[0]!.offsetPx -= view.width / 2 + 100 - viewSnapshot.views[1]!.offsetPx -= view.width / 2 + 100 + snap.views[0]!.offsetPx -= view.width / 2 + 100 + snap.views[1]!.offsetPx -= view.width / 2 + 100 const newViewId = `${chordTrack.id}_spawned` const viewInStack = session.views.find(v => v.id === newViewId) diff --git a/plugins/variants/src/VariantFeatureWidget/LaunchBreakendPanel.tsx b/plugins/variants/src/VariantFeatureWidget/LaunchBreakendPanel.tsx index e7fbdc25cf..401d99a9b2 100644 --- a/plugins/variants/src/VariantFeatureWidget/LaunchBreakendPanel.tsx +++ b/plugins/variants/src/VariantFeatureWidget/LaunchBreakendPanel.tsx @@ -91,11 +91,9 @@ function LaunchBreakpointSplitViewPanel({ BreakendMultiLevelOptionDialog, { handleClose, - model, + session, feature: simpleFeature, stableViewId: `${model.id}_${assemblyName}_breakpointsplitview_multilevel`, - // @ts-expect-error - viewType, view: model.view, assemblyName, }, @@ -112,11 +110,9 @@ function LaunchBreakpointSplitViewPanel({ BreakendSingleLevelOptionDialog, { handleClose, - model, + session, feature: simpleFeature, stableViewId: `${model.id}_${assemblyName}_breakpointsplitview_singlelevel`, - // @ts-expect-error - viewType, view: model.view, assemblyName, },