jbrowse-plugin-graphgenomeviewer 7.4.2 → 7.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +53 -24
  2. package/dist/chunks/{AddTrackWorkflow-LK25EC3H.js → AddTrackWorkflow-NLSZYK56.js} +2 -2
  3. package/dist/chunks/{GbzBaseSyntenyAdapter-BVLBK4OO.js → GbzBaseSyntenyAdapter-A5MNPCKX.js} +2 -2
  4. package/dist/chunks/{GbzBaseSyntenyAdapter-BVLBK4OO.js.map → GbzBaseSyntenyAdapter-A5MNPCKX.js.map} +1 -1
  5. package/dist/chunks/{GraphGenomeView-UCOAMRF7.js → GraphGenomeView-SEAPDRXS.js} +2 -2
  6. package/dist/chunks/{GraphTrackSettingsDialog-MR7X75EY.js → GraphTrackSettingsDialog-EVIIGC5G.js} +2 -2
  7. package/dist/chunks/{HighlightColorDialog-ZBAZDYIR.js → HighlightColorDialog-QAYWLO5I.js} +2 -2
  8. package/dist/chunks/{LinearGraphDisplay-AJ5OZN27.js → LinearGraphDisplay-ZC4BQ27J.js} +2 -2
  9. package/dist/chunks/MinigraphBubbleAdapter-3QM4XV47.js +2 -0
  10. package/dist/chunks/{MinigraphBubbleAdapter-PEQK5NII.js.map → MinigraphBubbleAdapter-3QM4XV47.js.map} +1 -1
  11. package/dist/chunks/{RgfaTabixAdapter-Q63YLPK4.js → RgfaTabixAdapter-XSOIEWBK.js} +2 -2
  12. package/dist/chunks/WalkFormsAdapter-FT2SW4H6.js +2 -0
  13. package/dist/chunks/WalkFormsAdapter-FT2SW4H6.js.map +7 -0
  14. package/dist/chunks/{WalkTabixSyntenyAdapter-N5PFI4LS.js → WalkTabixSyntenyAdapter-3WRVNDLB.js} +2 -2
  15. package/dist/chunks/{chunk-2XZYJLCE.js → chunk-2KVE7YAS.js} +1 -1
  16. package/dist/chunks/{chunk-2XZYJLCE.js.map → chunk-2KVE7YAS.js.map} +1 -1
  17. package/dist/chunks/{chunk-RCXOOBLX.js → chunk-6SWXMRAV.js} +1 -1
  18. package/dist/chunks/{chunk-RCXOOBLX.js.map → chunk-6SWXMRAV.js.map} +1 -1
  19. package/dist/chunks/{chunk-WY5W5X62.js → chunk-6VNU7SD4.js} +2 -2
  20. package/dist/chunks/{chunk-WY5W5X62.js.map → chunk-6VNU7SD4.js.map} +1 -1
  21. package/dist/chunks/{chunk-DXNE2CFX.js → chunk-6WUWQX2P.js} +2 -2
  22. package/dist/chunks/{chunk-DXNE2CFX.js.map → chunk-6WUWQX2P.js.map} +1 -1
  23. package/dist/chunks/{chunk-ZG2CQXOW.js → chunk-7RJMG6I2.js} +2 -2
  24. package/dist/chunks/{chunk-ZG2CQXOW.js.map → chunk-7RJMG6I2.js.map} +1 -1
  25. package/dist/chunks/chunk-FTXDTU27.js +3 -0
  26. package/dist/chunks/chunk-FTXDTU27.js.map +7 -0
  27. package/dist/chunks/chunk-G53BI7VK.js +2 -0
  28. package/dist/chunks/{chunk-MEMUEG5Z.js.map → chunk-G53BI7VK.js.map} +4 -4
  29. package/dist/chunks/chunk-GRTN25B6.js +3 -0
  30. package/dist/chunks/chunk-GRTN25B6.js.map +7 -0
  31. package/dist/chunks/{chunk-62AHPP6H.js → chunk-HCDKP3IP.js} +2 -2
  32. package/dist/chunks/{chunk-62AHPP6H.js.map → chunk-HCDKP3IP.js.map} +1 -1
  33. package/dist/chunks/{chunk-HI3YK2SO.js → chunk-HF4H6LQI.js} +2 -2
  34. package/dist/chunks/{chunk-MVVXRZXV.js → chunk-JRJHXLGP.js} +2 -2
  35. package/dist/chunks/{chunk-MVVXRZXV.js.map → chunk-JRJHXLGP.js.map} +2 -2
  36. package/dist/chunks/{chunk-LFTZNUXB.js → chunk-KIYRMZWP.js} +2 -2
  37. package/dist/chunks/{chunk-LFTZNUXB.js.map → chunk-KIYRMZWP.js.map} +1 -1
  38. package/dist/chunks/{chunk-WJGCVNGH.js → chunk-LE43I4PT.js} +2 -2
  39. package/dist/chunks/{chunk-U5PIXAF2.js → chunk-LE7FZM6Y.js} +2 -2
  40. package/dist/chunks/{chunk-U5PIXAF2.js.map → chunk-LE7FZM6Y.js.map} +1 -1
  41. package/dist/chunks/chunk-MUONTMYZ.js +2 -0
  42. package/dist/chunks/chunk-MUONTMYZ.js.map +7 -0
  43. package/dist/chunks/{chunk-SFYSQ52W.js → chunk-OJWBLPPH.js} +2 -2
  44. package/dist/chunks/{chunk-JSRGX4TC.js → chunk-P3FBI5ML.js} +1 -1
  45. package/dist/chunks/{chunk-JSRGX4TC.js.map → chunk-P3FBI5ML.js.map} +1 -1
  46. package/dist/chunks/chunk-QV7SSOMJ.js +2 -0
  47. package/dist/chunks/chunk-S333HOG5.js +2 -0
  48. package/dist/chunks/chunk-S333HOG5.js.map +7 -0
  49. package/dist/chunks/{chunk-QJ43NDYS.js → chunk-TYV6UXJA.js} +2 -2
  50. package/dist/chunks/chunk-VZQ6VOUP.js +2 -0
  51. package/dist/chunks/chunk-VZQ6VOUP.js.map +7 -0
  52. package/dist/chunks/chunk-WLRRI35A.js +3 -0
  53. package/dist/chunks/chunk-WLRRI35A.js.map +7 -0
  54. package/dist/chunks/{chunk-PUREF4BM.js → chunk-XXBE4IFZ.js} +2 -2
  55. package/dist/chunks/{chunk-PUREF4BM.js.map → chunk-XXBE4IFZ.js.map} +1 -1
  56. package/dist/chunks/{renderSvg-Z7YIXVQ5.js → renderSvg-ZIUOPWJQ.js} +2 -2
  57. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +3 -4
  58. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
  59. package/package.json +3 -3
  60. package/src/GraphGenomeView/model.test.ts +2 -109
  61. package/src/GraphGenomeView/pane/fitViews.ts +0 -109
  62. package/src/GraphGenomeView/pane/graphViews.ts +0 -4
  63. package/src/GraphGenomeView/pane/launchActions.ts +0 -22
  64. package/src/GraphGenomeView/pane/loadActions.ts +1 -1
  65. package/src/GraphGenomeView/pane/paneBase.ts +0 -7
  66. package/src/GraphGenomeView/viewModel.ts +0 -3
  67. package/src/RgfaTabixAdapter/configSchema.ts +3 -3
  68. package/src/RgfaTabixAdapter/walkFiles.test.ts +6 -6
  69. package/src/WalkFormsAdapter/WalkFormsAdapter.test.ts +204 -0
  70. package/src/WalkFormsAdapter/WalkFormsAdapter.ts +232 -0
  71. package/src/WalkFormsAdapter/configSchema.ts +127 -0
  72. package/src/WalkFormsAdapter/formRows.test.ts +258 -0
  73. package/src/WalkFormsAdapter/formRows.ts +231 -0
  74. package/src/WalkFormsAdapter/formsMenu.test.ts +237 -0
  75. package/src/WalkFormsAdapter/formsMenu.ts +202 -0
  76. package/src/WalkFormsAdapter/index.ts +21 -0
  77. package/src/WalkFormsAdapter/pinned.ts +33 -0
  78. package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.test.ts +5 -5
  79. package/src/WalkTabixSyntenyAdapter/configSchema.ts +2 -2
  80. package/src/index.ts +4 -0
  81. package/src/panSNAliases/panSNAliases.test.ts +2 -2
  82. package/src/version.ts +1 -1
  83. package/dist/chunks/MinigraphBubbleAdapter-PEQK5NII.js +0 -2
  84. package/dist/chunks/chunk-55DIX25J.js +0 -3
  85. package/dist/chunks/chunk-55DIX25J.js.map +0 -7
  86. package/dist/chunks/chunk-AWGMTI73.js +0 -3
  87. package/dist/chunks/chunk-AWGMTI73.js.map +0 -7
  88. package/dist/chunks/chunk-FWVXBJ64.js +0 -3
  89. package/dist/chunks/chunk-FWVXBJ64.js.map +0 -7
  90. package/dist/chunks/chunk-J3AXN7JQ.js +0 -2
  91. package/dist/chunks/chunk-MEMUEG5Z.js +0 -2
  92. package/dist/chunks/chunk-Q3BXNKEL.js +0 -2
  93. package/dist/chunks/chunk-Q3BXNKEL.js.map +0 -7
  94. package/dist/chunks/chunk-S75MFZDE.js +0 -2
  95. package/dist/chunks/chunk-S75MFZDE.js.map +0 -7
  96. /package/dist/chunks/{AddTrackWorkflow-LK25EC3H.js.map → AddTrackWorkflow-NLSZYK56.js.map} +0 -0
  97. /package/dist/chunks/{GraphGenomeView-UCOAMRF7.js.map → GraphGenomeView-SEAPDRXS.js.map} +0 -0
  98. /package/dist/chunks/{GraphTrackSettingsDialog-MR7X75EY.js.map → GraphTrackSettingsDialog-EVIIGC5G.js.map} +0 -0
  99. /package/dist/chunks/{HighlightColorDialog-ZBAZDYIR.js.map → HighlightColorDialog-QAYWLO5I.js.map} +0 -0
  100. /package/dist/chunks/{LinearGraphDisplay-AJ5OZN27.js.map → LinearGraphDisplay-ZC4BQ27J.js.map} +0 -0
  101. /package/dist/chunks/{RgfaTabixAdapter-Q63YLPK4.js.map → RgfaTabixAdapter-XSOIEWBK.js.map} +0 -0
  102. /package/dist/chunks/{WalkTabixSyntenyAdapter-N5PFI4LS.js.map → WalkTabixSyntenyAdapter-3WRVNDLB.js.map} +0 -0
  103. /package/dist/chunks/{chunk-HI3YK2SO.js.map → chunk-HF4H6LQI.js.map} +0 -0
  104. /package/dist/chunks/{chunk-WJGCVNGH.js.map → chunk-LE43I4PT.js.map} +0 -0
  105. /package/dist/chunks/{chunk-SFYSQ52W.js.map → chunk-OJWBLPPH.js.map} +0 -0
  106. /package/dist/chunks/{chunk-J3AXN7JQ.js.map → chunk-QV7SSOMJ.js.map} +0 -0
  107. /package/dist/chunks/{chunk-QJ43NDYS.js.map → chunk-TYV6UXJA.js.map} +0 -0
  108. /package/dist/chunks/{renderSvg-Z7YIXVQ5.js.map → renderSvg-ZIUOPWJQ.js.map} +0 -0
  109. /package/src/RgfaTabixAdapter/test_data/{walks4_follow.GRCh38.walks.bed.gz → walks5_follow.GRCh38.walks.bed.gz} +0 -0
  110. /package/src/RgfaTabixAdapter/test_data/{walks4_follow.GRCh38.walks.bed.gz.tbi → walks5_follow.GRCh38.walks.bed.gz.tbi} +0 -0
  111. /package/src/RgfaTabixAdapter/test_data/{walks4_header.GRCh38.walks.bed.gz → walks5_header.GRCh38.walks.bed.gz} +0 -0
  112. /package/src/RgfaTabixAdapter/test_data/{walks4_header.GRCh38.walks.bed.gz.tbi → walks5_header.GRCh38.walks.bed.gz.tbi} +0 -0
  113. /package/src/RgfaTabixAdapter/test_data/{walks4_inversion.GRCh38.walks.bed.gz → walks5_inversion.GRCh38.walks.bed.gz} +0 -0
  114. /package/src/RgfaTabixAdapter/test_data/{walks4_inversion.GRCh38.walks.bed.gz.tbi → walks5_inversion.GRCh38.walks.bed.gz.tbi} +0 -0
  115. /package/src/RgfaTabixAdapter/test_data/{walks4_lanepairs.GRCh38.walks.bed.gz → walks5_lanepairs.GRCh38.walks.bed.gz} +0 -0
  116. /package/src/RgfaTabixAdapter/test_data/{walks4_lanepairs.GRCh38.walks.bed.gz.tbi → walks5_lanepairs.GRCh38.walks.bed.gz.tbi} +0 -0
  117. /package/src/RgfaTabixAdapter/test_data/{walks4_maxnode.GRCh38.walks.bed.gz → walks5_maxnode.GRCh38.walks.bed.gz} +0 -0
  118. /package/src/RgfaTabixAdapter/test_data/{walks4_maxnode.GRCh38.walks.bed.gz.tbi → walks5_maxnode.GRCh38.walks.bed.gz.tbi} +0 -0
  119. /package/src/RgfaTabixAdapter/test_data/{walks4_rejoin.GRCh38.walks.bed.gz → walks5_rejoin.GRCh38.walks.bed.gz} +0 -0
  120. /package/src/RgfaTabixAdapter/test_data/{walks4_rejoin.GRCh38.walks.bed.gz.tbi → walks5_rejoin.GRCh38.walks.bed.gz.tbi} +0 -0
  121. /package/src/RgfaTabixAdapter/test_data/{walks4_twopart.GRCh38.walks.bed.gz → walks5_twopart.GRCh38.walks.bed.gz} +0 -0
  122. /package/src/RgfaTabixAdapter/test_data/{walks4_twopart.GRCh38.walks.bed.gz.tbi → walks5_twopart.GRCh38.walks.bed.gz.tbi} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jbrowse-plugin-graphgenomeviewer",
