jbrowse-plugin-graphgenomeviewer 7.4.4 → 7.5.0
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/dist/chunks/{GbzBaseSyntenyAdapter-A5MNPCKX.js → GbzBaseSyntenyAdapter-4KVWVUN6.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-A5MNPCKX.js.map → GbzBaseSyntenyAdapter-4KVWVUN6.js.map} +1 -1
- package/dist/chunks/{GraphGenomeView-SEAPDRXS.js → GraphGenomeView-KI4FQ5A7.js} +2 -2
- package/dist/chunks/GraphTrackSettingsDialog-UQNMDXTE.js +4 -0
- package/dist/chunks/GraphTrackSettingsDialog-UQNMDXTE.js.map +7 -0
- package/dist/chunks/{HighlightColorDialog-QAYWLO5I.js → HighlightColorDialog-D4RCUCUF.js} +2 -2
- package/dist/chunks/LinearGraphDisplay-RPZUZSM2.js +2 -0
- package/dist/chunks/{LinearGraphDisplay-ZC4BQ27J.js.map → LinearGraphDisplay-RPZUZSM2.js.map} +2 -2
- package/dist/chunks/{MinigraphBubbleAdapter-3QM4XV47.js → MinigraphBubbleAdapter-TJYI7KEN.js} +2 -2
- package/dist/chunks/RgfaTabixAdapter-5ZMJLFZP.js +2 -0
- package/dist/chunks/RgfaTabixAdapter-5ZMJLFZP.js.map +7 -0
- package/dist/chunks/{WalkFormsAdapter-FT2SW4H6.js → WalkFormsAdapter-CAJOWOEX.js} +2 -2
- package/dist/chunks/WalkTabixSyntenyAdapter-VI3KA2GX.js +2 -0
- package/dist/chunks/{WalkTabixSyntenyAdapter-3WRVNDLB.js.map → WalkTabixSyntenyAdapter-VI3KA2GX.js.map} +2 -2
- package/dist/chunks/{chunk-6WUWQX2P.js → chunk-22ANI2KL.js} +2 -2
- package/dist/chunks/{chunk-6WUWQX2P.js.map → chunk-22ANI2KL.js.map} +1 -1
- package/dist/chunks/chunk-42KD4AVY.js +2 -0
- package/dist/chunks/{chunk-HCDKP3IP.js.map → chunk-42KD4AVY.js.map} +4 -4
- package/dist/chunks/chunk-4NIWMRKH.js +2 -0
- package/dist/chunks/{chunk-S333HOG5.js.map → chunk-4NIWMRKH.js.map} +4 -4
- package/dist/chunks/chunk-BCOXRUNQ.js +3 -0
- package/dist/chunks/chunk-BCOXRUNQ.js.map +7 -0
- package/dist/chunks/chunk-DAPI7M4M.js +4 -0
- package/dist/chunks/chunk-DAPI7M4M.js.map +7 -0
- package/dist/chunks/{chunk-G53BI7VK.js → chunk-DEWZOJSC.js} +2 -2
- package/dist/chunks/{chunk-G53BI7VK.js.map → chunk-DEWZOJSC.js.map} +1 -1
- package/dist/chunks/{chunk-6SWXMRAV.js → chunk-FCMXEFJA.js} +1 -1
- package/dist/chunks/{chunk-6SWXMRAV.js.map → chunk-FCMXEFJA.js.map} +1 -1
- package/dist/chunks/{chunk-P3FBI5ML.js → chunk-GYY6LF5L.js} +1 -1
- package/dist/chunks/{chunk-P3FBI5ML.js.map → chunk-GYY6LF5L.js.map} +1 -1
- package/dist/chunks/{chunk-HF4H6LQI.js → chunk-HHMJNAA3.js} +2 -2
- package/dist/chunks/{chunk-XXBE4IFZ.js → chunk-HTTONSO4.js} +2 -2
- package/dist/chunks/{chunk-XXBE4IFZ.js.map → chunk-HTTONSO4.js.map} +1 -1
- package/dist/chunks/{chunk-MUONTMYZ.js → chunk-K4JL57NR.js} +2 -2
- package/dist/chunks/chunk-KOC23YNN.js +4 -0
- package/dist/chunks/chunk-KOC23YNN.js.map +7 -0
- package/dist/chunks/{chunk-OJWBLPPH.js → chunk-LTNFVL2F.js} +2 -2
- package/dist/chunks/chunk-OZEKEYT3.js +2 -0
- package/dist/chunks/{chunk-6VNU7SD4.js.map → chunk-OZEKEYT3.js.map} +4 -4
- package/dist/chunks/{chunk-KIYRMZWP.js → chunk-POUV7OPY.js} +2 -2
- package/dist/chunks/{chunk-KIYRMZWP.js.map → chunk-POUV7OPY.js.map} +1 -1
- package/dist/chunks/{chunk-LE7FZM6Y.js → chunk-SPL3UO2D.js} +2 -2
- package/dist/chunks/{chunk-LE7FZM6Y.js.map → chunk-SPL3UO2D.js.map} +1 -1
- package/dist/chunks/chunk-UR2MJJUP.js +2 -0
- package/dist/chunks/chunk-VTMJQFN5.js +2 -0
- package/dist/chunks/chunk-VTMJQFN5.js.map +7 -0
- package/dist/chunks/{chunk-2KVE7YAS.js → chunk-XZGYJ3JC.js} +1 -1
- package/dist/chunks/{chunk-2KVE7YAS.js.map → chunk-XZGYJ3JC.js.map} +1 -1
- package/dist/chunks/{chunk-7RJMG6I2.js → chunk-Z3IKZAYC.js} +1 -1
- package/dist/chunks/{chunk-7RJMG6I2.js.map → chunk-Z3IKZAYC.js.map} +1 -1
- package/dist/chunks/{chunk-FTXDTU27.js → chunk-ZV5FNOHS.js} +3 -3
- package/dist/chunks/{chunk-FTXDTU27.js.map → chunk-ZV5FNOHS.js.map} +2 -2
- package/dist/chunks/{renderSvg-ZIUOPWJQ.js → renderSvg-3PPAEMBT.js} +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -3
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +2 -2
- package/src/GraphComputeLayout.test.ts +24 -0
- package/src/GraphComputeLayout.ts +4 -2
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +16 -0
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +83 -0
- package/src/GraphGenomeView/components/usePaneGestures.test.tsx +82 -5
- package/src/GraphGenomeView/components/usePaneGestures.ts +10 -4
- package/src/GraphGenomeView/graphLayers.ts +14 -1
- package/src/GraphGenomeView/legendSections.ts +32 -0
- package/src/GraphGenomeView/model.test.ts +292 -4
- package/src/GraphGenomeView/model.ts +3 -10
- package/src/GraphGenomeView/nodeSize.test.ts +21 -5
- package/src/GraphGenomeView/nodeSize.ts +24 -8
- package/src/GraphGenomeView/pane/fitViews.ts +5 -1
- package/src/GraphGenomeView/pane/graphViews.ts +96 -16
- package/src/GraphGenomeView/pane/launchActions.ts +55 -11
- package/src/GraphGenomeView/pane/launchViews.ts +46 -0
- package/src/GraphGenomeView/pane/loadActions.ts +85 -25
- package/src/GraphGenomeView/pane/morph.test.ts +25 -1
- package/src/GraphGenomeView/pane/morph.ts +3 -1
- package/src/GraphGenomeView/pane/paneBase.ts +5 -0
- package/src/GraphGenomeView/pane/renderingActions.ts +61 -2
- package/src/GraphGenomeView/pane/settingActions.ts +62 -4
- package/src/GraphGenomeView/pane/walkRowViews.ts +17 -6
- package/src/GraphGenomeView/tubeMapPanels.ts +2 -1
- package/src/GraphGenomeView/viewModel.ts +4 -6
- package/src/GraphGenomeView/walkRowBlocks.ts +50 -0
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +35 -5
- package/src/LinearGraphDisplay/components/GraphStatusChrome.test.tsx +18 -0
- package/src/LinearGraphDisplay/components/GraphStatusChrome.tsx +4 -2
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.test.tsx +41 -1
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.tsx +33 -19
- package/src/LinearGraphDisplay/configSchema.ts +4 -3
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +2 -9
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +3 -6
- package/src/hoverSync/graphViewHighlights.test.ts +53 -0
- package/src/hoverSync/graphViewHighlights.ts +25 -0
- package/src/hoverSync/lgvHover.test.ts +100 -1
- package/src/hoverSync/lgvHover.ts +77 -6
- package/src/launchFromGraph/contributors.test.ts +52 -0
- package/src/launchFromGraph/contributors.ts +105 -1
- package/src/launchFromGraph/launchFromGraph.ts +2 -2
- package/src/panSNTabix.test.ts +28 -0
- package/src/panSNTabix.ts +26 -8
- package/src/version.ts +1 -1
- package/dist/chunks/GraphTrackSettingsDialog-EVIIGC5G.js +0 -6
- package/dist/chunks/GraphTrackSettingsDialog-EVIIGC5G.js.map +0 -7
- package/dist/chunks/LinearGraphDisplay-ZC4BQ27J.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-XSOIEWBK.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-XSOIEWBK.js.map +0 -7
- package/dist/chunks/WalkTabixSyntenyAdapter-3WRVNDLB.js +0 -2
- package/dist/chunks/chunk-6VNU7SD4.js +0 -2
- package/dist/chunks/chunk-GRTN25B6.js +0 -3
- package/dist/chunks/chunk-GRTN25B6.js.map +0 -7
- package/dist/chunks/chunk-HCDKP3IP.js +0 -2
- package/dist/chunks/chunk-LE43I4PT.js +0 -4
- package/dist/chunks/chunk-LE43I4PT.js.map +0 -7
- package/dist/chunks/chunk-QV7SSOMJ.js +0 -2
- package/dist/chunks/chunk-S333HOG5.js +0 -2
- package/dist/chunks/chunk-VZQ6VOUP.js +0 -2
- package/dist/chunks/chunk-VZQ6VOUP.js.map +0 -7
- package/dist/chunks/chunk-WLRRI35A.js +0 -3
- package/dist/chunks/chunk-WLRRI35A.js.map +0 -7
- package/src/launchFromGraph/launchTracks.test.ts +0 -95
- package/src/launchFromGraph/launchTracks.ts +0 -49
- /package/dist/chunks/{GraphGenomeView-SEAPDRXS.js.map → GraphGenomeView-KI4FQ5A7.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-QAYWLO5I.js.map → HighlightColorDialog-D4RCUCUF.js.map} +0 -0
- /package/dist/chunks/{MinigraphBubbleAdapter-3QM4XV47.js.map → MinigraphBubbleAdapter-TJYI7KEN.js.map} +0 -0
- /package/dist/chunks/{WalkFormsAdapter-FT2SW4H6.js.map → WalkFormsAdapter-CAJOWOEX.js.map} +0 -0
- /package/dist/chunks/{chunk-HF4H6LQI.js.map → chunk-HHMJNAA3.js.map} +0 -0
- /package/dist/chunks/{chunk-MUONTMYZ.js.map → chunk-K4JL57NR.js.map} +0 -0
- /package/dist/chunks/{chunk-OJWBLPPH.js.map → chunk-LTNFVL2F.js.map} +0 -0
- /package/dist/chunks/{chunk-QV7SSOMJ.js.map → chunk-UR2MJJUP.js.map} +0 -0
- /package/dist/chunks/{renderSvg-ZIUOPWJQ.js.map → renderSvg-3PPAEMBT.js.map} +0 -0
|
@@ -156,6 +156,7 @@ vi.mock('@jbrowse/core/util/io', () => ({
|
|
|
156
156
|
}))
|
|
157
157
|
|
|
158
158
|
const SIMPLE_GFA = 'H\tVN:Z:1.0\nS\t1\tACGT\nS\t2\tGGCC\nL\t1\t+\t2\t+\t0M\n'
|
|
159
|
+
const WALKED_GFA = `${SIMPLE_GFA}W\tGRCh38\t0\tchr1\t0\t8\t>1>2\nW\tA\t1\tctg\t0\t8\t>1>2\n`
|
|
159
160
|
|
|
160
161
|
const MOCK_LAYOUT = {
|
|
161
162
|
nodePositions: {
|
|
@@ -218,7 +219,9 @@ function createAnchoredModel(props: { layers?: GraphLayers } = {}) {
|
|
|
218
219
|
|
|
219
220
|
// A view attached to a tree, so afterAttach installs its reactions. A root
|
|
220
221
|
// model never attaches.
|
|
221
|
-
function attachedModel(
|
|
222
|
+
function attachedModel(
|
|
223
|
+
props: { layoutMode?: LayoutModeValue; [key: string]: unknown } = {},
|
|
224
|
+
) {
|
|
222
225
|
return types
|
|
223
226
|
.model({ view: stateModelFactory() })
|
|
224
227
|
.create({ view: { type: 'GraphGenomeView', ...props } }).view
|
|
@@ -366,6 +369,30 @@ describe('restoring a stated cut', () => {
|
|
|
366
369
|
)
|
|
367
370
|
})
|
|
368
371
|
|
|
372
|
+
// Only a GBZ cut follows snarls past the window; a walk file cuts the same
|
|
373
|
+
// graph either way, so the strip's toggle has nothing to fetch.
|
|
374
|
+
test.each([
|
|
375
|
+
['RgfaTabixAdapter', 0],
|
|
376
|
+
['GbzBaseSyntenyAdapter', 1],
|
|
377
|
+
])('the walk strip on a %s track cuts again %i times', async (type, n) => {
|
|
378
|
+
rpcRespond()
|
|
379
|
+
mockSession.tracks = [{ trackId: 'rgfa-track', adapter: { type } }]
|
|
380
|
+
const model = attachedModel({
|
|
381
|
+
layoutMode: 'force',
|
|
382
|
+
loadedTrackId: 'rgfa-track',
|
|
383
|
+
loadedRegion: TEST_REGION,
|
|
384
|
+
})
|
|
385
|
+
await vi.waitFor(() => {
|
|
386
|
+
expect(model.graph).toBeDefined()
|
|
387
|
+
})
|
|
388
|
+
const cuts = () =>
|
|
389
|
+
mockRpcCall.mock.calls.filter(c => c[1] === 'GetSubgraph').length
|
|
390
|
+
const before = cuts()
|
|
391
|
+
model.setWalkStrip(!model.walkStrip)
|
|
392
|
+
await layoutsSettled(model)
|
|
393
|
+
expect(cuts()).toBe(before + n)
|
|
394
|
+
})
|
|
395
|
+
|
|
369
396
|
test('a stated track the session lacks is reported without a fetch', async () => {
|
|
370
397
|
mockSession.tracks = []
|
|
371
398
|
const model = restoredView()
|
|
@@ -1136,7 +1163,7 @@ describe('layoutMode', () => {
|
|
|
1136
1163
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
1137
1164
|
rpcRespond()
|
|
1138
1165
|
const model = attachedModel()
|
|
1139
|
-
await model.loadGFA(
|
|
1166
|
+
await model.loadGFA(WALKED_GFA, 'plain')
|
|
1140
1167
|
|
|
1141
1168
|
model.setMaxGraphNodes(1)
|
|
1142
1169
|
await layoutsSettled(model)
|
|
@@ -1434,7 +1461,7 @@ describe('node budget', () => {
|
|
|
1434
1461
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
1435
1462
|
rpcRespond()
|
|
1436
1463
|
const model = withLimit(1)
|
|
1437
|
-
await model.loadGFA(
|
|
1464
|
+
await model.loadGFA(WALKED_GFA, 'two nodes')
|
|
1438
1465
|
expect(String(model.error)).toMatch(/too large to draw/i)
|
|
1439
1466
|
|
|
1440
1467
|
await model.switchLayout('walkrows')
|
|
@@ -1443,6 +1470,21 @@ describe('node budget', () => {
|
|
|
1443
1470
|
consoleSpy.mockRestore()
|
|
1444
1471
|
})
|
|
1445
1472
|
|
|
1473
|
+
// walk rows hand a graph with no walks to the force engine, which draws
|
|
1474
|
+
// every node
|
|
1475
|
+
test('walk rows of a graph with no walks keep the budget', async () => {
|
|
1476
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
1477
|
+
rpcRespond()
|
|
1478
|
+
const model = withLimit(1)
|
|
1479
|
+
model.setLayoutMode('walkrows')
|
|
1480
|
+
await model.loadGFA(SIMPLE_GFA, 'two nodes')
|
|
1481
|
+
expect(String(model.error)).toMatch(/too large to draw/i)
|
|
1482
|
+
expect(
|
|
1483
|
+
mockRpcCall.mock.calls.filter(c => c[1] === 'GraphComputeLayout'),
|
|
1484
|
+
).toHaveLength(0)
|
|
1485
|
+
consoleSpy.mockRestore()
|
|
1486
|
+
})
|
|
1487
|
+
|
|
1446
1488
|
test('a declined graph does not keep the last graph drawn under it', async () => {
|
|
1447
1489
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
1448
1490
|
rpcRespond()
|
|
@@ -1466,7 +1508,7 @@ describe('node budget', () => {
|
|
|
1466
1508
|
rpcRespond()
|
|
1467
1509
|
const model = withLimit(1)
|
|
1468
1510
|
model.setLayoutMode('walkrows')
|
|
1469
|
-
await model.loadGFA(
|
|
1511
|
+
await model.loadGFA(WALKED_GFA, 'two nodes')
|
|
1470
1512
|
expect(model.error).toBeUndefined()
|
|
1471
1513
|
expect(model.layoutResult).toBeDefined()
|
|
1472
1514
|
})
|
|
@@ -2279,6 +2321,61 @@ describe('launching out of the graph', () => {
|
|
|
2279
2321
|
// Where every contributor is a loaded assembly the graph offers one panel per
|
|
2280
2322
|
// strain, and a synteny view of all of them — the item is disabled while no
|
|
2281
2323
|
// synteny track aligns them, rather than absent.
|
|
2324
|
+
// Sakai's allele is drawn bare unless Sakai's own annotation is read onto
|
|
2325
|
+
// it: the reference's genes only reach the backbone
|
|
2326
|
+
test("a loaded strain's own genes are read onto its nodes off the reference", async () => {
|
|
2327
|
+
mockSession.assemblyNames = ['K12', 'Sakai']
|
|
2328
|
+
mockSession.tracks = [
|
|
2329
|
+
{
|
|
2330
|
+
trackId: 'Sakai_genes',
|
|
2331
|
+
name: 'Sakai genes',
|
|
2332
|
+
assemblyNames: ['Sakai'],
|
|
2333
|
+
adapter: { type: 'Gff3TabixAdapter' },
|
|
2334
|
+
},
|
|
2335
|
+
]
|
|
2336
|
+
const model = await loadedGraph(ECOLI_RGFA, {
|
|
2337
|
+
refName: 'chr',
|
|
2338
|
+
assemblyName: 'K12',
|
|
2339
|
+
start: 1000,
|
|
2340
|
+
end: 1020,
|
|
2341
|
+
})
|
|
2342
|
+
expect(model.ownGeneReads).toEqual([
|
|
2343
|
+
{
|
|
2344
|
+
stableRefName: 'Sakai#1#chr',
|
|
2345
|
+
trackId: 'Sakai_genes',
|
|
2346
|
+
region: {
|
|
2347
|
+
assemblyName: 'Sakai',
|
|
2348
|
+
refName: 'chr',
|
|
2349
|
+
start: 90000,
|
|
2350
|
+
end: 90004,
|
|
2351
|
+
},
|
|
2352
|
+
},
|
|
2353
|
+
])
|
|
2354
|
+
mockRpcCall.mockImplementation((_sid: unknown, method: string) =>
|
|
2355
|
+
method === 'CoreGetFeatures'
|
|
2356
|
+
? Promise.resolve([
|
|
2357
|
+
{
|
|
2358
|
+
name: 'stx2A',
|
|
2359
|
+
refName: 'chr',
|
|
2360
|
+
start: 90001,
|
|
2361
|
+
end: 90003,
|
|
2362
|
+
strand: 1,
|
|
2363
|
+
subfeatures: [{ type: 'exon', start: 90001, end: 90003 }],
|
|
2364
|
+
},
|
|
2365
|
+
])
|
|
2366
|
+
: Promise.reject(new Error(`Unexpected RPC: ${method}`)),
|
|
2367
|
+
)
|
|
2368
|
+
await model.loadOwnGenes()
|
|
2369
|
+
expect(model.ownGeneFeatures).toEqual([
|
|
2370
|
+
expect.objectContaining({
|
|
2371
|
+
name: 'stx2A · Sakai#1',
|
|
2372
|
+
refName: 'Sakai#1#chr',
|
|
2373
|
+
start: 90001,
|
|
2374
|
+
end: 90003,
|
|
2375
|
+
}),
|
|
2376
|
+
])
|
|
2377
|
+
})
|
|
2378
|
+
|
|
2282
2379
|
test('loaded strains offer a per-strain linear view and a synteny item', async () => {
|
|
2283
2380
|
mockSession.assemblyNames = ['K12', 'Sakai']
|
|
2284
2381
|
const model = await loadedGraph(ECOLI_RGFA, {
|
|
@@ -2345,6 +2442,68 @@ describe('launching out of the graph', () => {
|
|
|
2345
2442
|
expect(model.connectedViewId).toBe('view-1')
|
|
2346
2443
|
})
|
|
2347
2444
|
|
|
2445
|
+
test('an opened view carries one gene track, not every feature track', async () => {
|
|
2446
|
+
mockSession.assemblyNames = ['hg38']
|
|
2447
|
+
const feature = (trackId: string, name: string, type: string) => ({
|
|
2448
|
+
trackId,
|
|
2449
|
+
name,
|
|
2450
|
+
type: 'FeatureTrack',
|
|
2451
|
+
assemblyNames: ['hg38'],
|
|
2452
|
+
adapter: { type },
|
|
2453
|
+
})
|
|
2454
|
+
mockSession.tracks = [
|
|
2455
|
+
feature('rgfa-track', 'graph', 'RgfaTabixAdapter'),
|
|
2456
|
+
feature('clinvar', 'ClinVar', 'BigBedAdapter'),
|
|
2457
|
+
feature('ncbi', 'NCBI RefSeq genes', 'Gff3TabixAdapter'),
|
|
2458
|
+
feature('gwas', 'GWAS catalog', 'BedTabixAdapter'),
|
|
2459
|
+
]
|
|
2460
|
+
const model = await loadedGraph(HPRC_RGFA)
|
|
2461
|
+
const { reference } = model.nodeLaunchTargets('1+')
|
|
2462
|
+
model.showInLinearView(reference!)
|
|
2463
|
+
|
|
2464
|
+
expect(mockSession.addedViews).toEqual([
|
|
2465
|
+
[
|
|
2466
|
+
'LinearGenomeView',
|
|
2467
|
+
expect.objectContaining({
|
|
2468
|
+
init: expect.objectContaining({ tracks: ['rgfa-track', 'ncbi'] }),
|
|
2469
|
+
}),
|
|
2470
|
+
],
|
|
2471
|
+
])
|
|
2472
|
+
})
|
|
2473
|
+
|
|
2474
|
+
// a BED named for no annotation is not picked as genes, but beats `No
|
|
2475
|
+
// tracks active`
|
|
2476
|
+
test('a strain with no gene-looking track opens on its first feature track', async () => {
|
|
2477
|
+
mockSession.assemblyNames = ['K12', 'Sakai']
|
|
2478
|
+
mockSession.tracks = [
|
|
2479
|
+
{
|
|
2480
|
+
trackId: 'Sakai_segments',
|
|
2481
|
+
name: 'Sakai segments',
|
|
2482
|
+
type: 'FeatureTrack',
|
|
2483
|
+
assemblyNames: ['Sakai'],
|
|
2484
|
+
adapter: { type: 'BedAdapter' },
|
|
2485
|
+
},
|
|
2486
|
+
]
|
|
2487
|
+
const model = await loadedGraph(ECOLI_RGFA, {
|
|
2488
|
+
refName: 'chr',
|
|
2489
|
+
assemblyName: 'K12',
|
|
2490
|
+
start: 1000,
|
|
2491
|
+
end: 1020,
|
|
2492
|
+
})
|
|
2493
|
+
model.showInLinearView(model.nodeLaunchTargets('3+').own!)
|
|
2494
|
+
expect(mockSession.addedViews).toEqual([
|
|
2495
|
+
[
|
|
2496
|
+
'LinearGenomeView',
|
|
2497
|
+
expect.objectContaining({
|
|
2498
|
+
init: expect.objectContaining({
|
|
2499
|
+
assembly: 'Sakai',
|
|
2500
|
+
tracks: ['Sakai_segments'],
|
|
2501
|
+
}),
|
|
2502
|
+
}),
|
|
2503
|
+
],
|
|
2504
|
+
])
|
|
2505
|
+
})
|
|
2506
|
+
|
|
2348
2507
|
// A pairing is not stolen from a view still on screen: a graph launched from
|
|
2349
2508
|
// a linear view came in on that sync. One whose view has been closed is held
|
|
2350
2509
|
// by nothing, and kept, the view the graph opens next never got its hover.
|
|
@@ -3244,6 +3403,71 @@ describe('walk rows', () => {
|
|
|
3244
3403
|
expect(model.walkStripRows).toBeUndefined()
|
|
3245
3404
|
})
|
|
3246
3405
|
|
|
3406
|
+
// node 2 is off GRCh38's path and both A#1 and B#1 walk it
|
|
3407
|
+
test('the block two rows walk off the reference is lettered on both', async () => {
|
|
3408
|
+
rpcRespond()
|
|
3409
|
+
const model = stateModelFactory().create({
|
|
3410
|
+
type: 'GraphGenomeView',
|
|
3411
|
+
layoutMode: 'walkrows',
|
|
3412
|
+
})
|
|
3413
|
+
await model.loadGFA(WALKS_GFA, 'walks')
|
|
3414
|
+
expect(model.sharedBlocks).toBeUndefined()
|
|
3415
|
+
model.setLayer('walkBlocks', true)
|
|
3416
|
+
const rows = [model.walkRowBars!.reference, ...model.walkRowBars!.rows]
|
|
3417
|
+
const lettered = model.sharedBlocks!.map(b => ({
|
|
3418
|
+
label: rows[b.row]!.label,
|
|
3419
|
+
start: b.start,
|
|
3420
|
+
bp: b.bp,
|
|
3421
|
+
block: b.block,
|
|
3422
|
+
}))
|
|
3423
|
+
expect(lettered.sort((a, b) => a.label.localeCompare(b.label))).toEqual([
|
|
3424
|
+
{ label: 'A#1', start: 4, bp: 8, block: 0 },
|
|
3425
|
+
{ label: 'B#1', start: 4, bp: 8, block: 0 },
|
|
3426
|
+
])
|
|
3427
|
+
})
|
|
3428
|
+
|
|
3429
|
+
test('the shared order stacks rows that walk the same nodes together', async () => {
|
|
3430
|
+
rpcRespond()
|
|
3431
|
+
const model = stateModelFactory().create({
|
|
3432
|
+
type: 'GraphGenomeView',
|
|
3433
|
+
layoutMode: 'walkrows',
|
|
3434
|
+
})
|
|
3435
|
+
await model.loadGFA(WALKS_GFA, 'walks')
|
|
3436
|
+
model.setWalkRowSamples(['A', 'B'])
|
|
3437
|
+
const before = model.walkRowBars!.rows.map(r => r.label)
|
|
3438
|
+
model.setWalkRowOrder('shared')
|
|
3439
|
+
const labels = model.walkRowBars!.rows.map(r => r.label)
|
|
3440
|
+
expect(labels).not.toEqual(before)
|
|
3441
|
+
// A#1 and B#1 share node 2, so B#1 stacks under A#1 rather than after
|
|
3442
|
+
// A#2, which walks GRCh38's own route
|
|
3443
|
+
expect(labels).toEqual(['A#1', 'B#1', 'A#2'])
|
|
3444
|
+
// the sample filter and the order sit side by side in the grammar
|
|
3445
|
+
expect(model.walkRowSamples).toEqual(['A', 'B'])
|
|
3446
|
+
model.setWalkRowSamples(undefined)
|
|
3447
|
+
expect(model.walkRowOrder).toBe('shared')
|
|
3448
|
+
})
|
|
3449
|
+
|
|
3450
|
+
test('ribbons join neighbouring walk rows where they walk the same nodes', async () => {
|
|
3451
|
+
rpcRespond()
|
|
3452
|
+
const model = stateModelFactory().create({
|
|
3453
|
+
type: 'GraphGenomeView',
|
|
3454
|
+
layoutMode: 'walkrows',
|
|
3455
|
+
})
|
|
3456
|
+
await model.loadGFA(WALKS_GFA, 'walks')
|
|
3457
|
+
expect(model.walkRowRibbons).toBeUndefined()
|
|
3458
|
+
model.setLayer('walkRibbons', true)
|
|
3459
|
+
const ribbons = model.walkRowRibbons!
|
|
3460
|
+
expect(ribbons.length).toBeGreaterThan(0)
|
|
3461
|
+
// every row passes nodes 1 and 3, so each pair of neighbours shares a
|
|
3462
|
+
// stretch that starts at both bars' left ends
|
|
3463
|
+
const rows = [model.walkRowBars!.reference, ...model.walkRowBars!.rows]
|
|
3464
|
+
for (let row = 0; row < rows.length - 1; row++) {
|
|
3465
|
+
expect(ribbons.some(r => r.row === row && r.a0 === 0 && r.b0 === 0)).toBe(
|
|
3466
|
+
true,
|
|
3467
|
+
)
|
|
3468
|
+
}
|
|
3469
|
+
})
|
|
3470
|
+
|
|
3247
3471
|
test('walk rows and tube maps take no strip', async () => {
|
|
3248
3472
|
rpcRespond()
|
|
3249
3473
|
const model = stateModelFactory().create({
|
|
@@ -3801,6 +4025,33 @@ describe('walk rows', () => {
|
|
|
3801
4025
|
model.setHoveredRowWalks([])
|
|
3802
4026
|
expect(model.walkLift).toBeUndefined()
|
|
3803
4027
|
})
|
|
4028
|
+
|
|
4029
|
+
// each lift rebuilds the drawing, and a sweep down a lane crosses many rows
|
|
4030
|
+
test('moving between lane rows lifts the next once the pointer settles', async () => {
|
|
4031
|
+
rpcRespond()
|
|
4032
|
+
const model = stateModelFactory().create({
|
|
4033
|
+
type: 'GraphGenomeView',
|
|
4034
|
+
layoutMode: 'auto',
|
|
4035
|
+
})
|
|
4036
|
+
await model.loadGFA(WALKS_GFA, 'walks')
|
|
4037
|
+
vi.useFakeTimers()
|
|
4038
|
+
try {
|
|
4039
|
+
model.setHoveredRowWalks(['A#1#ctg'])
|
|
4040
|
+
expect(model.hoveredRowWalks).toEqual(['A#1#ctg'])
|
|
4041
|
+
model.setHoveredRowWalks(['A#2#ctg'])
|
|
4042
|
+
model.setHoveredRowWalks(['B#1#ctg'])
|
|
4043
|
+
vi.advanceTimersByTime(40)
|
|
4044
|
+
// more moves within the row do not put the lift off
|
|
4045
|
+
model.setHoveredRowWalks(['B#1#ctg'])
|
|
4046
|
+
expect(model.hoveredRowWalks).toEqual(['A#1#ctg'])
|
|
4047
|
+
vi.advanceTimersByTime(60)
|
|
4048
|
+
expect(model.hoveredRowWalks).toEqual(['B#1#ctg'])
|
|
4049
|
+
model.setHoveredRowWalks([])
|
|
4050
|
+
expect(model.hoveredRowWalks).toEqual([])
|
|
4051
|
+
} finally {
|
|
4052
|
+
vi.useRealTimers()
|
|
4053
|
+
}
|
|
4054
|
+
})
|
|
3804
4055
|
})
|
|
3805
4056
|
|
|
3806
4057
|
describe('annotation reads beside a cut', () => {
|
|
@@ -3900,6 +4151,43 @@ describe('annotation reads beside a cut', () => {
|
|
|
3900
4151
|
expect(model.error).toBeUndefined()
|
|
3901
4152
|
})
|
|
3902
4153
|
|
|
4154
|
+
// most graphs have no bubble index, and a pan re-cuts many times
|
|
4155
|
+
test('a bubble index that failed to read is not asked for again', async () => {
|
|
4156
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
4157
|
+
const bubbleReads = () =>
|
|
4158
|
+
mockRpcCall.mock.calls.filter(
|
|
4159
|
+
c =>
|
|
4160
|
+
(c[2] as { adapterConfig?: { type?: string } }).adapterConfig
|
|
4161
|
+
?.type === 'MinigraphBubbleAdapter',
|
|
4162
|
+
).length
|
|
4163
|
+
const respond = mockRpcCall.getMockImplementation()!
|
|
4164
|
+
mockRpcCall.mockImplementation(
|
|
4165
|
+
(sid: unknown, method: string, args: Record<string, unknown>) =>
|
|
4166
|
+
(args.adapterConfig as { type?: string } | undefined)?.type ===
|
|
4167
|
+
'MinigraphBubbleAdapter'
|
|
4168
|
+
? Promise.reject(new Error('404'))
|
|
4169
|
+
: respond(sid, method, args),
|
|
4170
|
+
)
|
|
4171
|
+
const model = attachedModel()
|
|
4172
|
+
const settleAll = async () => {
|
|
4173
|
+
await vi.waitFor(() => {
|
|
4174
|
+
expect(reads.length).toBeGreaterThanOrEqual(2)
|
|
4175
|
+
})
|
|
4176
|
+
for (const read of reads.splice(0)) {
|
|
4177
|
+
read.settle([])
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
let loaded = cut(model)
|
|
4181
|
+
await settleAll()
|
|
4182
|
+
await loaded
|
|
4183
|
+
expect(bubbleReads()).toBe(1)
|
|
4184
|
+
loaded = cut(model)
|
|
4185
|
+
await settleAll()
|
|
4186
|
+
await loaded
|
|
4187
|
+
expect(bubbleReads()).toBe(1)
|
|
4188
|
+
warn.mockRestore()
|
|
4189
|
+
})
|
|
4190
|
+
|
|
3903
4191
|
test('a cut too large to draw still reads its genes, for when the limit is raised', async () => {
|
|
3904
4192
|
vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
3905
4193
|
const model = attachedModel()
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { cutsWholeWalks as wholeWalksRule } from '@jbrowse/bandage-core/layout/walkStrip'
|
|
2
|
-
import { layoutModeByValue } from '@jbrowse/bandage-core/layoutModes'
|
|
3
1
|
import { getSession } from '@jbrowse/core/util'
|
|
4
2
|
|
|
5
3
|
import {
|
|
@@ -46,16 +44,11 @@ export const GraphPane = withLaunchActions
|
|
|
46
44
|
canonicalAssemblyName(session, region.assemblyName),
|
|
47
45
|
)
|
|
48
46
|
},
|
|
49
|
-
//
|
|
50
|
-
// walks,
|
|
51
|
-
// that changes whether the drawing needs them cuts the track again.
|
|
47
|
+
// A layout change that changes the cut options cuts the track again:
|
|
48
|
+
// whole walks (cutsWholeWalksIn), or the walk-row step budget.
|
|
52
49
|
cutChangedFrom(from: LayoutModeValue) {
|
|
53
|
-
const before = wholeWalksRule(layoutModeByValue(from), {
|
|
54
|
-
walkStrip: self.walkStrip,
|
|
55
|
-
host: !!self.host,
|
|
56
|
-
})
|
|
57
50
|
return (
|
|
58
|
-
self.cutsWholeWalks !==
|
|
51
|
+
self.cutsWholeWalks !== self.cutsWholeWalksIn(from) ||
|
|
59
52
|
(from === 'walkrows') !== (self.chosenLayoutMode === 'walkrows')
|
|
60
53
|
)
|
|
61
54
|
},
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import { nodeSizeOf,
|
|
1
|
+
import { nodeSizeOf, sizeOf } from './nodeSize'
|
|
2
2
|
|
|
3
3
|
test('a number is a uniform thickness, an object scales by its field', () => {
|
|
4
|
-
expect(nodeSizeOf(9)).toEqual({ width: 'uniform', px: 9 })
|
|
4
|
+
expect(nodeSizeOf(9)).toEqual({ width: 'uniform', px: 9, links: 'uniform' })
|
|
5
5
|
expect(nodeSizeOf({ field: 'depth', value: 4 })).toEqual({
|
|
6
6
|
width: 'depth',
|
|
7
7
|
px: 4,
|
|
8
|
+
links: 'uniform',
|
|
9
|
+
})
|
|
10
|
+
expect(nodeSizeOf(undefined)).toEqual({
|
|
11
|
+
width: 'depth',
|
|
12
|
+
px: 6,
|
|
13
|
+
links: 'uniform',
|
|
14
|
+
})
|
|
15
|
+
expect(nodeSizeOf({ value: 5, links: 'walks' })).toEqual({
|
|
16
|
+
width: 'uniform',
|
|
17
|
+
px: 5,
|
|
18
|
+
links: 'walks',
|
|
8
19
|
})
|
|
9
|
-
expect(nodeSizeOf(undefined)).toEqual({ width: 'depth', px: 6 })
|
|
10
20
|
})
|
|
11
21
|
|
|
12
|
-
test.each([
|
|
13
|
-
|
|
22
|
+
test.each([
|
|
23
|
+
['depth', 'uniform'],
|
|
24
|
+
['uniform', 'uniform'],
|
|
25
|
+
['depth', 'walks'],
|
|
26
|
+
['uniform', 'walks'],
|
|
27
|
+
] as const)('nodes %s, links %s survive the trip', (width, links) => {
|
|
28
|
+
const size = { width, px: 5, links }
|
|
29
|
+
expect(nodeSizeOf(sizeOf(size))).toEqual(size)
|
|
14
30
|
})
|
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
import { isRecord } from '../isRecord'
|
|
2
2
|
|
|
3
|
+
import type { EdgeWidth } from '@jbrowse/bandage-core/edgeWidths'
|
|
3
4
|
import type { NodeWidth } from '@jbrowse/bandage-core/nodeWidths'
|
|
4
5
|
|
|
5
|
-
// The
|
|
6
|
+
// The mark thickness as a config or session writes it: a px number draws every
|
|
6
7
|
// node that thick; `{ field: 'depth', value }` thickens a node the more paths
|
|
7
|
-
// carry it, around `value` px
|
|
8
|
-
|
|
8
|
+
// carry it, around `value` px; `links: 'walks'` thickens a link the more walks
|
|
9
|
+
// take it. Unset is nodes by depth around 6, links uniform.
|
|
10
|
+
export type NodeSize =
|
|
11
|
+
number | { field?: 'depth'; value?: number; links?: 'walks' }
|
|
9
12
|
|
|
10
13
|
const DEFAULT_PX = 6
|
|
11
14
|
|
|
12
|
-
export
|
|
15
|
+
export interface MarkSize {
|
|
16
|
+
width: NodeWidth
|
|
17
|
+
px: number
|
|
18
|
+
links: EdgeWidth
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function nodeSizeOf(size: unknown): MarkSize {
|
|
13
22
|
return typeof size === 'number'
|
|
14
|
-
? { width: 'uniform', px: size }
|
|
23
|
+
? { width: 'uniform', px: size, links: 'uniform' }
|
|
15
24
|
: isRecord(size)
|
|
16
25
|
? {
|
|
17
26
|
width: size.field === 'depth' ? 'depth' : 'uniform',
|
|
18
27
|
px: typeof size.value === 'number' ? size.value : DEFAULT_PX,
|
|
28
|
+
links: size.links === 'walks' ? 'walks' : 'uniform',
|
|
19
29
|
}
|
|
20
|
-
: { width: 'depth', px: DEFAULT_PX }
|
|
30
|
+
: { width: 'depth', px: DEFAULT_PX, links: 'uniform' }
|
|
21
31
|
}
|
|
22
32
|
|
|
23
|
-
export function
|
|
24
|
-
return width === '
|
|
33
|
+
export function sizeOf({ width, px, links }: MarkSize): NodeSize {
|
|
34
|
+
return width === 'uniform' && links === 'uniform'
|
|
35
|
+
? px
|
|
36
|
+
: {
|
|
37
|
+
...(width === 'depth' ? { field: 'depth' as const } : {}),
|
|
38
|
+
value: px,
|
|
39
|
+
...(links === 'walks' ? { links } : {}),
|
|
40
|
+
}
|
|
25
41
|
}
|
|
@@ -520,6 +520,7 @@ export const withFitViews = withHostViews
|
|
|
520
520
|
connectorThickness: self.connectorThickness,
|
|
521
521
|
drawPaths,
|
|
522
522
|
nodeWidth: self.nodeWidth,
|
|
523
|
+
edgeWidth: self.linkWidth,
|
|
523
524
|
highlight,
|
|
524
525
|
// Untracked, so a zoom does not eagerly rebuild geometry — the
|
|
525
526
|
// debounced viewportDirty bump drives the scale-dependent rebuild
|
|
@@ -573,13 +574,16 @@ export const withFitViews = withHostViews
|
|
|
573
574
|
facet: self.facetSpec,
|
|
574
575
|
colorScheme: self.effectiveColorScheme,
|
|
575
576
|
nodeWidth: self.nodeWidth,
|
|
577
|
+
edgeWidth: self.linkWidth,
|
|
576
578
|
showDeletionEdges: self.showDeletionEdges,
|
|
577
579
|
linearLayout: self.linearLayout,
|
|
578
580
|
contigThickness: self.contigThickness,
|
|
579
581
|
connectorThickness: self.connectorThickness,
|
|
580
582
|
region: self.graphRegion,
|
|
581
583
|
colorDomain: self.statedColorDomain,
|
|
582
|
-
genes: self.showGenes
|
|
584
|
+
genes: self.showGenes
|
|
585
|
+
? [...(self.backboneGenes ?? []), ...(self.ownGeneFeatures ?? [])]
|
|
586
|
+
: undefined,
|
|
583
587
|
walkRows: self.walkRowBars,
|
|
584
588
|
walkRowPitch: self.walkRowPitch,
|
|
585
589
|
rowGenes: self.walkRowGenes,
|