jbrowse-plugin-graphgenomeviewer 4.7.0 → 5.0.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 (65) hide show
  1. package/README.md +7 -1
  2. package/dist/chunks/ChooseWalksDialog-GVQLSGHX.js +2 -0
  3. package/dist/chunks/ChooseWalksDialog-GVQLSGHX.js.map +7 -0
  4. package/dist/chunks/GbzBaseSyntenyAdapter-CKKVHC2R.js +4 -0
  5. package/dist/chunks/GbzBaseSyntenyAdapter-CKKVHC2R.js.map +7 -0
  6. package/dist/chunks/{GraphGenomeView-HZ2EQUOC.js → GraphGenomeView-YYP4IFSI.js} +2 -2
  7. package/dist/chunks/{GraphTrackSettingsDialog-JVPM25GV.js → GraphTrackSettingsDialog-2WPJRAJC.js} +2 -2
  8. package/dist/chunks/LinearGraphDisplay-7YE624D5.js +2 -0
  9. package/dist/chunks/LinearGraphDisplay-7YE624D5.js.map +7 -0
  10. package/dist/chunks/chunk-2H7GDQ3R.js +6 -0
  11. package/dist/chunks/chunk-2H7GDQ3R.js.map +7 -0
  12. package/dist/chunks/chunk-BZOIENU5.js +2 -0
  13. package/dist/chunks/chunk-BZOIENU5.js.map +7 -0
  14. package/dist/chunks/{chunk-UTWFU4X6.js → chunk-H54W6OQQ.js} +2 -2
  15. package/dist/chunks/{chunk-UTWFU4X6.js.map → chunk-H54W6OQQ.js.map} +4 -4
  16. package/dist/chunks/{chunk-F2EPKNTV.js → chunk-O4XV3K35.js} +2 -2
  17. package/dist/chunks/{chunk-F2EPKNTV.js.map → chunk-O4XV3K35.js.map} +4 -4
  18. package/dist/chunks/chunk-SJNG2XK7.js +2 -0
  19. package/dist/chunks/chunk-SJNG2XK7.js.map +7 -0
  20. package/dist/chunks/chunk-WKVVIDCS.js +3 -0
  21. package/dist/chunks/{chunk-KCL5TF5X.js.map → chunk-WKVVIDCS.js.map} +3 -3
  22. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +5 -5
  23. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
  24. package/package.json +3 -3
  25. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +36 -2
  26. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +67 -1
  27. package/src/GbzBaseSyntenyAdapter/test_data/fragmented.haplotype-index.db +0 -0
  28. package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.haplotype-index.db +0 -0
  29. package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.sampled.haplotype-index.db +0 -0
  30. package/src/GetHaplotypeOverview.ts +77 -0
  31. package/src/GraphGenomeView/components/ChooseWalksDialog.tsx +86 -0
  32. package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +205 -187
  33. package/src/GraphGenomeView/components/TubeMapLegend.test.tsx +29 -0
  34. package/src/GraphGenomeView/components/TubeMapOverlay.tsx +8 -0
  35. package/src/GraphGenomeView/host.ts +6 -0
  36. package/src/GraphGenomeView/model.ts +10 -4224
  37. package/src/GraphGenomeView/pane/fitViews.ts +589 -0
  38. package/src/GraphGenomeView/pane/graphViews.ts +829 -0
  39. package/src/GraphGenomeView/pane/hostViews.ts +193 -0
  40. package/src/GraphGenomeView/pane/launchActions.ts +407 -0
  41. package/src/GraphGenomeView/pane/launchViews.ts +191 -0
  42. package/src/GraphGenomeView/pane/loadActions.ts +701 -0
  43. package/src/GraphGenomeView/pane/paneBase.ts +479 -0
  44. package/src/GraphGenomeView/pane/renderingActions.ts +281 -0
  45. package/src/GraphGenomeView/pane/settingActions.ts +467 -0
  46. package/src/GraphGenomeView/pane/walkRowViews.ts +205 -0
  47. package/src/HaplotypeOverview/HaplotypeOverviewCanvas.tsx +203 -0
  48. package/src/HaplotypeOverview/draw.test.ts +136 -0
  49. package/src/HaplotypeOverview/draw.ts +433 -0
  50. package/src/HaplotypeOverview/model.ts +163 -0
  51. package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +184 -0
  52. package/src/LinearGraphDisplay/components/LinearGraphDisplay.tsx +12 -2
  53. package/src/LinearGraphDisplay/model.ts +107 -3
  54. package/src/gaf/gafFile.ts +2 -0
  55. package/src/index.ts +2 -0
  56. package/src/version.ts +1 -1
  57. package/dist/chunks/GbzBaseSyntenyAdapter-AZBYMDIW.js +0 -8
  58. package/dist/chunks/GbzBaseSyntenyAdapter-AZBYMDIW.js.map +0 -7
  59. package/dist/chunks/LinearGraphDisplay-MJ4ZBJRX.js +0 -2
  60. package/dist/chunks/LinearGraphDisplay-MJ4ZBJRX.js.map +0 -7
  61. package/dist/chunks/chunk-BUXVOMDL.js +0 -2
  62. package/dist/chunks/chunk-BUXVOMDL.js.map +0 -7
  63. package/dist/chunks/chunk-KCL5TF5X.js +0 -3
  64. /package/dist/chunks/{GraphGenomeView-HZ2EQUOC.js.map → GraphGenomeView-YYP4IFSI.js.map} +0 -0
  65. /package/dist/chunks/{GraphTrackSettingsDialog-JVPM25GV.js.map → GraphTrackSettingsDialog-2WPJRAJC.js.map} +0 -0