3
- "version": "7.4.2",
3
+ "version": "7.4.4",
4
4
  "description": "JBrowse 2 graph genome (pangenome) view: rGFA reference-anchored layouts, the Bandage force-directed engine, and haplotype lanes read from a gbz-base database",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -32,7 +32,7 @@
32
32
  "@jbrowse/core": "5.0.0-beta.11",
33
33
  "@jbrowse/display-kit": "5.0.0-beta.11",
34
34
  "@jbrowse/display-ui": "5.0.0-beta.11",
35
- "@jbrowse/graph-walks": "^4.0.0",
35
+ "@jbrowse/graph-walks": "^4.2.0",
36
36
  "@jbrowse/render-core": "5.0.0-beta.11",
37
37
  "@jbrowse/synteny-core": "5.0.0-beta.11"
38
38
  },
@@ -40,7 +40,7 @@
40
40
  "@emotion/react": "^11.14.0",
41
41
  "@emotion/styled": "^11.14.1",
42
42
  "@eslint/js": "^10.0.1",
43
- "@jbrowse/bandage-core": "^11.7.1",
43
+ "@jbrowse/bandage-core": "^11.9.0",
44
44
  "@jbrowse/cli": "^4.3.0",
45
45
  "@jbrowse/mobx-state-tree": "^6.9.0",
