jbrowse-plugin-graphgenomeviewer 7.1.0 → 7.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -8
- package/dist/chunks/AddTrackWorkflow-RPH4ZFSX.js +2 -0
- package/dist/chunks/{AddTrackWorkflow-Q6RY23LF.js.map → AddTrackWorkflow-RPH4ZFSX.js.map} +2 -2
- package/dist/chunks/{ChooseSamplesDialog-BNVYBGJQ.js → ChooseSamplesDialog-JRN4YLBY.js} +2 -2
- package/dist/chunks/{ChooseWalksDialog-EN3C6AQA.js → ChooseWalksDialog-UXLFZTZJ.js} +2 -2
- package/dist/chunks/GbzBaseSyntenyAdapter-LRECKL74.js +2 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-LRECKL74.js.map +7 -0
- package/dist/chunks/{GraphGenomeView-Z2WZKEEO.js → GraphGenomeView-NJ5XCREB.js} +2 -2
- package/dist/chunks/GraphTrackSettingsDialog-47PQ6LM7.js +6 -0
- package/dist/chunks/GraphTrackSettingsDialog-47PQ6LM7.js.map +7 -0
- package/dist/chunks/{HighlightColorDialog-EYBKFSJU.js → HighlightColorDialog-UK5VDDV6.js} +2 -2
- package/dist/chunks/{LinearGraphDisplay-WIBVWRQE.js → LinearGraphDisplay-TXTKAACH.js} +2 -2
- package/dist/chunks/MinigraphBubbleAdapter-HW7XQMAI.js +2 -0
- package/dist/chunks/MinigraphBubbleAdapter-HW7XQMAI.js.map +7 -0
- package/dist/chunks/RgfaTabixAdapter-PSKYWD2X.js +3 -0
- package/dist/chunks/RgfaTabixAdapter-PSKYWD2X.js.map +7 -0
- package/dist/chunks/{TrackWarningsDialog-QT4YEXGD.js → TrackWarningsDialog-TRYBOIHX.js} +2 -2
- package/dist/chunks/WalkTabixSyntenyAdapter-U53JLYVU.js +2 -0
- package/dist/chunks/WalkTabixSyntenyAdapter-U53JLYVU.js.map +7 -0
- package/dist/chunks/chunk-2MCOY3YG.js +3 -0
- package/dist/chunks/chunk-2MCOY3YG.js.map +7 -0
- package/dist/chunks/{chunk-EWFJBQDV.js → chunk-5O6YRJHL.js} +2 -2
- package/dist/chunks/{chunk-EWFJBQDV.js.map → chunk-5O6YRJHL.js.map} +1 -1
- package/dist/chunks/chunk-BUY2PV7D.js +2 -0
- package/dist/chunks/chunk-BUY2PV7D.js.map +7 -0
- package/dist/chunks/chunk-DVCZ3BOC.js +2 -0
- package/dist/chunks/chunk-DVCZ3BOC.js.map +7 -0
- package/dist/chunks/{chunk-SAMJE6GH.js → chunk-EGDLEQ47.js} +3 -3
- package/dist/chunks/{chunk-SAMJE6GH.js.map → chunk-EGDLEQ47.js.map} +4 -4
- package/dist/chunks/chunk-FHZXW7IG.js +2 -0
- package/dist/chunks/chunk-FHZXW7IG.js.map +7 -0
- package/dist/chunks/{chunk-DZ4OUKQV.js → chunk-FITPB5X6.js} +2 -2
- package/dist/chunks/{chunk-DZ4OUKQV.js.map → chunk-FITPB5X6.js.map} +1 -1
- package/dist/chunks/chunk-FVK2THE4.js +2 -0
- package/dist/chunks/chunk-FVK2THE4.js.map +7 -0
- package/dist/chunks/{chunk-AHFQHYJI.js → chunk-IBWORBGY.js} +2 -2
- package/dist/chunks/chunk-MND7GAOG.js +2 -0
- package/dist/chunks/chunk-MND7GAOG.js.map +7 -0
- package/dist/chunks/chunk-MURN3JPK.js +2 -0
- package/dist/chunks/chunk-MURN3JPK.js.map +7 -0
- package/dist/chunks/{chunk-3Q4RBDCL.js → chunk-N6PQZSAX.js} +1 -1
- package/dist/chunks/{chunk-3Q4RBDCL.js.map → chunk-N6PQZSAX.js.map} +1 -1
- package/dist/chunks/chunk-NWJYLP6Z.js +4 -0
- package/dist/chunks/chunk-NWJYLP6Z.js.map +7 -0
- package/dist/chunks/{chunk-YJHYBDYF.js → chunk-S75W27NT.js} +2 -2
- package/dist/chunks/{chunk-YJHYBDYF.js.map → chunk-S75W27NT.js.map} +1 -1
- package/dist/chunks/{chunk-IXQK7JAW.js → chunk-T4HOC6O3.js} +1 -1
- package/dist/chunks/{chunk-IXQK7JAW.js.map → chunk-T4HOC6O3.js.map} +1 -1
- package/dist/chunks/{chunk-AU73K7LX.js → chunk-TUKJRVBS.js} +1 -1
- package/dist/chunks/{chunk-AU73K7LX.js.map → chunk-TUKJRVBS.js.map} +1 -1
- package/dist/chunks/chunk-UGCNPRKC.js +2 -0
- package/dist/chunks/{chunk-E3WYW7Z6.js.map → chunk-UGCNPRKC.js.map} +2 -2
- package/dist/chunks/{chunk-YSTMDXXX.js → chunk-VJ3SO4CH.js} +1 -1
- package/dist/chunks/{chunk-YSTMDXXX.js.map → chunk-VJ3SO4CH.js.map} +1 -1
- package/dist/chunks/{chunk-XT766CZ5.js → chunk-W6EQLQ6H.js} +1 -1
- package/dist/chunks/{chunk-XT766CZ5.js.map → chunk-W6EQLQ6H.js.map} +1 -1
- package/dist/chunks/chunk-WAMBI7OX.js +2 -0
- package/dist/chunks/chunk-WAMBI7OX.js.map +7 -0
- package/dist/chunks/chunk-WCGVUZWP.js +2 -0
- package/dist/chunks/chunk-WCGVUZWP.js.map +7 -0
- package/dist/chunks/chunk-WEGWTC52.js +2 -0
- package/dist/chunks/chunk-WEGWTC52.js.map +7 -0
- package/dist/chunks/chunk-YU7T5WOQ.js +2 -0
- package/dist/chunks/{chunk-6OPB7FOJ.js.map → chunk-YU7T5WOQ.js.map} +2 -2
- package/dist/chunks/{chunk-446RWIA3.js → chunk-ZMOMFU2B.js} +1 -1
- package/dist/chunks/{chunk-446RWIA3.js.map → chunk-ZMOMFU2B.js.map} +1 -1
- package/dist/chunks/renderSvg-KSQI5DH6.js +2 -0
- package/dist/chunks/renderSvg-KSQI5DH6.js.map +7 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -4
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +8 -9
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +12 -10
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +31 -39
- package/src/GbzBaseSyntenyAdapter/configSchema.ts +5 -29
- package/src/GetGraphHaplotypes.ts +6 -15
- package/src/GetGraphReads.ts +7 -14
- package/src/GetSubgraph.ts +11 -20
- package/src/GraphAddTrackWorkflow/AddTrackWorkflow.test.tsx +73 -0
- package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +1 -0
- package/src/GraphAddTrackWorkflow/buildTrackConfig.test.ts +3 -2
- package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +2 -11
- package/src/GraphAddTrackWorkflow/graphManifest.ts +15 -3
- package/src/GraphGenomeView/components/BubbleHalos.tsx +5 -24
- package/src/GraphGenomeView/components/ElTree.tsx +6 -1
- package/src/GraphGenomeView/components/FacetPanels.test.tsx +65 -0
- package/src/GraphGenomeView/components/FacetPanels.tsx +17 -12
- package/src/GraphGenomeView/components/GenePins.tsx +5 -22
- package/src/GraphGenomeView/components/GraphCanvas.tsx +40 -234
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +102 -108
- package/src/GraphGenomeView/components/GraphToolbar.tsx +5 -6
- package/src/GraphGenomeView/components/HoverLayer.tsx +3 -7
- package/src/GraphGenomeView/components/HoverTooltip.test.tsx +39 -0
- package/src/GraphGenomeView/components/HoverTooltip.tsx +122 -0
- package/src/GraphGenomeView/components/LabelChip.tsx +3 -5
- package/src/GraphGenomeView/components/LabelLayer.tsx +4 -17
- package/src/GraphGenomeView/components/LabelledSelect.tsx +1 -1
- package/src/GraphGenomeView/components/Legend.tsx +14 -23
- package/src/GraphGenomeView/components/PaneContextMenu.test.tsx +48 -0
- package/src/GraphGenomeView/components/PaneContextMenu.tsx +144 -0
- package/src/GraphGenomeView/components/PaneLayer.tsx +91 -0
- package/src/GraphGenomeView/components/PaneSvg.tsx +32 -50
- package/src/GraphGenomeView/components/ReferenceStripOverlay.test.tsx +48 -0
- package/src/GraphGenomeView/components/ReferenceStripOverlay.tsx +13 -42
- package/src/GraphGenomeView/components/SettingsMenu.test.tsx +29 -0
- package/src/GraphGenomeView/components/SettingsMenu.tsx +56 -65
- package/src/GraphGenomeView/components/TubeMapOverlay.tsx +15 -46
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +58 -132
- package/src/GraphGenomeView/components/WalkStrip.tsx +17 -20
- package/src/GraphGenomeView/components/WalkStripLocator.tsx +8 -15
- package/src/GraphGenomeView/components/usePaneGestures.test.tsx +35 -0
- package/src/GraphGenomeView/components/usePaneGestures.ts +88 -91
- package/src/GraphGenomeView/genes/geneFeatures.ts +27 -45
- package/src/GraphGenomeView/graphLayers.ts +4 -4
- package/src/GraphGenomeView/host.ts +1 -1
- package/src/GraphGenomeView/legendSections.test.ts +23 -1
- package/src/GraphGenomeView/legendSections.ts +68 -74
- package/src/GraphGenomeView/model.test.ts +373 -39
- package/src/GraphGenomeView/model.ts +67 -11
- package/src/GraphGenomeView/pane/fitViews.ts +57 -514
- package/src/GraphGenomeView/pane/graphViews.ts +219 -160
- package/src/GraphGenomeView/pane/hostViews.ts +2 -10
- package/src/GraphGenomeView/pane/keepInView.ts +5 -4
- package/src/GraphGenomeView/pane/launchActions.ts +117 -139
- package/src/GraphGenomeView/pane/launchViews.ts +8 -27
- package/src/GraphGenomeView/pane/loadActions.ts +208 -187
- package/src/GraphGenomeView/pane/morph.ts +3 -9
- package/src/GraphGenomeView/pane/paneBase.ts +22 -11
- package/src/GraphGenomeView/pane/renderingActions.ts +65 -11
- package/src/GraphGenomeView/pane/settingActions.ts +17 -42
- package/src/GraphGenomeView/pane/tubeMapViews.ts +445 -0
- package/src/GraphGenomeView/pane/walkRowViews.ts +29 -37
- package/src/GraphGenomeView/renderPipeline.test.ts +1 -1
- package/src/GraphGenomeView/repeats/repeatFeatures.test.ts +23 -0
- package/src/GraphGenomeView/repeats/repeatFeatures.ts +16 -23
- package/src/GraphGenomeView/repeats/walkCalls.test.ts +18 -0
- package/src/GraphGenomeView/repeats/walkCalls.ts +46 -38
- package/src/GraphGenomeView/subgraphLoad.test.ts +67 -3
- package/src/GraphGenomeView/trackFeatures.ts +24 -0
- package/src/GraphGenomeView/tubeMapPanels.ts +28 -36
- package/src/GraphGenomeView/viewModel.ts +8 -56
- package/src/GraphGenomeView/walkRowGroups.ts +104 -30
- package/src/GraphGenomeView/walkRowSpans.test.ts +6 -0
- package/src/GraphGenomeView/walkRowSpans.ts +22 -31
- package/src/GraphGenomeView/walkRowsCut.ts +8 -17
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +173 -26
- package/src/LinearGraphDisplay/components/SubgraphContextSelect.test.tsx +1 -1
- package/src/LinearGraphDisplay/components/SubgraphContextSelect.tsx +3 -3
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.test.tsx +3 -3
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.tsx +7 -10
- package/src/LinearGraphDisplay/configSchema.ts +0 -3
- package/src/LinearGraphDisplay/denseWindow.ts +8 -11
- package/src/LinearGraphDisplay/model.ts +89 -82
- package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.test.ts +21 -0
- package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.ts +6 -14
- package/src/MinigraphBubbleAdapter/configSchema.ts +3 -9
- package/src/MinigraphBubbleAdapter/index.ts +1 -2
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.test.ts +45 -1
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +111 -276
- package/src/RgfaTabixAdapter/configSchema.ts +15 -49
- package/src/RgfaTabixAdapter/index.ts +1 -2
- package/src/RgfaTabixAdapter/rgfaBed.ts +25 -15
- package/src/RgfaTabixAdapter/rgfaCut.ts +139 -0
- package/src/RgfaTabixAdapter/test_data/walks2_twopart.GRCh38.walks.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/walks2_twopart.GRCh38.walks.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/walkFiles.test.ts +33 -0
- package/src/RgfaTabixAdapter/walkReader.ts +103 -27
- package/src/RgfaTabixAdapter/walkRowRuns.test.ts +53 -0
- package/src/RgfaTabixAdapter/walkRowRuns.ts +42 -24
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.test.ts +37 -6
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +23 -45
- package/src/WalkTabixSyntenyAdapter/configSchema.ts +11 -24
- package/src/WalkTabixSyntenyAdapter/index.ts +1 -2
- package/src/WalkTabixSyntenyAdapter/walkLanes.ts +35 -43
- package/src/adapterImplementing.ts +27 -0
- package/src/graphTrackDefaults/index.ts +14 -20
- package/src/hoverSync/lgvHover.ts +2 -4
- package/src/launchFromGraph/contributors.ts +1 -5
- package/src/launchFromGraph/graphMenuItems.ts +3 -4
- package/src/launchFromGraph/launchFromGraph.test.ts +0 -1
- package/src/launchFromGraph/launchFromGraph.ts +8 -13
- package/src/panSNTabix.test.ts +1 -26
- package/src/panSNTabix.ts +54 -51
- package/src/synteny/lanePairs.ts +8 -0
- package/src/synteny/matchFeature.ts +52 -0
- package/src/tabixRange.ts +13 -10
- package/src/version.ts +1 -1
- package/dist/chunks/AddTrackWorkflow-Q6RY23LF.js +0 -2
- package/dist/chunks/GbzBaseSyntenyAdapter-TIXR4Y3J.js +0 -2
- package/dist/chunks/GbzBaseSyntenyAdapter-TIXR4Y3J.js.map +0 -7
- package/dist/chunks/GraphTrackSettingsDialog-TAM6PMXD.js +0 -6
- package/dist/chunks/GraphTrackSettingsDialog-TAM6PMXD.js.map +0 -7
- package/dist/chunks/MinigraphBubbleAdapter-AGI2UYQR.js +0 -2
- package/dist/chunks/MinigraphBubbleAdapter-AGI2UYQR.js.map +0 -7
- package/dist/chunks/RgfaTabixAdapter-2OL5X3HF.js +0 -3
- package/dist/chunks/RgfaTabixAdapter-2OL5X3HF.js.map +0 -7
- package/dist/chunks/WalkTabixSyntenyAdapter-UKFHCFOF.js +0 -2
- package/dist/chunks/WalkTabixSyntenyAdapter-UKFHCFOF.js.map +0 -7
- package/dist/chunks/chunk-5SFGHDQA.js +0 -2
- package/dist/chunks/chunk-5SFGHDQA.js.map +0 -7
- package/dist/chunks/chunk-6OPB7FOJ.js +0 -2
- package/dist/chunks/chunk-AMFIBQVR.js +0 -2
- package/dist/chunks/chunk-AMFIBQVR.js.map +0 -7
- package/dist/chunks/chunk-BJMRBVG3.js +0 -2
- package/dist/chunks/chunk-BJMRBVG3.js.map +0 -7
- package/dist/chunks/chunk-E3WYW7Z6.js +0 -2
- package/dist/chunks/chunk-EHCF6AXH.js +0 -2
- package/dist/chunks/chunk-EHCF6AXH.js.map +0 -7
- package/dist/chunks/chunk-GOFS7BXK.js +0 -3
- package/dist/chunks/chunk-GOFS7BXK.js.map +0 -7
- package/dist/chunks/chunk-IJQTSJGT.js +0 -4
- package/dist/chunks/chunk-IJQTSJGT.js.map +0 -7
- package/dist/chunks/chunk-J6NZIAZF.js +0 -2
- package/dist/chunks/chunk-J6NZIAZF.js.map +0 -7
- package/dist/chunks/chunk-LUTMKRXY.js +0 -2
- package/dist/chunks/chunk-LUTMKRXY.js.map +0 -7
- package/dist/chunks/chunk-MHPJB22Z.js +0 -2
- package/dist/chunks/chunk-MHPJB22Z.js.map +0 -7
- package/dist/chunks/chunk-TXI43PEN.js +0 -2
- package/dist/chunks/chunk-TXI43PEN.js.map +0 -7
- package/dist/chunks/chunk-WRH2EJKX.js +0 -2
- package/dist/chunks/chunk-WRH2EJKX.js.map +0 -7
- package/dist/chunks/chunk-WX26KTZM.js +0 -2
- package/dist/chunks/chunk-WX26KTZM.js.map +0 -7
- package/dist/chunks/renderSvg-ST2GBBOC.js +0 -2
- package/dist/chunks/renderSvg-ST2GBBOC.js.map +0 -7
- package/src/GraphGenomeView/components/WalkRowContextMenu.tsx +0 -105
- /package/dist/chunks/{ChooseSamplesDialog-BNVYBGJQ.js.map → ChooseSamplesDialog-JRN4YLBY.js.map} +0 -0
- /package/dist/chunks/{ChooseWalksDialog-EN3C6AQA.js.map → ChooseWalksDialog-UXLFZTZJ.js.map} +0 -0
- /package/dist/chunks/{GraphGenomeView-Z2WZKEEO.js.map → GraphGenomeView-NJ5XCREB.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-EYBKFSJU.js.map → HighlightColorDialog-UK5VDDV6.js.map} +0 -0
- /package/dist/chunks/{LinearGraphDisplay-WIBVWRQE.js.map → LinearGraphDisplay-TXTKAACH.js.map} +0 -0
- /package/dist/chunks/{TrackWarningsDialog-QT4YEXGD.js.map → TrackWarningsDialog-TRYBOIHX.js.map} +0 -0
- /package/dist/chunks/{chunk-AHFQHYJI.js.map → chunk-IBWORBGY.js.map} +0 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { formatBp } from '@jbrowse/bandage-core/graphLabels'
|
|
2
|
+
import { getDpr } from '@jbrowse/bandage-core/renderer/canvas'
|
|
3
|
+
import { referenceBoxes, rulerBoxes } from '@jbrowse/bandage-core/tubeMap/axis'
|
|
4
|
+
import {
|
|
5
|
+
connectorAt,
|
|
6
|
+
drawLitConnector,
|
|
7
|
+
tubeMapConnectors,
|
|
8
|
+
} from '@jbrowse/bandage-core/tubeMap/connectors'
|
|
9
|
+
import { deviationMarks } from '@jbrowse/bandage-core/tubeMap/deviations'
|
|
10
|
+
import {
|
|
11
|
+
drawTubeMapHighlight,
|
|
12
|
+
mismatchOnScreen,
|
|
13
|
+
mismatchesLegible,
|
|
14
|
+
tubeMapMismatchAt,
|
|
15
|
+
tubeMapPicture,
|
|
16
|
+
tubeMapTrackAt,
|
|
17
|
+
} from '@jbrowse/bandage-core/tubeMap/draw'
|
|
18
|
+
import {
|
|
19
|
+
tubeMapFrame,
|
|
20
|
+
tubeMapNodeAt,
|
|
21
|
+
} from '@jbrowse/bandage-core/tubeMap/frame'
|
|
22
|
+
import { tubeMapNodeColors } from '@jbrowse/bandage-core/tubeMap/nodeColors'
|
|
23
|
+
import { computed } from 'mobx'
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
NO_VALUE_TUBE,
|
|
27
|
+
PANEL_GAP,
|
|
28
|
+
PANEL_TUBE,
|
|
29
|
+
TINTED_TUBES,
|
|
30
|
+
boxOf,
|
|
31
|
+
memberAt,
|
|
32
|
+
paintPanelHover,
|
|
33
|
+
walksThrough,
|
|
34
|
+
} from '../tubeMapPanels'
|
|
35
|
+
import { withFitViews } from './fitViews'
|
|
36
|
+
import { HOVER_BRIGHTEN } from './paneBase'
|
|
37
|
+
|
|
38
|
+
import type { HoveredTube } from './paneBase'
|
|
39
|
+
import type { Canvas2DRenderer } from '@jbrowse/bandage-core/renderer/Canvas2DRenderer'
|
|
40
|
+
import type { RenderBatch } from '@jbrowse/bandage-core/renderer/types'
|
|
41
|
+
import type { TubeMapPicture } from '@jbrowse/bandage-core/tubeMap/draw'
|
|
42
|
+
import type { TubeMapTransform } from '@jbrowse/bandage-core/tubeMap/frame'
|
|
43
|
+
import type { TubeMapMismatch } from '@jbrowse/bandage-core/tubeMap/mismatches'
|
|
44
|
+
|
|
45
|
+
const MAX_SHOWN_BASES = 20
|
|
46
|
+
// a squeezed stack of panels thins no further, as one tube map does
|
|
47
|
+
const MIN_PANEL_Y_SCALE = 0.05
|
|
48
|
+
|
|
49
|
+
const MAX_LISTED = 6
|
|
50
|
+
|
|
51
|
+
function listed(names: string[]) {
|
|
52
|
+
const more = names.length - MAX_LISTED
|
|
53
|
+
return more > 0
|
|
54
|
+
? `${names.slice(0, MAX_LISTED).join(', ')} and ${more} more`
|
|
55
|
+
: names.join(', ')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function mismatchText(m: TubeMapMismatch) {
|
|
59
|
+
if (m.kind === 'deletion') {
|
|
60
|
+
return `deletion of ${formatBp(m.length)}`
|
|
61
|
+
}
|
|
62
|
+
if (m.kind === 'substitution') {
|
|
63
|
+
return `substitution ${m.seq}`
|
|
64
|
+
}
|
|
65
|
+
const seq = m.seq ?? ''
|
|
66
|
+
const shown =
|
|
67
|
+
seq.length > MAX_SHOWN_BASES ? `${seq.slice(0, MAX_SHOWN_BASES)}…` : seq
|
|
68
|
+
return `insertion of ${formatBp(seq.length)} ${shown}`.trim()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Which kinds of read mark are on screen, stopping once all three are found
|
|
72
|
+
function mismatchKindsShown(
|
|
73
|
+
picture: TubeMapPicture | undefined,
|
|
74
|
+
frame: TubeMapTransform | undefined,
|
|
75
|
+
width: number,
|
|
76
|
+
) {
|
|
77
|
+
const kinds = { substitution: false, insertion: false, deletion: false }
|
|
78
|
+
if (picture && frame && mismatchesLegible(frame.yScale)) {
|
|
79
|
+
let missing = 3
|
|
80
|
+
for (const m of picture.mismatches) {
|
|
81
|
+
if (!kinds[m.kind] && mismatchOnScreen(m, { x: frame.x, width })) {
|
|
82
|
+
kinds[m.kind] = true
|
|
83
|
+
if (--missing === 0) {
|
|
84
|
+
break
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return kinds
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const withTubeMapViews = withFitViews
|
|
93
|
+
.views(self => ({
|
|
94
|
+
get tubeMapFrame() {
|
|
95
|
+
const drawing = self.layoutResult?.tubeMap
|
|
96
|
+
return drawing
|
|
97
|
+
? tubeMapFrame(drawing, {
|
|
98
|
+
...self.screenTransform,
|
|
99
|
+
usableHeight: self.usableHeight,
|
|
100
|
+
})
|
|
101
|
+
: undefined
|
|
102
|
+
},
|
|
103
|
+
// the reference ruler under the tubes, unless the linear view's ruler
|
|
104
|
+
// is already the axis
|
|
105
|
+
get tubeMapRulerBoxes() {
|
|
106
|
+
const reference = self.tubeMapReference
|
|
107
|
+
return reference && !self.hostPlacesX && !self.layoutResult?.tubeMapPanels
|
|
108
|
+
? rulerBoxes(reference)
|
|
109
|
+
: undefined
|
|
110
|
+
},
|
|
111
|
+
// Split on the reference axis, the panels share x, so one ruler under
|
|
112
|
+
// the last reads for them all; on the own axis each panel has its own
|
|
113
|
+
get tubeMapPanelRulerBoxes() {
|
|
114
|
+
const { layoutResult } = self
|
|
115
|
+
const last = layoutResult?.tubeMapPanels?.at(-1)
|
|
116
|
+
return last && layoutResult?.referenceAxis && !self.hostPlacesX
|
|
117
|
+
? rulerBoxes(referenceBoxes(last.result.tubeMap))
|
|
118
|
+
: undefined
|
|
119
|
+
},
|
|
120
|
+
// The connectors run from the top of the pane down to the tubes' top
|
|
121
|
+
get connectorZoneBottom() {
|
|
122
|
+
const picture = self.tubeMapPicture
|
|
123
|
+
const frame = this.tubeMapFrame
|
|
124
|
+
return picture && frame ? frame.y(picture.bounds.minY) - 2 : 0
|
|
125
|
+
},
|
|
126
|
+
// The bands' tops follow every frame of a pan in the linear view and
|
|
127
|
+
// their bottoms every pan of the tubes
|
|
128
|
+
get tubeMapConnectors() {
|
|
129
|
+
const boxes = self.tubeMapConnectedBoxes
|
|
130
|
+
const frame = this.tubeMapFrame
|
|
131
|
+
const bp = boxes && frame ? self.hostBpFrame : undefined
|
|
132
|
+
return boxes && frame && bp
|
|
133
|
+
? tubeMapConnectors(
|
|
134
|
+
boxes,
|
|
135
|
+
b => b * bp.scale + bp.translateX,
|
|
136
|
+
frame.x,
|
|
137
|
+
self.paneWidth,
|
|
138
|
+
)
|
|
139
|
+
: []
|
|
140
|
+
},
|
|
141
|
+
// What each panel of a split tube map draws, which no pan or hover
|
|
142
|
+
// changes: its picture, its tubes, its boxes' tints and its folded
|
|
143
|
+
// variants
|
|
144
|
+
get tubeMapPanelPictures() {
|
|
145
|
+
const panels = self.layoutResult?.tubeMapPanels
|
|
146
|
+
if (!panels) {
|
|
147
|
+
return undefined
|
|
148
|
+
}
|
|
149
|
+
const { field } = self.facetSetting
|
|
150
|
+
return panels.map(panel => {
|
|
151
|
+
const drawing = panel.result.tubeMap
|
|
152
|
+
const { graph, coarse } = drawing
|
|
153
|
+
return {
|
|
154
|
+
panel,
|
|
155
|
+
label:
|
|
156
|
+
panel.label ??
|
|
157
|
+
(field === 'walk' ? self.walkLabel(panel.paths[0]!) : panel.key),
|
|
158
|
+
picture: tubeMapPicture(drawing),
|
|
159
|
+
tubeColors: self.tubeColorsOf(graph, drawing.pathColors),
|
|
160
|
+
nodeColors: self.tubeMapNodeColors
|
|
161
|
+
? tubeMapNodeColors(
|
|
162
|
+
graph,
|
|
163
|
+
self.effectiveColorScheme,
|
|
164
|
+
self.referenceRamp,
|
|
165
|
+
)
|
|
166
|
+
: undefined,
|
|
167
|
+
deviations: coarse ? deviationMarks(drawing, coarse.deviations) : [],
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
},
|
|
171
|
+
}))
|
|
172
|
+
.views(self => ({
|
|
173
|
+
// The node the pointer is on: inside a merged run, the member at the
|
|
174
|
+
// pointer's bp, so a run spanning the window counts what is under it
|
|
175
|
+
get hoveredMember() {
|
|
176
|
+
const id = self.hoveredNode
|
|
177
|
+
const members =
|
|
178
|
+
id === null ? undefined : self.hoverDrawing?.members.get(id)
|
|
179
|
+
if (!members) {
|
|
180
|
+
return id
|
|
181
|
+
}
|
|
182
|
+
const at = self.hoveredAt
|
|
183
|
+
const i = self.hoveredPanel
|
|
184
|
+
const under = i === null ? undefined : this.tubeMapPanelViews?.[i]
|
|
185
|
+
// sparse, so forEach, which skips its holes, as tubeMapNodeAt does
|
|
186
|
+
let box: { x: number; pixelWidth: number } | undefined
|
|
187
|
+
under?.panel.result.tubeMap.layout.nodes.forEach(n => {
|
|
188
|
+
if (n.name === id) {
|
|
189
|
+
box = n
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
const member =
|
|
193
|
+
at && under && box
|
|
194
|
+
? memberAt(
|
|
195
|
+
members,
|
|
196
|
+
m => self.nodeSpan(m),
|
|
197
|
+
under.frame.x(box.x),
|
|
198
|
+
under.frame.x(box.x + box.pixelWidth),
|
|
199
|
+
at.x,
|
|
200
|
+
)
|
|
201
|
+
: undefined
|
|
202
|
+
return member ?? members[0]
|
|
203
|
+
},
|
|
204
|
+
// For the hovered box, the box each panel draws it in and how many of
|
|
205
|
+
// that panel's haplotypes pass through it: a frequency per group
|
|
206
|
+
get tubeMapPanelHover() {
|
|
207
|
+
const id = self.hoveredNode
|
|
208
|
+
const pictures = self.tubeMapPanelPictures
|
|
209
|
+
if (id === null || !pictures) {
|
|
210
|
+
return undefined
|
|
211
|
+
}
|
|
212
|
+
const node = this.hoveredMember ?? id
|
|
213
|
+
return pictures.map(({ panel }) => {
|
|
214
|
+
const drawing = panel.result.tubeMap
|
|
215
|
+
const box = boxOf(drawing, node)
|
|
216
|
+
return box ? { box, ...walksThrough(drawing.graph, box) } : undefined
|
|
217
|
+
})
|
|
218
|
+
},
|
|
219
|
+
// apart from the pictures, so a lane row's hover redraws no panel
|
|
220
|
+
get tubeMapPanelTubeColors() {
|
|
221
|
+
return self.tubeMapPanelPictures?.map(({ panel, tubeColors }) => {
|
|
222
|
+
const { graph } = panel.result.tubeMap
|
|
223
|
+
return self.fadeUnhovered(
|
|
224
|
+
tubeColors,
|
|
225
|
+
graph.paths ?? [],
|
|
226
|
+
graph.referencePath,
|
|
227
|
+
)
|
|
228
|
+
})
|
|
229
|
+
},
|
|
230
|
+
// Each panel through the pane's transform at its place down the stack.
|
|
231
|
+
// On the reference axis the stack squeezes into the pane as one map does.
|
|
232
|
+
get tubeMapPanelViews() {
|
|
233
|
+
const pictures = self.tubeMapPanelPictures
|
|
234
|
+
const tubeColors = this.tubeMapPanelTubeColors
|
|
235
|
+
const layout = self.layoutResult
|
|
236
|
+
if (!pictures || !tubeColors || !layout) {
|
|
237
|
+
return undefined
|
|
238
|
+
}
|
|
239
|
+
// the titles keep their px and the drawings squeeze into the rest
|
|
240
|
+
const titles = pictures.length * PANEL_GAP
|
|
241
|
+
const drawings = pictures.reduce((h, p) => h + p.panel.height, 0)
|
|
242
|
+
const usable = self.usableHeight - titles
|
|
243
|
+
const yScale =
|
|
244
|
+
self.scaleY *
|
|
245
|
+
(layout.referenceAxis && drawings > 0
|
|
246
|
+
? Math.max(MIN_PANEL_Y_SCALE, Math.min(1, usable / drawings))
|
|
247
|
+
: 1)
|
|
248
|
+
let y = self.translateY
|
|
249
|
+
return pictures.map((p, i) => {
|
|
250
|
+
const { panel } = p
|
|
251
|
+
const minY = panel.result.extent?.minY ?? 0
|
|
252
|
+
const titleTop = y
|
|
253
|
+
const top = titleTop + PANEL_GAP
|
|
254
|
+
y = top + panel.height * yScale
|
|
255
|
+
return {
|
|
256
|
+
...p,
|
|
257
|
+
tubeColors: tubeColors[i]!,
|
|
258
|
+
frame: tubeMapFrame(panel.result.tubeMap, {
|
|
259
|
+
scaleX: self.scaleX,
|
|
260
|
+
translateX: self.translateX,
|
|
261
|
+
scaleY: yScale,
|
|
262
|
+
translateY: top - minY * yScale,
|
|
263
|
+
usableHeight: Infinity,
|
|
264
|
+
}),
|
|
265
|
+
titleTop,
|
|
266
|
+
top,
|
|
267
|
+
bottom: y,
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
},
|
|
271
|
+
// the panel a pane y falls in, its title's gap included
|
|
272
|
+
tubeMapPanelAt(sy: number) {
|
|
273
|
+
const i =
|
|
274
|
+
this.tubeMapPanelViews?.findIndex(
|
|
275
|
+
v => v.titleTop <= sy && sy <= v.bottom,
|
|
276
|
+
) ?? -1
|
|
277
|
+
return i < 0 ? null : i
|
|
278
|
+
},
|
|
279
|
+
}))
|
|
280
|
+
.views(self => ({
|
|
281
|
+
// The hover alone, onto a transparent canvas over the drawing, so a
|
|
282
|
+
// pointer move repaints what it lights rather than every stroke under
|
|
283
|
+
// it. `batch` is what the canvas below holds.
|
|
284
|
+
paintHover(r: Canvas2DRenderer, batch: RenderBatch) {
|
|
285
|
+
const picture = self.tubeMapPicture
|
|
286
|
+
const frame = self.tubeMapFrame
|
|
287
|
+
const node = self.hoverLayerNode
|
|
288
|
+
if (picture && frame) {
|
|
289
|
+
const { ctx, canvas } = r
|
|
290
|
+
const dpr = getDpr()
|
|
291
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
|
292
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
293
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
|
294
|
+
const panels = self.tubeMapPanelViews
|
|
295
|
+
if (panels) {
|
|
296
|
+
paintPanelHover(ctx, panels, self.tubeMapPanelHover, {
|
|
297
|
+
width: self.paneWidth,
|
|
298
|
+
lit: node !== null,
|
|
299
|
+
})
|
|
300
|
+
} else {
|
|
301
|
+
drawTubeMapHighlight(ctx, picture, {
|
|
302
|
+
...frame,
|
|
303
|
+
width: self.paneWidth,
|
|
304
|
+
highlightNode: node,
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
drawLitConnector(
|
|
308
|
+
ctx,
|
|
309
|
+
self.tubeMapConnectors,
|
|
310
|
+
self.connectorZoneBottom,
|
|
311
|
+
node,
|
|
312
|
+
)
|
|
313
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
|
314
|
+
} else {
|
|
315
|
+
r.uploadGeometry(batch)
|
|
316
|
+
r.setNodeHighlights(
|
|
317
|
+
node === null ? new Map() : new Map([[node, HOVER_BRIGHTEN]]),
|
|
318
|
+
)
|
|
319
|
+
r.setEdgeHighlight(self.hoveredEdge, HOVER_BRIGHTEN)
|
|
320
|
+
r.updateTransform(self.paintTransform)
|
|
321
|
+
r.renderHighlights()
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
}))
|
|
325
|
+
.views(self => {
|
|
326
|
+
// Compared by value: the marks in view are read off the transform, and
|
|
327
|
+
// the legend should redraw when a kind comes or goes, not every pan frame
|
|
328
|
+
const keys = computed(
|
|
329
|
+
() => {
|
|
330
|
+
const layout = self.layoutResult
|
|
331
|
+
const split = layout?.tubeMapPanels !== undefined
|
|
332
|
+
const reads = split ? [] : (layout?.tubeMap?.layout.reads ?? [])
|
|
333
|
+
const paths = self.drawnGraph?.paths ?? []
|
|
334
|
+
const coloring = self.tubeMapColoring
|
|
335
|
+
const kind = self.tubeColorKind
|
|
336
|
+
return {
|
|
337
|
+
bundled: paths.some(p => p.members),
|
|
338
|
+
// tubes in a column's hues, keyed value by value
|
|
339
|
+
values:
|
|
340
|
+
kind === 'value' && coloring
|
|
341
|
+
? [
|
|
342
|
+
...[...coloring.palette].map(([value, color]) => ({
|
|
343
|
+
value,
|
|
344
|
+
color,
|
|
345
|
+
})),
|
|
346
|
+
...(coloring.unvalued
|
|
347
|
+
? [
|
|
348
|
+
{
|
|
349
|
+
value: `${coloring.field}: none`,
|
|
350
|
+
color: NO_VALUE_TUBE,
|
|
351
|
+
},
|
|
352
|
+
]
|
|
353
|
+
: []),
|
|
354
|
+
]
|
|
355
|
+
: undefined,
|
|
356
|
+
// tubes coloured reference and haplotype rather than per walk
|
|
357
|
+
roles: kind === 'role' || kind === 'tinted',
|
|
358
|
+
haplotypeTube: kind === 'tinted' ? TINTED_TUBES[0]! : PANEL_TUBE,
|
|
359
|
+
logWidths: layout?.tubeMap !== undefined && !layout.referenceAxis,
|
|
360
|
+
foldBp:
|
|
361
|
+
self.tubeMapDeviations.length > 0 ? self.tubeMapFold : undefined,
|
|
362
|
+
forwardReads: reads.some(r => !r.is_reverse),
|
|
363
|
+
reverseReads: reads.some(r => r.is_reverse),
|
|
364
|
+
...mismatchKindsShown(
|
|
365
|
+
split ? undefined : self.tubeMapPicture,
|
|
366
|
+
self.tubeMapFrame,
|
|
367
|
+
self.paneWidth,
|
|
368
|
+
),
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{ equals: (a, b) => JSON.stringify(a) === JSON.stringify(b) },
|
|
372
|
+
)
|
|
373
|
+
return {
|
|
374
|
+
// What the tube map's legend has to explain: that on the own axis a
|
|
375
|
+
// box is as wide as the log of its length, the fold its walks' ticks
|
|
376
|
+
// stand for, the reads' strands, and the marks on the reads in view
|
|
377
|
+
get tubeMapKeys() {
|
|
378
|
+
return keys.get()
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
.views(self => ({
|
|
383
|
+
// The drawing under a pane y: its panel's while the map is split
|
|
384
|
+
tubeMapUnder(sy: number) {
|
|
385
|
+
const views = self.tubeMapPanelViews
|
|
386
|
+
if (views) {
|
|
387
|
+
const i = self.tubeMapPanelAt(sy)
|
|
388
|
+
const v = i === null ? undefined : views[i]
|
|
389
|
+
return v && { drawing: v.panel.result.tubeMap, ...v }
|
|
390
|
+
}
|
|
391
|
+
const drawing = self.layoutResult?.tubeMap
|
|
392
|
+
const picture = self.tubeMapPicture
|
|
393
|
+
const frame = self.tubeMapFrame
|
|
394
|
+
return drawing && picture && frame
|
|
395
|
+
? { drawing, picture, frame }
|
|
396
|
+
: undefined
|
|
397
|
+
},
|
|
398
|
+
tubeMapNodeAt(sx: number, sy: number) {
|
|
399
|
+
const under = this.tubeMapUnder(sy)
|
|
400
|
+
return (
|
|
401
|
+
(under ? tubeMapNodeAt(under.drawing, under.frame, sx, sy) : null) ??
|
|
402
|
+
connectorAt(self.tubeMapConnectors, self.connectorZoneBottom, sx, sy) ??
|
|
403
|
+
null
|
|
404
|
+
)
|
|
405
|
+
},
|
|
406
|
+
// The tube under a pane point, and the mismatch mark on it if any
|
|
407
|
+
tubeAt(sx: number, sy: number): HoveredTube | null {
|
|
408
|
+
const under = this.tubeMapUnder(sy)
|
|
409
|
+
if (!under) {
|
|
410
|
+
return null
|
|
411
|
+
}
|
|
412
|
+
const { picture, frame } = under
|
|
413
|
+
const mismatch = tubeMapMismatchAt(picture, frame, sx, sy)
|
|
414
|
+
const track = mismatch?.readId ?? tubeMapTrackAt(picture, frame, sx, sy)
|
|
415
|
+
return track === undefined ? null : { track, mismatch }
|
|
416
|
+
},
|
|
417
|
+
// A tube names its haplotype; a read its MAPQ and strand, and the
|
|
418
|
+
// mismatch under the pointer
|
|
419
|
+
get hoveredTubeText() {
|
|
420
|
+
const tube = self.hoveredTube
|
|
421
|
+
const layout = self.layoutResult?.tubeMap?.layout
|
|
422
|
+
const read = tube && layout?.reads.find(r => r.id === tube.track)
|
|
423
|
+
if (tube && read) {
|
|
424
|
+
const mapq = read.mapping_quality
|
|
425
|
+
return {
|
|
426
|
+
label: read.name ?? 'read',
|
|
427
|
+
readout: [
|
|
428
|
+
`MAPQ ${mapq ?? 'unknown'}`,
|
|
429
|
+
`${read.is_reverse ? 'reverse' : 'forward'} strand`,
|
|
430
|
+
...(tube.mismatch ? [mismatchText(tube.mismatch)] : []),
|
|
431
|
+
].join(', '),
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const path = tube ? self.hoverGraph?.paths?.[tube.track] : undefined
|
|
435
|
+
const members = path?.members
|
|
436
|
+
return members
|
|
437
|
+
? {
|
|
438
|
+
label: `${members.length} haplotypes`,
|
|
439
|
+
readout: `one route: ${listed(members.map(m => self.walkLabel(m)))}`,
|
|
440
|
+
}
|
|
441
|
+
: path
|
|
442
|
+
? { label: self.walkLabel(path.name), readout: 'haplotype' }
|
|
443
|
+
: undefined
|
|
444
|
+
},
|
|
445
|
+
}))
|
|
@@ -17,6 +17,7 @@ import { WALK_GENE_ROWS } from './paneBase'
|
|
|
17
17
|
import { resolveLocationAssembly } from '../../launchFromGraph/contributors'
|
|
18
18
|
|
|
19
19
|
import type { SubgraphRegion } from '../../GetSubgraph'
|
|
20
|
+
import type { WalkAxis, WalkRow } from '@jbrowse/bandage-core/layout/walkRows'
|
|
20
21
|
|
|
21
22
|
export const withWalkRowViews = withLaunchViews
|
|
22
23
|
.views(self => ({
|
|
@@ -63,16 +64,29 @@ export const withWalkRowViews = withLaunchViews
|
|
|
63
64
|
? stripMarks(self.graph, [bars.reference, ...bars.rows], node)
|
|
64
65
|
: []
|
|
65
66
|
},
|
|
67
|
+
// Where a row's bar lies on its contig, and the session assembly its
|
|
68
|
+
// haplotype (`HG00097#1`) or else its sample names
|
|
69
|
+
rowLocation(row: WalkRow, axis: WalkAxis) {
|
|
70
|
+
const named = {
|
|
71
|
+
sample: row.sample,
|
|
72
|
+
haplotype:
|
|
73
|
+
row.haplotype === undefined
|
|
74
|
+
? undefined
|
|
75
|
+
: `${row.sample}#${row.haplotype}`,
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
...named,
|
|
79
|
+
assembly: resolveLocationAssembly(self.assemblyResolver, named),
|
|
80
|
+
span: { refName: axis.contig, ...rowSpan(axis, row.bp) },
|
|
81
|
+
}
|
|
82
|
+
},
|
|
66
83
|
// Where the drawing has the node a strip point lights, for the ring
|
|
67
84
|
// that finds it in a hairball
|
|
68
85
|
get walkStripLocator() {
|
|
69
86
|
const id = self.stripHover ? self.hoveredNode : null
|
|
70
87
|
return id === null
|
|
71
88
|
? undefined
|
|
72
|
-
: nodeAnchor(self.nodePositions?.[id],
|
|
73
|
-
x: p.x * self.scaleX + self.translateX,
|
|
74
|
-
y: p.y * self.scaleY + self.translateY,
|
|
75
|
-
}))
|
|
89
|
+
: nodeAnchor(self.nodePositions?.[id], self.toScreen)
|
|
76
90
|
},
|
|
77
91
|
}))
|
|
78
92
|
.views(self => ({
|
|
@@ -97,28 +111,18 @@ export const withWalkRowViews = withLaunchViews
|
|
|
97
111
|
unplaced++
|
|
98
112
|
continue
|
|
99
113
|
}
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
row.haplotype === undefined
|
|
104
|
-
? undefined
|
|
105
|
-
: `${row.sample}#${row.haplotype}`,
|
|
106
|
-
})
|
|
107
|
-
const track = assemblyName
|
|
108
|
-
? self.geneTracksByAssembly.get(assemblyName)
|
|
114
|
+
const { assembly, span } = self.rowLocation(row, row.axis)
|
|
115
|
+
const track = assembly
|
|
116
|
+
? self.geneTracksByAssembly.get(assembly)
|
|
109
117
|
: undefined
|
|
110
|
-
if (!
|
|
118
|
+
if (!assembly || !track) {
|
|
111
119
|
untrackedRows++
|
|
112
120
|
continue
|
|
113
121
|
}
|
|
114
122
|
reads.push({
|
|
115
123
|
row: row.name,
|
|
116
124
|
trackId: track.trackId,
|
|
117
|
-
region: {
|
|
118
|
-
assemblyName,
|
|
119
|
-
refName: row.axis.contig,
|
|
120
|
-
...rowSpan(row.axis, row.bp),
|
|
121
|
-
},
|
|
125
|
+
region: { assemblyName: assembly, ...span },
|
|
122
126
|
})
|
|
123
127
|
}
|
|
124
128
|
return {
|
|
@@ -142,26 +146,14 @@ export const withWalkRowViews = withLaunchViews
|
|
|
142
146
|
if (!row?.axis) {
|
|
143
147
|
return undefined
|
|
144
148
|
}
|
|
145
|
-
const haplotype =
|
|
146
|
-
row
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const assembly =
|
|
150
|
-
index === 0
|
|
151
|
-
? self.graphRegion?.assemblyName
|
|
152
|
-
: resolveLocationAssembly(self.assemblyResolver, {
|
|
153
|
-
sample: row.sample,
|
|
154
|
-
haplotype,
|
|
155
|
-
})
|
|
149
|
+
const { sample, haplotype, assembly, span } = self.rowLocation(
|
|
150
|
+
row,
|
|
151
|
+
row.axis,
|
|
152
|
+
)
|
|
156
153
|
return {
|
|
157
154
|
label: row.label,
|
|
158
|
-
assembly,
|
|
159
|
-
location: {
|
|
160
|
-
sample: row.sample,
|
|
161
|
-
haplotype,
|
|
162
|
-
refName: row.axis.contig,
|
|
163
|
-
...rowSpan(row.axis, row.bp),
|
|
164
|
-
},
|
|
155
|
+
assembly: index === 0 ? self.graphRegion?.assemblyName : assembly,
|
|
156
|
+
location: { sample, haplotype, ...span },
|
|
165
157
|
}
|
|
166
158
|
},
|
|
167
159
|
// Each row's genes as offsets along its bar: the reference row's from
|
|
@@ -225,7 +225,7 @@ async function pathModel(count: number) {
|
|
|
225
225
|
)
|
|
226
226
|
const model = stateModelFactory().create({ type: 'GraphGenomeView' })
|
|
227
227
|
await model.loadGFA(pathGFA(count), 'paths')
|
|
228
|
-
model.
|
|
228
|
+
model.setLayer('paths', true)
|
|
229
229
|
return model
|
|
230
230
|
}
|
|
231
231
|
|
|
@@ -68,6 +68,18 @@ test('vamos motifs and an unnamed array fall back to a motif or locus name', ()
|
|
|
68
68
|
)
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
test("an unnamed array's locus name reads the same in every locale", () => {
|
|
72
|
+
const format = Number.prototype.toLocaleString
|
|
73
|
+
const german = vi
|
|
74
|
+
.spyOn(Number.prototype, 'toLocaleString')
|
|
75
|
+
.mockImplementation(function (this: number, locales, options) {
|
|
76
|
+
return format.call(this, locales ?? 'de-DE', options)
|
|
77
|
+
})
|
|
78
|
+
const [array] = repeatArraysFrom([{ ...base, period: 5548 }])
|
|
79
|
+
german.mockRestore()
|
|
80
|
+
expect(array!.name).toBe('chr6:160,616,003-160,646,753')
|
|
81
|
+
})
|
|
82
|
+
|
|
71
83
|
test('a feature stating no unit is left out, and rows sort by start', () => {
|
|
72
84
|
const arrays = repeatArraysFrom([
|
|
73
85
|
{ ...base, start: 5000, end: 6000, period: 2 },
|
|
@@ -150,6 +162,17 @@ test("VCF 4.5 <CNV:TR>: each allele's length is its runs' RB", () => {
|
|
|
150
162
|
})
|
|
151
163
|
})
|
|
152
164
|
|
|
165
|
+
// a haploid chrX genotype is written '.', and a sample may have no GT at all
|
|
166
|
+
test('a missing genotype calls no allele', () => {
|
|
167
|
+
const [array] = repeatArraysFrom([
|
|
168
|
+
{
|
|
169
|
+
...tandem,
|
|
170
|
+
samples: { HG00097: { GT: ['.'] }, HG00099: {}, HG00128: { GT: ['1'] } },
|
|
171
|
+
},
|
|
172
|
+
])
|
|
173
|
+
expect(array!.calls).toEqual({ HG00128: [{ bp: 49 }] })
|
|
174
|
+
})
|
|
175
|
+
|
|
153
176
|
test('a run stating only RUL and RUC is their product, and a summary record states no calls', () => {
|
|
154
177
|
const [array] = repeatArraysFrom([
|
|
155
178
|
{
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { namedTrack, ownField, trackLabel } from '../trackFeatures'
|
|
2
|
+
|
|
3
|
+
import type { FeatureLike, TrackChoice } from '../trackFeatures'
|
|
2
4
|
|
|
3
5
|
export interface RepeatCall {
|
|
4
6
|
bp: number
|
|
@@ -46,24 +48,15 @@ export const REPEAT_ADAPTER_TYPES = new Set([
|
|
|
46
48
|
export const REPEAT_TRACK_HINT =
|
|
47
49
|
/repeat|tandem|\bstr\b|vntr|trf|trgt|vamos|expansion|microsat/i
|
|
48
50
|
|
|
49
|
-
export function pickRepeatTrack<
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
export function pickRepeatTrack<T extends TrackChoice>(
|
|
52
|
+
tracks: T[],
|
|
53
|
+
named: string,
|
|
54
|
+
) {
|
|
52
55
|
if (named) {
|
|
53
|
-
return tracks
|
|
56
|
+
return namedTrack(tracks, named)
|
|
54
57
|
}
|
|
55
58
|
const candidates = tracks.filter(t => REPEAT_ADAPTER_TYPES.has(t.adapterType))
|
|
56
|
-
return candidates.find(t =>
|
|
57
|
-
REPEAT_TRACK_HINT.test(`${t.trackId} ${t.name ?? ''}`),
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
type FeatureLike = Feature | Record<string, unknown>
|
|
62
|
-
|
|
63
|
-
function own(f: FeatureLike, name: string): unknown {
|
|
64
|
-
return typeof (f as Feature).get === 'function'
|
|
65
|
-
? (f as Feature).get(name)
|
|
66
|
-
: (f as Record<string, unknown>)[name]
|
|
59
|
+
return candidates.find(t => REPEAT_TRACK_HINT.test(trackLabel(t)))
|
|
67
60
|
}
|
|
68
61
|
|
|
69
62
|
// A TRGT repeat catalogue is a BED whose name column packs the record:
|
|
@@ -72,7 +65,7 @@ function own(f: FeatureLike, name: string): unknown {
|
|
|
72
65
|
const PACKED_NAME = /^\w+=[^;]*(;\w+=[^;]*)*$/
|
|
73
66
|
|
|
74
67
|
function packedName(f: FeatureLike) {
|
|
75
|
-
const name =
|
|
68
|
+
const name = ownField(f, 'name')
|
|
76
69
|
if (typeof name !== 'string' || !PACKED_NAME.test(name)) {
|
|
77
70
|
return undefined
|
|
78
71
|
}
|
|
@@ -87,11 +80,11 @@ function packedName(f: FeatureLike) {
|
|
|
87
80
|
// A field at the feature's top level, in a VCF record's INFO, or in a packed
|
|
88
81
|
// name column, in that order.
|
|
89
82
|
function field(f: FeatureLike, name: string): unknown {
|
|
90
|
-
const top =
|
|
83
|
+
const top = ownField(f, name)
|
|
91
84
|
if (top !== undefined) {
|
|
92
85
|
return top
|
|
93
86
|
}
|
|
94
|
-
const info =
|
|
87
|
+
const info = ownField(f, 'INFO') as Record<string, unknown> | undefined
|
|
95
88
|
return info?.[name] ?? packedName(f)?.[name]
|
|
96
89
|
}
|
|
97
90
|
|
|
@@ -173,7 +166,7 @@ const TANDEM_REPEAT = '<CNV:TR>'
|
|
|
173
166
|
// its runs' RB, or unit length times count where RB is missing. An allele that
|
|
174
167
|
// is not <CNV:TR>, or has a run stating neither, has none.
|
|
175
168
|
function tandemAlleleBp(f: FeatureLike) {
|
|
176
|
-
const alts = strings(
|
|
169
|
+
const alts = strings(ownField(f, 'ALT'))
|
|
177
170
|
if (!alts.includes(TANDEM_REPEAT)) {
|
|
178
171
|
return undefined
|
|
179
172
|
}
|
|
@@ -216,7 +209,7 @@ function genotyped(
|
|
|
216
209
|
) {
|
|
217
210
|
const phased = gt?.includes('|')
|
|
218
211
|
return (gt ?? '').split(/[/|]/).flatMap((index, k): RepeatCall[] => {
|
|
219
|
-
const i = Number(index)
|
|
212
|
+
const i = index === '' ? NaN : Number(index)
|
|
220
213
|
const bp = i > 0 ? alleleBp[i - 1] : referenceBp
|
|
221
214
|
if (!Number.isInteger(i) || bp === undefined) {
|
|
222
215
|
return []
|
|
@@ -230,7 +223,7 @@ function callsOf(
|
|
|
230
223
|
alleleBp: (number | undefined)[] | undefined,
|
|
231
224
|
referenceBp: number,
|
|
232
225
|
) {
|
|
233
|
-
const samples =
|
|
226
|
+
const samples = ownField(f, 'samples') as
|
|
234
227
|
Record<string, Record<string, unknown>> | undefined
|
|
235
228
|
const called: Record<string, RepeatCall[]> = {}
|
|
236
229
|
for (const [sample, fields] of Object.entries(samples ?? {})) {
|
|
@@ -280,7 +273,7 @@ export function repeatArraysFrom(features: FeatureLike[]): RepeatArray[] {
|
|
|
280
273
|
(stated && !PACKED_NAME.test(stated) ? stated : undefined) ??
|
|
281
274
|
(motif && motif.length <= 12
|
|
282
275
|
? `(${motif})n`
|
|
283
|
-
: `${refName}:${(start + 1).toLocaleString()}-${end.toLocaleString()}`),
|
|
276
|
+
: `${refName}:${(start + 1).toLocaleString('en-US')}-${end.toLocaleString('en-US')}`),
|
|
284
277
|
refName,
|
|
285
278
|
start,
|
|
286
279
|
end,
|