jbrowse-plugin-graphgenomeviewer 4.1.1 → 4.3.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.
Files changed (39) hide show
  1. package/README.md +4 -4
  2. package/dist/chunks/{GraphGenomeView-QOBHGMLE.js → GraphGenomeView-ZB4N6Q3H.js} +2 -2
  3. package/dist/chunks/{GraphTrackSettingsDialog-RTSJTM6A.js → GraphTrackSettingsDialog-LXYUYPZS.js} +2 -2
  4. package/dist/chunks/{LinearGraphDisplay-TPXW6POY.js → LinearGraphDisplay-BCTYOYQ5.js} +2 -2
  5. package/dist/chunks/{RgfaTabixAdapter-5MD2ZZ2F.js → RgfaTabixAdapter-QPKEJZXA.js} +3 -3
  6. package/dist/chunks/{RgfaTabixAdapter-5MD2ZZ2F.js.map → RgfaTabixAdapter-QPKEJZXA.js.map} +2 -2
  7. package/dist/chunks/{chunk-4C4CMI6K.js → chunk-CNFN6EVE.js} +2 -2
  8. package/dist/chunks/chunk-PPYB6N2Q.js +2 -0
  9. package/dist/chunks/chunk-PPYB6N2Q.js.map +7 -0
  10. package/dist/chunks/chunk-PSOLXUFN.js +3 -0
  11. package/dist/chunks/chunk-PSOLXUFN.js.map +7 -0
  12. package/dist/chunks/chunk-UTWFU4X6.js +2 -0
  13. package/dist/chunks/chunk-UTWFU4X6.js.map +7 -0
  14. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +5 -5
  15. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
  16. package/package.json +2 -2
  17. package/src/GraphGenomeView/components/GraphCanvas.tsx +6 -53
  18. package/src/GraphGenomeView/components/WalkRowContextMenu.tsx +63 -0
  19. package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +1 -1
  20. package/src/GraphGenomeView/components/WalkStrip.tsx +207 -0
  21. package/src/GraphGenomeView/components/WalkStripLocator.tsx +49 -0
  22. package/src/GraphGenomeView/components/usePaneGestures.ts +1 -0
  23. package/src/GraphGenomeView/model.test.ts +42 -3
  24. package/src/GraphGenomeView/model.ts +143 -19
  25. package/src/GraphGenomeView/viewModel.ts +23 -7
  26. package/src/RgfaTabixAdapter/RgfaTabixAdapter.test.ts +8 -8
  27. package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +5 -6
  28. package/src/RgfaTabixAdapter/rgfaBed.test.ts +7 -7
  29. package/src/version.ts +1 -1
  30. package/dist/chunks/chunk-ECKLYJWP.js +0 -2
  31. package/dist/chunks/chunk-ECKLYJWP.js.map +0 -7
  32. package/dist/chunks/chunk-HW55QCJG.js +0 -3
  33. package/dist/chunks/chunk-HW55QCJG.js.map +0 -7
  34. package/dist/chunks/chunk-SIP5IHWK.js +0 -2
  35. package/dist/chunks/chunk-SIP5IHWK.js.map +0 -7
  36. /package/dist/chunks/{GraphGenomeView-QOBHGMLE.js.map → GraphGenomeView-ZB4N6Q3H.js.map} +0 -0
  37. /package/dist/chunks/{GraphTrackSettingsDialog-RTSJTM6A.js.map → GraphTrackSettingsDialog-LXYUYPZS.js.map} +0 -0
  38. /package/dist/chunks/{LinearGraphDisplay-TPXW6POY.js.map → LinearGraphDisplay-BCTYOYQ5.js.map} +0 -0
  39. /package/dist/chunks/{chunk-4C4CMI6K.js.map → chunk-CNFN6EVE.js.map} +0 -0
@@ -40,6 +40,11 @@ import {
40
40
  } from '@jbrowse/bandage-core/layout/walkRowDraw'
41
41
  import { walkRowsExtent } from '@jbrowse/bandage-core/layout/walkRowLayout'
42
42
  import { walkRows } from '@jbrowse/bandage-core/layout/walkRows'