46
46
  "@jbrowse/plugin-linear-genome-view": "5.0.0-beta.11",
@@ -874,27 +874,7 @@ describe('loadGFAFromLocation', () => {
874
874
  expect(model.graphRegion).toEqual(TEST_REGION)
875
875
  })
876
876
 
877
- test('a graph read from a url gives the figure spec that reads it again', async () => {
878
- rpcRespond()
879
- mockReadFile.mockResolvedValue(SIMPLE_GFA)
880
- const model = stateModelFactory().create({
881
- type: 'GraphGenomeView',
882
- gfaLocation: location,
883
- loadedRegion: TEST_REGION,
884
- layoutMode: 'auto',
885
- })
886
- await model.load()
887
- expect(model.figureSpec()).toMatchObject({
888
- gfa: location.uri,
889
- region: 'chr1:1000-5000',
890
- layout: 'auto',
891
- })
892
- expect(model.figure()).toContain(
893
- '"gfa":"https://example.com/graphs/small.gfa"',
894
- )
895
- })
896
-
897
- test('a gene track in tabix shorthand gives the figure spec its index', async () => {
877
+ test('a gene track in tabix shorthand turns genes on', async () => {
898
878
  rpcRespond()
899
879
  mockReadFile.mockResolvedValue(SIMPLE_GFA)
900
880
  mockSession.tracks = [
@@ -916,11 +896,7 @@ describe('loadGFAFromLocation', () => {
916
896
  })
917
897
  await model.load()
918
898
  expect(model.showGenes).toBe(true)
919
- expect(model.figureSpec()?.genes).toEqual({
920
- file: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeq.gff.gz',
921
- index: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeq.gff.gz.csi',
922
- format: 'gff3',
923
- })
899
+ expect(model.geneTrack?.trackId).toBe('ncbiRefSeq')
924
900
  mockSession.tracks = []
925
901
  })
926
902
 
@@ -3413,89 +3389,6 @@ describe('walk rows', () => {
3413
3389
  expect(svg).toContain("'s path")
3414
3390
  })
3415
3391
 
3416
- test("the figure spec keeps the strip's sample filter", async () => {
3417
- rpcRespond()
3418
- mockReadFile.mockResolvedValue(WALKS_GFA)
3419
- const model = stateModelFactory().create({
3420
- type: 'GraphGenomeView',
3421
- gfaLocation: {
3422
- uri: 'https://example.com/graphs/walks.gfa',
3423
- locationType: 'UriLocation',
3424
- },
3425
- loadedRegion: TEST_REGION,
3426
- layoutMode: 'ordered',
3427
- layers: { walkStrip: true },
3428
- rows: { kept: ['B'] },
3429
- })
3430
- await model.load()
3431
- expect(model.walkStripRows!.rows.map(r => r.label)).toEqual(['B#1'])
3432
- expect(model.figureSpec()).toMatchObject({
3433
- walkStrip: true,
3434
- walkRowSamples: ['B'],
3435
- })
3436
- model.setWalkStrip(false)
3437
- expect(model.figureSpec()?.walkRowSamples).toBeUndefined()
3438
- })
3439
-
3440
- // bandage-figure shows deletion edges unless a spec says otherwise
3441
- test('the figure spec states deletion edges only when they are off', async () => {
3442
- rpcRespond()
3443
- mockReadFile.mockResolvedValue(WALKS_GFA)
3444
- const model = stateModelFactory().create({
3445
- type: 'GraphGenomeView',
3446
- gfaLocation: {
3447
- uri: 'https://example.com/graphs/walks.gfa',
3448
- locationType: 'UriLocation',
3449
- },
3450
- loadedRegion: TEST_REGION,
3451
- layoutMode: 'ordered',
3452
- })
3453
- await model.load()
3454
- expect(model.figureSpec()).not.toHaveProperty('showDeletionEdges')
3455
- model.setLayer('deletions', false)
3456
- expect(model.figureSpec()?.showDeletionEdges).toBe(false)
3457
- })
3458
-
3459
- // bandage-figure laid a linear layout out as an ordinary one, and drew its
3460
- // own line widths, while the spec said nothing about either
3461
- test('the figure spec carries the settings that change the picture', async () => {
3462
- rpcRespond()
3463
- mockReadFile.mockResolvedValue(WALKS_GFA)
3464
- const model = stateModelFactory().create({
3465
- type: 'GraphGenomeView',
3466
- gfaLocation: {
3467
- uri: 'https://example.com/graphs/walks.gfa',
3468
- locationType: 'UriLocation',
3469
- },
3470
- loadedRegion: TEST_REGION,
3471
- layoutMode: 'ordered',
3472
- size: 9,
3473
- })
3474
- await model.load()
3475
- expect(model.figureSpec()).toMatchObject({ contigThickness: 9 })
3476
- expect(model.figureSpec()).not.toHaveProperty('connectorThickness')
3477
- expect(model.figureSpec()).not.toHaveProperty('linearLayout')
3478
- model.setLinearLayout(true)
3479
- expect(model.figureSpec()?.linearLayout).toBe(true)
3480
- })
3481
-
3482
- test('walk rows offer no figure spec, since bandage-figure draws no walk rows', async () => {
3483
- rpcRespond()
3484
- mockReadFile.mockResolvedValue(WALKS_GFA)
3485
- const model = stateModelFactory().create({
3486
- type: 'GraphGenomeView',
3487
- gfaLocation: {
3488
- uri: 'https://example.com/graphs/walks.gfa',
3489
- locationType: 'UriLocation',
3490
- },
3491
- loadedRegion: TEST_REGION,
3492
- layoutMode: 'walkrows',
3493
- })
3494
- await model.load()
3495
- expect(model.walkRowBars).toBeDefined()
3496
- expect(model.figureSpecUnavailable).toMatch(/^bandage-figure draws no /)
3497
- })
3498
-
3499
3392
  test("a picker's set keeps lifted walks this cut does not hold", async () => {
3500
3393
  rpcRespond()
3501
3394
  const model = stateModelFactory().create({
@@ -5,7 +5,6 @@ import {
5
5
  facetGrid,
6
6
  } from '@jbrowse/bandage-core/facetGrid'
7
7
  import { figureSvg } from '@jbrowse/bandage-core/figure'
8
- import { figureSpecSettings } from '@jbrowse/bandage-core/figureSettings'
9
8
  import { rowLabelBox } from '@jbrowse/bandage-core/graphLabels'
10
9
  import {
11
10
  LEGEND_INSET_PX,
@@ -34,10 +33,6 @@ import {
34
33
  } from '@jbrowse/bandage-core/tubeMap/genes'
35
34
  import { padded } from '@jbrowse/bandage-core/viewport'
36
35
  import { walkPosition } from '@jbrowse/bandage-core/walkKey'
37
- import {
38
- expandTabixShorthand,
39
- readConfObject,
40
- } from '@jbrowse/core/configuration'
41
36
  import { untracked } from 'mobx'
42
37
 
43
38
  import { SECTION_HEADER_PX, sectionPlacement } from '../walkRowGroups'
@@ -52,7 +47,6 @@ import {
52
47
  VIEWPORT_PANES_BUILT,
53
48
  dependOn,
54
49
  paneViewportOf,
55
- uriOf,
56
50
  } from './paneBase'
57
51
  import { trackedDpr } from '../trackedDpr'
58
52
 
@@ -63,7 +57,6 @@ import type {
63
57
  } from '@jbrowse/bandage-core/layout/walkRowDraw'
64
58
  import type { Renderer } from '@jbrowse/bandage-core/renderer/types'
65
59
  import type { LiftedWalk, WalkLift } from '@jbrowse/bandage-core/walkHighlight'
66
- import type { FileLocation } from '@jbrowse/core/util/types'
67
60
 
68
61
  export const TOOLTIP_INSET_PX = 8
69
62
  export const FACET_KEY_ROW_PX = 19
@@ -557,96 +550,6 @@ export const withFitViews = withHostViews
557
550
  ? (walkPosition(walk, node.id, node.length) ?? 'not on this walk')
558
551
  : undefined
559
552
  },
560
- // The spec bandage-figure makes this drawing from with no browser, or
561
- // undefined for a graph it cannot read again: one cut from a gbz-base
562
- // track or read from a GFA url, with the genes of a GFF3 tabix track
563
- figureSpec() {
564
- const region = self.graphRegion
565
- const adapter = self.sourceAdapter
566
- const panSN = adapter?.assemblyNameToPanSN as
567
- Record<string, string> | undefined
568
- const window = region && `${region.refName}:${region.start}-${region.end}`
569
- const gfa = uriOf(self.sourceGfaLocation)
570
- const db =
571
- adapter?.type === 'GbzBaseSyntenyAdapter'
572
- ? uriOf(adapter.gbzDbLocation as FileLocation)
573
- : undefined
574
- const source = gfa
575
- ? { gfa, region: window }
576
- : db && region && window
577
- ? {
578
- gbz: {
579
- db,
580
- index: uriOf(adapter!.haplotypeIndexLocation as FileLocation),
581
- region: window,
582
- // the adapter cuts a lane by its PanSN prefix
583
- haplotypes: self.cutHaplotypes?.map(
584
- lane => panSN?.[lane] ?? lane,
585
- ),
586
- referenceSample:
587
- (adapter!.referenceSample as string | undefined) ||
588
- panSN?.[region.assemblyName] ||
589
- region.assemblyName,
590
- context: adapter!.context as number | undefined,
591
- snarls: self.cutsWholeWalks
592
- ? 'overlapping'
593
- : (adapter!.subgraphSnarls as string | undefined),
594
- },
595
- }
596
- : undefined
597
- if (!source) {
598
- return undefined
599
- }
600
- const geneTrack = self.showGenes ? self.geneTrack : undefined
601
- const geneConf = geneTrack
602
- ? self.sessionTrack(geneTrack.trackId)
603
- : undefined
604
- const geneAdapter = geneConf
605
- ? expandTabixShorthand(
606
- readConfObject(geneConf, 'adapter') as Record<string, unknown>,
607
- 'gffGzLocation',
608
- )
609
- : undefined
610
- const genes =
611
- geneAdapter?.type === 'Gff3TabixAdapter'
612
- ? {
613
- file: uriOf(geneAdapter.gffGzLocation as FileLocation),
614
- index: uriOf(
615
- (geneAdapter.index as { location?: FileLocation } | undefined)
616
- ?.location,
617
- ),
618
- format: 'gff3',
619
- }
620
- : undefined
621
- return JSON.parse(
622
- JSON.stringify({
623
- ...source,
624
- genes: genes?.file && genes.index ? genes : undefined,
625
- referencePath: self.referencePath || undefined,
626
- layout: self.chosenLayoutMode,
627
- ...figureSpecSettings({
628
- engine: self.layoutEngine,
629
- quality: self.layoutQuality,
630
- linearLayout: self.linearLayout,
631
- bubbleSpread: self.bubbleSpread,
632
- showDeletionEdges: self.showDeletionEdges,
633
- colorScheme: self.chosenColorScheme,
634
- nodeWidth: self.nodeWidth,
635
- contigThickness: self.contigThickness,
636
- connectorThickness: self.connectorThickness,
637
- colorDomain: self.statedColorDomain,
638
- }),
639
- walks: self.walkLayers.length
640
- ? self.walkLayers.map(l => (l.color ? l : l.walk))
641
- : undefined,
642
- facet: self.walkLayers.length > 1 ? self.facetSpec : undefined,
643
- walkStrip: self.walkStripShown || undefined,
644
- walkRowSamples: self.walkStripShown ? self.walkRowSamples : undefined,
645
- width: self.paneWidth,
646
- height: self.paneCeiling,
647
- }),
648
- ) as Record<string, unknown>
649
- },
650
553
  // Why the drawing cannot be written as SVG, which draws the canvas's
651
554
  // nodes, or walk rows' bars
652
555
  get figureUnavailable() {
@@ -658,15 +561,6 @@ export const withFitViews = withHostViews
658
561
  ? undefined
659
562
  : `${layoutModeByValue(self.chosenLayoutMode).label} draws a picture of its own, which the SVG export does not`
660
563
  },
661
- // Why bandage-figure can't make this drawing again: it draws only
662
- // layouts with nodes, from a graph it can read for itself
663
- get figureSpecUnavailable() {
664
- return !self.drawsNodes
665
- ? `bandage-figure draws no ${layoutModeByValue(self.chosenLayoutMode).label} layout`
666
- : this.figureSpec()
667
- ? undefined
668
- : 'bandage-figure reads a graph cut from a gbz-base track or a GFA url'
669
- },
670
564
  // The drawing as a standalone SVG, fitted, with its genes, its lifted
671
565
  // walks' keys, facet panels and the strip of walk rows; see figureSvg
672
566
  figure() {
@@ -695,9 +589,6 @@ export const withFitViews = withHostViews
695
589
  rowGenes: self.walkRowGenes,
696
590
  rowGeneGaps: self.walkRowGeneGaps,
697
591
  },
698
- // the SVG carries the spec only where the spec draws this same
699
- // picture: one of nodes (figureSpecUnavailable)
700
- spec: self.drawsNodes ? this.figureSpec() : undefined,
701
592
  })
702
593
  : undefined
703
594
  },
@@ -666,10 +666,6 @@ export const withGraphViews = paneBase
666
666
  get cutHaplotypes(): string[] | undefined {
667
667
  return undefined
668
668
  },
669
- // the GFA the graph on screen was read from, where a host says
670
- get sourceGfaLocation(): FileLocation | undefined {
671
- return undefined
672
- },
673
669
  get sourceAdapter() {
674
670
  const track = self.sourceTrack
675
671
  return track ? trackAdapterConfig(self, track) : undefined
@@ -5,7 +5,6 @@ import {
5
5
  layoutModeByValue,
6
6
  } from '@jbrowse/bandage-core/layoutModes'
7
7
  import { getSession } from '@jbrowse/core/util'
8
- import ContentCopyIcon from '@mui/icons-material/ContentCopy'
9
8
  import HubIcon from '@mui/icons-material/Hub'
10
9
  import PaletteIcon from '@mui/icons-material/Palette'
11
10
  import PhotoCameraIcon from '@mui/icons-material/PhotoCamera'
@@ -338,27 +337,6 @@ export const withLaunchActions = withRenderingActions
338
337
  }
339
338
  },
340
339
  },
341
- {
342
- label: 'Copy figure spec',
343
- icon: ContentCopyIcon,
344
- disabled: self.figureSpecUnavailable !== undefined,
345
- disabledHelpText: self.figureSpecUnavailable,
346
- onClick: () => {
347
- const spec = self.figureSpec()
348
- const session = getSession(self)
349
- navigator.clipboard
350
- .writeText(`${JSON.stringify(spec, null, 2)}\n`)
351
- .then(() => {
352
- session.notify(
353
- 'Figure spec copied. Save it as spec.json and run: npx -p @jbrowse/bandage-core bandage-figure spec.json -o figure.svg',
354
- 'info',
355
- )
356
- })
357
- .catch((e: unknown) => {
358
- session.notify(`Could not copy the figure spec: ${String(e)}`)
359
- })
360
- },
361
- },
362
340
  ]
363
341
  },
