jbrowse-plugin-graphgenomeviewer 4.2.0 → 4.4.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 (33) hide show
  1. package/dist/chunks/{GraphGenomeView-QOBHGMLE.js → GraphGenomeView-NHYVE7GZ.js} +2 -2
  2. package/dist/chunks/{GraphTrackSettingsDialog-RTSJTM6A.js → GraphTrackSettingsDialog-LXYUYPZS.js} +2 -2
  3. package/dist/chunks/{LinearGraphDisplay-TPXW6POY.js → LinearGraphDisplay-742CW5KL.js} +2 -2
  4. package/dist/chunks/chunk-5GUQPO7Y.js +3 -0
  5. package/dist/chunks/chunk-5GUQPO7Y.js.map +7 -0
  6. package/dist/chunks/{chunk-4C4CMI6K.js → chunk-CNFN6EVE.js} +2 -2
  7. package/dist/chunks/chunk-QHMGWDDH.js +2 -0
  8. package/dist/chunks/chunk-QHMGWDDH.js.map +7 -0
  9. package/dist/chunks/chunk-UTWFU4X6.js +2 -0
  10. package/dist/chunks/chunk-UTWFU4X6.js.map +7 -0
  11. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +5 -5
  12. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
  13. package/package.json +2 -2
  14. package/src/GraphGenomeView/components/GraphCanvas.tsx +6 -53
  15. package/src/GraphGenomeView/components/WalkRowContextMenu.tsx +63 -0
  16. package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +1 -1
  17. package/src/GraphGenomeView/components/WalkStrip.tsx +210 -0
  18. package/src/GraphGenomeView/components/WalkStripLocator.tsx +49 -0
  19. package/src/GraphGenomeView/components/usePaneGestures.ts +1 -0
  20. package/src/GraphGenomeView/model.test.ts +42 -3
  21. package/src/GraphGenomeView/model.ts +226 -105
  22. package/src/GraphGenomeView/viewModel.ts +23 -7
  23. package/src/version.ts +1 -1
  24. package/dist/chunks/chunk-ECKLYJWP.js +0 -2
  25. package/dist/chunks/chunk-ECKLYJWP.js.map +0 -7
  26. package/dist/chunks/chunk-HW55QCJG.js +0 -3
  27. package/dist/chunks/chunk-HW55QCJG.js.map +0 -7
  28. package/dist/chunks/chunk-SIP5IHWK.js +0 -2
  29. package/dist/chunks/chunk-SIP5IHWK.js.map +0 -7
  30. /package/dist/chunks/{GraphGenomeView-QOBHGMLE.js.map → GraphGenomeView-NHYVE7GZ.js.map} +0 -0
  31. /package/dist/chunks/{GraphTrackSettingsDialog-RTSJTM6A.js.map → GraphTrackSettingsDialog-LXYUYPZS.js.map} +0 -0
  32. /package/dist/chunks/{LinearGraphDisplay-TPXW6POY.js.map → LinearGraphDisplay-742CW5KL.js.map} +0 -0
  33. /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,
@@ -298,6 +303,22 @@ const VIEWPORT_PANES_BUILT = 1
298
303
  // How many walk rows read their haplotype's genes, the first rows down
299
304
  const WALK_GENE_ROWS = 40
300
305
 
306
+ // The rows a sample filter keeps, in the order it names them
307
+ function filterSamples<R extends { sample: string; label: string }>(
308
+ rows: R[],
309
+ samples: string[] | undefined,
310
+ ) {
311
+ return samples
312
+ ? rows
313
+ .filter(r => samples.includes(r.sample))
314
+ .sort(
315
+ (a, b) =>
316
+ samples.indexOf(a.sample) - samples.indexOf(b.sample) ||
317
+ a.label.localeCompare(b.label),
318
+ )
319
+ : rows
320
+ }
321
+
301
322
  // What the canvas draws under the tube map, whose ink is all TubeMapOverlay's
