jbrowse-plugin-graphgenomeviewer 4.3.0 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jbrowse-plugin-graphgenomeviewer",
3
- "version": "4.3.0",
3
+ "version": "4.4.1",
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": {
@@ -76,7 +76,7 @@
76
76
  "typescript-eslint": "^8.70.1",
77
77
  "typescript7": "npm:typescript@^7.0.2",
78
78
  "vitest": "^5.0.2",
79
- "@jbrowse/bandage-core": "4.3.0"
79
+ "@jbrowse/bandage-core": "4.4.1"
80
80
  },
81
81
  "peerDependencies": {
82
82
  "react": ">=18.0.0",
@@ -6,8 +6,10 @@ import {
6
6
  walkRowsTree,
7
7
  } from '@jbrowse/bandage-core/layout/walkRowDraw'
8
8
  import {
9
+ stripGeneGaps,
9
10
  stripRowAt,
10
11
  walkMarksTree,
12
+ walkStripLabelsTree,
11
13
  } from '@jbrowse/bandage-core/layout/walkStrip'
12
14
  import { RAMP_GRADIENT_CSS } from '@jbrowse/bandage-core/referenceRampCss'
13
15
  import { isAlive } from '@jbrowse/mobx-state-tree'
@@ -19,6 +21,7 @@ import { Swatch } from './WalkRowsOverlay'
19
21
  import { legendRowStyle } from './legendStyles'
20
22
 
21
23
  import type { GraphPaneModel } from '../model'
24
+ import type { RowGene } from '@jbrowse/bandage-core/layout/walkRowDraw'
22
25
  import type { WalkRows } from '@jbrowse/bandage-core/layout/walkRows'
23
26
  import type { StripFrame } from '@jbrowse/bandage-core/layout/walkStrip'
24
27
 
@@ -50,20 +53,23 @@ const tipStyle = {
50
53
  zIndex: 4,
51
54
  }
52
55
 
53
- // The bars, built once per rows, frame and colouring rather than per hover
56
+ // The bars, built once per rows, frame, colouring and genes rather than per
57
+ // hover
54
58
  const StripBars = observer(function StripBars({
55
59
  bars,
56
60
  frame,
57
61
  ramp,
62
+ rowGenes,
58
63
  }: {
59
64
  bars: WalkRows
60
65
  frame: StripFrame
61
66
  ramp: { start: number; end: number } | undefined
67
+ rowGenes: Map<string, RowGene[]> | undefined
62
68
  }) {
63
69
  const idPrefix = useId().replace(/[^\w-]/g, '')
64
70
  const tree = useMemo(
65
- () => walkRowsTree(bars, frame, { ramp, idPrefix }),
66
- [bars, frame, ramp, idPrefix],
71
+ () => walkRowsTree(bars, frame, { ramp, rowGenes, idPrefix }),
72
+ [bars, frame, ramp, rowGenes, idPrefix],
67
73
  )
68
74
  return <ElTree el={tree} />
69
75
  })
@@ -84,7 +90,6 @@ const WalkStrip = observer(function WalkStrip({
84
90
  if (!bars || !frame) {
85
91
  return null
86
92
  }
87
- const rows = [bars.reference, ...bars.rows]
88
93
  const hit = (e: React.MouseEvent<SVGSVGElement>) => {
89
94
  const box = e.currentTarget.getBoundingClientRect()
90
95
  const x = e.clientX - box.left
@@ -94,6 +99,7 @@ const WalkStrip = observer(function WalkStrip({
94
99
  const key = walkRowsKey(bars, {
95
100
  ramp: model.referenceRampDomain,
96
101
  rampCss: RAMP_GRADIENT_CSS,
102
+ genes: stripGeneGaps(frame, model.walkRowGeneGaps, model.walkRowGenes),
97
103
  })
98
104
  return (
99
105
  <div
@@ -149,25 +155,20 @@ const WalkStrip = observer(function WalkStrip({
149
155
  }
150
156
  }}
151
157
  >
152
- <StripBars bars={bars} frame={frame} ramp={model.referenceRampDomain} />
158
+ <StripBars
159
+ bars={bars}
160
+ frame={frame}
161
+ ramp={model.referenceRampDomain}
162
+ rowGenes={model.walkRowGenes}
163
+ />
153
164
  <ElTree el={walkMarksTree(bars, frame, model.walkStripMarks)} />
154
- {frame.labelled
155
- ? rows.map((row, i) => (
156
- <text
157
- key={row.name}
158
- x={6}
159
- y={i * frame.rowPx + frame.translateY + 4}
160
- fontSize={11}
161
- fontFamily="sans-serif"
162
- fill="#333"
163
- fontWeight={
164
- model.walkLayers.some(l => l.walk === row.name) ? 600 : 400
165
- }
166
- >
167
- {row.label}
168
- </text>
169
- ))
170
- : null}
165
+ <ElTree
166
+ el={walkStripLabelsTree(
167
+ bars,
168
+ frame,
169
+ new Set(model.walkLayers.map(l => l.walk)),
170
+ )}
171
+ />
171
172
  </svg>
172
173
  {tip ? (
173
174
  <div style={{ ...tipStyle, left: tip.x + 12, top: tip.y - 18 }}>
@@ -52,7 +52,6 @@ import {
52
52
  modeUsesLayoutEngine,
53
53
  } from '@jbrowse/bandage-core/layoutModes'
54
54
  import { NODE_WIDTH_VALUES, nodeInk } from '@jbrowse/bandage-core/nodeWidths'
55
- import { LABEL_CHAR_PX } from '@jbrowse/bandage-core/overlayLabels'
56
55
  import {
57
56
  anchorFromPaths,
58
57
  chooseReferencePath,
@@ -303,9 +302,6 @@ const VIEWPORT_DEBOUNCE_MS = 150
303
302
  const VIEWPORT_PANES_BUILT = 1
304
303
  // How many walk rows read their haplotype's genes, the first rows down
305
304
  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
305
 
310
306
  // The rows a sample filter keeps, in the order it names them
311
307
  function filterSamples<R extends { sample: string; label: string }>(
@@ -1550,131 +1546,6 @@ export function GraphPaneMixin() {
1550
1546
  )
1551
1547
  },
1552
1548
  }))
1553
- .views(self => ({
1554
- // The gene reads walk rows need: for each row whose haplotype
1555
- // (`HG00097#1`) names an assembly with a gene track, that track over the
1556
- // span of its own contig the row's bar covers. The first WALK_GENE_ROWS
1557
- // such rows are read, and the rest counted for the key.
1558
- get walkGeneReads() {
1559
- const bars = self.walkRowBars
1560
- if (!bars || !self.showGenes) {
1561
- return undefined
1562
- }
1563
- const reads: {
1564
- row: string
1565
- trackId: string
1566
- region: SubgraphRegion
1567
- }[] = []
1568
- let untracked = 0
1569
- let unplaced = 0
1570
- for (const row of bars.rows) {
1571
- if (!row.axis) {
1572
- unplaced++
1573
- continue
1574
- }
1575
- const assemblyName = resolveLocationAssembly(self.assemblyResolver, {
1576
- sample: row.sample,
1577
- haplotype:
1578
- row.haplotype === undefined
1579
- ? undefined
1580
- : `${row.sample}#${row.haplotype}`,
1581
- })
1582
- const track = assemblyName
1583
- ? self.geneTracksByAssembly.get(assemblyName)
1584
- : undefined
1585
- if (!assemblyName || !track) {
1586
- untracked++
1587
- continue
1588
- }
1589
- reads.push({
1590
- row: row.name,
1591
- trackId: track.trackId,
1592
- region: {
1593
- assemblyName,
1594
- refName: row.axis.contig,
1595
- ...rowSpan(row.axis, row.bp),
1596
- },
1597
- })
1598
- }
1599
- return {
1600
- reads: reads.slice(0, WALK_GENE_ROWS),
1601
- gaps: {
1602
- untracked,
1603
- unplaced,
1604
- unread: Math.max(0, reads.length - WALK_GENE_ROWS),
1605
- },
1606
- }
1607
- },
1608
- get walkRowGeneGaps() {
1609
- return this.walkGeneReads?.gaps
1610
- },
1611
- // The walk row under a pane point, as its index among the reference row
1612
- // and the rows below it, where the point is on its bar
1613
- walkRowAt(screenX: number, screenY: number) {
1614
- const bars = self.walkRowBars
1615
- if (!bars) {
1616
- return undefined
1617
- }
1618
- const i = Math.round(
1619
- (screenY - self.translateY) / (ROW_HEIGHT_PX * self.scaleY),
1620
- )
1621
- const row = [bars.reference, ...bars.rows][i]
1622
- const y = i * ROW_HEIGHT_PX * self.scaleY + self.translateY
1623
- const bp = (screenX - self.translateX) / self.scaleX - bars.origin
1624
- return row &&
1625
- Math.abs(screenY - y) <= WALK_BAR_PX / 2 + 2 &&
1626
- bp >= 0 &&
1627
- bp <= row.bp
1628
- ? i
1629
- : undefined
1630
- },
1631
- // Where a walk row's bar lies in its own assembly, for a linear view to
1632
- // open: the span of its contig the bar covers, on the session assembly
1633
- // its haplotype names (the cut's own assembly for the reference row).
1634
- // The label names the haplotype when no assembly is loaded for it.
1635
- walkRowLaunchTarget(index: number, bars = self.walkRowBars) {
1636
- const row = bars ? [bars.reference, ...bars.rows][index] : undefined
1637
- if (!row?.axis) {
1638
- return undefined
1639
- }
1640
- const haplotype =
1641
- row.haplotype === undefined
1642
- ? undefined
1643
- : `${row.sample}#${row.haplotype}`
1644
- const assembly =
1645
- index === 0
1646
- ? self.graphRegion?.assemblyName
1647
- : resolveLocationAssembly(self.assemblyResolver, {
1648
- sample: row.sample,
1649
- haplotype,
1650
- })
1651
- return {
1652
- label: row.label,
1653
- assembly,
1654
- location: {
1655
- sample: row.sample,
1656
- haplotype,
1657
- refName: row.axis.contig,
1658
- ...rowSpan(row.axis, row.bp),
1659
- },
1660
- }
1661
- },
1662
- // Each row's genes as offsets along its bar: the reference row's from
1663
- // the backbone's gene track, every other row's from its own assembly
1664
- get walkRowGenes() {
1665
- const bars = self.walkRowBars
1666
- if (!bars || !self.showGenes) {
1667
- return undefined
1668
- }
1669
- const byRow = new Map(self.walkGeneFeatures ?? [])
1670
- if (self.geneFeatures) {
1671
- byRow.set(bars.reference.name, self.geneFeatures)
1672
- }
1673
- return byRow.size
1674
- ? placeRowGenes([bars.reference, ...bars.rows], byRow)
1675
- : undefined
1676
- },
1677
- }))
1678
1549
  .views(self => ({
1679
1550
  // The contributors a view can actually be opened on: those naming an
1680
1551
  // assembly this session has loaded. Every strain of an E. coli pangenome
@@ -1795,14 +1666,7 @@ export function GraphPaneMixin() {
1795
1666
  if (!bars) {
1796
1667
  return undefined
1797
1668
  }
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
- })
1669
+ return walkStripFrame(bars, { width: self.paneWidth })
1806
1670
  },
1807
1671
  // Whether a cut must follow every snarl a walk leaves the window by, so
1808
1672
  // walks come back whole: walk rows measure them, and so does the strip
@@ -1833,6 +1697,131 @@ export function GraphPaneMixin() {
1833
1697
  }))
1834
1698
  },
1835
1699
  }))
1700
+ .views(self => ({
1701
+ // The gene reads walk rows need, as the layout or the strip: for each row whose haplotype
1702
+ // (`HG00097#1`) names an assembly with a gene track, that track over the
1703
+ // span of its own contig the row's bar covers. The first WALK_GENE_ROWS
1704
+ // such rows are read, and the rest counted for the key.
1705
+ get walkGeneReads() {
1706
+ const bars = self.walkRowBars ?? self.walkStripRows
1707
+ if (!bars || !self.showGenes) {
1708
+ return undefined
1709
+ }
1710
+ const reads: {
1711
+ row: string
1712
+ trackId: string
1713
+ region: SubgraphRegion
1714
+ }[] = []
1715
+ let untracked = 0
1716
+ let unplaced = 0
1717
+ for (const row of bars.rows) {
1718
+ if (!row.axis) {
1719
+ unplaced++
1720
+ continue
1721
+ }
1722
+ const assemblyName = resolveLocationAssembly(self.assemblyResolver, {
1723
+ sample: row.sample,
1724
+ haplotype:
1725
+ row.haplotype === undefined
1726
+ ? undefined
1727
+ : `${row.sample}#${row.haplotype}`,
1728
+ })
1729
+ const track = assemblyName
1730
+ ? self.geneTracksByAssembly.get(assemblyName)
1731
+ : undefined
1732
+ if (!assemblyName || !track) {
1733
+ untracked++
1734
+ continue
1735
+ }
1736
+ reads.push({
1737
+ row: row.name,
1738
+ trackId: track.trackId,
1739
+ region: {
1740
+ assemblyName,
1741
+ refName: row.axis.contig,
1742
+ ...rowSpan(row.axis, row.bp),
1743
+ },
1744
+ })
1745
+ }
1746
+ return {
1747
+ reads: reads.slice(0, WALK_GENE_ROWS),
1748
+ gaps: {
1749
+ untracked,
1750
+ unplaced,
1751
+ unread: Math.max(0, reads.length - WALK_GENE_ROWS),
1752
+ },
1753
+ }
1754
+ },
1755
+ get walkRowGeneGaps() {
1756
+ return this.walkGeneReads?.gaps
1757
+ },
1758
+ // The walk row under a pane point, as its index among the reference row
1759
+ // and the rows below it, where the point is on its bar
1760
+ walkRowAt(screenX: number, screenY: number) {
1761
+ const bars = self.walkRowBars
1762
+ if (!bars) {
1763
+ return undefined
1764
+ }
1765
+ const i = Math.round(
1766
+ (screenY - self.translateY) / (ROW_HEIGHT_PX * self.scaleY),
1767
+ )
1768
+ const row = [bars.reference, ...bars.rows][i]
1769
+ const y = i * ROW_HEIGHT_PX * self.scaleY + self.translateY
1770
+ const bp = (screenX - self.translateX) / self.scaleX - bars.origin
1771
+ return row &&
1772
+ Math.abs(screenY - y) <= WALK_BAR_PX / 2 + 2 &&
1773
+ bp >= 0 &&
1774
+ bp <= row.bp
1775
+ ? i
1776
+ : undefined
1777
+ },
1778
+ // Where a walk row's bar lies in its own assembly, for a linear view to
1779
+ // open: the span of its contig the bar covers, on the session assembly
1780
+ // its haplotype names (the cut's own assembly for the reference row).
1781
+ // The label names the haplotype when no assembly is loaded for it.
1782
+ walkRowLaunchTarget(index: number, bars = self.walkRowBars) {
1783
+ const row = bars ? [bars.reference, ...bars.rows][index] : undefined
1784
+ if (!row?.axis) {
1785
+ return undefined
1786
+ }
1787
+ const haplotype =
1788
+ row.haplotype === undefined
1789
+ ? undefined
1790
+ : `${row.sample}#${row.haplotype}`
1791
+ const assembly =
1792
+ index === 0
1793
+ ? self.graphRegion?.assemblyName
1794
+ : resolveLocationAssembly(self.assemblyResolver, {
1795
+ sample: row.sample,
1796
+ haplotype,
1797
+ })
1798
+ return {
1799
+ label: row.label,
1800
+ assembly,
1801
+ location: {
1802
+ sample: row.sample,
1803
+ haplotype,
1804
+ refName: row.axis.contig,
1805
+ ...rowSpan(row.axis, row.bp),
1806
+ },
1807
+ }
1808
+ },
1809
+ // Each row's genes as offsets along its bar: the reference row's from
1810
+ // the backbone's gene track, every other row's from its own assembly
1811
+ get walkRowGenes() {
1812
+ const bars = self.walkRowBars ?? self.walkStripRows
1813
+ if (!bars || !self.showGenes) {
1814
+ return undefined
1815
+ }
1816
+ const byRow = new Map(self.walkGeneFeatures ?? [])
1817
+ if (self.geneFeatures) {
1818
+ byRow.set(bars.reference.name, self.geneFeatures)
1819
+ }
1820
+ return byRow.size
1821
+ ? placeRowGenes([bars.reference, ...bars.rows], byRow)
1822
+ : undefined
1823
+ },
1824
+ }))
1836
1825
  .views(self => ({
1837
1826
  // Whether the drawing's x is reference bp a host can place. Walk rows
1838
1827
  // put the backbone at its bp too, but their bars are lengths, so the
@@ -2355,7 +2344,9 @@ export function GraphPaneMixin() {
2355
2344
  panSN?.[region.assemblyName] ||
2356
2345
  region.assemblyName,
2357
2346
  context: adapter!.context as number | undefined,
2358
- snarls: adapter!.subgraphSnarls as string | undefined,
2347
+ snarls: self.cutsWholeWalks
2348
+ ? 'overlapping'
2349
+ : (adapter!.subgraphSnarls as string | undefined),
2359
2350
  },
2360
2351
  }
2361
2352
  : undefined
@@ -2395,6 +2386,7 @@ export function GraphPaneMixin() {
2395
2386
  ? self.walkLayers.map(l => (l.color ? l : l.walk))
2396
2387
  : undefined,
2397
2388
  facet: self.walkLayers.length > 1 ? self.facetSpec : undefined,
2389
+ walkStrip: self.walkStripShown || undefined,
2398
2390
  width: self.paneWidth,
2399
2391
  height: self.paneCeiling,
2400
2392
  colorScheme: self.chosenColorScheme,
@@ -2413,7 +2405,7 @@ export function GraphPaneMixin() {
2413
2405
  : `${layoutModeByValue(self.chosenLayoutMode).label} draws a picture of its own, which the SVG export does not`
2414
2406
  },
2415
2407
  // The drawing as a standalone SVG, fitted, with its genes, its lifted
2416
- // walks' keys and facet panels; see figureSvg
2408
+ // walks' keys, facet panels and the strip of walk rows; see figureSvg
2417
2409
  figure() {
2418
2410
  const { graph, layoutResult } = self
2419
2411
  return graph && layoutResult && !this.figureUnavailable
@@ -2434,6 +2426,11 @@ export function GraphPaneMixin() {
2434
2426
  walkRows: self.walkRowBars,
2435
2427
  rowGenes: self.walkRowGenes,
2436
2428
  rowGeneGaps: self.walkRowGeneGaps,
2429
+ walkStrip: self.walkStripRows && {
2430
+ rows: self.walkStripRows,
2431
+ rowGenes: self.walkRowGenes,
2432
+ rowGeneGaps: self.walkRowGeneGaps,
2433
+ },
2437
2434
  spec: this.figureSpec(),
2438
2435
  })
2439
2436
  : undefined
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '4.3.0'
1
+ export const version = '4.4.1'