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.
- package/README.md +7 -1
- package/dist/chunks/ChooseWalksDialog-GVQLSGHX.js +2 -0
- package/dist/chunks/ChooseWalksDialog-GVQLSGHX.js.map +7 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-CKKVHC2R.js +4 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-CKKVHC2R.js.map +7 -0
- package/dist/chunks/{GraphGenomeView-HZ2EQUOC.js → GraphGenomeView-YYP4IFSI.js} +2 -2
- package/dist/chunks/{GraphTrackSettingsDialog-JVPM25GV.js → GraphTrackSettingsDialog-2WPJRAJC.js} +2 -2
- package/dist/chunks/LinearGraphDisplay-7YE624D5.js +2 -0
- package/dist/chunks/LinearGraphDisplay-7YE624D5.js.map +7 -0
- package/dist/chunks/chunk-2H7GDQ3R.js +6 -0
- package/dist/chunks/chunk-2H7GDQ3R.js.map +7 -0
- package/dist/chunks/chunk-BZOIENU5.js +2 -0
- package/dist/chunks/chunk-BZOIENU5.js.map +7 -0
- package/dist/chunks/{chunk-UTWFU4X6.js → chunk-H54W6OQQ.js} +2 -2
- package/dist/chunks/{chunk-UTWFU4X6.js.map → chunk-H54W6OQQ.js.map} +4 -4
- package/dist/chunks/{chunk-F2EPKNTV.js → chunk-O4XV3K35.js} +2 -2
- package/dist/chunks/{chunk-F2EPKNTV.js.map → chunk-O4XV3K35.js.map} +4 -4
- package/dist/chunks/chunk-SJNG2XK7.js +2 -0
- package/dist/chunks/chunk-SJNG2XK7.js.map +7 -0
- package/dist/chunks/chunk-WKVVIDCS.js +3 -0
- package/dist/chunks/{chunk-KCL5TF5X.js.map → chunk-WKVVIDCS.js.map} +3 -3
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +5 -5
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +3 -3
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +36 -2
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +67 -1
- package/src/GbzBaseSyntenyAdapter/test_data/fragmented.haplotype-index.db +0 -0
- package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.haplotype-index.db +0 -0
- package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.sampled.haplotype-index.db +0 -0
- package/src/GetHaplotypeOverview.ts +77 -0
- package/src/GraphGenomeView/components/ChooseWalksDialog.tsx +86 -0
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +205 -187
- package/src/GraphGenomeView/components/TubeMapLegend.test.tsx +29 -0
- package/src/GraphGenomeView/components/TubeMapOverlay.tsx +8 -0
- package/src/GraphGenomeView/host.ts +6 -0
- package/src/GraphGenomeView/model.ts +10 -4224
- package/src/GraphGenomeView/pane/fitViews.ts +589 -0
- package/src/GraphGenomeView/pane/graphViews.ts +829 -0
- package/src/GraphGenomeView/pane/hostViews.ts +193 -0
- package/src/GraphGenomeView/pane/launchActions.ts +407 -0
- package/src/GraphGenomeView/pane/launchViews.ts +191 -0
- package/src/GraphGenomeView/pane/loadActions.ts +701 -0
- package/src/GraphGenomeView/pane/paneBase.ts +479 -0
- package/src/GraphGenomeView/pane/renderingActions.ts +281 -0
- package/src/GraphGenomeView/pane/settingActions.ts +467 -0
- package/src/GraphGenomeView/pane/walkRowViews.ts +205 -0
- package/src/HaplotypeOverview/HaplotypeOverviewCanvas.tsx +203 -0
- package/src/HaplotypeOverview/draw.test.ts +136 -0
- package/src/HaplotypeOverview/draw.ts +433 -0
- package/src/HaplotypeOverview/model.ts +163 -0
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +184 -0
- package/src/LinearGraphDisplay/components/LinearGraphDisplay.tsx +12 -2
- package/src/LinearGraphDisplay/model.ts +107 -3
- package/src/gaf/gafFile.ts +2 -0
- package/src/index.ts +2 -0
- package/src/version.ts +1 -1
- package/dist/chunks/GbzBaseSyntenyAdapter-AZBYMDIW.js +0 -8
- package/dist/chunks/GbzBaseSyntenyAdapter-AZBYMDIW.js.map +0 -7
- package/dist/chunks/LinearGraphDisplay-MJ4ZBJRX.js +0 -2
- package/dist/chunks/LinearGraphDisplay-MJ4ZBJRX.js.map +0 -7
- package/dist/chunks/chunk-BUXVOMDL.js +0 -2
- package/dist/chunks/chunk-BUXVOMDL.js.map +0 -7
- package/dist/chunks/chunk-KCL5TF5X.js +0 -3
- /package/dist/chunks/{GraphGenomeView-HZ2EQUOC.js.map → GraphGenomeView-YYP4IFSI.js.map} +0 -0
- /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
|
+
}))
|