302
323
  const EMPTY_BATCH: RenderBatch = {
303
324
  nodeStrokes: [],
@@ -494,7 +515,7 @@ export function GraphPaneMixin() {
494
515
  // with a label that opens it. Off by default: on a base-level cut
495
516
  // every SNP's halo is a blob.
496
517
  showBubbles: types.optional(types.boolean, false),
497
- showDeletionEdges: types.optional(types.boolean, false),
518
+ showDeletionEdges: types.optional(types.boolean, true),
498
519
  // The session's genes drawn onto the backbone: exons along the nodes
499
520
  // that carry them, names pinned at their midpoints. See genes/.
500
521
  showGenes: types.optional(types.boolean, true),
@@ -517,6 +538,9 @@ export function GraphPaneMixin() {
517
538
  // Samples whose walks the walk rows show, by the name before the
518
539
  // haplotype number; undefined shows every walk the cut holds.
519
540
  walkRowSamples: types.maybe(types.frozen<string[]>()),
541
+ // Walk rows in a strip under a layout that draws nodes, linked to the
542
+ // drawing: a node's passes tick each bar, a bar's point lights its node
543
+ walkStrip: types.optional(types.boolean, false),
520
544
  // Walks lifted out of the drawing, each a layer with a lane of its own
521
545
  // and the rest fading, coloured by the encoding it states or by the
522
546
  // default one. See walkEncoding.ts. Empty lifts none.
@@ -622,6 +646,8 @@ export function GraphPaneMixin() {
622
646
  // whether the pointer is over this pane, whose own hit test then says
623
647
  // what it is on
624
648
  pointerInPane: false,
649
+ // the strip point the pointer is over, which lights its node above
650
+ stripHover: null as { row: string; offset: number } | null,
625
651
  hoveredEdge: null as number | null,
626
652
  selectedNode: null as string | null,
627
653
  viewportDirty: 0,
@@ -966,6 +992,9 @@ export function GraphPaneMixin() {
966
992
  get alleleDeletions() {
967
993
  return self.layoutResult?.alleleDeletions ?? []
968
994
  },
995
+ get deletionRoutes() {
996
+ return self.layoutResult?.deletionRoutes
997
+ },
969
998
  // Whether the current layout states y in screen px rather than in the same
970
999
  // units as x (LayoutResult.pixelRows). Everything that has to put the two
971
1000
  // axes in one expression reads this through scaleX/scaleY.
@@ -1157,16 +1186,7 @@ export function GraphPaneMixin() {
1157
1186
  if (!bars) {
1158
1187
  return undefined
1159
1188
  }
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
1189
+ const rows = filterSamples(bars.rows, self.walkRowSamples)
1170
1190
  const [reference, ...paired] = withCalls(
1171
1191
  [bars.reference, ...rows],
1172
1192
  repeat?.calls,
@@ -1527,12 +1547,163 @@ export function GraphPaneMixin() {
1527
1547
  },
1528
1548
  }))
1529
1549
  .views(self => ({
1530
- // The gene reads walk rows need: for each row whose haplotype
1550
+ // The contributors a view can actually be opened on: those naming an
1551
+ // assembly this session has loaded. Every strain of an E. coli pangenome
1552
+ // demo is its own assembly, so all of them resolve; an HPRC graph names
1553
+ // hundreds of haplotypes that no session loads, so only the reference
1554
+ // does.
1555
+ get launchableAssemblies() {
1556
+ return resolveContributors(
1557
+ withReferenceRegion(self.contributingAssemblies, self.graphRegion),
1558
+ self.assemblyResolver,
1559
+ )
1560
+ },
1561
+ // Whether there is a linear view this graph may draw a highlight into —
1562
+ // the paired one, or the session's only one on the reference assembly.
1563
+ // Read by the node menu, which offers the item only when it would land
1564
+ // somewhere.
1565
+ get canHighlightInLinearView() {
1566
+ const region = self.graphRegion
1567
+ return (
1568
+ region !== undefined &&
1569
+ linearViewTarget({
1570
+ views: [...getSession(self).views],
1571
+ connectedViewId: self.connectedViewId,
1572
+ assemblyName: region.assemblyName,
1573
+ }) !== undefined
1574
+ )
1575
+ },
1576
+ // Where one node can be opened: on its own assembly, and on the reference
1577
+ // the graph was cut against. Both are padded to a readable window — a
1578
+ // base-level allele is a few bp, and a linear view framed on exactly that
1579
+ // shows no context at all.
1580
+ nodeLaunchTargets(nodeId: string) {
1581
+ const node = self.nodeById?.get(nodeId)
1582
+ const own = node ? nodeOwnLocation(node) : undefined
1583
+ const ownAssembly = own
1584
+ ? resolveLocationAssembly(self.assemblyResolver, own)
1585
+ : undefined
1586
+ const region = self.graphRegion
1587
+ const nodeById = self.nodeById
1588
+ const neighbors = self.nodeNeighbors
1589
+ const span =
1590
+ region && nodeById && neighbors
1591
+ ? nodeReferenceSpan({ nodeId, nodeById, neighbors })
1592
+ : undefined
1593
+ const onReference =
1594
+ region && span
1595
+ ? {
1596
+ sample: region.assemblyName,
1597
+ haplotype: undefined,
1598
+ refName: region.refName,
1599
+ ...span,
1600
+ }
1601
+ : undefined
1602
+ return {
1603
+ own:
1604
+ own && ownAssembly
1605
+ ? { location: paddedLocation(own), assembly: ownAssembly }
1606
+ : undefined,
1607
+ reference:
1608
+ region && onReference
1609
+ ? {
1610
+ location: paddedLocation(onReference),
1611
+ assembly: region.assemblyName,
1612
+ }
1613
+ : undefined,
1614
+ // Unpadded: a mark is of the node, the same span its hover band
1615
+ // draws. The padding above is room to read a view opened on it.
1616
+ highlight:
1617
+ region && onReference
1618
+ ? { location: onReference, assembly: region.assemblyName }
1619
+ : undefined,
1620
+ }
1621
+ },
1622
+ }))
1623
+ .views(self => ({
1624
+ // The linear view this pane draws inside, when a display hosts it: x is
1625
+ // that view's window and the cut is re-made when the window leaves it.
1626
+ // A pane that is a view of its own has none.
1627
+ get host(): LinearHost | undefined {
1628
+ let view: unknown
1629
+ try {
1630
+ view = getContainingView(self)
1631
+ } catch {
1632
+ return undefined
1633
+ }
1634
+ return isLinearHost(view) ? view : undefined
1635
+ },
1636
+ }))
1637
+ .views(self => ({
1638
+ // Whether the strip of walk rows sits under the drawing: asked for, on a
1639
+ // standalone view whose layout draws nodes, for a graph with walks, and
1640
+ // not while a bubble is popped
1641
+ get walkStripShown() {
1642
+ return (
1643
+ self.walkStrip &&
1644
+ !self.host &&
1645
+ self.modeDrawsNodes &&
1646
+ !self.layoutResult?.tubeMap &&
1647
+ !self.walkRowBars &&
1648
+ self.popStack.length === 0 &&
1649
+ (self.graph?.paths?.length ?? 0) > 1
1650
+ )
1651
+ },
1652
+ // The strip's rows: the cut's, with the sample filter and no repeat
1653
+ // pick, which the Repeat menu only offers in walk rows
1654
+ get walkStripRows() {
1655
+ const graph = self.graph
1656
+ const bars =
1657
+ graph && this.walkStripShown
1658
+ ? walkRows(graph, self.graphRegion)
1659
+ : undefined
1660
+ return bars
1661
+ ? { ...bars, rows: filterSamples(bars.rows, self.walkRowSamples) }
1662
+ : undefined
1663
+ },
1664
+ get walkStripFrame() {
1665
+ const bars = this.walkStripRows
1666
+ if (!bars) {
1667
+ return undefined
1668
+ }
1669
+ return walkStripFrame(bars, { width: self.paneWidth })
1670
+ },
1671
+ // Whether a cut must follow every snarl a walk leaves the window by, so
1672
+ // walks come back whole: walk rows measure them, and so does the strip
1673
+ get cutsWholeWalks() {
1674
+ return (
1675
+ layoutModeByValue(self.chosenLayoutMode).wholeWalks ||
1676
+ (self.walkStrip && !self.host)
1677
+ )
1678
+ },
1679
+ // Where each walk passes the hovered node, or the selected one
1680
+ get walkStripMarks() {
1681
+ const bars = this.walkStripRows
1682
+ const id = self.hoveredNode ?? self.selectedNode
1683
+ const node = id === null ? undefined : self.nodeById?.get(id)
1684
+ return bars && node && self.graph
1685
+ ? stripMarks(self.graph, [bars.reference, ...bars.rows], node)
1686
+ : []
1687
+ },
1688
+ // Where the drawing has the node a strip point lights, for the ring
1689
+ // that finds it in a hairball
1690
+ get walkStripLocator() {
1691
+ const id = self.stripHover ? self.hoveredNode : null
1692
+ return id === null
1693
+ ? undefined
1694
+ : nodeAnchor(self.nodePositions?.[id], p => ({
1695
+ x: p.x * self.scaleX + self.translateX,
1696
+ y: p.y * self.scaleY + self.translateY,
1697
+ }))
1698
+ },
1699
+ }))
1700
+ .views(self => ({
1701
+ // The gene reads walk rows need, as the layout or the strip: for each row whose haplotype
1531
1702
  // (`HG00097#1`) names an assembly with a gene track, that track over the
1532
1703
  // span of its own contig the row's bar covers. The first WALK_GENE_ROWS
1533
1704
  // such rows are read, and the rest counted for the key.
1534
1705
  get walkGeneReads() {
1535
- const bars = self.walkRowBars
1706
+ const bars = self.walkRowBars ?? self.walkStripRows
1536
1707
  if (!bars || !self.showGenes) {
1537
1708
  return undefined
1538
1709
  }
@@ -1608,8 +1779,7 @@ export function GraphPaneMixin() {
1608
1779
  // open: the span of its contig the bar covers, on the session assembly
1609
1780
  // its haplotype names (the cut's own assembly for the reference row).
1610
1781
  // The label names the haplotype when no assembly is loaded for it.
1611
- walkRowLaunchTarget(index: number) {
1612
- const bars = self.walkRowBars
1782
+ walkRowLaunchTarget(index: number, bars = self.walkRowBars) {
1613
1783
  const row = bars ? [bars.reference, ...bars.rows][index] : undefined
1614
1784
  if (!row?.axis) {
1615
1785
  return undefined
@@ -1639,7 +1809,7 @@ export function GraphPaneMixin() {
1639
1809
  // Each row's genes as offsets along its bar: the reference row's from
1640
1810
  // the backbone's gene track, every other row's from its own assembly
1641
1811
  get walkRowGenes() {
1642
- const bars = self.walkRowBars
1812
+ const bars = self.walkRowBars ?? self.walkStripRows
1643
1813
  if (!bars || !self.showGenes) {
1644
1814
  return undefined
1645
1815
  }
@@ -1652,94 +1822,6 @@ export function GraphPaneMixin() {
1652
1822
  : undefined
1653
1823
  },
1654
1824
  }))
1655
- .views(self => ({
1656
- // The contributors a view can actually be opened on: those naming an
1657
- // assembly this session has loaded. Every strain of an E. coli pangenome
1658
- // demo is its own assembly, so all of them resolve; an HPRC graph names
1659
- // hundreds of haplotypes that no session loads, so only the reference
1660
- // does.
1661
- get launchableAssemblies() {
1662
- return resolveContributors(
1663
- withReferenceRegion(self.contributingAssemblies, self.graphRegion),
1664
- self.assemblyResolver,
1665
- )
1666
- },
1667
- // Whether there is a linear view this graph may draw a highlight into —
1668
- // the paired one, or the session's only one on the reference assembly.
1669
- // Read by the node menu, which offers the item only when it would land
1670
- // somewhere.
1671
- get canHighlightInLinearView() {
1672
- const region = self.graphRegion
1673
- return (
1674
- region !== undefined &&
1675
- linearViewTarget({
1676
- views: [...getSession(self).views],
1677
- connectedViewId: self.connectedViewId,
1678
- assemblyName: region.assemblyName,
1679
- }) !== undefined
1680
- )
1681
- },
1682
- // Where one node can be opened: on its own assembly, and on the reference
1683
- // the graph was cut against. Both are padded to a readable window — a
1684
- // base-level allele is a few bp, and a linear view framed on exactly that
1685
- // shows no context at all.
1686
- nodeLaunchTargets(nodeId: string) {
1687
- const node = self.nodeById?.get(nodeId)
1688
- const own = node ? nodeOwnLocation(node) : undefined
1689
- const ownAssembly = own
1690
- ? resolveLocationAssembly(self.assemblyResolver, own)
1691
- : undefined
1692
- const region = self.graphRegion
1693
- const nodeById = self.nodeById
1694
- const neighbors = self.nodeNeighbors
1695
- const span =
1696
- region && nodeById && neighbors
1697
- ? nodeReferenceSpan({ nodeId, nodeById, neighbors })
1698
- : undefined
1699
- const onReference =
1700
- region && span
1701
- ? {
1702
- sample: region.assemblyName,
1703
- haplotype: undefined,
1704
- refName: region.refName,
1705
- ...span,
1706
- }
1707
- : undefined
1708
- return {
1709
- own:
1710
- own && ownAssembly
1711
- ? { location: paddedLocation(own), assembly: ownAssembly }
1712
- : undefined,
1713
- reference:
1714
- region && onReference
1715
- ? {
1716
- location: paddedLocation(onReference),
1717
- assembly: region.assemblyName,
1718
- }
1719
- : undefined,
1720
- // Unpadded: a mark is of the node, the same span its hover band
1721
- // draws. The padding above is room to read a view opened on it.
1722
- highlight:
1723
- region && onReference
1724
- ? { location: onReference, assembly: region.assemblyName }
1725
- : undefined,
1726
- }
1727
- },
1728
- }))
1729
- .views(self => ({
1730
- // The linear view this pane draws inside, when a display hosts it: x is
1731
- // that view's window and the cut is re-made when the window leaves it.
1732
- // A pane that is a view of its own has none.
1733
- get host(): LinearHost | undefined {
1734
- let view: unknown
1735
- try {
1736
- view = getContainingView(self)
1737
- } catch {
1738
- return undefined
1739
- }
1740
- return isLinearHost(view) ? view : undefined
1741
- },
1742
- }))
1743
1825
  .views(self => ({
1744
1826
  // Whether the drawing's x is reference bp a host can place. Walk rows
1745
1827
  // put the backbone at its bp too, but their bars are lengths, so the
@@ -2212,6 +2294,7 @@ export function GraphPaneMixin() {
2212
2294
  // `deletions` is and for the same reason — see `referenceRamp`.
2213
2295
  referenceRamp: self.referenceRamp,
2214
2296
  deletions: self.deletionEdgeIndexes,
2297
+ deletionRoutes: self.deletionRoutes,
2215
2298
  hiddenEdges: self.hiddenEdgeIndexes,
2216
2299
  // passed so the shared edge-curve cache can tell a drag from a pan
2217
2300
  version: self.positionsVersion,
@@ -2301,6 +2384,7 @@ export function GraphPaneMixin() {
2301
2384
  ? self.walkLayers.map(l => (l.color ? l : l.walk))
2302
2385
  : undefined,
2303
2386
  facet: self.walkLayers.length > 1 ? self.facetSpec : undefined,
2387
+ walkStrip: self.walkStripShown || undefined,
2304
2388
  width: self.paneWidth,
2305
2389
  height: self.paneCeiling,
2306
2390
  colorScheme: self.chosenColorScheme,
@@ -2319,7 +2403,7 @@ export function GraphPaneMixin() {
2319
2403
  : `${layoutModeByValue(self.chosenLayoutMode).label} draws a picture of its own, which the SVG export does not`
2320
2404
  },
2321
2405
  // The drawing as a standalone SVG, fitted, with its genes, its lifted
2322
- // walks' keys and facet panels; see figureSvg
2406
+ // walks' keys, facet panels and the strip of walk rows; see figureSvg
2323
2407
  figure() {
2324
2408
  const { graph, layoutResult } = self
2325
2409
  return graph && layoutResult && !this.figureUnavailable
@@ -2340,6 +2424,11 @@ export function GraphPaneMixin() {
2340
2424
  walkRows: self.walkRowBars,
2341
2425
  rowGenes: self.walkRowGenes,
2342
2426
  rowGeneGaps: self.walkRowGeneGaps,
2427
+ walkStrip: self.walkStripRows && {
2428
+ rows: self.walkStripRows,
2429
+ rowGenes: self.walkRowGenes,
2430
+ rowGeneGaps: self.walkRowGeneGaps,
2431
+ },
2343
2432
  spec: this.figureSpec(),
2344
2433
  })
2345
2434
  : undefined
@@ -2598,6 +2687,26 @@ export function GraphPaneMixin() {
2598
2687
  setPointerInPane(inside: boolean) {
2599
2688
  self.pointerInPane = inside
2600
2689
  },
2690
+ setWalkStrip(show: boolean) {
2691
+ self.walkStrip = show
2692
+ self.stripHover = null
2693
+ },
2694
+ // The pointer over the strip: the node under it becomes the hovered
2695
+ // node, so the drawing, the tooltip and a linked linear view follow
2696
+ setStripHover(hover: { row: string; offset: number } | null) {
2697
+ self.stripHover = hover
2698
+ const bars = self.walkStripRows
2699
+ const frame = self.walkStripFrame
2700
+ const row =
2701
+ hover && bars
2702
+ ? [bars.reference, ...bars.rows].find(r => r.name === hover.row)
2703
+ : undefined
2704
+ const node =
2705
+ row && frame && self.graph
2706
+ ? segmentAt(self.graph, row, hover!.offset, frame.scaleX)
2707
+ : undefined
2708
+ self.hoveredNode = node ?? null
2709
+ },
2601
2710
  setLegendSize(size: { width: number; height: number }) {
2602
2711
  self.legendSize = size
2603
2712
  },
@@ -4039,6 +4148,18 @@ export function GraphPaneMixin() {
4039
4148
  self.setShowGenes(!self.showGenes)
4040
4149
  },
4041
4150
  },
4151
+ ...(self.modeDrawsNodes && (self.graph?.paths?.length ?? 0) > 1
4152
+ ? [
4153
+ {
4154
+ type: 'checkbox' as const,
4155
+ label: 'Walk rows under the graph',
4156
+ checked: self.walkStrip,
4157
+ onClick: () => {
4158
+ self.setWalkStrip(!self.walkStrip)
4159
+ },
4160
+ },
4161
+ ]
4162
+ : []),
4042
4163
  ...(self.referenceStripApplies
4043
4164
  ? [
4044
4165
  {
@@ -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
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '4.2.0'
1
+ export const version = '4.4.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