364
342
  // `picks` false where a toolbar already picks the walks, leaving what is
@@ -349,7 +349,7 @@ export const withLoadActions = withEngineSettings.actions(self => {
349
349
  : parsed
350
350
  const folded =
351
351
  foldBelowBp > 0 && !readsOf
352
- ? foldVariants(anchored, foldBelowBp)
352
+ ? foldVariants(anchored, foldBelowBp, { cut: true })
353
353
  : anchored
354
354
  const graph =
355
355
  region && folded !== anchored
@@ -177,13 +177,6 @@ export function fileName(location: FileLocation) {
177
177
  return path.split(/[\\/]/).at(-1) ?? ''
178
178
  }
179
179
 
180
- // A url bandage-figure can read again, or undefined for a local file
181
- export function uriOf(location: FileLocation | undefined) {
182
- return location && 'uri' in location && location.uri
183
- ? new URL(location.uri, location.baseUri ?? window.location.href).href
184
- : undefined
185
- }
186
-
187
180
  type ViewportOwner = 'fit' | 'user' | 'host'
188
181
 
189
182
  interface BuiltViewport {
@@ -93,9 +93,6 @@ export default function stateModelFactory() {
93
93
  get paneMeasured() {
94
94
  return self.widthMeasured
95
95
  },
96
- get sourceGfaLocation() {
97
- return self.gfaLocation
98
- },
99
96
  get sourceTrack(): AnyConfigurationModel | undefined {
100
97
  return self.loadedTrackId
101
98
  ? self.sessionTrack(self.loadedTrackId)
@@ -30,8 +30,8 @@ import type { Instance } from '@jbrowse/mobx-state-tree'
30
30
  * `walksUri` takes the prefix of a walk file in place of `uri`, and resolves
31
31
  * `<walksUri>.walks.bed.gz` and its `.tbi`. `gfa-to-tabix --walks -o <prefix>`
32
32
  * writes one per reference sample, so a track names one as
33
- * `<prefix>.<sample>`. The adapter reads walk format 4, which gfa-to-tabix
34
- * writes from 1.2, through @jbrowse/graph-walks. Such a graph's cut carries
33
+ * `<prefix>.<sample>`. The adapter reads walk format 5, which gfa-to-tabix
34
+ * writes from 1.4, through @jbrowse/graph-walks. Such a graph's cut carries
35
35
  * the haplotypes' walks, and `defaultHaplotypes` names the ones it is cut for
36
36
  * until the user picks others.
37
37
  *
@@ -152,7 +152,7 @@ const RgfaTabixAdapter = ConfigurationSchema(
152
152
  ),
153
153
  /**
154
154
  * #slot
155
- * a `gfa-to-tabix --walks` file in walk format 4: per reference chunk,
155
+ * a `gfa-to-tabix --walks` file in walk format 5: per reference chunk,
156
156
  * the reference's path, the ways haplotypes leave it and each haplotype's
157
157
  * piece. Unset means the graph has no walks.
158
158
  */
@@ -35,27 +35,27 @@ const fixture = (name: string) =>
35
35
  // W HG002 1 chr1 >1>2>7>4>5>6 7 replaces 3
36
36
  // W HG002 2 chr1 >1>2>3>4>8>6 8 replaces 5
37
37
  // W HG00097 1 chr1 >1>2>3>4>5>6
38
- const headerPrefix = fixture('walks4_header')
38
+ const headerPrefix = fixture('walks5_header')
39
39
 
40
- // walks4_header's walks with the reference named as pggb names a P-line,
40
+ // walks5_header's walks with the reference named as pggb names a P-line,
41
41
  // GRCh38#chr1
42
- const twoPartPrefix = fixture('walks4_twopart')
42
+ const twoPartPrefix = fixture('walks5_twopart')
43
43
 
44
44
  // An unchopped graph, node 1 spanning 0-2600 and filed under chunk 0 alone:
45
45
  // W GRCh38 0 chr1 >1>2>4
46
46
  // W HG002 1 chr1 >1>3>4 3 replaces 2
47
- const maxNodePrefix = fixture('walks4_maxnode')
47
+ const maxNodePrefix = fixture('walks5_maxnode')
48
48
 
49
49
  // Twelve 500 bp reference nodes over chr1:0-6000 in one 10 kb chunk:
50
50
  // W GRCh38 0 chr1 >1>2>3>4>5>6>7>8>9>10>11>12
51
51
  // W HG002 1 chr1 >1>2>3>20>21>9>10>11>12 20 and 21, 2 kb each, replace 4-8
52
52
  // W HG002 2 chr1 >1>2>3>4>5>6>7>8>9>10>11>12
53
53
  // W HG003 1 chr1 >1>2>3>22 the contig ends on 22, 300 bp
54
- const rejoinPrefix = fixture('walks4_rejoin')
54
+ const rejoinPrefix = fixture('walks5_rejoin')
55
55
 
56
56
  // The same graph in 1 kb chunks, whose rows name the chunks their path goes on
57
57
  // in: HG002#1 leaves chunk 1000 for 20 and 21 and rejoins at 9, in chunk 4000
58
- const followPrefix = fixture('walks4_follow')
58
+ const followPrefix = fixture('walks5_follow')
59
59
 
60
60
  function makeAdapter(prefix: string, slots = {}) {
61
61
  const local = (path: string) => ({
@@ -0,0 +1,204 @@
1
+ import PluginManager from '@jbrowse/core/PluginManager'
2
+ import { ExpansionTooLargeError, formsCut } from '@jbrowse/graph-walks'
3
+ import { firstValueFrom } from 'rxjs'
4
+ import { toArray } from 'rxjs/operators'
5
+
6
+ import Adapter from './WalkFormsAdapter.ts'
7
+ import configSchema from './configSchema.ts'
8
+ import WalkFormsAdapterF from './index.ts'
9
+
10
+ import type * as GraphWalks from '@jbrowse/graph-walks'
11
+
12
+ vi.mock('@jbrowse/graph-walks', async importOriginal => {
13
+ const actual = await importOriginal<typeof GraphWalks>()
14
+ return { ...actual, formsCut: vi.fn(actual.formsCut) }
15
+ })
16
+
17
+ // Four walks over a 3.6 kb reference of six 600 bp nodes, in 1 kb chunks
18
+ // (RgfaTabixAdapter/walkFiles.test.ts builds on the same file):
19
+ // W GRCh38 0 chr1 >1>2>3>4>5>6
20
+ // W HG002 1 chr1 >1>2>7>4>5>6 7 (550 bp) replaces 3
21
+ // W HG002 2 chr1 >1>2>3>4>8>6 8 (20 bp) replaces 5
22
+ // W HG00097 1 chr1 >1>2>3>4>5>6
23
+ const walks =
24
+ require.resolve('../RgfaTabixAdapter/test_data/walks5_header.GRCh38.walks.bed.gz')
25
+
26
+ const local = (path: string) => ({
27
+ localPath: path,
28
+ locationType: 'LocalPathLocation',
29
+ })
30
+
31
+ // Each test pins its own window, since the cut cache outlives an adapter
32
+ function makeAdapter(slots: Record<string, unknown>) {
33
+ return new Adapter(
34
+ configSchema.create({
35
+ walksLocation: local(walks),
36
+ walksIndex: { location: local(`${walks}.tbi`) },
37
+ ...slots,
38
+ }),
39
+ )
40
+ }
41
+
42
+ const pinned = (start: number) => ({ refName: 'chr1', start, end: 3600 })
43
+
44
+ const region = (start: number, end: number, refName = 'chr1') => ({
45
+ refName,
46
+ assemblyName: 'hg38',
47
+ start,
48
+ end,
49
+ })
50
+
51
+ const featuresOf = (adapter: Adapter, query = region(0, 3600)) =>
52
+ firstValueFrom(adapter.getFeatures(query).pipe(toArray()))
53
+
54
+ const placeholders = (features: Awaited<ReturnType<typeof featuresOf>>) =>
55
+ features
56
+ .filter(f => f.id().startsWith('forms-reference-'))
57
+ .map(f => ({
58
+ start: f.get('start'),
59
+ end: f.get('end'),
60
+ genotypes: f.get('genotypes'),
61
+ }))
62
+
63
+ test('registers the adapter type', () => {
64
+ const pm = new PluginManager()
65
+ WalkFormsAdapterF(pm)
66
+ pm.createPluggableElements()
67
+ expect(pm.getAdapterType('WalkFormsAdapter').name).toBe('WalkFormsAdapter')
68
+ })
69
+
70
+ test('names the reference sample contigs as the assembly does', async () => {
71
+ expect(await makeAdapter({ pinned: pinned(0) }).getRefNames()).toEqual([
72
+ 'chr1',
73
+ ])
74
+ })
75
+
76
+ test('one source per form, labelled by share and change', async () => {
77
+ const adapter = makeAdapter({ pinned: pinned(1) })
78
+ expect(await adapter.getSourcesAndWarnings()).toEqual({
79
+ sources: [
80
+ { name: 'F1', label: '33% · ref', haplotypes: 1, forms: 1 },
81
+ {
82
+ name: 'F2',
83
+ label: '33% · repl 600bp->550bp',
84
+ haplotypes: 1,
85
+ forms: 1,
86
+ },
87
+ { name: 'F3', label: '33% · del 580bp', haplotypes: 1, forms: 1 },
88
+ ],
89
+ warnings: [],
90
+ })
91
+ })
92
+
93
+ test('features are the pinned window changes, with a genotype per form', async () => {
94
+ const features = await featuresOf(makeAdapter({ pinned: pinned(2) }))
95
+ expect(
96
+ features.map(f => ({
97
+ start: f.get('start'),
98
+ end: f.get('end'),
99
+ consequence: f.get('consequence'),
100
+ svType: f.get('svType'),
101
+ type: f.get('type'),
102
+ genotypes: f.get('genotypes'),
103
+ })),
104
+ ).toEqual([
105
+ {
106
+ start: 1200,
107
+ end: 1800,
108
+ consequence: 'loss',
109
+ svType: 'CPX',
110
+ type: 'complex_substitution',
111
+ genotypes: { F1: '0', F2: '1', F3: '0' },
112
+ },
113
+ {
114
+ start: 2400,
115
+ end: 3000,
116
+ consequence: 'loss',
117
+ svType: 'DEL',
118
+ type: 'deletion',
119
+ genotypes: { F1: '0', F2: '0', F3: '1' },
120
+ },
121
+ ])
122
+ })
123
+
124
+ test('a query with no change gives one reference placeholder', async () => {
125
+ const adapter = makeAdapter({ pinned: pinned(3) })
126
+ expect(placeholders(await featuresOf(adapter, region(1900, 2300)))).toEqual([
127
+ { start: 1900, end: 1901, genotypes: { F1: '0', F2: '0', F3: '0' } },
128
+ ])
129
+ expect(
130
+ placeholders(await featuresOf(adapter, region(0, 3600, 'chr2'))),
131
+ ).toHaveLength(1)
132
+ expect(
133
+ placeholders(await featuresOf(adapter, region(2900, 3600))),
134
+ ).toHaveLength(0)
135
+ })
136
+
137
+ test('a floor change regroups the cut without cutting again', async () => {
138
+ vi.mocked(formsCut).mockClear()
139
+ const fine = makeAdapter({ pinned: pinned(4), floorBp: 50 })
140
+ const coarse = makeAdapter({ pinned: pinned(4), floorBp: 1000 })
141
+ expect((await fine.getSourcesAndWarnings()).sources).toHaveLength(3)
142
+ expect((await coarse.getSourcesAndWarnings()).sources).toEqual([
143
+ { name: 'F1', label: '100% · ref', haplotypes: 3, forms: 1 },
144
+ ])
145
+ expect(placeholders(await featuresOf(coarse))).toEqual([
146
+ { start: 0, end: 1, genotypes: { F1: '0' } },
147
+ ])
148
+ expect(formsCut).toHaveBeenCalledTimes(1)
149
+ })
150
+
151
+ test('exclude leaves samples out of every form', async () => {
152
+ const adapter = makeAdapter({ pinned: pinned(5), exclude: ['HG002'] })
153
+ expect((await adapter.getSourcesAndWarnings()).sources).toEqual([
154
+ { name: 'F1', label: '100% · ref', haplotypes: 1, forms: 1 },
155
+ ])
156
+ })
157
+
158
+ test('maxForms gives the smaller forms one empty row', async () => {
159
+ const adapter = makeAdapter({ pinned: pinned(6), maxForms: 1 })
160
+ const { sources, warnings } = await adapter.getSourcesAndWarnings()
161
+ expect(sources.map(s => s.label)).toEqual(['33% · ref', 'other forms · 67%'])
162
+ expect(warnings).toEqual([
163
+ expect.stringMatching(/^Forms of chr1:7-3,600: 2 smaller forms/),
164
+ ])
165
+ expect(placeholders(await featuresOf(adapter))).toEqual([
166
+ { start: 0, end: 1, genotypes: { F1: '0' } },
167
+ ])
168
+ })
169
+
170
+ test('a pinned window past walkByteBudget says so, without a zoom-in notice', async () => {
171
+ const error = await makeAdapter({ pinned: pinned(7), walkByteBudget: 1 })
172
+ .getSourcesAndWarnings()
173
+ .catch((e: unknown) => e)
174
+ expect(error).toBeInstanceOf(Error)
175
+ expect(error).not.toHaveProperty('regionTooLarge')
176
+ expect(`${error}`).toMatch(
177
+ /^Error: The pinned window chr1:8-3,600 is too large to cut for structural forms: .*walkByteBudget, 0\.0 MB\. .*Raise walkByteBudget$/,
178
+ )
179
+ })
180
+
181
+ test('a window whose walks are too many to read in full says so', async () => {
182
+ vi.mocked(formsCut).mockClear()
183
+ vi.mocked(formsCut).mockRejectedValueOnce(
184
+ new ExpansionTooLargeError(24_000_000, 16_000_000, 3592),
185
+ )
186
+ const error = await makeAdapter({ pinned: pinned(9) })
187
+ .getSourcesAndWarnings()
188
+ .catch((e: unknown) => e)
189
+ expect(formsCut).toHaveBeenCalledWith(
190
+ expect.anything(),
191
+ expect.anything(),
192
+ expect.objectContaining({ partial: false }),
193
+ )
194
+ expect(error).not.toHaveProperty('regionTooLarge')
195
+ expect(`${error}`).toBe(
196
+ 'Error: The pinned window chr1:10-3,600 is too large to read every walk in full for structural forms: its walks take 24.0 M steps, over the 16.0 M step budget, and about 2.39Kbp of it fits. Pin a narrower window',
197
+ )
198
+ })
199
+
200
+ test('an adapter with no pinned window says so', async () => {
201
+ await expect(makeAdapter({}).getSourcesAndWarnings()).rejects.toThrow(
202
+ /needs `pinned`/,
203
+ )
204
+ })