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,178 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs'
|
|
2
|
+
|
|
3
|
+
import { walkRows } from './walkRows'
|
|
4
|
+
import { parseGFA } from '../../gfa-core/index'
|
|
5
|
+
import { convertGFAToGraph } from '../gfa/gfaConverter'
|
|
6
|
+
import { anchorGraph, pathOrigin } from '../pathAnchoring'
|
|
7
|
+
|
|
8
|
+
function pggbGraph() {
|
|
9
|
+
const gfa = readFileSync(
|
|
10
|
+
require.resolve('../../../test_data/ecoli_pggb_subgraph.gfa'),
|
|
11
|
+
'utf8',
|
|
12
|
+
)
|
|
13
|
+
return anchorGraph(convertGFAToGraph(parseGFA(gfa), 'pggb'), 'K12')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const KIV2 = '.test-jbrowse-variants/test_data/graphgenomeview/kiv2_eight.gfa'
|
|
17
|
+
|
|
18
|
+
test('one row per non-reference path, longest first, bp summed over its nodes', () => {
|
|
19
|
+
const graph = pggbGraph()
|
|
20
|
+
const result = walkRows(graph)!
|
|
21
|
+
expect(result.reference.label).toBe('K12')
|
|
22
|
+
expect(result.rows.map(r => r.label).sort()).toEqual([
|
|
23
|
+
'CFT073',
|
|
24
|
+
'IAI39',
|
|
25
|
+
'NCTC86',
|
|
26
|
+
'Sakai',
|
|
27
|
+
])
|
|
28
|
+
for (let i = 1; i < result.rows.length; i++) {
|
|
29
|
+
expect(result.rows[i - 1]!.bp).toBeGreaterThanOrEqual(result.rows[i]!.bp)
|
|
30
|
+
}
|
|
31
|
+
const byId = new Map(graph.nodes.map(n => [n.id, n.length]))
|
|
32
|
+
for (const row of [result.reference, ...result.rows]) {
|
|
33
|
+
const path = graph.paths!.find(p => p.name === row.name)!
|
|
34
|
+
const total = path.nodeIds.reduce((s, id) => s + byId.get(id)!, 0)
|
|
35
|
+
expect(row.bp).toBe(total)
|
|
36
|
+
expect(row.runs.reduce((s, r) => s + r.bp, 0)).toBe(total)
|
|
37
|
+
expect(row.complete).toBe(true)
|
|
38
|
+
}
|
|
39
|
+
expect(result.reference.offReferenceBp).toBe(0)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('runs alternate and cover the row without gaps', () => {
|
|
43
|
+
const { rows } = walkRows(pggbGraph())!
|
|
44
|
+
for (const row of rows) {
|
|
45
|
+
let pos = 0
|
|
46
|
+
row.runs.forEach((run, i) => {
|
|
47
|
+
expect(run.start).toBe(pos)
|
|
48
|
+
if (i > 0) {
|
|
49
|
+
expect(run.onReference).not.toBe(row.runs[i - 1]!.onReference)
|
|
50
|
+
}
|
|
51
|
+
pos += run.bp
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('a region measures between its flanking reference nodes', () => {
|
|
57
|
+
const graph = pggbGraph()
|
|
58
|
+
const byId = new Map(graph.nodes.map(n => [n.id, n.length]))
|
|
59
|
+
const reference = graph.paths!.find(
|
|
60
|
+
p => pathOrigin(p.name).name === graph.referencePath,
|
|
61
|
+
)!
|
|
62
|
+
const start = graph.anchorPaths!.find(
|
|
63
|
+
p => p.name === graph.referencePath,
|
|
64
|
+
)!.start
|
|
65
|
+
const ends = [start]
|
|
66
|
+
for (const id of reference.nodeIds) {
|
|
67
|
+
ends.push(ends.at(-1)! + byId.get(id)!)
|
|
68
|
+
}
|
|
69
|
+
const n = reference.nodeIds.length
|
|
70
|
+
// flanks are the 2nd node and the 2nd-to-last, so the slice is nodes 2..n-3
|
|
71
|
+
const region = { start: ends[2]!, end: ends[n - 2]! }
|
|
72
|
+
const cut = walkRows(graph, region)!
|
|
73
|
+
expect(cut.origin).toBe(region.start)
|
|
74
|
+
expect(cut.reference.bp).toBe(ends[n - 2]! - ends[2]!)
|
|
75
|
+
expect(cut.reference.complete).toBe(true)
|
|
76
|
+
const whole = walkRows(graph)!
|
|
77
|
+
expect(cut.reference.bp).toBeLessThan(whole.reference.bp)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('a walk that skips the flanking node is cut at the next one it visits', () => {
|
|
81
|
+
const graph = pggbGraph()
|
|
82
|
+
const byId = new Map(graph.nodes.map(n => [n.id, n.length]))
|
|
83
|
+
const reference = graph.paths!.find(
|
|
84
|
+
p => pathOrigin(p.name).name === graph.referencePath,
|
|
85
|
+
)!
|
|
86
|
+
const start = graph.anchorPaths!.find(
|
|
87
|
+
p => p.name === graph.referencePath,
|
|
88
|
+
)!.start
|
|
89
|
+
const ends = [start]
|
|
90
|
+
for (const id of reference.nodeIds) {
|
|
91
|
+
ends.push(ends.at(-1)! + byId.get(id)!)
|
|
92
|
+
}
|
|
93
|
+
const n = reference.nodeIds.length
|
|
94
|
+
const region = { start: ends[3]!, end: ends[n - 3]! }
|
|
95
|
+
const skipper = {
|
|
96
|
+
...reference,
|
|
97
|
+
name: 'skipper',
|
|
98
|
+
nodeIds: reference.nodeIds.filter((_, i) => i !== 2 && i !== n - 3),
|
|
99
|
+
}
|
|
100
|
+
const cut = walkRows({ ...graph, paths: [...graph.paths!, skipper] }, region)!
|
|
101
|
+
const row = cut.rows.find(r => r.name === 'skipper')!
|
|
102
|
+
expect(row.complete).toBe(true)
|
|
103
|
+
expect(row.bp).toBe(cut.reference.bp)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// The numbers scripts/layout-lab/copycount.mjs reports for the hosted
|
|
107
|
+
// eight-haplotype KIV-2 cut: GRCh38 about 33 kb through the window, HG00133
|
|
108
|
+
// about 149 kb, so HG00133 carries roughly 21 more units of 5,548 bp.
|
|
109
|
+
test.skipIf(!existsSync(KIV2))(
|
|
110
|
+
'KIV-2: HG00133 carries ~116 kb GRCh38 does not',
|
|
111
|
+
() => {
|
|
112
|
+
const graph = anchorGraph(
|
|
113
|
+
convertGFAToGraph(parseGFA(readFileSync(KIV2, 'utf8')), 'kiv2'),
|
|
114
|
+
'GRCh38',
|
|
115
|
+
)
|
|
116
|
+
const result = walkRows(graph)!
|
|
117
|
+
expect(result.reference.label).toBe('GRCh38#0')
|
|
118
|
+
expect(result.reference.bp).toBeGreaterThan(32_000)
|
|
119
|
+
expect(result.reference.bp).toBeLessThan(34_000)
|
|
120
|
+
expect(result.rows[0]!.label).toBe('HG00133#1')
|
|
121
|
+
expect(result.rows[0]!.bp).toBeGreaterThan(145_000)
|
|
122
|
+
expect(result.rows[0]!.offReferenceBp / 5548).toBeGreaterThan(19)
|
|
123
|
+
expect(result.rows.map(r => r.label)).toHaveLength(8)
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
test('a cut that stops at the window leaves whole walks, not partial ones', () => {
|
|
128
|
+
const graph = pggbGraph()
|
|
129
|
+
const byId = new Map(graph.nodes.map(n => [n.id, n.length]))
|
|
130
|
+
const reference = graph.paths!.find(
|
|
131
|
+
p => pathOrigin(p.name).name === graph.referencePath,
|
|
132
|
+
)!
|
|
133
|
+
const start = graph.anchorPaths!.find(
|
|
134
|
+
p => p.name === graph.referencePath,
|
|
135
|
+
)!.start
|
|
136
|
+
let end = start
|
|
137
|
+
for (const id of reference.nodeIds) {
|
|
138
|
+
end += byId.get(id)!
|
|
139
|
+
}
|
|
140
|
+
// the region IS the reference walk, so no node lies outside it
|
|
141
|
+
const rows = walkRows(graph, { start, end })!
|
|
142
|
+
expect(rows.rows.every(r => r.complete)).toBe(true)
|
|
143
|
+
expect(rows.reference.complete).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('a walk that crosses the window backwards reads in reference direction', () => {
|
|
147
|
+
const graph = pggbGraph()
|
|
148
|
+
const byId = new Map(graph.nodes.map(n => [n.id, n.length]))
|
|
149
|
+
const reference = graph.paths!.find(
|
|
150
|
+
p => pathOrigin(p.name).name === graph.referencePath,
|
|
151
|
+
)!
|
|
152
|
+
const start = graph.anchorPaths!.find(
|
|
153
|
+
p => p.name === graph.referencePath,
|
|
154
|
+
)!.start
|
|
155
|
+
const ends = [start]
|
|
156
|
+
for (const id of reference.nodeIds) {
|
|
157
|
+
ends.push(ends.at(-1)! + byId.get(id)!)
|
|
158
|
+
}
|
|
159
|
+
const n = reference.nodeIds.length
|
|
160
|
+
const region = { start: ends[2]!, end: ends[n - 2]! }
|
|
161
|
+
const forward = walkRows(graph, region)!.rows.find(
|
|
162
|
+
r => r.complete && r.runs.length > 1,
|
|
163
|
+
)!
|
|
164
|
+
expect(forward).toBeDefined()
|
|
165
|
+
const path = graph.paths!.find(p => p.name === forward.name)!
|
|
166
|
+
const backwards = {
|
|
167
|
+
...path,
|
|
168
|
+
name: 'backwards',
|
|
169
|
+
nodeIds: [...path.nodeIds].reverse(),
|
|
170
|
+
}
|
|
171
|
+
const cut = walkRows(
|
|
172
|
+
{ ...graph, paths: [...graph.paths!, backwards] },
|
|
173
|
+
region,
|
|
174
|
+
)!
|
|
175
|
+
const row = cut.rows.find(r => r.name === 'backwards')!
|
|
176
|
+
expect(row.complete).toBe(true)
|
|
177
|
+
expect(row.runs).toEqual(forward.runs)
|
|
178
|
+
})
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { panSNSample } from '../../pansn'
|
|
2
|
+
import { pathOrigin } from '../pathAnchoring'
|
|
3
|
+
|
|
4
|
+
import type { Graph, GraphPath } from '../types'
|
|
5
|
+
|
|
6
|
+
// Each haplotype walk on its own bp axis, so what a walk carries through the
|
|
7
|
+
// window is its bar length. Reference-anchored rows cannot state that: a
|
|
8
|
+
// haplotype's private copies of a repeat consume no reference and collapse to
|
|
9
|
+
// a mark, which is why the KIV-2 array reads as a knot in every node layout
|
|
10
|
+
// and as a ladder here.
|
|
11
|
+
//
|
|
12
|
+
// A base-level graph does not revisit reference nodes through a repeat array,
|
|
13
|
+
// so a copy count is not a visit count; it is the sequence a walk spends
|
|
14
|
+
// between the reference nodes flanking the window, divided by the unit. Runs
|
|
15
|
+
// distinguish sequence the reference walk also carries from sequence it does
|
|
16
|
+
// not, so an expansion is the purple stretch of a row.
|
|
17
|
+
|
|
18
|
+
export interface WalkRun {
|
|
19
|
+
// bp offset from the start of this walk's slice
|
|
20
|
+
start: number
|
|
21
|
+
bp: number
|
|
22
|
+
onReference: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface WalkRow {
|
|
26
|
+
name: string
|
|
27
|
+
label: string
|
|
28
|
+
sample: string
|
|
29
|
+
bp: number
|
|
30
|
+
offReferenceBp: number
|
|
31
|
+
// false when the walk does not reach both flanking reference nodes, in which
|
|
32
|
+
// case the whole walk is measured and the bar says so
|
|
33
|
+
complete: boolean
|
|
34
|
+
runs: WalkRun[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface WalkRows {
|
|
38
|
+
// reference bp the rows' bars start at, so a row's x is origin + run.start
|
|
39
|
+
origin: number
|
|
40
|
+
// repeat unit in bp when a repeat annotation supplied one; the bars tile by it
|
|
41
|
+
unit?: number
|
|
42
|
+
reference: WalkRow
|
|
43
|
+
// every other walk, longest first
|
|
44
|
+
rows: WalkRow[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sampleOf(path: GraphPath) {
|
|
48
|
+
return path.sample ?? panSNSample(path.name)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function labelOf(path: GraphPath) {
|
|
52
|
+
return path.haplotype !== undefined && path.sample !== undefined
|
|
53
|
+
? `${path.sample}#${path.haplotype}`
|
|
54
|
+
: sampleOf(path)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Each walk is cut at the nearest reference nodes IT visits on either side of
|
|
58
|
+
// the region, so a walk that skips one flanking node at a SNP is still measured
|
|
59
|
+
// between flanks rather than whole.
|
|
60
|
+
function sliceBetween(
|
|
61
|
+
nodeIds: string[],
|
|
62
|
+
span: Map<string, { start: number; end: number }>,
|
|
63
|
+
region: { start: number; end: number } | undefined,
|
|
64
|
+
flanked = true,
|
|
65
|
+
) {
|
|
66
|
+
// A cut that stops at the window carries no flanking reference for anyone,
|
|
67
|
+
// so every walk is whole and the slice is the walk.
|
|
68
|
+
if (!region || !flanked) {
|
|
69
|
+
return { ids: nodeIds, complete: true }
|
|
70
|
+
}
|
|
71
|
+
let i0 = -1
|
|
72
|
+
let i1 = -1
|
|
73
|
+
let bestEnd = -Infinity
|
|
74
|
+
let bestStart = Infinity
|
|
75
|
+
nodeIds.forEach((id, i) => {
|
|
76
|
+
const s = span.get(id)
|
|
77
|
+
if (s) {
|
|
78
|
+
if (s.end <= region.start && s.end > bestEnd) {
|
|
79
|
+
bestEnd = s.end
|
|
80
|
+
i0 = i
|
|
81
|
+
}
|
|
82
|
+
if (s.start >= region.end && s.start < bestStart) {
|
|
83
|
+
bestStart = s.start
|
|
84
|
+
i1 = i
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
if (i0 < 0 || i1 < 0) {
|
|
89
|
+
return { ids: nodeIds, complete: false }
|
|
90
|
+
}
|
|
91
|
+
const ids = nodeIds.slice(Math.min(i0, i1) + 1, Math.max(i0, i1))
|
|
92
|
+
return { ids: i0 < i1 ? ids : ids.reverse(), complete: true }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function walkRows(
|
|
96
|
+
graph: Graph,
|
|
97
|
+
region?: { start: number; end: number },
|
|
98
|
+
unit?: number,
|
|
99
|
+
): WalkRows | undefined {
|
|
100
|
+
const paths = graph.paths ?? []
|
|
101
|
+
// `referencePath` is the anchor name, which pathOrigin has already stripped
|
|
102
|
+
// of the range suffix odgi leaves on a P record's name
|
|
103
|
+
const reference =
|
|
104
|
+
paths.find(p => pathOrigin(p.name).name === graph.referencePath) ?? paths[0]
|
|
105
|
+
if (!reference || paths.length < 2) {
|
|
106
|
+
return undefined
|
|
107
|
+
}
|
|
108
|
+
const lengthOf = new Map(graph.nodes.map(n => [n.id, n.length]))
|
|
109
|
+
const onReference = new Set(reference.nodeIds)
|
|
110
|
+
const referenceStart =
|
|
111
|
+
graph.anchorPaths?.find(p => p.name === pathOrigin(reference.name).name)
|
|
112
|
+
?.start ?? 0
|
|
113
|
+
|
|
114
|
+
const cut = region && region.end > region.start ? region : undefined
|
|
115
|
+
const span = new Map<string, { start: number; end: number }>()
|
|
116
|
+
let pos = referenceStart
|
|
117
|
+
for (const id of reference.nodeIds) {
|
|
118
|
+
const len = lengthOf.get(id) ?? 0
|
|
119
|
+
if (!span.has(id)) {
|
|
120
|
+
span.set(id, { start: pos, end: pos + len })
|
|
121
|
+
}
|
|
122
|
+
pos += len
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Whether the reference reaches past the region on both sides, i.e. whether
|
|
126
|
+
// a flanking node exists for any walk to be cut at.
|
|
127
|
+
const flanked =
|
|
128
|
+
cut === undefined ||
|
|
129
|
+
([...span.values()].some(s => s.end <= cut.start) &&
|
|
130
|
+
[...span.values()].some(s => s.start >= cut.end))
|
|
131
|
+
|
|
132
|
+
const rowOf = (path: GraphPath): WalkRow => {
|
|
133
|
+
const { ids, complete } = sliceBetween(path.nodeIds, span, cut, flanked)
|
|
134
|
+
const runs: WalkRun[] = []
|
|
135
|
+
let bp = 0
|
|
136
|
+
let offReferenceBp = 0
|
|
137
|
+
for (const id of ids) {
|
|
138
|
+
const len = lengthOf.get(id) ?? 0
|
|
139
|
+
const shared = onReference.has(id)
|
|
140
|
+
const last = runs.at(-1)
|
|
141
|
+
if (last?.onReference === shared) {
|
|
142
|
+
last.bp += len
|
|
143
|
+
} else {
|
|
144
|
+
runs.push({ start: bp, bp: len, onReference: shared })
|
|
145
|
+
}
|
|
146
|
+
bp += len
|
|
147
|
+
if (!shared) {
|
|
148
|
+
offReferenceBp += len
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
name: path.name,
|
|
153
|
+
label: labelOf(path),
|
|
154
|
+
sample: sampleOf(path),
|
|
155
|
+
bp,
|
|
156
|
+
offReferenceBp,
|
|
157
|
+
complete,
|
|
158
|
+
runs,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const origin = cut ? cut.start : referenceStart
|
|
163
|
+
return {
|
|
164
|
+
origin,
|
|
165
|
+
unit,
|
|
166
|
+
reference: rowOf(reference),
|
|
167
|
+
rows: paths
|
|
168
|
+
.filter(p => p !== reference)
|
|
169
|
+
.map(rowOf)
|
|
170
|
+
.sort(
|
|
171
|
+
(a, b) =>
|
|
172
|
+
Number(b.complete) - Number(a.complete) ||
|
|
173
|
+
b.bp - a.bp ||
|
|
174
|
+
a.label.localeCompare(b.label),
|
|
175
|
+
),
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { isBackbone, isOffReference } from './anchoredNodes'
|
|
2
|
+
import { anchoredLayout } from './layout/anchoredLayout'
|
|
3
|
+
import { orderedLayout } from './layout/orderedLayout'
|
|
4
|
+
import { sampleRowLayout } from './layout/sampleRowLayout'
|
|
5
|
+
import { variantMapLayout } from './layout/variantMapLayout'
|
|
6
|
+
import { walkRowLayout } from './layout/walkRowLayout'
|
|
7
|
+
|
|
8
|
+
import type { Graph, LayoutResult } from './types'
|
|
9
|
+
|
|
10
|
+
// The force layout's menu label. jbrowse-components' check-menu-labels scans
|
|
11
|
+
// this src/ for string literals, so a documented **Layout → …** path is held
|
|
12
|
+
// to what the dropdown renders.
|
|
13
|
+
export const FORCE_LAYOUT_LABEL = 'Force-directed layout'
|
|
14
|
+
|
|
15
|
+
// One table drives the persisted enum, the toolbar dropdown, and the dispatch in
|
|
16
|
+
// the model, so adding a layout is one entry rather than several edits that can
|
|
17
|
+
// disagree.
|
|
18
|
+
//
|
|
19
|
+
// `run` returning undefined means "this mode can't draw this graph" and hands
|
|
20
|
+
// off to the remote Bandage FMMM engine. 'auto' uses it when a GFA has no
|
|
21
|
+
// rank-0 backbone — neither declared in rGFA tags nor derivable from P/W lines
|
|
22
|
+
// (pathAnchoring.ts) — and 'force' is simply the mode that always takes it.
|
|
23
|
+
export interface LayoutMode {
|
|
24
|
+
value: string
|
|
25
|
+
label: string
|
|
26
|
+
description: string
|
|
27
|
+
// `region` is the interval the cut was made for, absent for a whole-file
|
|
28
|
+
// import. The reference-anchored modes scale their rows and their allele
|
|
29
|
+
// floor against it rather than against the backbone they were handed, which
|
|
30
|
+
// a long-range allele's far anchor stretches well past the window
|
|
31
|
+
// (referenceSpan).
|
|
32
|
+
run: (
|
|
33
|
+
graph: Graph,
|
|
34
|
+
region?: { start: number; end: number },
|
|
35
|
+
) => LayoutResult | undefined
|
|
36
|
+
// whether this mode can draw this graph at all; the dropdown greys out the
|
|
37
|
+
// rest rather than hiding them, so the reason a mode is unavailable stays
|
|
38
|
+
// visible instead of the menu silently changing shape between graphs
|
|
39
|
+
available: (graph: Graph) => boolean
|
|
40
|
+
// whether `run` draws this graph itself, as opposed to handing off to the
|
|
41
|
+
// engine. Not the same question as `available` and not derivable from it:
|
|
42
|
+
// 'force' is available for every graph and draws none of them itself. The two
|
|
43
|
+
// coincide for the anchored modes, which is stated by sharing the predicate
|
|
44
|
+
// rather than by restating it.
|
|
45
|
+
drawsLocally: (graph: Graph) => boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const hasBackbone = (graph: Graph) => graph.nodes.some(isBackbone)
|
|
49
|
+
const hasWalks = (graph: Graph) =>
|
|
50
|
+
hasBackbone(graph) && (graph.paths?.length ?? 0) > 1
|
|
51
|
+
const hasAlleles = (graph: Graph) =>
|
|
52
|
+
hasBackbone(graph) && graph.nodes.some(isOffReference)
|
|
53
|
+
|
|
54
|
+
// `satisfies` rather than a `: LayoutMode[]` annotation: the annotation widened
|
|
55
|
+
// every `value` to string, which collapsed LayoutModeValue to string and left the
|
|
56
|
+
// persisted enum unable to state which values it accepts.
|
|
57
|
+
export const LAYOUT_MODES = [
|
|
58
|
+
{
|
|
59
|
+
value: 'auto',
|
|
60
|
+
label: 'Anchored',
|
|
61
|
+
description:
|
|
62
|
+
'x is reference bp, one row per stable rank. Needs rGFA tags or a reference path.',
|
|
63
|
+
run: anchoredLayout,
|
|
64
|
+
available: hasBackbone,
|
|
65
|
+
drawsLocally: hasBackbone,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
value: 'samplerows',
|
|
69
|
+
label: 'Sample rows',
|
|
70
|
+
description:
|
|
71
|
+
'x is reference bp, one row per contributing assembly. Needs rGFA tags or a reference path.',
|
|
72
|
+
run: sampleRowLayout,
|
|
73
|
+
available: hasAlleles,
|
|
74
|
+
drawsLocally: hasAlleles,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
value: 'walkrows',
|
|
78
|
+
label: 'Walk rows',
|
|
79
|
+
description:
|
|
80
|
+
"x is each walk's own bp: one bar per haplotype, sequence the reference also carries in blue and sequence it does not in purple, so a repeat expansion reads as bar length. Needs W or P lines.",
|
|
81
|
+
run: walkRowLayout,
|
|
82
|
+
available: hasWalks,
|
|
83
|
+
drawsLocally: hasWalks,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
value: 'ordered',
|
|
87
|
+
label: 'Ordered',
|
|
88
|
+
description:
|
|
89
|
+
'x is reference order, not bp: every node gets room, bubbles read as lenses. Needs rGFA tags or a reference path.',
|
|
90
|
+
run: orderedLayout,
|
|
91
|
+
available: hasBackbone,
|
|
92
|
+
drawsLocally: hasBackbone,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
value: 'variants',
|
|
96
|
+
label: 'Variant map',
|
|
97
|
+
description:
|
|
98
|
+
'The reference as a line with one typed glyph per bubble, from the bubble index or from the graph itself: SNP, indel, deletion, inversion, repeat array. Click a glyph to open the graph inside it.',
|
|
99
|
+
run: variantMapLayout,
|
|
100
|
+
available: hasBackbone,
|
|
101
|
+
drawsLocally: hasBackbone,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
value: 'force',
|
|
105
|
+
label: FORCE_LAYOUT_LABEL,
|
|
106
|
+
description: 'OGDF FMMM, via the external Bandage engine.',
|
|
107
|
+
run: () => undefined,
|
|
108
|
+
available: () => true,
|
|
109
|
+
drawsLocally: () => false,
|
|
110
|
+
},
|
|
111
|
+
] as const satisfies readonly LayoutMode[]
|
|
112
|
+
|
|
113
|
+
export type LayoutModeValue = (typeof LAYOUT_MODES)[number]['value']
|
|
114
|
+
|
|
115
|
+
export const LAYOUT_MODE_VALUES = LAYOUT_MODES.map(m => m.value)
|
|
116
|
+
|
|
117
|
+
// Whether the drawing a mode produces for a graph comes from the FMMM engine.
|
|
118
|
+
// The settings that only the engine reads — layout quality, bubble spread —
|
|
119
|
+
// are shown against this, so a control that cannot do anything to the drawing
|
|
120
|
+
// on screen says so instead of appearing to work.
|
|
121
|
+
export function modeUsesLayoutEngine(value: string, graph: Graph) {
|
|
122
|
+
return !layoutModeByValue(value).drawsLocally(graph)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function layoutModeByValue(value: string) {
|
|
126
|
+
// An unknown value can only come from a snapshot written by a build that had
|
|
127
|
+
// a mode this one doesn't. It falls back to 'auto' rather than to the model's
|
|
128
|
+
// own default of 'force', because 'auto' declines any graph it cannot draw and
|
|
129
|
+
// hands off to force anyway, so this is the fallback that covers both.
|
|
130
|
+
return LAYOUT_MODES.find(m => m.value === value) ?? LAYOUT_MODES[0]
|
|
131
|
+
}
|