43
+ import {
44
+ segmentAt,
45
+ stripMarks,
46
+ walkStripFrame,
47
+ } from '@jbrowse/bandage-core/layout/walkStrip'
43
48
  import {
44
49
  LAYOUT_MODES,
45
50
  LAYOUT_MODE_VALUES,
@@ -47,6 +52,7 @@ import {
47
52
  modeUsesLayoutEngine,
48
53
  } from '@jbrowse/bandage-core/layoutModes'
49
54
  import { NODE_WIDTH_VALUES, nodeInk } from '@jbrowse/bandage-core/nodeWidths'
55
+ import { LABEL_CHAR_PX } from '@jbrowse/bandage-core/overlayLabels'
50
56
  import {
51
57
  anchorFromPaths,
52
58
  chooseReferencePath,
@@ -297,6 +303,25 @@ const VIEWPORT_DEBOUNCE_MS = 150
297
303
  const VIEWPORT_PANES_BUILT = 1
298
304
  // How many walk rows read their haplotype's genes, the first rows down
299
305
  const WALK_GENE_ROWS = 40
306
+ // The most of the pane's height the strip of walk rows under a node layout
307
+ // takes; past it rows shrink, down to a dense overview
308
+ const WALK_STRIP_CEILING = 260
309
+
310
+ // The rows a sample filter keeps, in the order it names them
311
+ function filterSamples<R extends { sample: string; label: string }>(
312
+ rows: R[],
313
+ samples: string[] | undefined,
314
+ ) {
315
+ return samples
316
+ ? rows
317
+ .filter(r => samples.includes(r.sample))
318
+ .sort(
319
+ (a, b) =>
320
+ samples.indexOf(a.sample) - samples.indexOf(b.sample) ||
321
+ a.label.localeCompare(b.label),
322
+ )
323
+ : rows
324
+ }
300
325
 
301
326
  // What the canvas draws under the tube map, whose ink is all TubeMapOverlay's
302
327
  const EMPTY_BATCH: RenderBatch = {
@@ -494,7 +519,7 @@ export function GraphPaneMixin() {
494
519
  // with a label that opens it. Off by default: on a base-level cut
495
520
  // every SNP's halo is a blob.
496
521
  showBubbles: types.optional(types.boolean, false),
497
- showDeletionEdges: types.optional(types.boolean, false),
522
+ showDeletionEdges: types.optional(types.boolean, true),
498
523
  // The session's genes drawn onto the backbone: exons along the nodes
499
524
  // that carry them, names pinned at their midpoints. See genes/.
500
525
  showGenes: types.optional(types.boolean, true),
@@ -517,6 +542,9 @@ export function GraphPaneMixin() {
517
542
  // Samples whose walks the walk rows show, by the name before the
518
543
  // haplotype number; undefined shows every walk the cut holds.
519
544
  walkRowSamples: types.maybe(types.frozen<string[]>()),
545
+ // Walk rows in a strip under a layout that draws nodes, linked to the
546
+ // drawing: a node's passes tick each bar, a bar's point lights its node
547
+ walkStrip: types.optional(types.boolean, false),
520
548
  // Walks lifted out of the drawing, each a layer with a lane of its own
521
549
  // and the rest fading, coloured by the encoding it states or by the
522
550
  // default one. See walkEncoding.ts. Empty lifts none.
@@ -622,6 +650,8 @@ export function GraphPaneMixin() {
622
650
  // whether the pointer is over this pane, whose own hit test then says
623
651
  // what it is on
624
652
  pointerInPane: false,
653
+ // the strip point the pointer is over, which lights its node above
654
+ stripHover: null as { row: string; offset: number } | null,
625
655
  hoveredEdge: null as number | null,
626
656
  selectedNode: null as string | null,
627
657
  viewportDirty: 0,
@@ -966,6 +996,9 @@ export function GraphPaneMixin() {
966
996
  get alleleDeletions() {
967
997
  return self.layoutResult?.alleleDeletions ?? []
968
998
  },
999
+ get deletionRoutes() {
1000
+ return self.layoutResult?.deletionRoutes
1001
+ },
969
1002
  // Whether the current layout states y in screen px rather than in the same
970
1003
  // units as x (LayoutResult.pixelRows). Everything that has to put the two
971
1004
  // axes in one expression reads this through scaleX/scaleY.
@@ -1157,16 +1190,7 @@ export function GraphPaneMixin() {
1157
1190
  if (!bars) {
1158
1191
  return undefined
1159
1192
  }
1160
- const samples = self.walkRowSamples
1161
- const rows = samples
1162
- ? bars.rows
1163
- .filter(r => samples.includes(r.sample))
1164
- .sort(
1165
- (a, b) =>
1166
- samples.indexOf(a.sample) - samples.indexOf(b.sample) ||
1167
- a.label.localeCompare(b.label),
1168
- )
1169
- : bars.rows
1193
+ const rows = filterSamples(bars.rows, self.walkRowSamples)
1170
1194
  const [reference, ...paired] = withCalls(
1171
1195
  [bars.reference, ...rows],
1172
1196
  repeat?.calls,
@@ -1608,8 +1632,7 @@ export function GraphPaneMixin() {
1608
1632
  // open: the span of its contig the bar covers, on the session assembly
1609
1633
  // its haplotype names (the cut's own assembly for the reference row).
1610
1634
  // The label names the haplotype when no assembly is loaded for it.
1611
- walkRowLaunchTarget(index: number) {
1612
- const bars = self.walkRowBars
1635
+ walkRowLaunchTarget(index: number, bars = self.walkRowBars) {
1613
1636
  const row = bars ? [bars.reference, ...bars.rows][index] : undefined
1614
1637
  if (!row?.axis) {
1615
1638
  return undefined
@@ -1740,6 +1763,76 @@ export function GraphPaneMixin() {
1740
1763
  return isLinearHost(view) ? view : undefined
1741
1764
  },
1742
1765
  }))
1766
+ .views(self => ({
1767
+ // Whether the strip of walk rows sits under the drawing: asked for, on a
1768
+ // standalone view whose layout draws nodes, for a graph with walks, and
1769
+ // not while a bubble is popped
1770
+ get walkStripShown() {
1771
+ return (
1772
+ self.walkStrip &&
1773
+ !self.host &&
1774
+ self.modeDrawsNodes &&
1775
+ !self.layoutResult?.tubeMap &&
1776
+ !self.walkRowBars &&
1777
+ self.popStack.length === 0 &&
1778
+ (self.graph?.paths?.length ?? 0) > 1
1779
+ )
1780
+ },
1781
+ // The strip's rows: the cut's, with the sample filter and no repeat
1782
+ // pick, which the Repeat menu only offers in walk rows
1783
+ get walkStripRows() {
1784
+ const graph = self.graph
1785
+ const bars =
1786
+ graph && this.walkStripShown
1787
+ ? walkRows(graph, self.graphRegion)
1788
+ : undefined
1789
+ return bars
1790
+ ? { ...bars, rows: filterSamples(bars.rows, self.walkRowSamples) }
1791
+ : undefined
1792
+ },
1793
+ get walkStripFrame() {
1794
+ const bars = this.walkStripRows
1795
+ if (!bars) {
1796
+ return undefined
1797
+ }
1798
+ const longest = Math.max(
1799
+ ...[bars.reference, ...bars.rows].map(r => r.label.length),
1800
+ )
1801
+ return walkStripFrame(bars, {
1802
+ width: self.paneWidth,
1803
+ maxHeight: WALK_STRIP_CEILING,
1804
+ labelPx: longest * LABEL_CHAR_PX + 16,
1805
+ })
1806
+ },
1807
+ // Whether a cut must follow every snarl a walk leaves the window by, so
1808
+ // walks come back whole: walk rows measure them, and so does the strip
1809
+ get cutsWholeWalks() {
1810
+ return (
1811
+ layoutModeByValue(self.chosenLayoutMode).wholeWalks ||
1812
+ (self.walkStrip && !self.host)
1813
+ )
1814
+ },
1815
+ // Where each walk passes the hovered node, or the selected one
1816
+ get walkStripMarks() {
1817
+ const bars = this.walkStripRows
1818
+ const id = self.hoveredNode ?? self.selectedNode
1819
+ const node = id === null ? undefined : self.nodeById?.get(id)
1820
+ return bars && node && self.graph
1821
+ ? stripMarks(self.graph, [bars.reference, ...bars.rows], node)
1822
+ : []
1823
+ },
1824
+ // Where the drawing has the node a strip point lights, for the ring
1825
+ // that finds it in a hairball
1826
+ get walkStripLocator() {
1827
+ const id = self.stripHover ? self.hoveredNode : null
1828
+ return id === null
1829
+ ? undefined
1830
+ : nodeAnchor(self.nodePositions?.[id], p => ({
1831
+ x: p.x * self.scaleX + self.translateX,
1832
+ y: p.y * self.scaleY + self.translateY,
1833
+ }))
1834
+ },
1835
+ }))
1743
1836
  .views(self => ({
1744
1837
  // Whether the drawing's x is reference bp a host can place. Walk rows
1745
1838
  // put the backbone at its bp too, but their bars are lengths, so the
@@ -2212,6 +2305,7 @@ export function GraphPaneMixin() {
2212
2305
  // `deletions` is and for the same reason — see `referenceRamp`.
2213
2306
  referenceRamp: self.referenceRamp,
2214
2307
  deletions: self.deletionEdgeIndexes,
2308
+ deletionRoutes: self.deletionRoutes,
2215
2309
  hiddenEdges: self.hiddenEdgeIndexes,
2216
2310
  // passed so the shared edge-curve cache can tell a drag from a pan
2217
2311
  version: self.positionsVersion,
@@ -2598,6 +2692,26 @@ export function GraphPaneMixin() {
2598
2692
  setPointerInPane(inside: boolean) {
2599
2693
  self.pointerInPane = inside
2600
2694
  },
2695
+ setWalkStrip(show: boolean) {
2696
+ self.walkStrip = show
2697
+ self.stripHover = null
2698
+ },
2699
+ // The pointer over the strip: the node under it becomes the hovered
2700
+ // node, so the drawing, the tooltip and a linked linear view follow
2701
+ setStripHover(hover: { row: string; offset: number } | null) {
2702
+ self.stripHover = hover
2703
+ const bars = self.walkStripRows
2704
+ const frame = self.walkStripFrame
2705
+ const row =
2706
+ hover && bars
2707
+ ? [bars.reference, ...bars.rows].find(r => r.name === hover.row)
2708
+ : undefined
2709
+ const node =
2710
+ row && frame && self.graph
2711
+ ? segmentAt(self.graph, row, hover!.offset, frame.scaleX)
2712
+ : undefined
2713
+ self.hoveredNode = node ?? null
2714
+ },
2601
2715
  setLegendSize(size: { width: number; height: number }) {
2602
2716
  self.legendSize = size
2603
2717
  },
@@ -2658,12 +2772,10 @@ export function GraphPaneMixin() {
2658
2772
  // the assembly to load to open this node on its own
2659
2773
  // coordinates
2660
2774
  contributingHaplotype: nodeOwnLocation(node)?.haplotype,
2661
- // Every assembly that traverses it, which is a different
2662
- // question and one only a path GFA can answer. Absent on an
2663
- // rGFA rather than approximated, so the two are never
2664
- // confused: there `contributingAssembly` is first-seen, not
2665
- // carriage.
2666
- carriedBy: node.samples?.join(', '),
2775
+ // Every haplotype whose path visits it, which only a path
2776
+ // GFA records. Absent on an rGFA, where
2777
+ // `contributingAssembly` is the first-seen assembly alone.
2778
+ samples: node.samples?.join(', '),
2667
2779
  ...(region && span
2668
2780
  ? {
2669
2781
  refName: region.refName,
@@ -4041,6 +4153,18 @@ export function GraphPaneMixin() {
4041
4153
  self.setShowGenes(!self.showGenes)
4042
4154
  },
4043
4155
  },
4156
+ ...(self.modeDrawsNodes && (self.graph?.paths?.length ?? 0) > 1
4157
+ ? [
4158
+ {
4159
+ type: 'checkbox' as const,
4160
+ label: 'Walk rows under the graph',
4161
+ checked: self.walkStrip,
4162
+ onClick: () => {
4163
+ self.setWalkStrip(!self.walkStrip)
4164
+ },
4165
+ },
4166
+ ]
4167
+ : []),
4044
4168
  ...(self.referenceStripApplies
4045
4169
  ? [
4046
4170
  {
@@ -2,7 +2,8 @@ import { layoutModeByValue } from '@jbrowse/bandage-core/layoutModes'
2
2
  import { readConfObject } from '@jbrowse/core/configuration'
3
3
  import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
4
4
  import { getSession } from '@jbrowse/core/util'
5
- import { types } from '@jbrowse/mobx-state-tree'
5
+ import { addDisposer, types } from '@jbrowse/mobx-state-tree'
6
+ import { reaction } from 'mobx'
6
7
 
7
8
  import { GraphPaneMixin, MAX_GRAPH_REGION_BP, formatSpanBp } from './model'
8
9
  import {
@@ -134,7 +135,7 @@ export default function stateModelFactory() {
134
135
  return self.cutSubgraph(readConfObject(track, 'adapter'), region, {
135
136
  hops: self.subgraphContext,
136
137
  haplotypes: self.subgraphHaplotypes,
137
- ...(layoutModeByValue(self.chosenLayoutMode).wholeWalks
138
+ ...(self.cutsWholeWalks
138
139
  ? { snarls: 'overlapping' as const }
139
140
  : {}),
140
141
  })
@@ -147,13 +148,14 @@ export default function stateModelFactory() {
147
148
  retryLoad() {
148
149
  void self.load()
149
150
  },
150
- // walk rows measures whole walks, which a GBZ cut only follows when
151
- // asked, so entering or leaving it cuts the track again
151
+ // walk rows, and the strip of them under a node layout, measure whole
152
+ // walks, which a GBZ cut only follows when asked, so a change in whether
153
+ // the drawing needs them cuts the track again; the strip's toggle does
154
+ // the same (afterAttach)
152
155
  switchLayout(mode: LayoutModeValue) {
153
- const from = layoutModeByValue(self.chosenLayoutMode)
156
+ const before = self.cutsWholeWalks
154
157
  self.setLayoutMode(mode)
155
- return self.loadedTrackId &&
156
- layoutModeByValue(mode).wholeWalks !== from.wholeWalks
158
+ return self.loadedTrackId && self.cutsWholeWalks !== before
157
159
  ? self.load()
158
160
  : self.recomputeLayout()
159
161
  },
@@ -164,6 +166,20 @@ export default function stateModelFactory() {
164
166
  if (!self.graph) {
165
167
  void self.load()
166
168
  }
169
+ addDisposer(
170
+ self,
171
+ reaction(
172
+ () => self.walkStrip,
173
+ () => {
174
+ if (
175
+ self.loadedTrackId &&
176
+ !layoutModeByValue(self.chosenLayoutMode).wholeWalks
177
+ ) {
178
+ void self.load()
179
+ }
180
+ },
181
+ ),
182
+ )
167
183
  },
168
184
  }))
169
185
  }
@@ -203,7 +203,7 @@ test('getFeatures carries SM:Z: onto the feature', async () => {
203
203
  const private1199 = features.find(f => f.get('name') === '196827')!
204
204
  expect(private1199.get('end') - private1199.get('start')).toBe(1199)
205
205
  expect(private1199.get('samples')).toEqual(['K12.1'])
206
- expect(private1199.get('carriers')).toBe(1)
206
+ expect(private1199.get('sampleCount')).toBe(1)
207
207
 
208
208
  const core = features.find(f => f.get('name') === '196843')!
209
209
  expect(core.get('samples')).toEqual([
@@ -213,10 +213,10 @@ test('getFeatures carries SM:Z: onto the feature', async () => {
213
213
  'NCTC86.1',
214
214
  'IAI39.1',
215
215
  ])
216
- expect(core.get('carriers')).toBe(5)
216
+ expect(core.get('sampleCount')).toBe(5)
217
217
  })
218
218
 
219
- // The other half of the same fixture: the graph route reads carriage off the
219
+ // The other half of the same fixture: the graph route reads the samples off the
220
220
  // synthesized S-line, and rgfaBed.test.ts asserts that from a hand-written row.
221
221
  // This is the same claim against a real indexed file.
222
222
  test('getSubgraph keeps the tag on the S-line it synthesizes', async () => {
@@ -226,16 +226,16 @@ test('getSubgraph keeps the tag on the S-line it synthesizes', async () => {
226
226
  )
227
227
  })
228
228
 
229
- // An rGFA has no sixth column, so the attribute is absent rather than 0 — a
230
- // color expression reading `carriers` on such a track gets undefined, not a
231
- // claim that nothing carries the segment.
232
- test('getFeatures omits carriage on a graph that states none', async () => {
229
+ // An rGFA has no sixth column, so the attribute is absent rather than 0: a
230
+ // color expression reading `sampleCount` on such a track gets undefined, not a
231
+ // count of zero.
232
+ test('getFeatures omits samples on a graph that records none', async () => {
233
233
  const features = await firstValueFrom(
234
234
  makeAdapter().getFeatures(k12).pipe(toArray()),
235
235
  )
236
236
  expect(features.length).toBeGreaterThan(0)
237
237
  expect(features.every(f => f.get('samples') === undefined)).toBe(true)
238
- expect(features.every(f => f.get('carriers') === undefined)).toBe(true)
238
+ expect(features.every(f => f.get('sampleCount') === undefined)).toBe(true)
239
239
  })
240
240
 
241
241
  // Minigraph-Cactus writes PanSN stable names, so HPRC release 2 calls the
@@ -124,11 +124,10 @@ export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAd
124
124
  signal,
125
125
  lineCallback: line => {
126
126
  const segment = parseSegmentLine(line)
127
- // `samples` is who, `carriers` is how many, and the second is not
128
- // just a convenience: a lane colored by carriage is a jexl
129
- // expression in a config, where counting a list means relying on
130
- // jexl resolving `.length` through a member access on an array.
131
- // The count is the axis the color reads, so the index states it.
127
+ // `samples` lists the haplotypes whose paths visit the segment and
128
+ // `sampleCount` counts them. A lane's color reads the count, and
129
+ // a count in jexl would otherwise rely on `.length` resolving
130
+ // through a member access on an array.
132
131
  const samples = segmentSamples(segment)
133
132
  observer.next(
134
133
  new SimpleFeature({
@@ -140,7 +139,7 @@ export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAd
140
139
  type: 'segment',
141
140
  rank: segment.rank,
142
141
  stableName: segment.refName,
143
- ...(samples && { samples, carriers: samples.length }),
142
+ ...(samples && { samples, sampleCount: samples.length }),
144
143
  }),
145
144
  )
146
145
  },
@@ -119,15 +119,15 @@ test('a valid tag beside a malformed one keeps only the valid one', () => {
119
119
  )
120
120
  })
121
121
 
122
- test('segmentSamples reads the carriers off the tag column', () => {
122
+ test('segmentSamples reads the samples off the tag column', () => {
123
123
  const segment = parseSegmentLine(
124
124
  'K12#1#chr\t1004477\t1004500\ts119690\t0\tSM:Z:K12.1,Sakai.1,NCTC86.1',
125
125
  )
126
126
  expect(segmentSamples(segment)).toEqual(['K12.1', 'Sakai.1', 'NCTC86.1'])
127
127
  })
128
128
 
129
- // Undefined, not [], so a lane can tell a graph that states nothing about
130
- // carriage apart from a segment nothing carries.
129
+ // Undefined, not [], so a lane can tell a graph that records no samples from a
130
+ // segment no path visits.
131
131
  test('segmentSamples is undefined when the column says nothing', () => {
132
132
  expect(segmentSamples(parseSegmentLine(RGFA_SEG))).toBeUndefined()
133
133
  expect(segmentSamples(parseSegmentLine(TAGGED_SEG))).toBeUndefined()
@@ -148,11 +148,11 @@ test('segmentSamples finds the tag beside other tags', () => {
148
148
  expect(segmentSamples(segment)).toEqual(['HG002.1', 'HG002.2'])
149
149
  })
150
150
 
151
- // The whole carriage chain in one test, because it spans three files and each
151
+ // The whole samples chain in one test, because it spans three files and each
152
152
  // half is covered on its own: a segs.bed SM:Z: column -> the synthesized S-line
153
- // -> the GFA parser -> GraphNode.samples, which is what model.ts renders as
154
- // `carriedBy` in the node popup. An indexed cut has no P/W lines, so this tag is
155
- // the only statement of carriage it can carry, and nothing else asserts that it
153
+ // -> the GFA parser -> GraphNode.samples, which model.ts renders as `samples`
154
+ // in the node popup. An indexed cut has no P/W lines, so this tag is its only
155
+ // record of which haplotypes visit a segment, and nothing else asserts that it
156
156
  // survives the round trip.
157
157
  test('SM:Z: on a segs row reaches GraphNode.samples', async () => {
158
158
  const { parseGFA } = await import('@jbrowse/bandage-core/gfa-core/index')
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '4.1.1'
1
+ export const version = '4.3.0'
@@ -1,2 +0,0 @@
1
- import{b as R,g as ie}from"./chunk-7BKUWQE5.js";import{a as se,b as ce}from"./chunk-KULNKXPT.js";import{a as Re}from"./chunk-FY3BTKY4.js";var jt=Re((Kn,Oe)=>{Oe.exports=JBrowseExports["tss-react/mui"]});function Kt(e){let t=n=>Math.round(n*100)/100;return e.map((n,r)=>`${r?"L":"M"}${t(n.x)},${t(n.y)}`).join("")}function ue(e,t,n,r,a){let o=Math.hypot(r-t,n-e);if(o===0)return[n,r];let s=(n-e)/o,c=(r-t)/o;return[n+a*s,r+a*c]}function j(e,t){let n=1-t,r=n*n*n,a=3*n*n*t,o=3*n*t*t,s=t*t*t;return{x:r*e.x0+a*e.cx0+o*e.cx1+s*e.x1,y:r*e.y0+a*e.cy0+o*e.cy1+s*e.y1}}function he(e){let t=e[Math.floor(e.length/2)];return t?j(t,.5):void 0}function xe(e){let t=1/0,n=1/0,r=-1/0,a=-1/0;for(let o of e)t=Math.min(t,o.x0,o.cx0,o.cx1,o.x1),r=Math.max(r,o.x0,o.cx0,o.cx1,o.x1),n=Math.min(n,o.y0,o.cy0,o.cy1,o.y1),a=Math.max(a,o.y0,o.cy0,o.cy1,o.y1);return{minX:t,minY:n,maxX:r,maxY:a}}function De(e,t,n){let r=j(e,t),a=j(e,n),o=(n-t)/3,s=de(e,t),c=de(e,n);return{x0:r.x,y0:r.y,cx0:r.x+s.x*o,cy0:r.y+s.y*o,cx1:a.x-c.x*o,cy1:a.y-c.y*o,x1:a.x,y1:a.y}}function de(e,t){let n=1-t,r=3*n*n,a=6*n*t,o=3*t*t;return{x:r*(e.cx0-e.x0)+a*(e.cx1-e.cx0)+o*(e.x1-e.cx1),y:r*(e.cy0-e.y0)+a*(e.cy1-e.cy0)+o*(e.y1-e.cy1)}}function Jt(e,t){let n=[];for(let r of e){let a=Math.hypot(r.cx0-r.x0,r.cy0-r.y0)+Math.hypot(r.cx1-r.cx0,r.cy1-r.cy0)+Math.hypot(r.x1-r.cx1,r.y1-r.cy1),o=Math.max(3,Math.round(a/t)|1);for(let s=0;s<o;s+=2)n.push([De(r,s/o,(s+1)/o)])}return n}function Qt(e,t,n){let r=new Array(e.length);for(let a=0;a<e.length;a++){let o=e[a];r[a]={x0:o.x0+t,y0:o.y0+n,cx0:o.cx0+t,cy0:o.cy0+n,cx1:o.cx1+t,cy1:o.cy1+n,x1:o.x1+t,y1:o.y1+n}}return r}function W(e,t){let n=e.length-1;return t==="end"?{at:e[n],inward:e[n-1]}:{at:e[0],inward:e[1]}}var He=.5,Xe=80;function me(e){let t=1/0,n=-1/0;for(let r of e)t=Math.min(t,r.x),n=Math.max(n,r.x);return{min:t,max:n}}function le(e,t){return Math.max(0,t.min-e,e-t.max)}function pe(e,t,n){let r=le(e[0].x,t),a=le(e[e.length-1].x,t);return r===a?n:r<a?"start":"end"}function ke(e,t){return{from:pe(e,me(t),"end"),to:pe(t,me(e),"start")}}var Fe=.75,ze=1.15,$e=.35,We=60;function Ye(e,t,n,r,a,o){let s=Math.hypot(n-e,r-t),c=s===0?1:(n-e)/s,u=s===0?0:(r-t)/s,d=0,f=1/0,b=-1/0;for(let l of a){let p=l.x-e,x=l.y-t,y=p*-u+x*c;Math.abs(y)>Math.abs(d)&&(d=y);let A=p*c+x*u;f=Math.min(f,A),b=Math.max(b,A)}let i=a.length?b-f:0,h=Math.max(Math.abs(d)*ze/Fe,i*$e),m=o===void 0?h:Math.min(h,o);return d<0?-m:m}function be({scaleX:e,scaleY:t}){return t/e}var Ue=3;function Y(e,t,n){let r=t.x-e.x,a=(t.y-e.y)*n,o=Math.hypot(r,a);return o===0?void 0:{x:r/o,y:a/o}}function Zt(e,t,n,r,a=Ue){let o=be(r),s=Y(e[e.length-1],t[0],o)??Y(e[0],e.at(-1),o)??Y(t[0],t.at(-1),o)??{x:1,y:0},c=[];for(let u=0;u<n;u++){let d=(u-(n-1)/2)*a;c.push({x:-s.y*d,y:s.x*d/o})}return c}function U(e,t){return e.map(n=>({x:n.x,y:n.y*t}))}function je(e,t){for(let n of e)n.y0/=t,n.cy0/=t,n.cy1/=t,n.y1/=t;return e}var Ve=12,qe=40;function fe(e,t){let n=0;for(let o=1;o<e.length;o++)n+=Math.hypot(e[o].x-e[o-1].x,e[o].y-e[o-1].y);let r=n*t;return Math.min(qe,Math.max(Ve,r/2))/Math.max(t,Number.EPSILON)}function en(e){return e.from!==e.to?!1:e.fromStrand&&e.toStrand&&e.fromStrand!==e.toStrand?e.fromStrand==="+"?"end":"start":!0}function Ke(e,t,n,r){let{at:a,inward:o}=e,s=o?a.x-o.x:1,c=o?a.y-o.y:0,u=Math.hypot(s,c)||1,d=s/u,f=c/u,b=a.x+n,i=a.y+r,h=b+d*t/3,m=i+f*t/3,l=b+d*t,p=i+f*t,x=-f*t/2,y=d*t/2;return[{x0:b,y0:i,cx0:h,cy0:m,cx1:l+x,cy1:p+y,x1:l,y1:p},{x0:l,y0:p,cx0:l-x,cy0:p-y,cx1:h,cy1:m,x1:b,y1:i}]}function V(e,t,n,r,a,o,s=[],c){let u=o.scaleX,d=be(o),f=c??(o.pixelRows?We:void 0);if(d!==1)return je(V(U(e,d),U(t,d),n,r,a*d,{scaleX:u,scaleY:u},U(s,d),f===void 0?void 0:f*d),d);if(typeof n=="string")return Ke(W(e,n),fe(e,u),r,a);let b=n?{from:"end",to:"start"}:ke(e,t),i=W(e,b.from),h=W(t,b.to),m=i.at,l=h.at,p=m.x+r,x=m.y+a,y=l.x+r,A=l.y+a;if(n){let L=1,N=0;if(i.inward){let I=m.x-i.inward.x,O=m.y-i.inward.y,C=Math.hypot(I,O);C>0&&(L=I/C,N=O/C)}let g=fe(e,u),E=-N,M=L,S=(m.x+l.x)/2+r+E*g,_=(m.y+l.y)/2+a+M*g,G=p+L*g,w=x+N*g,P=y-L*g,B=A-N*g;return[{x0:p,y0:x,cx0:G,cy0:w,cx1:G+E*g,cy1:w+M*g,x1:S,y1:_},{x0:S,y0:_,cx0:P+E*g,cy0:B+M*g,cx1:P,cy1:B,x1:y,y1:A}]}else{let L=i.inward??{x:m.x-(l.x-m.x),y:m.y-(l.y-m.y)},N=h.inward??{x:l.x-(m.x-l.x),y:l.y-(m.y-l.y)},g=Math.hypot(y-p,A-x),E=(te,ne,Ce,Ie)=>{let re=ne.x-te.x,oe=ne.y-te.y,ae=Math.hypot(re,oe),Te=ae===0?0:(Ce*re+Ie*oe)/ae;return Math.max(0,Math.min(g*He,Xe,Te*.5))},M=l.x-m.x,S=l.y-m.y,[_,G]=ue(L.x,L.y,m.x,m.y,E(L,m,M,S)),[w,P]=ue(N.x,N.y,l.x,l.y,E(N,l,-M,-S)),B=s.length?Ye(p,x,y,A,s,f):0,I=g===0?1:g,O=(y-p)/I,C=(A-x)/I,J=-C*B,Q=O*B,Z=O*Math.abs(B),ee=C*Math.abs(B);return[{x0:p,y0:x,cx0:_+r+J-Z,cy0:G+a+Q-ee,cx1:w+r+J+Z,cy1:P+a+Q+ee,x1:y,y1:A}]}}var Je=1;function Qe(e,t){return t.flatMap(n=>e[n]??[])}function ye(e,t,n){let r=e[t.from],a=e[t.to];return r?.length&&a?.length?V(r,a,t.from===t.to,0,0,n,Qe(e,t.bypassed)):void 0}function Ze(e){let t=new Map;for(let n of e.nodes)if(R(n)){let r=t.get(n.stable.refName);r?r.push(n):t.set(n.stable.refName,[n])}for(let n of t.values())n.sort((r,a)=>r.stable.start-a.stable.start);return t}function et(e,t,n){let r=[];if(e)for(let a=ie(e,t);a<e.length;a++){let o=e[a];if(o.stable.start>=n)break;o.stable.start+o.length<=n&&r.push(o.id)}return r}function tt(e,t,n,r){let{fromStrand:a,toStrand:o}=e;if(a===void 0||o===void 0)return!0;let s=a===(t.stable.strand??"+"),c=o===(n.stable.strand??"+");return r?s&&c:!s&&!c}function on(e){let t=new Map(e.nodes.map(a=>[a.id,a])),n=Ze(e),r=[];for(let a=0;a<e.edges.length;a++){let o=e.edges[a],s=t.get(o.from),c=t.get(o.to);if(s!==void 0&&c!==void 0&&R(s)&&R(c)&&s.stable.refName===c.stable.refName){let u=s.stable.start<=c.stable.start,[d,f]=u?[s,c]:[c,s],b=d.stable.start+d.length,i=f.stable.start,h=i-b;if(h>=Je&&tt(o,s,c,u)){let m=d.stable.refName;r.push({edgeIndex:a,from:o.from,to:o.to,refName:m,start:b,end:i,bp:h,bypassed:et(n.get(m),b,i)})}}}return r}function v(e){return e.endsWith("-")?"-":"+"}function ge(e){return e==="+"?"-":"+"}function nt(e){let t=(e.fromStrand??v(e.from))===v(e.from)?"end":"start",n=(e.toStrand??v(e.to))===v(e.to)?"start":"end";return{from:t,to:n}}function cn(e){let t=e.edges.map(nt),n=new Map,r=(i,h,m)=>{let l=`${i}|${h}`,p=n.get(l);p?p.push(m):n.set(l,[m])};e.edges.forEach((i,h)=>{r(i.from,t[h].from,h),r(i.to,t[h].to,h)});let a=i=>{let h=n.get(`${i}|end`);if(h?.length!==1)return;let m=h[0],l=e.edges[m];return l.from===i&&l.to!==i&&t[m].from==="end"&&t[m].to==="start"&&n.get(`${l.to}|start`)?.length===1?m:void 0},o=new Set,s=new Map,c=new Map;for(let i of e.nodes){let h=n.get(`${i.id}|start`),m=h?.length===1?e.edges[h[0]].from:void 0;if(s.has(i.id)||m!==void 0&&a(m)===h[0])continue;let l=[i.id];for(;;){let p=a(l.at(-1)),x=p===void 0?void 0:e.edges[p].to;if(x===void 0||s.has(x))break;o.add(p),l.push(x)}for(let p of l)s.set(p,i.id);l.length>1&&c.set(i.id,l)}for(let i of e.nodes)s.has(i.id)||s.set(i.id,i.id);let u=new Map(e.nodes.map(i=>[i.id,i])),d=[];for(let i of e.nodes){if(s.get(i.id)!==i.id)continue;let h=c.get(i.id);if(!h){d.push(i);continue}let m=h.map(x=>u.get(x)),l=m.filter(R),p=l.length?l.reduce((x,y)=>x.stable.start<=y.stable.start?x:y):m[0];d.push({id:i.id,name:i.name,length:m.reduce((x,y)=>x+y.length,0),depth:i.depth,stable:p.stable})}let f=new Set,b=[];return e.edges.forEach((i,h)=>{let m=s.get(i.from),l=s.get(i.to),p=t[h],x=`${m}|${p.from}>${l}|${p.to}`;o.has(h)||f.has(x)||(f.add(x),b.push({from:m,to:l,...p.from==="start"?{fromStrand:ge(v(m))}:{},...p.to==="end"?{toStrand:ge(v(l))}:{}}))}),{graph:{...e,nodes:d,edges:b,paths:void 0,pathVisits:void 0},runs:c}}function un(e,t,n){let r={...e};for(let[a,o]of t){let s=e[a];if(!s?.length)continue;delete r[a];let c=K(s),u=c.at(-1),d=o.map(i=>Math.max(n(i),0)),f=d.reduce((i,h)=>i+h,0)||o.length,b=0;o.forEach((i,h)=>{let m=f?d[h]/f*u:u/o.length,l=h===o.length-1?u:b+m;r[i]=Le(s,c,b,l),b=l})}return r}function dn(e,t,n){if(e.length===0)return[];let r=K(e),a=r.at(-1);return Le(e,r,t*a,n*a)}function Ne(e){let t=K(e);return q(e,t,t.at(-1)/2)}function K(e){let t=[0];for(let n=1;n<e.length;n++)t.push(t[n-1]+Math.hypot(e[n].x-e[n-1].x,e[n].y-e[n-1].y));return t}function q(e,t,n){if(n<=0)return{...e[0]};let r=1;for(;r<t.length&&t[r]<n;)r++;if(r>=t.length)return{...e.at(-1)};let a=t[r]-t[r-1],o=a>0?(n-t[r-1])/a:0,s=e[r-1],c=e[r];return{x:s.x+(c.x-s.x)*o,y:s.y+(c.y-s.y)*o}}function Le(e,t,n,r){let a=[q(e,t,n)];for(let o=0;o<e.length;o++)t[o]>n&&t[o]<r&&a.push({...e[o]});return a.push(q(e,t,r)),a}function rt(e){return e.length*6.2+8}function ot(e,t){return e.x0<t.x1&&e.x1>t.x0&&e.y0<t.y1&&e.y1>t.y0}function Me(e,t){return e.x1<=0||e.x0>=t.width||e.y1<=0||e.y0>=t.height}function at(e,t){return e.x0>=0&&e.x1<=t.width&&e.y0>=0&&e.y1<=t.height}function st(e,t=[]){let n=[...t];return r=>Me(r,e)||n.some(a=>ot(a,r))?!1:(n.push(r),!0)}function ln(e,t,n){let r=[],o=(s,c)=>{let u=rt(c),d=s.y>t.height/2?-1:1;for(let f=0;f<=(s.stack??0);f++){let b=s.y+d*f*19,i={x0:s.x-u/2,x1:s.x+u/2,y0:b-11-4,y1:b+4};if(Me(i,t))return;if(at(i,t)&&n(i))return{...s,text:c,y:b,w:u}}};for(let s of e){let c=o(s,s.text)??(s.fallback===void 0?void 0:o(s,s.fallback));c&&r.push(c)}return r}var ct=26,it=4e4,ut=8,dt=8,mt=5.7,lt=6,pt=13,H=3,X=2;function z(e){return(e.length*mt+lt+H)/2}var T=(pt+H)/2,ft=z("1 bp");function k(e,t,n){return{x0:t-e,x1:t+e,y0:n-T,y1:n+T}}function yn(e,t){return{x0:6,x1:6+e.length*6.2+8+H,y0:t-8-H/2,y1:t+8+H/2}}function F(e,t,n){return n<t?e:Math.min(Math.max(e,t),n)}function _e(e){return String(e).replace(/\B(?=(\d{3})+$)/g,",")}function Be(e){return e>=1e6?`${+(e/1e6).toFixed(1)} Mb`:e>=1e3?`${+(e/1e3).toFixed(1)} kb`:`${e} bp`}function Ae(e){return`${Be(e)} del`}function ht(e,t,n,r){let a=e[0],o=e[e.length-1],s=(t.x-(a.x0+o.x1)/2)*n,c=(t.y-(a.y0+o.y1)/2)*r,u=Math.hypot(s,c);return u===0?{x:0,y:-1}:{x:s/u,y:c/u}}function xt(e,t){return Math.abs(t.x)*(e.x1-e.x0)/2+Math.abs(t.y)*(e.y1-e.y0)/2}function bt(e,t){let n=(e.x1-e.x0)/2,r=(e.y1-e.y0)/2;return Math.min(t.x===0?1/0:n/Math.abs(t.x),t.y===0?1/0:r/Math.abs(t.y))}function yt(e,t,n){let r=0;for(let a=1;a<e.length;a++){let o=e[a-1],s=e[a];r+=Math.hypot((s.x-o.x)*t,(s.y-o.y)*n)}return r}function gt(e,t){let n=1/0,r=1/0,a=-1/0,o=-1/0;for(let s of e)for(let c of t[s]??[])n=Math.min(n,c.x),a=Math.max(a,c.x),r=Math.min(r,c.y),o=Math.max(o,c.y);return n===1/0?void 0:{minX:n,minY:r,maxX:a,maxY:o}}var Ee=new WeakMap;function Nt(e,t){let n=Ee.get(e);if(n?.nodeLengths===t)return n.order;let r=Object.entries(e).filter(([a,o])=>o.length>0&&t.has(a)).sort(([a],[o])=>t.get(o)-t.get(a));return Ee.set(e,{nodeLengths:t,order:r}),r}var Se=new WeakMap;function Lt(e,t,n,r){let{scaleX:a,scaleY:o}=n,s=Se.get(e);if(s?.deletions===t&&s.scaleX===a&&s.scaleY===o&&s.version===r)return s.arcs;let c=[];for(let u of[...t].sort((d,f)=>f.bp-d.bp)){let d=ye(e,u,n),f=d?he(d):void 0;if(d&&f){let{minX:b,minY:i,maxX:h,maxY:m}=xe(d);c.push({deletion:u,curves:d,apex:f,extent:Math.max((h-b)*a,(m-i)*o)})}}return Se.set(e,{deletions:t,scaleX:a,scaleY:o,version:r,arcs:c}),c}function gn({nodePositions:e,nodeLengths:t,deletions:n,alleleDeletions:r,axis:a,translateX:o,translateY:s,width:c,height:u,version:d=0}){let{scaleX:f,scaleY:b}=a,i=[],h=[];for(let{deletion:p,curves:x,apex:y,extent:A}of Lt(e,n,a,d))if(A>=ct){let L=Ae(p.bp),N=z(L),g=k(N,0,0),E=y.x*f+o,M=y.y*b+s,S=A>=N*2/X,_=ht(x,y,f,b),G=xt(g,_)+dt,w=S?E:F(E+_.x*G,N,c-N),P=S?M:F(M+_.y*G,T,u-T),B=Math.hypot(w-E,P-M),I;if(B>0){let O={x:(w-E)/B,y:(P-M)/B},C=bt(g,O);I={arcX:E,arcY:M,labelX:w-O.x*C,labelY:P-O.y*C}}i.push({label:{key:`del:${p.edgeIndex}`,text:L,x:w,y:P,kind:"deletion",leader:I},box:k(N,w,P)})}let m=new Set,l=[...r??[]].sort((p,x)=>x.bp-p.bp);for(let p of l){let x=gt(p.nodeIds,e);if(!x)continue;let y=Math.max(x.minX*f+o,0),A=Math.min(x.maxX*f+o,c),L=Math.max(x.minY*b+s,0),N=Math.min(x.maxY*b+s,u);if(A<y||N<L)continue;let g=Math.max(A-y,N-L),E=Ae(p.bp),M=z(E);if(g>=M*2/X){for(let G of p.nodeIds)m.add(G);let S=F((y+A)/2,M,c-M),_=F((L+N)/2,T,u-T);i.push({label:{key:`alleledel:${p.nodeIds.join(",")}`,text:E,x:S,y:_,kind:"deletion"},box:k(M,S,_)})}}for(let[p,x]of Nt(e,t)){if(m.has(p))continue;let{x:y,y:A}=Ne(x),L=A*b+s;if(L+T<=0||L-T>=u)continue;let N=yt(x,f,b);if(N<ft*2/X)continue;let g=Be(t.get(p)),E=z(g);if(N>=E*2/X){let M=y*f+o;h.push({label:{key:`node:${p}`,text:g,x:M,y:L,kind:"node"},box:k(E,M,L)})}}return{deletions:i,nodes:h}}function Nn(e,t){return Math.max(ut,Math.floor(e*t/it))}function Ln(e,t,n=1/0){let r=[];for(let{label:a,box:o}of e){if(r.length>=n)break;t(o)&&r.push(a)}return r}var At=/:(\d+)-\d+$/;function $(e){let t=At.exec(e);return t?{name:e.slice(0,t.index),start:+t[1]}:{name:e,start:0}}function Sn(e,t){let n=[],r=new Map;for(let a of e){let o=se(a.name),s=a.start;for(let c of a.steps){let u={path:a.name,sample:o,start:s,strand:c.strand},d=r.get(c.id);d?d.push(u):r.set(c.id,[u]),s+=t(c.id)}n.push({name:a.name,sample:o,start:a.start,length:s-a.start})}return{anchorPaths:n,pathVisits:r}}function Et(e,t){return e.find(r=>r.sample===t||r.name===t)??e[0]}var St=1;function _t(e,t,n){let r=e;if(t&&t.length>0){let a=t.find(o=>o.path===n)??t[0];r={...e,stable:{refName:a.path,start:a.start,rank:a.path===n?0:St,strand:a.strand},samples:[...new Set(t.map(o=>o.sample))].sort()}}return r}function Bt(e,t){let{anchorPaths:n,pathVisits:r}=e,a=n&&r?Et(n,t):void 0;return a&&r?{...e,nodes:e.nodes.map(o=>_t(o,r.get(o.name),a.name)),anchoredBy:"paths",referencePath:a.name}:e}function _n(e,t){return e.anchoredBy==="tags"?e:Bt(e,t)}function Bn(e,t){if(!e.anchorPaths)return;let n=new Map(e.nodes.map(o=>[o.id,o.length])),r=(o,s=o.length)=>{let c=0;for(let u=0;u<s;u++)c+=n.get(o[u])??0;return c},a=new Map;for(let o of e.paths??[]){let s=$(o.name).name;a.set(s,[...a.get(s)??[],o])}return e.anchorPaths.map(o=>{let s=a.get(o.name),c=s?.length===1?s[0]:void 0,u=c&&t.get(c.name);return c&&u?{...o,start:o.start+r(c.nodeIds,u.first),length:r(u.nodeIds)}:o})}function Cn(e){let t=0;for(let n=0;n<e.length;n++)t=e.charCodeAt(n)+((t<<5)-t);return Math.abs(t%360)}function wt(e,t){return e*360/t}var Pt=.7,Gt=.5,Ot=16;function In(e){return e>0&&e<=Ot}function Ct(e,t){return`hsl(${wt(e,t)}, ${Pt*100}%, ${Gt*100}%)`}function Tn(e,t){return`hsl(0, 0%, ${t>1?25+45*e/(t-1):45}%)`}function It(e){let t=$(e).name.split("#"),n=t.join("#");if(t.length>=3&&ce(t[0])){let r=t.slice(2).join("#");return[r,r,n]}return[t[0],t.slice(0,2).join("#"),n]}function Tt(e,t){let{start:n}=$(e);return t.map(r=>`${r} @${_e(n)}`)}function Rn(e,t){let n=new Map;e.forEach((u,d)=>{n.has(u.name)||n.set(u.name,d)});let r=[...n].map(([u,d])=>({name:u,i:d})),a=r.map(u=>It(u.name)),o=[0,1,2].flatMap(u=>[r.map((d,f)=>a[f][u]),r.map((d,f)=>Tt(d.name,a[f])[u])]),c=o.find(u=>new Set(u).size===r.length)??o.at(-1);return r.map(({name:u,i:d},f)=>({name:u,label:c[f],color:t?.[d]??Ct(d,e.length)}))}function Dn(e,t){return Math.max(e.nodeLengthPerMegabase*t/1e6,e.minimumNodeLength)}function Rt(e,t=5){let r=e.nodes.reduce((o,s)=>o+s.length,0)/1e6,a=Math.max(e.nodes.length*40,500);return{nodeLengthPerMegabase:r>0?a/r:1e4,minimumNodeLength:Math.max(5,t),edgeLength:5,nodeSegmentLength:20}}var D={power:1,effect:1};function vt(e){return e.power===1&&e.effect===1}function Dt(e,t){let n=e.nodes.length,r=e.nodes.reduce((s,c)=>s+c.length,0),a=n>0?r/n:0,o=n>0?Math.max(n*40,500)/n:40;return s=>{if(a<=0)return o;let c=(s/a)**t.power;return Math.max(o*((c-1)*t.effect+1),5)}}var we=1e3,Ht={law:D,minNodeLength:0};function Hn(e,t=Ht){let{law:n,minNodeLength:r}=t,a=Rt(e,r);if(vt(n))return{nodes:e.nodes,opts:a};let o=Dt(e,n);return{nodes:e.nodes.map(s=>({...s,length:Math.round(o(s.length)*we)})),opts:{...a,nodeLengthPerMegabase:1e6/we,minimumNodeLength:5}}}var Pe=[{value:"auto",label:"Proportional",description:"Bandage's own scale: a node draws proportional to its length, so a 1 bp allele is a speck.",law:D,minNodeFactor:0},{value:"open",label:"Open bubbles",description:"Give every allele a drawn length, so a bubble reads as two arms rather than a knot.",law:D,minNodeFactor:2.5},{value:"wide",label:"Wide bubbles",description:"As open, spread further. Best on a window of a dozen bubbles.",law:D,minNodeFactor:10},{value:"compress",label:"Compress lengths",description:"Pull the longest and shortest nodes towards the graph\u2019s mean, so a cut spanning kb and bp fits one pane.",law:{power:.5,effect:.5},minNodeFactor:0}],Fn=Pe.map(e=>e.value);function zn(e){let t=Pe.find(n=>n.value===e);return{law:t?.law??D,minNodeLength:(t?.minNodeFactor??0)*40}}var kt=[{value:"auto",label:"Auto"},{value:"uniform",label:"Uniform"},{value:"random",label:"Random"},{value:"rainbow",label:"Rainbow"},{value:"depth",label:"Depth"},{value:"node-length",label:"Node Length"},{value:"stable-rank",label:"Stable rank"},{value:"reference-position",label:"Reference position"},{value:"grey",label:"Grey"}],Wn=kt.map(e=>e.value);function Yn(e,t){return e!=="auto"?e:t?.anchoredBy?"reference-position":"uniform"}var Ft=[{value:"depth",label:"By depth",description:"Thicker the more paths carry a node, on the square root of its depth against the mean. A graph with one depth everywhere draws uniform."},{value:"uniform",label:"Uniform",description:"Every node the same thickness."}],jn=Ft.map(e=>e.value),zt=.3,Ge=3;function $t(e){let t=0,n=0;for(let r of e.nodes)t+=r.depth*r.length,n+=r.length;return n>0?t/n:0}function Wt(e,t){return!(t>0)||!(e.depth>0)?1:Math.min(Ge,Math.max(zt,Math.sqrt(e.depth/t)))}function Yt(e,t,n,r){return t*(n==="depth"?Wt(e,r):1)}function Ut(e,t){return e*(t==="depth"?Ge:1)}function Vn(e,t,n,r){let a=e?$t(e):0;return{maxHalfWidthPx:Ut(n,r)/2,halfWidthPx:o=>{let s=t?.get(o);return s?Yt(s,n,r,a)/2:n/2}}}export{$ as a,Sn as b,Et as c,Bt as d,_n as e,Bn as f,Kt as g,xe as h,Jt as i,Qt as j,be as k,Zt as l,en as m,V as n,Qe as o,on as p,cn as q,un as r,dn as s,st as t,ln as u,yn as v,_e as w,Be as x,gn as y,Nn as z,Ln as A,Cn as B,wt as C,Pt as D,Gt as E,Ot as F,In as G,Ct as H,Tn as I,Rn as J,Dn as K,Hn as L,Pe as M,Fn as N,zn as O,kt as P,Wn as Q,Yn as R,Ft as S,jn as T,$t as U,Yt as V,Vn as W,jt as X};
2
- //# sourceMappingURL=chunk-ECKLYJWP.js.map