jbrowse-plugin-graphgenomeviewer 1.0.0 → 3.0.3
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/LICENSE +674 -0
- package/README.md +202 -24
- package/dist/chunks/AddTrackWorkflow-Y3OBHM33.js +2 -0
- package/dist/chunks/AddTrackWorkflow-Y3OBHM33.js.map +7 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-EFIOI2SN.js +8 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-EFIOI2SN.js.map +7 -0
- package/dist/chunks/GraphGenomeView-UD5WCV3Q.js +14 -0
- package/dist/chunks/GraphGenomeView-UD5WCV3Q.js.map +7 -0
- package/dist/chunks/MinigraphBubbleAdapter-B75VQK7T.js +2 -0
- package/dist/chunks/MinigraphBubbleAdapter-B75VQK7T.js.map +7 -0
- package/dist/chunks/RgfaTabixAdapter-IT7I5RR3.js +3 -0
- package/dist/chunks/RgfaTabixAdapter-IT7I5RR3.js.map +7 -0
- package/dist/chunks/bandage-layout-FIOZ7B3E.js +1660 -0
- package/dist/chunks/bandage-layout-FIOZ7B3E.js.map +7 -0
- package/dist/chunks/chunk-D7XFR2IV.js +2 -0
- package/dist/chunks/chunk-D7XFR2IV.js.map +7 -0
- package/dist/chunks/chunk-F4C3BEPV.js +2 -0
- package/dist/chunks/chunk-F4C3BEPV.js.map +7 -0
- package/dist/chunks/chunk-FCWXYIFC.js +2 -0
- package/dist/chunks/chunk-FCWXYIFC.js.map +7 -0
- package/dist/chunks/chunk-FY3BTKY4.js +2 -0
- package/dist/chunks/chunk-FY3BTKY4.js.map +7 -0
- package/dist/chunks/chunk-J4OLMLS5.js +2 -0
- package/dist/chunks/chunk-J4OLMLS5.js.map +7 -0
- package/dist/chunks/chunk-JCG7RLMB.js +2 -0
- package/dist/chunks/chunk-JCG7RLMB.js.map +7 -0
- package/dist/chunks/chunk-KWAXLUPQ.js +2 -0
- package/dist/chunks/chunk-KWAXLUPQ.js.map +7 -0
- package/dist/chunks/chunk-LGKM2PZH.js +2 -0
- package/dist/chunks/chunk-LGKM2PZH.js.map +7 -0
- package/dist/chunks/chunk-TN6FK5UD.js +2 -0
- package/dist/chunks/chunk-TN6FK5UD.js.map +7 -0
- package/dist/chunks/chunk-X3X74OGB.js +2 -0
- package/dist/chunks/chunk-X3X74OGB.js.map +7 -0
- package/dist/chunks/chunk-XEJ47DLD.js +2 -0
- package/dist/chunks/chunk-XEJ47DLD.js.map +7 -0
- package/dist/chunks/chunk-ZNGDWZ6Z.js +2 -0
- package/dist/chunks/chunk-ZNGDWZ6Z.js.map +7 -0
- package/dist/chunks/executeDiagonalize-XMPEDHVZ.js +2 -0
- package/dist/chunks/executeDiagonalize-XMPEDHVZ.js.map +7 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +3 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +7 -0
- package/package.json +94 -49
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +427 -0
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +461 -0
- package/src/GbzBaseSyntenyAdapter/configSchema.ts +181 -0
- package/src/GbzBaseSyntenyAdapter/index.ts +27 -0
- package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.gbz.db +0 -0
- package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.haplotype-index.db +0 -0
- package/src/GetSubgraph.test.ts +193 -0
- package/src/GetSubgraph.ts +93 -0
- package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +167 -0
- package/src/GraphAddTrackWorkflow/buildTrackConfig.test.ts +150 -0
- package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +114 -0
- package/src/GraphAddTrackWorkflow/index.ts +20 -0
- package/src/GraphComputeLayout.ts +46 -0
- package/src/GraphGenomeView/anchoredNodes.ts +101 -0
- package/src/GraphGenomeView/bubbleSpreads.ts +102 -0
- package/src/GraphGenomeView/bubbles/bubbleHalos.test.ts +58 -0
- package/src/GraphGenomeView/bubbles/bubbleHalos.ts +149 -0
- package/src/GraphGenomeView/bubbles/bubbleRoutes.test.ts +141 -0
- package/src/GraphGenomeView/bubbles/bubblesFromGraph.test.ts +239 -0
- package/src/GraphGenomeView/bubbles/bubblesFromGraph.ts +210 -0
- package/src/GraphGenomeView/bubbles/classifyBubble.test.ts +172 -0
- package/src/GraphGenomeView/bubbles/classifyBubble.ts +138 -0
- package/src/GraphGenomeView/bubbles/popBubble.test.ts +73 -0
- package/src/GraphGenomeView/bubbles/popBubble.ts +26 -0
- package/src/GraphGenomeView/colorSchemes.ts +36 -0
- package/src/GraphGenomeView/components/BubbleHalos.tsx +162 -0
- package/src/GraphGenomeView/components/BubbleOverlay.tsx +181 -0
- package/src/GraphGenomeView/components/ColorSchemeSelect.tsx +41 -0
- package/src/GraphGenomeView/components/GenePins.tsx +107 -0
- package/src/GraphGenomeView/components/GraphCanvas.tsx +759 -0
- package/src/GraphGenomeView/components/GraphGenomeView.test.tsx +77 -0
- package/src/GraphGenomeView/components/GraphGenomeView.tsx +18 -17
- package/src/GraphGenomeView/components/GraphLoadStatus.tsx +84 -0
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +327 -0
- package/src/GraphGenomeView/components/GraphStats.tsx +82 -0
- package/src/GraphGenomeView/components/GraphToolbar.tsx +84 -0
- package/src/GraphGenomeView/components/ImportForm.tsx +124 -0
- package/src/GraphGenomeView/components/LabelChip.tsx +65 -0
- package/src/GraphGenomeView/components/LayoutSelect.tsx +56 -0
- package/src/GraphGenomeView/components/RepeatSelect.tsx +51 -0
- package/src/GraphGenomeView/components/SettingsMenu.tsx +82 -0
- package/src/GraphGenomeView/components/SubgraphContextSelect.tsx +75 -0
- package/src/GraphGenomeView/components/SubgraphHaplotypesField.tsx +86 -0
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +252 -0
- package/src/GraphGenomeView/components/WalkSelect.tsx +51 -0
- package/src/GraphGenomeView/components/overlayLabels.test.ts +56 -0
- package/src/GraphGenomeView/components/overlayLabels.ts +75 -0
- package/src/GraphGenomeView/deletionEdges.test.ts +198 -0
- package/src/GraphGenomeView/deletionEdges.ts +217 -0
- package/src/GraphGenomeView/genes/geneFeatures.ts +107 -0
- package/src/GraphGenomeView/genes/genePins.ts +130 -0
- package/src/GraphGenomeView/genes/genes.test.ts +153 -0
- package/src/GraphGenomeView/gfa/gfaConverter.test.ts +394 -0
- package/src/GraphGenomeView/gfa/gfaConverter.ts +235 -0
- package/src/GraphGenomeView/graphLabels.budget.test.ts +36 -0
- package/src/GraphGenomeView/graphLabels.test.ts +548 -0
- package/src/GraphGenomeView/graphLabels.ts +640 -0
- package/src/GraphGenomeView/index.ts +29 -7
- package/src/GraphGenomeView/laneRamp.test.ts +97 -0
- package/src/GraphGenomeView/laneRamp.ts +87 -0
- package/src/GraphGenomeView/launchKeys.test.ts +63 -0
- package/src/GraphGenomeView/layout/anchoredLayout.test.ts +213 -0
- package/src/GraphGenomeView/layout/anchoredLayout.ts +80 -0
- package/src/GraphGenomeView/layout/bubbleCrossing.test.ts +190 -0
- package/src/GraphGenomeView/layout/drawnScale.ts +199 -0
- package/src/GraphGenomeView/layout/mergeRuns.test.ts +100 -0
- package/src/GraphGenomeView/layout/mergeRuns.ts +203 -0
- package/src/GraphGenomeView/layout/orderedLayout.test.ts +212 -0
- package/src/GraphGenomeView/layout/orderedLayout.ts +396 -0
- package/src/GraphGenomeView/layout/orientToReference.test.ts +110 -0
- package/src/GraphGenomeView/layout/orientToReference.ts +79 -0
- package/src/GraphGenomeView/layout/placeOffReference.ts +147 -0
- package/src/GraphGenomeView/layout/referenceSeeds.test.ts +79 -0
- package/src/GraphGenomeView/layout/referenceSeeds.ts +89 -0
- package/src/GraphGenomeView/layout/rowSpacing.ts +5 -0
- package/src/GraphGenomeView/layout/sampleRowLayout.test.ts +318 -0
- package/src/GraphGenomeView/layout/sampleRowLayout.ts +134 -0
- package/src/GraphGenomeView/layout/variantMapLayout.test.ts +59 -0
- package/src/GraphGenomeView/layout/variantMapLayout.ts +20 -0
- package/src/GraphGenomeView/layout/walkRowLayout.ts +54 -0
- package/src/GraphGenomeView/layout/walkRows.test.ts +178 -0
- package/src/GraphGenomeView/layout/walkRows.ts +177 -0
- package/src/GraphGenomeView/layoutModes.ts +131 -0
- package/src/GraphGenomeView/model.test.ts +2696 -0
- package/src/GraphGenomeView/model.ts +2672 -94
- package/src/GraphGenomeView/nodeWidths.ts +67 -0
- package/src/GraphGenomeView/pathAnchoring.test.ts +252 -0
- package/src/GraphGenomeView/pathAnchoring.ts +164 -0
- package/src/GraphGenomeView/pathColors.test.ts +83 -0
- package/src/GraphGenomeView/pathColors.ts +122 -0
- package/src/GraphGenomeView/referenceSpan.test.ts +84 -0
- package/src/GraphGenomeView/referenceSpan.ts +133 -0
- package/src/GraphGenomeView/renderPasses.bench.ts +172 -0
- package/src/GraphGenomeView/renderPipeline.test.ts +226 -0
- package/src/GraphGenomeView/renderer/Canvas2DRenderer.test.ts +205 -0
- package/src/GraphGenomeView/renderer/Canvas2DRenderer.ts +246 -0
- package/src/GraphGenomeView/renderer/GeometryBuilder.test.ts +723 -0
- package/src/GraphGenomeView/renderer/GeometryBuilder.ts +882 -0
- package/src/GraphGenomeView/renderer/GraphRenderer.ts +13 -0
- package/src/GraphGenomeView/renderer/colorBits.ts +43 -0
- package/src/GraphGenomeView/renderer/recordingCanvas.ts +64 -0
- package/src/GraphGenomeView/renderer/types.ts +84 -0
- package/src/GraphGenomeView/renderer/walkFade.test.ts +100 -0
- package/src/GraphGenomeView/repeats/repeatFeatures.test.ts +114 -0
- package/src/GraphGenomeView/repeats/repeatFeatures.ts +202 -0
- package/src/GraphGenomeView/repeats/test_data/abca7_trgt_walks.json +575 -0
- package/src/GraphGenomeView/repeats/walkCalls.test.ts +127 -0
- package/src/GraphGenomeView/repeats/walkCalls.ts +132 -0
- package/src/GraphGenomeView/stress.test.ts +214 -0
- package/src/GraphGenomeView/subgraphLoad.test.ts +245 -0
- package/src/GraphGenomeView/types.ts +141 -0
- package/src/GraphGenomeView/util/SpatialIndex.test.ts +73 -0
- package/src/GraphGenomeView/util/SpatialIndex.ts +312 -0
- package/src/GraphGenomeView/util/edgeCurves.test.ts +93 -0
- package/src/GraphGenomeView/util/edgeCurves.ts +94 -0
- package/src/GraphGenomeView/util/geometry.test.ts +447 -0
- package/src/GraphGenomeView/util/geometry.ts +656 -0
- package/src/GraphGenomeView/util/hitDetection.test.ts +307 -0
- package/src/GraphGenomeView/util/hitDetection.ts +330 -0
- package/src/GraphGenomeView/util/wheelZoom.test.ts +40 -0
- package/src/GraphGenomeView/util/wheelZoom.ts +26 -0
- package/src/GraphGenomeView/walkHighlight.test.ts +54 -0
- package/src/GraphGenomeView/walkHighlight.ts +56 -0
- package/src/LaunchGraphGenomeView.ts +46 -0
- package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.test.ts +139 -0
- package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.ts +86 -0
- package/src/MinigraphBubbleAdapter/bubbleLine.ts +92 -0
- package/src/MinigraphBubbleAdapter/configSchema.ts +109 -0
- package/src/MinigraphBubbleAdapter/index.ts +22 -0
- package/src/MinigraphBubbleAdapter/test_data/hprc_bubbles_mhc.bed.gz +0 -0
- package/src/MinigraphBubbleAdapter/test_data/hprc_bubbles_mhc.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.test.ts +285 -0
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +194 -0
- package/src/RgfaTabixAdapter/bubbleTier.test.ts +84 -0
- package/src/RgfaTabixAdapter/configSchema.ts +142 -0
- package/src/RgfaTabixAdapter/index.ts +24 -0
- package/src/RgfaTabixAdapter/rgfaBed.test.ts +158 -0
- package/src/RgfaTabixAdapter/rgfaBed.ts +156 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.links.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.links.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.segs.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.segs.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.links.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.links.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.segs.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.segs.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.links.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.links.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.segs.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.segs.bed.gz.tbi +0 -0
- package/src/alleleProjection/projectAlleles.test.ts +75 -0
- package/src/alleleProjection/projectAlleles.ts +190 -0
- package/src/bandage/README.md +121 -0
- package/src/bandage/bandage-layout.d.ts +14 -0
- package/src/bandage/bandage-layout.js +0 -0
- package/src/bandage/native/CMakeLists.txt +45 -0
- package/src/bandage/native/include/graph.h +188 -0
- package/src/bandage/native/include/graphlayout.h +74 -0
- package/src/bandage/native/include/settings.h +86 -0
- package/src/bandage/native/include/types.h +56 -0
- package/src/bandage/native/src/bindings.cpp +164 -0
- package/src/bandage/native/src/graphlayout.cpp +621 -0
- package/src/bandage/resizableDecode.test.ts +58 -0
- package/src/gfa-core/gfaParser.bench.ts +55 -0
- package/src/gfa-core/gfaParser.test.ts +168 -0
- package/src/gfa-core/gfaParser.ts +237 -0
- package/src/gfa-core/index.ts +10 -0
- package/src/hoverSync/GraphNodeHighlight.tsx +62 -0
- package/src/hoverSync/graphViewHighlights.test.ts +158 -0
- package/src/hoverSync/graphViewHighlights.ts +119 -0
- package/src/hoverSync/index.test.tsx +36 -0
- package/src/hoverSync/index.tsx +19 -0
- package/src/hoverSync/lgvHover.test.ts +202 -0
- package/src/hoverSync/lgvHover.ts +136 -0
- package/src/index.ts +46 -15
- package/src/launchFromGraph/contributors.test.ts +197 -0
- package/src/launchFromGraph/contributors.ts +0 -0
- package/src/launchFromGraph/graphMenuItems.ts +211 -0
- package/src/launchFromGraph/launchFromGraph.test.ts +489 -0
- package/src/launchFromGraph/launchFromGraph.ts +265 -0
- package/src/launchFromGraph/launchTracks.test.ts +72 -0
- package/src/launchFromGraph/launchTracks.ts +50 -0
- package/src/launchFromGraph/linearViewTarget.test.ts +82 -0
- package/src/launchFromGraph/linearViewTarget.ts +75 -0
- package/src/launchFromGraph/syntenyTracks.ts +47 -0
- package/src/launchSubgraph/index.ts +152 -0
- package/src/launchSubgraph/launchSubgraph.test.ts +264 -0
- package/src/launchSubgraph/launchSubgraphView.ts +128 -0
- package/src/launchSubgraph/linearViewMenuItems.test.ts +176 -0
- package/src/launchSubgraph/linearViewMenuItems.ts +107 -0
- package/src/launchSubgraph/multiWayLaunch.test.ts +289 -0
- package/src/launchSubgraph/subgraphMenuItems.ts +73 -0
- package/src/launchSubgraph/subgraphTracks.test.ts +165 -0
- package/src/launchSubgraph/subgraphTracks.ts +151 -0
- package/src/launchSubgraph/testEnv.ts +258 -0
- package/src/loadBandage.ts +22 -0
- package/src/panSNTabix.test.ts +72 -0
- package/src/panSNTabix.ts +149 -0
- package/src/pansn.ts +106 -0
- package/src/pluginName.ts +3 -0
- package/src/synteny/ComparativeAdapterBase.ts +77 -0
- package/src/synteny/SyntenyFeature.ts +116 -0
- package/src/synteny/clipFeatureToRegion.test.ts +329 -0
- package/src/synteny/clipFeatureToRegion.ts +167 -0
- package/src/version.ts +1 -0
- package/dist/GraphGenomeView/components/FeatureDialog.d.ts +0 -5
- package/dist/GraphGenomeView/components/FeatureDialog.js +0 -18
- package/dist/GraphGenomeView/components/FeatureDialog.js.map +0 -1
- package/dist/GraphGenomeView/components/GraphGenomeView.d.ts +0 -6
- package/dist/GraphGenomeView/components/GraphGenomeView.js +0 -17
- package/dist/GraphGenomeView/components/GraphGenomeView.js.map +0 -1
- package/dist/GraphGenomeView/components/GraphPanel.d.ts +0 -6
- package/dist/GraphGenomeView/components/GraphPanel.js +0 -17
- package/dist/GraphGenomeView/components/GraphPanel.js.map +0 -1
- package/dist/GraphGenomeView/components/Header.d.ts +0 -6
- package/dist/GraphGenomeView/components/Header.js +0 -59
- package/dist/GraphGenomeView/components/Header.js.map +0 -1
- package/dist/GraphGenomeView/components/SettingsDialog.d.ts +0 -7
- package/dist/GraphGenomeView/components/SettingsDialog.js +0 -24
- package/dist/GraphGenomeView/components/SettingsDialog.js.map +0 -1
- package/dist/GraphGenomeView/index.d.ts +0 -2
- package/dist/GraphGenomeView/index.js +0 -15
- package/dist/GraphGenomeView/index.js.map +0 -1
- package/dist/GraphGenomeView/model.d.ts +0 -80
- package/dist/GraphGenomeView/model.js +0 -96
- package/dist/GraphGenomeView/model.js.map +0 -1
- package/dist/GraphGenomeView/util.d.ts +0 -1
- package/dist/GraphGenomeView/util.js +0 -8
- package/dist/GraphGenomeView/util.js.map +0 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -24
- package/dist/index.js.map +0 -1
- package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js +0 -67
- package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js.map +0 -7
- package/src/GraphGenomeView/components/FeatureDialog.tsx +0 -51
- package/src/GraphGenomeView/components/GraphPanel.tsx +0 -37
- package/src/GraphGenomeView/components/Header.tsx +0 -90
- package/src/GraphGenomeView/components/SettingsDialog.tsx +0 -71
- package/src/GraphGenomeView/util.ts +0 -7
- package/src/declare.d.ts +0 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { orientToReference } from './orientToReference'
|
|
2
|
+
|
|
3
|
+
import type { Graph, NodeSegment } from '../types'
|
|
4
|
+
|
|
5
|
+
const stable = (start: number, rank: number) => ({
|
|
6
|
+
refName: rank === 0 ? 'chr1' : 'alt',
|
|
7
|
+
start,
|
|
8
|
+
rank,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const graph: Graph = {
|
|
12
|
+
name: 'g',
|
|
13
|
+
nodes: [
|
|
14
|
+
{ id: 'b0+', name: 'b0', length: 10, depth: 1, stable: stable(0, 0) },
|
|
15
|
+
{ id: 'b1+', name: 'b1', length: 10, depth: 1, stable: stable(10, 0) },
|
|
16
|
+
{ id: 'b2+', name: 'b2', length: 10, depth: 1, stable: stable(20, 0) },
|
|
17
|
+
{ id: 'b3+', name: 'b3', length: 10, depth: 1, stable: stable(30, 0) },
|
|
18
|
+
{ id: 'a0+', name: 'a0', length: 4, depth: 1, stable: stable(10, 1) },
|
|
19
|
+
],
|
|
20
|
+
edges: [],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const segment = (x: number, y: number): NodeSegment[] => [
|
|
24
|
+
{ x, y },
|
|
25
|
+
{ x: x + 10, y },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
// backbone left to right on y = 0, the allele below it
|
|
29
|
+
const oriented: Record<string, NodeSegment[]> = {
|
|
30
|
+
'b0+': segment(0, 0),
|
|
31
|
+
'b1+': segment(20, 0),
|
|
32
|
+
'b2+': segment(40, 0),
|
|
33
|
+
'b3+': segment(60, 0),
|
|
34
|
+
'a0+': segment(25, 12),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function transform(
|
|
38
|
+
positions: Record<string, NodeSegment[]>,
|
|
39
|
+
angle: number,
|
|
40
|
+
reflect: boolean,
|
|
41
|
+
dx: number,
|
|
42
|
+
dy: number,
|
|
43
|
+
) {
|
|
44
|
+
const c = Math.cos(angle)
|
|
45
|
+
const s = Math.sin(angle)
|
|
46
|
+
return Object.fromEntries(
|
|
47
|
+
Object.entries(positions).map(([id, segs]) => [
|
|
48
|
+
id,
|
|
49
|
+
segs.map(p => {
|
|
50
|
+
const y = reflect ? -p.y : p.y
|
|
51
|
+
return { x: c * p.x - s * y + dx, y: s * p.x + c * y + dy }
|
|
52
|
+
}),
|
|
53
|
+
]),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const mid = (segs: NodeSegment[]) => segs[Math.floor(segs.length / 2)]!
|
|
58
|
+
|
|
59
|
+
function distance(a: NodeSegment, b: NodeSegment) {
|
|
60
|
+
return Math.hypot(a.x - b.x, a.y - b.y)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test('a rotated and reflected drawing comes back reading left to right', () => {
|
|
64
|
+
const scrambled = transform(oriented, 2.3, true, 100, -50)
|
|
65
|
+
const out = orientToReference(graph, scrambled)
|
|
66
|
+
const xs = ['b0+', 'b1+', 'b2+', 'b3+'].map(id => mid(out[id]!).x)
|
|
67
|
+
for (let i = 1; i < xs.length; i++) {
|
|
68
|
+
expect(xs[i]!).toBeGreaterThan(xs[i - 1]!)
|
|
69
|
+
}
|
|
70
|
+
const backboneY = mid(out['b1+']!).y
|
|
71
|
+
for (const p of out['a0+']!) {
|
|
72
|
+
expect(p.y).toBeGreaterThan(backboneY)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('the transform is rigid: pairwise distances survive', () => {
|
|
77
|
+
const scrambled = transform(oriented, -1.1, false, -30, 7)
|
|
78
|
+
const out = orientToReference(graph, scrambled)
|
|
79
|
+
const ids = Object.keys(oriented)
|
|
80
|
+
for (const a of ids) {
|
|
81
|
+
for (const b of ids) {
|
|
82
|
+
expect(distance(out[a]![0]!, out[b]![1]!)).toBeCloseTo(
|
|
83
|
+
distance(scrambled[a]![0]!, scrambled[b]![1]!),
|
|
84
|
+
9,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('an oriented drawing is returned where it was', () => {
|
|
91
|
+
const out = orientToReference(graph, oriented)
|
|
92
|
+
for (const [id, segs] of Object.entries(oriented)) {
|
|
93
|
+
segs.forEach((p, i) => {
|
|
94
|
+
expect(out[id]![i]!.x).toBeCloseTo(p.x, 9)
|
|
95
|
+
expect(out[id]![i]!.y).toBeCloseTo(p.y, 9)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('alleles above the axis are reflected below it', () => {
|
|
101
|
+
const above = transform(oriented, 0, true, 0, 0)
|
|
102
|
+
const out = orientToReference(graph, above)
|
|
103
|
+
expect(mid(out['a0+']!).y).toBeGreaterThan(mid(out['b1+']!).y)
|
|
104
|
+
expect(mid(out['b3+']!).x).toBeGreaterThan(mid(out['b0+']!).x)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('fewer than two placed backbone nodes leaves the drawing alone', () => {
|
|
108
|
+
const positions = { 'b0+': segment(3, 4), 'a0+': segment(9, -2) }
|
|
109
|
+
expect(orientToReference(graph, positions)).toBe(positions)
|
|
110
|
+
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { isBackbone } from '../anchoredNodes'
|
|
2
|
+
|
|
3
|
+
import type { Graph, NodeSegment } from '../types'
|
|
4
|
+
|
|
5
|
+
type Positions = Record<string, NodeSegment[]>
|
|
6
|
+
|
|
7
|
+
// Turn a finished force layout so the reference runs left to right: the
|
|
8
|
+
// rotation that best maps backbone midpoints onto their reference coordinate
|
|
9
|
+
// (a one-dimensional orthogonal Procrustes fit, weighted by segment length),
|
|
10
|
+
// then a reflection so the off-reference mass sits below the axis. A rigid
|
|
11
|
+
// transform, so the drawing FMMM made is unchanged; only its frame moves.
|
|
12
|
+
//
|
|
13
|
+
// About the length-weighted centroid, and translated back, so a drawing that is
|
|
14
|
+
// already oriented comes back where it was rather than recentred on the origin.
|
|
15
|
+
export function orientToReference(graph: Graph, positions: Positions) {
|
|
16
|
+
const backbone = graph.nodes.filter(
|
|
17
|
+
node => isBackbone(node) && (positions[node.id]?.length ?? 0) > 0,
|
|
18
|
+
)
|
|
19
|
+
if (backbone.length < 2) {
|
|
20
|
+
return positions
|
|
21
|
+
}
|
|
22
|
+
const points = backbone.map(node => {
|
|
23
|
+
const segments = positions[node.id]!
|
|
24
|
+
const mid = segments[Math.floor(segments.length / 2)]!
|
|
25
|
+
return {
|
|
26
|
+
x: mid.x,
|
|
27
|
+
y: mid.y,
|
|
28
|
+
r: node.stable!.start + node.length / 2,
|
|
29
|
+
w: node.length,
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
const weight = points.reduce((sum, p) => sum + p.w, 0)
|
|
33
|
+
const cx = points.reduce((sum, p) => sum + p.x * p.w, 0) / weight
|
|
34
|
+
const cy = points.reduce((sum, p) => sum + p.y * p.w, 0) / weight
|
|
35
|
+
const cr = points.reduce((sum, p) => sum + p.r * p.w, 0) / weight
|
|
36
|
+
// cross-covariance of (x, y) against (r, 0); only the first row is non-zero
|
|
37
|
+
let sxr = 0
|
|
38
|
+
let syr = 0
|
|
39
|
+
for (const p of points) {
|
|
40
|
+
sxr += p.w * (p.x - cx) * (p.r - cr)
|
|
41
|
+
syr += p.w * (p.y - cy) * (p.r - cr)
|
|
42
|
+
}
|
|
43
|
+
// the rotation taking the direction (sxr, syr) onto +x
|
|
44
|
+
const angle = -Math.atan2(syr, sxr)
|
|
45
|
+
const c = Math.cos(angle)
|
|
46
|
+
const s = Math.sin(angle)
|
|
47
|
+
|
|
48
|
+
const out: Positions = {}
|
|
49
|
+
for (const [id, segments] of Object.entries(positions)) {
|
|
50
|
+
out[id] = segments.map(p => {
|
|
51
|
+
const x = p.x - cx
|
|
52
|
+
const y = p.y - cy
|
|
53
|
+
return { x: cx + c * x - s * y, y: cy + s * x + c * y }
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let below = 0
|
|
58
|
+
let above = 0
|
|
59
|
+
for (const node of graph.nodes) {
|
|
60
|
+
if (isBackbone(node)) {
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
for (const p of out[node.id] ?? []) {
|
|
64
|
+
if (p.y > cy) {
|
|
65
|
+
below++
|
|
66
|
+
} else {
|
|
67
|
+
above++
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (above > below) {
|
|
72
|
+
for (const segments of Object.values(out)) {
|
|
73
|
+
for (const p of segments) {
|
|
74
|
+
p.y = 2 * cy - p.y
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out
|
|
79
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { projectAlleles } from '../../alleleProjection/projectAlleles'
|
|
2
|
+
import { buildNeighbors } from '../referenceSpan'
|
|
3
|
+
|
|
4
|
+
import type { AlleleDeletion, Graph, NodeSegment } from '../types'
|
|
5
|
+
|
|
6
|
+
// Where off-reference segments go on a reference x axis. Shared by the two
|
|
7
|
+
// row-structured layouts, which differ only in what a row means (stable rank
|
|
8
|
+
// vs contributing assembly); how far along the reference a segment reaches is
|
|
9
|
+
// the same question in both, and answering it twice is how they drifted.
|
|
10
|
+
//
|
|
11
|
+
// The rule: a segment occupies the reference its allele replaces, never its own
|
|
12
|
+
// sequence length. x is reference bp — the axis the backbone declares and the
|
|
13
|
+
// linear view beside it is drawn on — so an insertion, which consumes no
|
|
14
|
+
// reference, must not advance along it. Both layouts used to advance by
|
|
15
|
+
// sequence length, which made every off-reference x a false statement about
|
|
16
|
+
// reference position and, on a 1 kb window holding a 100 kb allele, drew 101x
|
|
17
|
+
// the window so that zoom-to-fit rendered the reference at 1% of the frame.
|
|
18
|
+
//
|
|
19
|
+
// Magnitude is the cost. An axis of reference coordinates has no way to state
|
|
20
|
+
// how much sequence an insertion adds, so it is not stated here: it is in the
|
|
21
|
+
// node tooltip, and drawn to scale in the allele-inventory track, where a CIGAR
|
|
22
|
+
// gives the alignments display a channel for length that is not the x axis.
|
|
23
|
+
|
|
24
|
+
// Reference bp an allele has to skip before the drawing calls it a deletion:
|
|
25
|
+
// the 1 bp floor `deletionEdges` uses, a guard against abutting anchors' zero
|
|
26
|
+
// gap rather than a biological threshold. Whether a small one is drawn is
|
|
27
|
+
// decided by its label's fit in graphLabels.
|
|
28
|
+
const MIN_DELETION_BP = 1
|
|
29
|
+
|
|
30
|
+
// Floor on an off-reference node's drawn span, as a fraction of the window.
|
|
31
|
+
// Node thickness is a constant number of screen pixels, so a segment on a
|
|
32
|
+
// sub-percent slice of the window draws wider than it is long and reads as a
|
|
33
|
+
// dot on a stalk, and a pure insertion occupies no reference at all. Rank-0
|
|
34
|
+
// nodes keep their exact offsets, so the reference axis is untouched.
|
|
35
|
+
const MIN_ALLELE_SPAN_FRACTION = 0.015
|
|
36
|
+
|
|
37
|
+
interface PlaceArgs {
|
|
38
|
+
graph: Graph
|
|
39
|
+
// the reference interval the drawing is scaled against (referenceSpan)
|
|
40
|
+
span: number
|
|
41
|
+
// y for a segment, which is the whole of what the two layouts disagree about
|
|
42
|
+
rowY: (node: Graph['nodes'][number]) => number
|
|
43
|
+
// the backbone, already placed at its declared offsets. Mutated in place, and
|
|
44
|
+
// read for the branch points the unanchored walk below starts from.
|
|
45
|
+
positions: Record<string, NodeSegment[]>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function placeOffReference({
|
|
49
|
+
graph,
|
|
50
|
+
span,
|
|
51
|
+
rowY,
|
|
52
|
+
positions,
|
|
53
|
+
}: PlaceArgs): AlleleDeletion[] {
|
|
54
|
+
const minSpan = span * MIN_ALLELE_SPAN_FRACTION
|
|
55
|
+
const alleles = projectAlleles(graph)
|
|
56
|
+
const byId = new Map(graph.nodes.map(n => [n.id, n]))
|
|
57
|
+
// Alleles standing in for more reference than they carry. Collected here
|
|
58
|
+
// rather than derived by the label pass, because the reason they need saying
|
|
59
|
+
// is the placement done in this function: a run drawn over the reference it
|
|
60
|
+
// replaces has a width that is not its length, and nothing downstream can
|
|
61
|
+
// tell that from a node drawn at its own scale. Same measure the deletion
|
|
62
|
+
// arcs use — reference bp a haplotype does not carry — so the two kinds of
|
|
63
|
+
// deletion in a drawing are stated in the same units and the same words.
|
|
64
|
+
const alleleDeletions: AlleleDeletion[] = []
|
|
65
|
+
|
|
66
|
+
for (const allele of alleles) {
|
|
67
|
+
const skipped = allele.refSpan - allele.pathLength
|
|
68
|
+
if (skipped >= MIN_DELETION_BP) {
|
|
69
|
+
alleleDeletions.push({ nodeIds: allele.nodeIds, bp: skipped })
|
|
70
|
+
}
|
|
71
|
+
const drawn = Math.max(allele.refSpan, minSpan)
|
|
72
|
+
// A node sits where it sits *within* the allele: its bp offset from the
|
|
73
|
+
// entry, scaled so the run's longest path fills the reference the allele
|
|
74
|
+
// replaces. So a bubble path occupies the reference exactly once however
|
|
75
|
+
// many segments it is built from, and two parallel branches share the x
|
|
76
|
+
// range rather than queue up after each other — they are alternatives over
|
|
77
|
+
// the same interval, not consecutive sequence.
|
|
78
|
+
//
|
|
79
|
+
// Offsets are measured over the graph rather than over nodeIds' order (see
|
|
80
|
+
// runOffsets). Concatenating in traversal order put graph neighbours 26 kb
|
|
81
|
+
// apart on a 27 kb allele, and the edges spanning those gaps crossed as a
|
|
82
|
+
// long X. A run of all zero-length segments has no distances to scale by;
|
|
83
|
+
// splitting evenly beats 0/0, which wrote NaN into every position after it.
|
|
84
|
+
//
|
|
85
|
+
// Where the floor widens a run past the reference it replaces, the overhang
|
|
86
|
+
// hangs off the right, so the run can end past the rank-0 segment it
|
|
87
|
+
// rejoins and its exit edge runs backwards. That is handled where it shows,
|
|
88
|
+
// in computeEdgeCurves, which sizes each control point by how far the other
|
|
89
|
+
// endpoint lies along its own tangent — a backwards edge gets a straight
|
|
90
|
+
// leader rather than the loop that read as a crossed bowtie.
|
|
91
|
+
const scale = allele.pathLength > 0 ? drawn / allele.pathLength : 0
|
|
92
|
+
allele.nodeIds.forEach((nodeId, i) => {
|
|
93
|
+
const node = byId.get(nodeId)
|
|
94
|
+
if (node) {
|
|
95
|
+
const offset = allele.nodeOffsets[i]!
|
|
96
|
+
const even = drawn / allele.nodeIds.length
|
|
97
|
+
// both ends from the offset rather than start-plus-width, so two nodes
|
|
98
|
+
// the sweep placed end to end land on exactly the same x instead of on
|
|
99
|
+
// two roundings of it
|
|
100
|
+
const at = (bp: number) => allele.start + bp * scale
|
|
101
|
+
const y = rowY(node)
|
|
102
|
+
positions[nodeId] = scale
|
|
103
|
+
? [
|
|
104
|
+
{ x: at(offset), y },
|
|
105
|
+
{ x: at(offset + node.length), y },
|
|
106
|
+
]
|
|
107
|
+
: [
|
|
108
|
+
{ x: allele.start + i * even, y },
|
|
109
|
+
{ x: allele.start + (i + 1) * even, y },
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// A run that reaches the edge of the fetched subgraph has only one backbone
|
|
116
|
+
// anchor, so no reference span can be stated for it. That is the normal case
|
|
117
|
+
// at a window boundary, not an error, and dropping it is indistinguishable
|
|
118
|
+
// from the layout being broken — the sample-rows layout did drop them.
|
|
119
|
+
//
|
|
120
|
+
// They get the floor each, chained from wherever they branch: the only honest
|
|
121
|
+
// claim available is "attaches here and continues past the edge", and a floor
|
|
122
|
+
// keeps a chain of them bounded by the window instead of by their sequence.
|
|
123
|
+
// The same undirected adjacency the reference walk builds, from the one
|
|
124
|
+
// function that builds it. The copy here rebuilt each list with a spread per
|
|
125
|
+
// edge, which is quadratic in a node's degree and is the shape a collapsed
|
|
126
|
+
// repeat's hub node is worst at.
|
|
127
|
+
const neighbors = buildNeighbors(graph)
|
|
128
|
+
// Read forward while appending, so this is a BFS with an O(1) dequeue rather
|
|
129
|
+
// than a quadratic shift() over what ends up being every node in the run.
|
|
130
|
+
const queue = Object.keys(positions)
|
|
131
|
+
for (const fromId of queue) {
|
|
132
|
+
const fromEnd = positions[fromId]!.at(-1)!.x
|
|
133
|
+
for (const nextId of neighbors.get(fromId) ?? []) {
|
|
134
|
+
const next = byId.get(nextId)
|
|
135
|
+
if (next && !(nextId in positions)) {
|
|
136
|
+
const y = rowY(next)
|
|
137
|
+
positions[nextId] = [
|
|
138
|
+
{ x: fromEnd, y },
|
|
139
|
+
{ x: fromEnd + minSpan, y },
|
|
140
|
+
]
|
|
141
|
+
queue.push(nextId)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return alleleDeletions
|
|
147
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { drawnNodeLength, layoutScaling } from './drawnScale'
|
|
2
|
+
import { referenceSeeds, seededNodes } from './referenceSeeds'
|
|
3
|
+
import { parseGFA } from '../../gfa-core/index'
|
|
4
|
+
import { spreadFor } from '../bubbleSpreads'
|
|
5
|
+
import { convertGFAToGraph } from '../gfa/gfaConverter'
|
|
6
|
+
|
|
7
|
+
// chr1 runs v1->v2->v3->v4; a1 replaces v3 and hangs a2 off itself, which no
|
|
8
|
+
// backbone node touches; lone is linked to nothing. Lengths span 1 bp to 7 kb so
|
|
9
|
+
// a compressing spread moves the spacing by more than rounding.
|
|
10
|
+
const GFA = `S\tv1\t${'A'.repeat(5000)}\tSN:Z:chr1\tSO:i:0\tSR:i:0
|
|
11
|
+
S\tv2\tCCC\tSN:Z:chr1\tSO:i:5000\tSR:i:0
|
|
12
|
+
S\tv3\tGG\tSN:Z:chr1\tSO:i:5003\tSR:i:0
|
|
13
|
+
S\tv4\t${'T'.repeat(7000)}\tSN:Z:chr1\tSO:i:5005\tSR:i:0
|
|
14
|
+
S\ta1\tAC\tSN:Z:foo\tSO:i:8\tSR:i:1
|
|
15
|
+
S\ta2\tT\tSN:Z:bar\tSO:i:8\tSR:i:2
|
|
16
|
+
S\tlone\tGATTACA\tSN:Z:baz\tSO:i:0\tSR:i:1
|
|
17
|
+
L\tv1\t+\tv2\t+\t0M
|
|
18
|
+
L\tv2\t+\tv3\t+\t0M
|
|
19
|
+
L\tv3\t+\tv4\t+\t0M
|
|
20
|
+
L\tv2\t+\ta1\t+\t0M
|
|
21
|
+
L\ta1\t+\tv4\t+\t0M
|
|
22
|
+
L\ta1\t+\ta2\t+\t0M`
|
|
23
|
+
|
|
24
|
+
const graph = convertGFAToGraph(parseGFA(GFA))
|
|
25
|
+
const BACKBONE = ['v1+', 'v2+', 'v3+', 'v4+']
|
|
26
|
+
|
|
27
|
+
test('the backbone runs along x, each node one drawn length and one edge on', () => {
|
|
28
|
+
const scaling = layoutScaling(graph)
|
|
29
|
+
const seeds = referenceSeeds(graph, scaling)
|
|
30
|
+
let x = 0
|
|
31
|
+
for (const id of BACKBONE) {
|
|
32
|
+
expect(seeds.get(id)).toEqual({ x, y: 0 })
|
|
33
|
+
const node = graph.nodes.find(n => n.id === id)!
|
|
34
|
+
x += drawnNodeLength(scaling.opts, node.length) + scaling.opts.edgeLength
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('an allele sits at its anchor midpoint one lane down, a lane more per step', () => {
|
|
39
|
+
const scaling = layoutScaling(graph)
|
|
40
|
+
const seeds = referenceSeeds(graph, scaling)
|
|
41
|
+
const v2 = graph.nodes.find(n => n.id === 'v2+')!
|
|
42
|
+
const a1 = graph.nodes.find(n => n.id === 'a1+')!
|
|
43
|
+
const anchor = seeds.get('v2+')!
|
|
44
|
+
expect(seeds.get('a1+')).toEqual({
|
|
45
|
+
x: anchor.x + drawnNodeLength(scaling.opts, v2.length) / 2,
|
|
46
|
+
y: 30,
|
|
47
|
+
})
|
|
48
|
+
expect(seeds.get('a2+')).toEqual({
|
|
49
|
+
x: seeds.get('a1+')!.x + drawnNodeLength(scaling.opts, a1.length) / 2,
|
|
50
|
+
y: 60,
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('a node no backbone reaches goes below everything', () => {
|
|
55
|
+
const seeds = referenceSeeds(graph, layoutScaling(graph))
|
|
56
|
+
expect(seeds.get('lone+')).toEqual({ x: 0, y: 90 })
|
|
57
|
+
expect(seeds.size).toBe(graph.nodes.length)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('spacing follows the scaled lengths the engine is handed, not bp', () => {
|
|
61
|
+
const compressed = layoutScaling(graph, spreadFor('compress'))
|
|
62
|
+
const seeds = referenceSeeds(graph, compressed)
|
|
63
|
+
const v1 = compressed.nodes.find(n => n.id === 'v1+')!
|
|
64
|
+
expect(v1.length).not.toBe(5000)
|
|
65
|
+
expect(seeds.get('v2+')!.x).toBe(
|
|
66
|
+
drawnNodeLength(compressed.opts, v1.length) + compressed.opts.edgeLength,
|
|
67
|
+
)
|
|
68
|
+
const proportional = referenceSeeds(graph, layoutScaling(graph))
|
|
69
|
+
expect(seeds.get('v2+')!.x).not.toBe(proportional.get('v2+')!.x)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('seededNodes carries every scaled node with its seed', () => {
|
|
73
|
+
const scaling = layoutScaling(graph, spreadFor('compress'))
|
|
74
|
+
const nodes = seededNodes(graph, scaling)
|
|
75
|
+
expect(nodes.map(n => n.length)).toEqual(scaling.nodes.map(n => n.length))
|
|
76
|
+
expect(nodes.every(n => Number.isFinite(n.x) && Number.isFinite(n.y))).toBe(
|
|
77
|
+
true,
|
|
78
|
+
)
|
|
79
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { isBackbone } from '../anchoredNodes'
|
|
2
|
+
import { buildNeighbors } from '../referenceSpan'
|
|
3
|
+
import { drawnNodeLength } from './drawnScale'
|
|
4
|
+
|
|
5
|
+
import type { LayoutScaling } from './drawnScale'
|
|
6
|
+
import type { Graph, GraphNode } from '../types'
|
|
7
|
+
|
|
8
|
+
// A node as the engine takes it: an optional `x`/`y` is where FMMM starts the
|
|
9
|
+
// node's chain (`addToOgdfGraph`, graphlayout.cpp).
|
|
10
|
+
export type LayoutNode = GraphNode & { x?: number; y?: number }
|
|
11
|
+
|
|
12
|
+
export interface Seed {
|
|
13
|
+
x: number
|
|
14
|
+
y: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// y per BFS step off the backbone. FMMM keeps only the coarse shape of these
|
|
18
|
+
// (OGDF applies the initial placement at its coarsest multilevel graph), so
|
|
19
|
+
// the gap needs to say "below the line", not to place a lane.
|
|
20
|
+
const LANE_GAP = 30
|
|
21
|
+
|
|
22
|
+
// Initial positions for a reference-anchored graph: the backbone end to end
|
|
23
|
+
// along x at drawn length, each off-reference node at the midpoint of the node
|
|
24
|
+
// a BFS from the backbone reached it from, a lane lower per step. Two alleles
|
|
25
|
+
// off one anchor share a seed; FMMM separates them. Nodes no backbone reaches
|
|
26
|
+
// spread along x below everything rather than piling on one point.
|
|
27
|
+
//
|
|
28
|
+
// Lengths come from `scaling.nodes`, not the graph's bp: under a compressing
|
|
29
|
+
// spread a node's `length` crosses the RPC as drawn units times 1000 with a
|
|
30
|
+
// matching `nodeLengthPerMegabase`, and a seed from bp would misplace every
|
|
31
|
+
// chain against the one the engine builds.
|
|
32
|
+
export function referenceSeeds(
|
|
33
|
+
graph: Graph,
|
|
34
|
+
scaling: LayoutScaling,
|
|
35
|
+
): Map<string, Seed> {
|
|
36
|
+
const { opts } = scaling
|
|
37
|
+
const drawn = new Map(
|
|
38
|
+
scaling.nodes.map(node => [node.id, drawnNodeLength(opts, node.length)]),
|
|
39
|
+
)
|
|
40
|
+
const drawnOf = (id: string) => drawn.get(id) ?? opts.minimumNodeLength
|
|
41
|
+
const backbone = graph.nodes
|
|
42
|
+
.filter(isBackbone)
|
|
43
|
+
.sort((a, b) => a.stable.start - b.stable.start)
|
|
44
|
+
|
|
45
|
+
const seeds = new Map<string, Seed>()
|
|
46
|
+
let x = 0
|
|
47
|
+
for (const node of backbone) {
|
|
48
|
+
seeds.set(node.id, { x, y: 0 })
|
|
49
|
+
x += drawnOf(node.id) + opts.edgeLength
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const adjacent = buildNeighbors(graph)
|
|
53
|
+
|
|
54
|
+
const depth = new Map(backbone.map(node => [node.id, 0]))
|
|
55
|
+
// for-of over an array that grows as it goes: the iterator reads the live
|
|
56
|
+
// length, so nodes pushed here are visited
|
|
57
|
+
const queue = backbone.map(node => node.id)
|
|
58
|
+
for (const id of queue) {
|
|
59
|
+
const at = seeds.get(id)!
|
|
60
|
+
const next = depth.get(id)! + 1
|
|
61
|
+
for (const neighbour of adjacent.get(id) ?? []) {
|
|
62
|
+
if (depth.has(neighbour)) {
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
depth.set(neighbour, next)
|
|
66
|
+
seeds.set(neighbour, { x: at.x + drawnOf(id) / 2, y: LANE_GAP * next })
|
|
67
|
+
queue.push(neighbour)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let unreached = 0
|
|
72
|
+
for (const node of graph.nodes) {
|
|
73
|
+
if (!seeds.has(node.id)) {
|
|
74
|
+
seeds.set(node.id, {
|
|
75
|
+
x: (unreached++ * x) / graph.nodes.length,
|
|
76
|
+
y: LANE_GAP * 3,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return seeds
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function seededNodes(
|
|
84
|
+
graph: Graph,
|
|
85
|
+
scaling: LayoutScaling,
|
|
86
|
+
): LayoutNode[] {
|
|
87
|
+
const seeds = referenceSeeds(graph, scaling)
|
|
88
|
+
return scaling.nodes.map(node => ({ ...node, ...seeds.get(node.id) }))
|
|
89
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Row pitch of the row layouts, in screen pixels rather than bp: a row holds a
|
|
2
|
+
// 10 px tube and an 11 px label whatever the window spans, so y is never
|
|
3
|
+
// scaled (LayoutResult.pixelRows) and rows past the pane are reached by panning.
|
|
4
|
+
// 20 px is the floor at which labels stop touching.
|
|
5
|
+
export const ROW_HEIGHT_PX = 20
|