@@ -0,0 +1,281 @@
1
+ import { contains } from '@jbrowse/bandage-core/viewport'
2
+ import { getSession } from '@jbrowse/core/util'
3
+ import { addDisposer } from '@jbrowse/mobx-state-tree'
4
+ import { autorun, reaction, untracked } from 'mobx'
5
+
6
+ import { withLoadActions } from './loadActions'
7
+ import { EMPTY_BATCH, dependOn, paneViewportOf } from './paneBase'
8
+ import {
9
+ hoverInRegion,
10
+ nodeForLgvHover,
11
+ readLgvHover,
12
+ } from '../../hoverSync/lgvHover'
13
+ import { walksForRow } from '../../hoverSync/rowWalks'
14
+
15
+ import type { LayoutModeValue } from '@jbrowse/bandage-core/layoutModes'
16
+ import type { Renderer } from '@jbrowse/bandage-core/renderer/types'
17
+
18
+ export const withRenderingActions = withLoadActions
19
+ .actions(self => ({
20
+ startRenderingBackend(backend: Renderer) {
21
+ if (!self.autorunsInstalled) {
22
+ // Autorun: keep the view fitted to the graph until the user moves it.
23
+ // Reading fittedTransform tracks everything the fit depends on, so
24
+ // it re-fits as the layout arrives, the canvas is measured and the
25
+ // genes over a tube map claim their rows. A manual pan/zoom (or a
26
+ // restored-session transform) makes the viewport the user's, and a
27
+ // host makes it the linear view's.
28
+ addDisposer(
29
+ self,
30
+ autorun(() => {
31
+ if (
32
+ self.fittedTransform &&
33
+ untracked(() => self.viewportOwner === 'fit')
34
+ ) {
35
+ self.zoomToFit()
36
+ }
37
+ }),
38
+ )
39
+
40
+ addDisposer(
41
+ self,
42
+ // Faceting on or off, its columns, its count of panels and what
43
+ // they split by each reshape the boxes the drawing is fitted into
44
+ reaction(
45
+ () => {
46
+ const grid = self.facetGrid
47
+ return grid
48
+ ? `${self.facet.field} ${self.facetPanels?.length} ${grid.columns}`
49
+ : ''
50
+ },
51
+ () => {
52
+ self.refitView()
53
+ },
54
+ ),
55
+ )
56
+ // The walk rows' genes follow the rows: a new cut, a repeat pick or
57
+ // a sample filter changes which haplotypes and spans they read
58
+ addDisposer(
59
+ self,
60
+ reaction(
61
+ () => ({
62
+ graph: self.graph,
63
+ reads: JSON.stringify(self.walkGeneReads?.reads ?? []),
64
+ }),
65
+ () => {
66
+ void self.loadWalkGenes()
67
+ },
68
+ {
69
+ fireImmediately: true,
70
+ equals: (a, b) => a.graph === b.graph && a.reads === b.reads,
71
+ },
72
+ ),
73
+ )
74
+ // A host fits the rows once, when it takes the pane over, which is
75
+ // before the legend measures the room it needs above them
76
+ addDisposer(
77
+ self,
78
+ reaction(
79
+ () => self.fitPadTop,
80
+ () => {
81
+ if (self.viewportOwner === 'host') {
82
+ self.zoomToFit()
83
+ }
84
+ },
85
+ ),
86
+ )
87
+
88
+ // Autorun: mirror a connected linear view's hover onto the graph. An
89
+ // LGV writes `{hoverPosition, hoverFeature}` to session.hovered on
90
+ // every mousemove; neither field names the source view, so the guard
91
+ // is that the position lies in the region this graph was cut from.
92
+ // A pointer over the pane itself is the host's too, at a bp its x
93
+ // only means on a reference-axis layout, so there the pane's own hit
94
+ // test is the hover.
95
+ //
96
+ // Only `hovered` is tracked — the graph reads are untracked, so a
97
+ // geometry rebuild can't re-fire this and clobber a hover the canvas
98
+ // itself set. Assigning an unchanged id doesn't notify, so a hover
99
+ // that travels within one segment costs nothing downstream.
100
+ addDisposer(
101
+ self,
102
+ autorun(() => {
103
+ const hover = readLgvHover(getSession(self).hovered)
104
+ untracked(() => {
105
+ const region = self.graphRegion
106
+ const graph = self.graph
107
+ const inRegion =
108
+ hover && region && hoverInRegion(hover, region)
109
+ ? hover
110
+ : undefined
111
+ // a row naming a walk is the hover, not the node at its bp,
112
+ // whose band would cover the row's own cells
113
+ const rowWalks =
114
+ inRegion?.featureName && graph?.paths
115
+ ? walksForRow(inRegion.featureName, graph.paths)
116
+ : []
117
+ if (region && graph && !self.pointerInPane) {
118
+ self.setHoveredNode(
119
+ inRegion && rowWalks.length === 0
120
+ ? nodeForLgvHover({ hover: inRegion, nodes: graph.nodes })
121
+ : null,
122
+ )
123
+ }
124
+ self.setHoveredRowWalks(rowWalks)
125
+ })
126
+ }),
127
+ )
128
+
129
+ // Reaction: drop a hover the pointer cannot still be over.
130
+ //
131
+ // A hover is set by a mousemove on the canvas, and here the drawing
132
+ // moves under a STATIONARY cursor on three axes — the wheel zoom, the
133
+ // toolbar's zoom and fit buttons, and a pan — none of which fire a
134
+ // pointer event. Left alone the tooltip goes on naming the node that
135
+ // used to be under the cursor, `hoverHighlight` goes on publishing its
136
+ // span, and the paired linear view paints a band over the wrong
137
+ // sequence until the mouse happens to move.
138
+ //
139
+ // This is jbrowse-components' `installClearHoverOnViewportChange`
140
+ // (BaseLinearDisplay), whose three axes are bpPerPx / offsetPx /
141
+ // scrollTop; a graph view's are its own transform. Its rule is worth
142
+ // restating rather than just citing: a sticky canvas gets no
143
+ // mousemove and no mouseleave for any of them. Not imported because
144
+ // it lives in the LGV plugin and reads a containing LGV, and this
145
+ // plugin deliberately takes no runtime dependency on that plugin
146
+ // (see hoverSync/index.tsx).
147
+ //
148
+ // A `reaction`, not an autorun, for the reason stated there: the
149
+ // effect touches hover state, and an autorun would re-enter itself.
150
+ // Selection is untouched — a click is a choice, and the content
151
+ // moving does not unmake it.
152
+ addDisposer(
153
+ self,
154
+ reaction(
155
+ () => `${self.scale}-${self.translateX}-${self.translateY}`,
156
+ () => {
157
+ self.setHoveredNode(null)
158
+ self.setHoveredBubble(null)
159
+ self.setHoveredEdge(null)
160
+ },
161
+ { name: 'GraphClearHoverOnViewportChange' },
162
+ ),
163
+ )
164
+
165
+ // Autorun: a zoom, or a pan that leaves the window the last build
166
+ // covered, schedules a debounced rebuild. A pan inside it costs a
167
+ // repaint and nothing else. Skips the first run.
168
+ let firstViewport = true
169
+ addDisposer(
170
+ self,
171
+ autorun(() => {
172
+ const { scale } = self
173
+ const viewport = paneViewportOf(self)
174
+ if (firstViewport) {
175
+ firstViewport = false
176
+ return
177
+ }
178
+ const built = untracked(() => self.builtViewport)
179
+ if (built?.scale === scale && contains(built.bounds, viewport)) {
180
+ return
181
+ }
182
+ self.scheduleViewportDirty()
183
+ }),
184
+ )
185
+
186
+ // Autorun: hover and selection. Tracks `geometryVersion` because an
187
+ // upload drops the renderer's edge highlight.
188
+ addDisposer(
189
+ self,
190
+ autorun(() => {
191
+ const b = self.currentRenderingBackend as Renderer | undefined
192
+ dependOn(self.geometryVersion)
193
+ if (b) {
194
+ self.applyHighlights(b)
195
+ self.renderNow()
196
+ }
197
+ }),
198
+ )
199
+ }
200
+
201
+ // The second argument is a SETUP THUNK, not the callbacks themselves:
202
+ // anything it closes over lives exactly as long as the callbacks that
203
+ // read it, which is what lets a per-backend memo work.
204
+ self.attachRenderingBackend<Renderer>(backend, () => ({
205
+ // Autorun: rebuild geometry when graph data or display options
206
+ // change. scale/translate are untracked so they don't trigger a full
207
+ // rebuild — only the debounced viewportDirty flag does.
208
+ upload: (b: Renderer) => {
209
+ b.resize(self.paneWidth, self.canvasHeight)
210
+ if (self.layoutResult?.tubeMap) {
211
+ dependOn(self.viewportDirty)
212
+ b.uploadGeometry(EMPTY_BATCH)
213
+ self.setGeometryMetrics(0, 0, {
214
+ scale: untracked(() => self.scale),
215
+ bounds: untracked(() => paneViewportOf(self)),
216
+ })
217
+ return true
218
+ }
219
+ if (self.facetPanels) {
220
+ dependOn(self.viewportDirty)
221
+ b.uploadGeometry(EMPTY_BATCH)
222
+ self.setGeometryMetrics(0, 0, {
223
+ scale: untracked(() => self.scale),
224
+ bounds: untracked(() => self.viewportToBuild()),
225
+ })
226
+ return true
227
+ }
228
+ const geometryStart = performance.now()
229
+ const built = self.buildDrawing(
230
+ self.walkLift,
231
+ self.effectiveDrawPaths,
232
+ )
233
+ if (built) {
234
+ b.uploadGeometry(built.batch)
235
+ self.setGeometryMetrics(
236
+ performance.now() - geometryStart,
237
+ built.batch.nodeStrokes.length,
238
+ {
239
+ scale: untracked(() => self.scale),
240
+ bounds: built.viewportBounds,
241
+ },
242
+ )
243
+ return true
244
+ }
245
+ // Nothing reached the backend, so the autorun may skip the redraw
246
+ // it would otherwise force. Safe despite the resize above: with no
247
+ // positions there is nothing to draw, which is the same case
248
+ // `render` below returns false for.
249
+ return false
250
+ },
251
+ // Autorun: re-render on pan/zoom/darkMode without rebuilding geometry
252
+ render: (b: Renderer) => {
253
+ if (!self.nodePositions) {
254
+ return false
255
+ }
256
+ self.paint(b)
257
+ self.markPainted()
258
+ return true
259
+ },
260
+ releaseTargets: (b: Renderer) => {
261
+ b.releaseOffscreenTargets()
262
+ },
263
+ }))
264
+ },
265
+ }))
266
+ .actions(self => ({
267
+ // A layout picked from a menu: the mode, then the drawing it makes.
268
+ switchLayout(mode: LayoutModeValue) {
269
+ self.setLayoutMode(mode)
270
+ return self.recomputeLayout()
271
+ },
272
+ // loads the source again, for a host that has one
273
+ retryLoad() {},
274
+ afterAttach() {
275
+ // A restored session that already carries a non-default transform is
276
+ // the user's own view — mark it so the fit autorun leaves it alone.
277
+ if (!self.isDefaultViewport) {
278
+ self.viewportOwner = 'user'
279
+ }
280
+ },
281
+ }))