jbrowse-plugin-graphgenomeviewer 8.3.5 → 8.3.6

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 (23) hide show
  1. package/dist/chunks/{GbzBaseSyntenyAdapter-RRLBONL5.js → GbzBaseSyntenyAdapter-BG6FNGHY.js} +2 -2
  2. package/dist/chunks/WalkTabixSyntenyAdapter-7COETKFB.js +2 -0
  3. package/dist/chunks/WalkTabixSyntenyAdapter-7COETKFB.js.map +7 -0
  4. package/dist/chunks/{chunk-HMIJBEUE.js → chunk-HLWIFWDZ.js} +2 -2
  5. package/dist/chunks/{chunk-HMIJBEUE.js.map → chunk-HLWIFWDZ.js.map} +2 -2
  6. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +2 -2
  7. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/RgfaTabixAdapter/test_data/walks5_skipfollow.GRCh38.walks.bed.gz +0 -0
  10. package/src/RgfaTabixAdapter/test_data/walks5_skipfollow.GRCh38.walks.bed.gz.tbi +0 -0
  11. package/src/RgfaTabixAdapter/test_data/walks5_skipinv.GRCh38.walks.bed.gz +0 -0
  12. package/src/RgfaTabixAdapter/test_data/walks5_skipinv.GRCh38.walks.bed.gz.tbi +0 -0
  13. package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.test.ts +111 -84
  14. package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +96 -93
  15. package/src/WalkTabixSyntenyAdapter/walkLanes.ts +71 -48
  16. package/src/synteny/clipFeatureToRegion.ts +3 -2
  17. package/src/synteny/matchFeature.ts +2 -3
  18. package/src/version.ts +1 -1
  19. package/dist/chunks/WalkTabixSyntenyAdapter-4I32E62Y.js +0 -2
  20. package/dist/chunks/WalkTabixSyntenyAdapter-4I32E62Y.js.map +0 -7
  21. package/src/RgfaTabixAdapter/test_data/walks5_skippair.GRCh38.walks.bed.gz +0 -0
  22. package/src/RgfaTabixAdapter/test_data/walks5_skippair.GRCh38.walks.bed.gz.tbi +0 -0
  23. /package/dist/chunks/{GbzBaseSyntenyAdapter-RRLBONL5.js.map → GbzBaseSyntenyAdapter-BG6FNGHY.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jbrowse-plugin-graphgenomeviewer",
3
- "version": "8.3.5",
3
+ "version": "8.3.6",
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": {
@@ -1,9 +1,11 @@
1
+ import { TabixIndexedFile } from '@gmod/tabix'
1
2
  import { graphTablesGFA } from '@jbrowse/bandage-core/gfa/graphTables'
2
3
  import { walkRowsOf } from '@jbrowse/bandage-core/layout/walkRowRuns'
3
4
  import { walkRows } from '@jbrowse/bandage-core/layout/walkRows'
4
5
  import { loadGraph } from '@jbrowse/bandage-core/pipeline'
5
6
  import PluginManager from '@jbrowse/core/PluginManager'
6
7
  import { readConfObject } from '@jbrowse/core/configuration'
8
+ import { WalkReader } from '@jbrowse/graph-walks'
7
9
  import { firstValueFrom } from 'rxjs'
8
10
  import { toArray } from 'rxjs/operators'
9
11
 
@@ -247,74 +249,136 @@ test('a window on a haplotype lane answers nothing', async () => {
247
249
  expect(found).toEqual([])
248
250
  })
249
251
 
250
- // gfa-to-tabix --walks --chunk 1000 of ten 1 kb reference nodes, whose chunks
251
- // over 2000-8000 hold skip rows for HG002#1 and HG004#1:
252
- // W GRCh38 0 chr1 >1>2>3>4>5>6>7>8>9>10
253
- // W HG002 1 ctg >1>2>9>10 a deletion of 2000-8000
254
- // W HG003 1 ctg >1>2>3>4>5>6>7>8>9>10
255
- // W HG004 1 ctg <10<9<2<1 the same deletion, walked backwards
256
- const skipWindow = { ...window, start: 3100, end: 6000 }
257
- const skipFeats = (opts: Record<string, unknown> = {}) =>
252
+ // gfa-to-tabix --walks --chunk 1000 of twenty 1 kb reference nodes; every
253
+ // haplotype but HG003 skips 2000-5000, so chunks 2000-4000 hold skip rows:
254
+ // W GRCh38 0 chr1 >1>2>3>...>20
255
+ // W HG002 1 ctg >1>2>6>...>20
256
+ // W HG004 1 ctg <20<...<6<2<1 the same walk backwards
257
+ // W HG003 1 ctg >1>2>3>...>20
258
+ // W HG007 1 ctg >1>2>6>...>20 a W line starting at 100,000
259
+ // W HG005 1 ctg >1>2>99>6>...>20 500 bp of node 99 in the gap
260
+ const skipWindow = { ...window, start: 3100, end: 3900 }
261
+ const skipFeats = (
262
+ opts: Record<string, unknown> = {},
263
+ name = 'walks5_skipfollow',
264
+ ) =>
258
265
  firstValueFrom(
259
- makeAdapter({}, 'walks5_skippair')
266
+ makeAdapter({}, name)
260
267
  .getFeaturesInMultipleRegions([skipWindow], opts as never)
261
268
  .pipe(toArray()),
262
269
  )
263
-
264
- test('a haplotype whose deletion spans the window is a lane, its record the skipped stretch with the point it crosses the deletion as its mate', async () => {
270
+ const skipSummary = (f: { get: (k: string) => unknown }) => ({
271
+ ...summary(f),
272
+ strand: f.get('strand'),
273
+ spansWindow: f.get('spansWindow'),
274
+ })
275
+ const deletion = (lane: string, mate: string, strand: number) => ({
276
+ lane,
277
+ refName: 'chr1',
278
+ start: 2000,
279
+ end: 5000,
280
+ mate,
281
+ cigar: undefined,
282
+ strand,
283
+ spansWindow: true,
284
+ })
285
+ const crossings = [
286
+ deletion('HG002#1', 'ctg:2000-2000', 1),
287
+ deletion('HG004#1', 'ctg:15000-15000', -1),
288
+ deletion('HG005#1', 'ctg:2000-2000', 1),
289
+ deletion('HG007#1', 'ctg:102000-102000', 1),
290
+ ]
291
+
292
+ test('a haplotype whose deletion spans the window is one spansWindow record of the whole deletion, its mate where it crosses', async () => {
265
293
  const found = await skipFeats()
266
- expect(found.map(f => ({ ...summary(f), strand: f.get('strand') }))).toEqual([
294
+ expect(found.map(skipSummary)).toEqual([
267
295
  {
268
296
  lane: 'HG003#1',
269
297
  refName: 'chr1',
270
298
  start: 2000,
271
- end: 7000,
272
- mate: 'ctg:2000-7000',
273
- cigar: '5000=',
274
- strand: 1,
275
- },
276
- {
277
- lane: 'HG002#1',
278
- refName: 'chr1',
279
- start: 2000,
280
- end: 8000,
281
- mate: 'ctg:2000-2000',
282
- cigar: undefined,
299
+ end: 5000,
300
+ mate: 'ctg:2000-5000',
301
+ cigar: '3000=',
283
302
  strand: 1,
303
+ spansWindow: undefined,
284
304
  },
285
- {
286
- lane: 'HG004#1',
287
- refName: 'chr1',
288
- start: 2000,
289
- end: 8000,
290
- mate: 'ctg:2000-2000',
291
- cigar: undefined,
292
- strand: -1,
293
- },
305
+ ...crossings,
294
306
  ])
307
+ expect(found[1]!.get('identity')).toBeUndefined()
295
308
  })
296
309
 
297
- test("the display's clip maps the whole window onto a skipping haplotype's crossing", async () => {
310
+ test("the display's clip passes a deletion through whole, so its extent is the deletion's length", async () => {
298
311
  const found = await skipFeats({
299
312
  haplotypes: ['HG002'],
300
313
  clipToRegion: true,
301
314
  splitAtGapBp: 10_000,
302
315
  })
303
- expect(found.map(summary)).toEqual([
304
- {
305
- lane: 'HG002#1',
306
- refName: 'chr1',
307
- start: 3100,
308
- end: 6000,
309
- mate: 'ctg:2000-2000',
310
- cigar: undefined,
311
- },
316
+ expect(found.map(skipSummary)).toEqual([crossings[0]])
317
+ })
318
+
319
+ test('a walk passing the node it leaves at inverted still crosses after it', async () => {
320
+ const found = await skipFeats({ haplotypes: ['HG008'] }, 'walks5_skipinv')
321
+ expect(found.map(skipSummary)).toEqual([
322
+ deletion('HG008#1', 'ctg:2000-2000', 1),
323
+ ])
324
+ })
325
+
326
+ test('a skipping haplotype outside the lanes asked for opens no lane', async () => {
327
+ const found = await skipFeats({ haplotypes: ['HG003'] })
328
+ expect(found.map(f => mateOf(f).assemblyName)).toEqual(['HG003#1'])
329
+ })
330
+
331
+ test('the crossing cut reads only the chunks before the node a haplotype leaves', async () => {
332
+ const getLines = vi.spyOn(TabixIndexedFile.prototype, 'getLines')
333
+ await skipFeats({ haplotypes: ['HG002'] })
334
+ const reads = getLines.mock.calls.map(call => `${call[1]}-${call[2]}`)
335
+ getLines.mockRestore()
336
+ expect(reads.at(-1)).toBe('0-2000')
337
+ })
338
+
339
+ test('a haplotype whose leaving node the cut lacks is crossed from the node it rejoins', async () => {
340
+ const cut = WalkReader.prototype.cut
341
+ const spy = vi
342
+ .spyOn(WalkReader.prototype, 'cut')
343
+ .mockImplementation(async function (
344
+ this: WalkReader,
345
+ refName,
346
+ region,
347
+ opts,
348
+ ) {
349
+ const found = await cut.call(this, refName, region, opts)
350
+ return opts?.contained && region.end === 2000
351
+ ? { ...found, fragments: [] }
352
+ : found
353
+ })
354
+ const found = await skipFeats()
355
+ spy.mockRestore()
356
+ // HG005 rejoins past the 500 bp it carries in the gap
357
+ expect(found.slice(1).map(skipSummary)).toEqual([
358
+ crossings[0],
359
+ crossings[1],
360
+ deletion('HG005#1', 'ctg:2500-2500', 1),
361
+ crossings[3],
312
362
  ])
313
363
  })
314
364
 
365
+ test('a failed crossing cut drops only its own lanes', async () => {
366
+ const cut = WalkReader.prototype.cut
367
+ const spy = vi
368
+ .spyOn(WalkReader.prototype, 'cut')
369
+ .mockImplementation(function (this: WalkReader, refName, region, opts) {
370
+ return opts?.contained
371
+ ? Promise.reject(new Error('no such chunk'))
372
+ : cut.call(this, refName, region, opts)
373
+ })
374
+ const found = await skipFeats()
375
+ spy.mockRestore()
376
+ expect(found.map(f => mateOf(f).assemblyName)).toEqual(['HG003#1'])
377
+ })
378
+
315
379
  // RgfaTabixAdapter/walkFiles.test.ts's walks5_skip: HG002#1 >1>2>9>10 over
316
380
  // ten 10 bp reference nodes in 10 bp chunks
317
- test('a named haplotype skipping the window opens its lane at the crossing', async () => {
381
+ test('a named haplotype skipping the window is a deletion of the skipped stretch', async () => {
318
382
  const found = await firstValueFrom(
319
383
  makeAdapter({}, 'walks5_skip')
320
384
  .getFeatures(
@@ -323,7 +387,7 @@ test('a named haplotype skipping the window opens its lane at the crossing', asy
323
387
  )
324
388
  .pipe(toArray()),
325
389
  )
326
- expect(found.map(summary)).toEqual([
390
+ expect(found.map(skipSummary)).toEqual([
327
391
  {
328
392
  lane: 'HG002#1',
329
393
  refName: 'chr1',
@@ -331,45 +395,8 @@ test('a named haplotype skipping the window opens its lane at the crossing', asy
331
395
  end: 80,
332
396
  mate: 'ctg:20-20',
333
397
  cigar: undefined,
334
- },
335
- ])
336
- })
337
-
338
- test('a skipping haplotype outside the lanes asked for opens no lane', async () => {
339
- const found = await skipFeats({ haplotypes: ['HG003'] })
340
- expect(found.map(f => mateOf(f).assemblyName)).toEqual(['HG003#1'])
341
- })
342
-
343
- test('a lane pair with one lane skipping the window joins the other lane to the crossing, either way round', async () => {
344
- const found = await skipFeats({
345
- lanePairs: [
346
- { queryAssemblyName: 'HG003#1', targetAssemblyName: 'HG002#1' },
347
- { queryAssemblyName: 'HG002#1', targetAssemblyName: 'HG003#1' },
348
- { queryAssemblyName: 'HG002#1', targetAssemblyName: 'HG004#1' },
349
- ],
350
- clipToRegion: true,
351
- splitAtGapBp: 10_000,
352
- })
353
- expect(
354
- found.map(f => ({ assemblyName: f.get('assemblyName'), ...summary(f) })),
355
- ).toEqual([
356
- {
357
- assemblyName: 'HG003#1',
358
- lane: 'HG002#1',
359
- refName: 'ctg',
360
- start: 2000,
361
- end: 7000,
362
- mate: 'ctg:2000-2000',
363
- cigar: undefined,
364
- },
365
- {
366
- assemblyName: 'HG002#1',
367
- lane: 'HG003#1',
368
- refName: 'ctg',
369
- start: 2000,
370
- end: 2000,
371
- mate: 'ctg:2000-7000',
372
- cigar: undefined,
398
+ strand: 1,
399
+ spansWindow: true,
373
400
  },
374
401
  ])
375
402
  })
@@ -13,11 +13,9 @@ import { walksKept } from '@jbrowse/graph-walks'
13
13
  import {
14
14
  anchorAligner,
15
15
  chainFeature,
16
- crossingFeature,
17
- crossingSide,
18
16
  lanePairAligner,
19
17
  skipCrossing,
20
- walkSide,
18
+ skipFeature,
21
19
  } from './walkLanes.ts'
22
20
  import { WalkIndex } from '../RgfaTabixAdapter/walkReader.ts'
23
21
  import { PanSNRefNames, openTabixSlot } from '../panSNTabix.ts'
@@ -30,9 +28,9 @@ import {
30
28
  import { laneZoomNotice } from '../synteny/laneZoomNotice.ts'
31
29
 
32
30
  import type { WalkTabixSyntenyAdapterConfig } from './configSchema.ts'
31
+ import type { SkipCrossing, SkipSide } from './walkLanes.ts'
33
32
  import type { SubgraphAdapterOptions } from '../GetSubgraph.ts'
34
33
  import type { LaneFeatureOptions, LanePair } from '../synteny/lanePairs.ts'
35
- import type { MatchSide } from '../synteny/matchFeature.ts'
36
34
  import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter'
37
35
  import type { Feature } from '@jbrowse/core/util'
38
36
  import type { Region } from '@jbrowse/core/util/types'
@@ -144,51 +142,82 @@ export default class WalkTabixSyntenyAdapter extends ComparativeAdapterBase<Walk
144
142
  }
145
143
 
146
144
  /**
147
- * Where each haplotype that skips the window crosses its deletion, read
148
- * from cuts over the reference nodes they leave at, one cut per chunk those
149
- * nodes fall in. A haplotype with a walk in the window draws that instead:
150
- * NA19036#1 skips the IGL locus on one contig and walks it on another.
145
+ * Where each skipping haplotype crosses its deletion, read from contained
146
+ * cuts over the reference node it leaves, one cut per chunk those nodes
147
+ * fall in, and for any that cut misses, over the node it rejoins. A cut
148
+ * that fails drops only its own haplotypes' crossings.
151
149
  */
152
150
  private async skipCrossings(
153
151
  refName: string,
154
- { skips, fragments }: { skips: WalkSkip[]; fragments: { name: string }[] },
152
+ skips: WalkSkip[],
153
+ opts: LaneFeatureOptions,
154
+ ) {
155
+ const found: SkipCrossing[] = []
156
+ let missed = skips
157
+ for (const side of ['leave', 'rejoin'] as const) {
158
+ const crossings = await this.sideCrossings(refName, missed, side, opts)
159
+ for (const crossing of crossings) {
160
+ found.push(crossing)
161
+ }
162
+ const placed = new Set(crossings.map(crossing => crossing.skip))
163
+ missed = missed.filter(skip => !placed.has(skip))
164
+ }
165
+ return found
166
+ }
167
+
168
+ private async sideCrossings(
169
+ refName: string,
170
+ skips: WalkSkip[],
171
+ side: SkipSide,
155
172
  opts: LaneFeatureOptions,
156
173
  ) {
157
174
  const { signal } = opts
158
- const walked = new Set(fragments.map(haplotypeOf))
159
- const crossing = skips.filter(skip => !walked.has(haplotypeOf(skip)))
160
175
  const { chunk } = await this.walks.reader.header({ signal })
176
+ const boundary = (skip: WalkSkip) =>
177
+ side === 'leave' ? skip.refStart - 1 : skip.refEnd
161
178
  const byChunk = new Map<
162
179
  number,
163
180
  { group: WalkSkip[]; start: number; end: number }
164
181
  >()
165
- for (const skip of crossing) {
166
- const cs = Math.floor((skip.refStart - 1) / chunk)
182
+ for (const skip of skips) {
183
+ const at = boundary(skip)
184
+ const cs = Math.floor(at / chunk)
167
185
  const held = byChunk.get(cs)
168
186
  if (held) {
169
187
  held.group.push(skip)
170
- held.start = Math.min(held.start, skip.refStart - 1)
171
- held.end = Math.max(held.end, skip.refStart)
188
+ held.start = Math.min(held.start, at)
189
+ held.end = Math.max(held.end, at + 1)
172
190
  } else {
173
- byChunk.set(cs, {
174
- group: [skip],
175
- start: skip.refStart - 1,
176
- end: skip.refStart,
177
- })
191
+ byChunk.set(cs, { group: [skip], start: at, end: at + 1 })
178
192
  }
179
193
  }
180
194
  const found = await Promise.all(
181
195
  [...byChunk.values()].map(async ({ group, start, end }) => {
182
196
  const names = new Set(group.map(skip => skip.name))
183
197
  const keep = (name: string) => names.has(name)
184
- const { graph, fragments } = await this.walks.reader.cut(
185
- refName,
186
- { start, end },
187
- { signal, keep, expand: keep, context: 0, links: false },
188
- )
189
- return group.map(skip =>
190
- skipCrossing(skip, refName, fragments, graph.nodes),
191
- )
198
+ try {
199
+ const { graph, fragments } = await this.walks.reader.cut(
200
+ refName,
201
+ { start, end },
202
+ {
203
+ ...this.walks.budgets(),
204
+ signal,
205
+ keep,
206
+ expand: keep,
207
+ context: 0,
208
+ contained: true,
209
+ links: false,
210
+ },
211
+ )
212
+ return group.map(skip =>
213
+ skipCrossing(skip, refName, fragments, graph.nodes, side),
214
+ )
215
+ } catch (error) {
216
+ if (signal?.aborted) {
217
+ throw error
218
+ }
219
+ return []
220
+ }
192
221
  }),
193
222
  )
194
223
  return found.flat().filter(crossing => crossing !== undefined)
@@ -197,8 +226,8 @@ export default class WalkTabixSyntenyAdapter extends ComparativeAdapterBase<Walk
197
226
  /**
198
227
  * One record per stretch a haplotype's walk shares with the reference's,
199
228
  * on the reference with the haplotype as its mate. A haplotype whose
200
- * deletion spans the window is one record of the skipped stretch, its mate
201
- * the point where the haplotype crosses the deletion.
229
+ * deletion spans the window, with no walk there on any contig, is one
230
+ * `spansWindow` record of the skipped stretch.
202
231
  */
203
232
  private async anchorFeatures(
204
233
  refName: string,
@@ -238,10 +267,18 @@ export default class WalkTabixSyntenyAdapter extends ComparativeAdapterBase<Walk
238
267
  prefixes === undefined
239
268
  ? 0
240
269
  : prefixes.findIndex(prefix => panSNMatchesPrefix(name, prefix))
270
+ // NA19036#1 skips the IGL locus on one contig and walks it on another
271
+ const walking = new Set(fragments.map(haplotypeOf))
241
272
  const crossings = await updateStatus(status, opts.statusCallback, () =>
242
273
  this.skipCrossings(
243
274
  refName,
244
- { skips: skips.filter(skip => rank(skip) >= 0), fragments },
275
+ skips.filter(
276
+ skip =>
277
+ rank(skip) >= 0 &&
278
+ !walking.has(haplotypeOf(skip)) &&
279
+ skip.refStart <= start &&
280
+ skip.refEnd >= end,
281
+ ),
245
282
  opts,
246
283
  ),
247
284
  )
@@ -267,25 +304,17 @@ export default class WalkTabixSyntenyAdapter extends ComparativeAdapterBase<Walk
267
304
  ),
268
305
  ),
269
306
  }))
270
- const skipped = crossings.map(crossing => {
271
- const { skip } = crossing
272
- return {
273
- rank: rank(skip),
274
- features: [
275
- crossingFeature({
276
- target: {
277
- name: refName,
278
- assemblyName: region.assemblyName,
279
- refName: region.refName,
280
- start: skip.refStart,
281
- end: skip.refEnd,
282
- },
283
- query: crossingSide(crossing, laneOf(skip)),
284
- strand: crossing.strand,
285
- }),
286
- ],
287
- }
288
- })
307
+ const skipped = crossings.map(crossing => ({
308
+ rank: rank(crossing.skip),
309
+ features: [
310
+ skipFeature({
311
+ crossing,
312
+ refName: region.refName,
313
+ assemblyName: region.assemblyName,
314
+ mateAssemblyName: laneOf(crossing.skip),
315
+ }),
316
+ ],
317
+ }))
289
318
  return [...walked, ...skipped]
290
319
  .sort((a, b) => a.rank - b.rank)
291
320
  .flatMap(lane => lane.features)
@@ -295,8 +324,7 @@ export default class WalkTabixSyntenyAdapter extends ComparativeAdapterBase<Walk
295
324
  * Each pair's two lanes read out of the anchor window, all pairs in one
296
325
  * read, and each walk of a pair's query lane aligned to each walk of its
297
326
  * target lane. A pair's records sit on its query lane's contigs, the lane
298
- * the display draws on top. A lane that skips the window pairs as the
299
- * point where it crosses its deletion.
327
+ * the display draws on top.
300
328
  */
301
329
  private async pairFeatures(
302
330
  refName: string,
@@ -309,57 +337,32 @@ export default class WalkTabixSyntenyAdapter extends ComparativeAdapterBase<Walk
309
337
  featurePrefix: resolvePanSNPrefix(this, pair.queryAssemblyName),
310
338
  matePrefix: resolvePanSNPrefix(this, pair.targetAssemblyName),
311
339
  }))
312
- const status = lanePairsStatus(pairs)
313
- const { graph, fragments, skips } = await this.laneWalks(
340
+ const { graph, fragments } = await this.laneWalks(
314
341
  refName,
315
342
  region,
316
343
  sides.flatMap(side => [side.featurePrefix, side.matePrefix]),
317
344
  opts,
318
- status,
319
- )
320
- const crossings = await updateStatus(status, opts.statusCallback, () =>
321
- this.skipCrossings(refName, { skips, fragments }, opts),
345
+ lanePairsStatus(pairs),
322
346
  )
323
347
  const align = lanePairAligner(graph.nodes)
324
348
  const walksOf = (prefix: string) =>
325
349
  fragments.filter(f => panSNMatchesPrefix(f.name, prefix))
326
- const walkSides = (prefix: string, assemblyName: string) =>
327
- walksOf(prefix).map(walk => walkSide(walk, assemblyName))
328
- const crossingSides = (prefix: string, assemblyName: string) =>
329
- crossings
330
- .filter(({ skip }) => panSNMatchesPrefix(skip.name, prefix))
331
- .map(crossing => crossingSide(crossing, assemblyName))
332
- const crossed = (own: MatchSide[], mates: MatchSide[]) =>
333
- own.flatMap(target =>
334
- mates.map(query => crossingFeature({ target, query, strand: '+' })),
335
- )
336
- return sides.flatMap(({ pair, featurePrefix, matePrefix }) => {
337
- const { queryAssemblyName, targetAssemblyName } = pair
338
- return [
339
- ...walksOf(featurePrefix).flatMap(target =>
340
- walksOf(matePrefix).flatMap(query =>
341
- align(query, target).map(chain =>
342
- chainFeature({
343
- chain,
344
- target,
345
- query,
346
- assemblyName: queryAssemblyName,
347
- refName: panSNContig(target.name),
348
- mateAssemblyName: targetAssemblyName,
349
- }),
350
- ),
350
+ return sides.flatMap(({ pair, featurePrefix, matePrefix }) =>
351
+ walksOf(featurePrefix).flatMap(target =>
352
+ walksOf(matePrefix).flatMap(query =>
353
+ align(query, target).map(chain =>
354
+ chainFeature({
355
+ chain,
356
+ target,
357
+ query,
358
+ assemblyName: pair.queryAssemblyName,
359
+ refName: panSNContig(target.name),
360
+ mateAssemblyName: pair.targetAssemblyName,
361
+ }),
351
362
  ),
352
363
  ),
353
- ...crossed(
354
- walkSides(featurePrefix, queryAssemblyName),
355
- crossingSides(matePrefix, targetAssemblyName),
356
- ),
357
- ...crossed(
358
- crossingSides(featurePrefix, queryAssemblyName),
359
- walkSides(matePrefix, targetAssemblyName),
360
- ),
361
- ]
362
- })
364
+ ),
365
+ )
363
366
  }
364
367
 
365
368
  // The files are filed under the reference alone, so a window on a