jbrowse-plugin-graphgenomeviewer 7.4.4 → 7.6.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/dist/chunks/{GbzBaseSyntenyAdapter-A5MNPCKX.js → GbzBaseSyntenyAdapter-3PVPTKNW.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-A5MNPCKX.js.map → GbzBaseSyntenyAdapter-3PVPTKNW.js.map} +1 -1
- package/dist/chunks/{GraphGenomeView-SEAPDRXS.js → GraphGenomeView-GQW4YC23.js} +2 -2
- package/dist/chunks/GraphTrackSettingsDialog-ARWCWJW4.js +4 -0
- package/dist/chunks/GraphTrackSettingsDialog-ARWCWJW4.js.map +7 -0
- package/dist/chunks/{HighlightColorDialog-QAYWLO5I.js → HighlightColorDialog-PD3KMF2U.js} +2 -2
- package/dist/chunks/LinearGraphDisplay-QNS4EAL3.js +2 -0
- package/dist/chunks/{LinearGraphDisplay-ZC4BQ27J.js.map → LinearGraphDisplay-QNS4EAL3.js.map} +2 -2
- package/dist/chunks/{MinigraphBubbleAdapter-3QM4XV47.js → MinigraphBubbleAdapter-L6JSQIOL.js} +2 -2
- package/dist/chunks/RgfaTabixAdapter-5HCAJ7QI.js +2 -0
- package/dist/chunks/RgfaTabixAdapter-5HCAJ7QI.js.map +7 -0
- package/dist/chunks/{WalkFormsAdapter-FT2SW4H6.js → WalkFormsAdapter-NX36GP35.js} +2 -2
- package/dist/chunks/WalkTabixSyntenyAdapter-JFNFX3JP.js +2 -0
- package/dist/chunks/{WalkTabixSyntenyAdapter-3WRVNDLB.js.map → WalkTabixSyntenyAdapter-JFNFX3JP.js.map} +2 -2
- package/dist/chunks/{chunk-KIYRMZWP.js → chunk-2CPD5NX2.js} +2 -2
- package/dist/chunks/{chunk-KIYRMZWP.js.map → chunk-2CPD5NX2.js.map} +1 -1
- package/dist/chunks/chunk-3AC33AXB.js +2 -0
- package/dist/chunks/{chunk-HF4H6LQI.js → chunk-4PHM6JNU.js} +2 -2
- package/dist/chunks/chunk-5LFPNQWH.js +2 -0
- package/dist/chunks/{chunk-6VNU7SD4.js.map → chunk-5LFPNQWH.js.map} +4 -4
- package/dist/chunks/{chunk-6WUWQX2P.js → chunk-CNEUOM7X.js} +2 -2
- package/dist/chunks/{chunk-6WUWQX2P.js.map → chunk-CNEUOM7X.js.map} +1 -1
- package/dist/chunks/{chunk-P3FBI5ML.js → chunk-DOC7QIE2.js} +1 -1
- package/dist/chunks/{chunk-P3FBI5ML.js.map → chunk-DOC7QIE2.js.map} +1 -1
- package/dist/chunks/{chunk-G53BI7VK.js → chunk-G4OPM6G7.js} +2 -2
- package/dist/chunks/{chunk-G53BI7VK.js.map → chunk-G4OPM6G7.js.map} +1 -1
- package/dist/chunks/chunk-GRLNDNX4.js +2 -0
- package/dist/chunks/{chunk-S333HOG5.js.map → chunk-GRLNDNX4.js.map} +4 -4
- package/dist/chunks/{chunk-7RJMG6I2.js → chunk-JYYF3U2H.js} +1 -1
- package/dist/chunks/{chunk-7RJMG6I2.js.map → chunk-JYYF3U2H.js.map} +1 -1
- package/dist/chunks/chunk-KDXVLEHS.js +3 -0
- package/dist/chunks/chunk-KDXVLEHS.js.map +7 -0
- package/dist/chunks/chunk-LGV6BTOY.js +2 -0
- package/dist/chunks/{chunk-HCDKP3IP.js.map → chunk-LGV6BTOY.js.map} +4 -4
- package/dist/chunks/{chunk-2KVE7YAS.js → chunk-LJ7O3736.js} +1 -1
- package/dist/chunks/{chunk-2KVE7YAS.js.map → chunk-LJ7O3736.js.map} +1 -1
- package/dist/chunks/{chunk-OJWBLPPH.js → chunk-OQHN2GWX.js} +2 -2
- package/dist/chunks/chunk-PTDOYJFH.js +4 -0
- package/dist/chunks/chunk-PTDOYJFH.js.map +7 -0
- package/dist/chunks/chunk-SGP63UMC.js +2 -0
- package/dist/chunks/chunk-SGP63UMC.js.map +7 -0
- package/dist/chunks/{chunk-XXBE4IFZ.js → chunk-SI5VLKVN.js} +2 -2
- package/dist/chunks/{chunk-XXBE4IFZ.js.map → chunk-SI5VLKVN.js.map} +1 -1
- package/dist/chunks/{chunk-6SWXMRAV.js → chunk-TWPUVVYH.js} +1 -1
- package/dist/chunks/{chunk-6SWXMRAV.js.map → chunk-TWPUVVYH.js.map} +1 -1
- package/dist/chunks/{chunk-FTXDTU27.js → chunk-XF6GU4GK.js} +3 -3
- package/dist/chunks/{chunk-FTXDTU27.js.map → chunk-XF6GU4GK.js.map} +2 -2
- package/dist/chunks/{chunk-MUONTMYZ.js → chunk-XSDGE4RF.js} +2 -2
- package/dist/chunks/{chunk-LE7FZM6Y.js → chunk-ZFJGNGIB.js} +2 -2
- package/dist/chunks/{chunk-LE7FZM6Y.js.map → chunk-ZFJGNGIB.js.map} +1 -1
- package/dist/chunks/chunk-ZI7RFIA2.js +4 -0
- package/dist/chunks/chunk-ZI7RFIA2.js.map +7 -0
- package/dist/chunks/{renderSvg-ZIUOPWJQ.js → renderSvg-NIWFCY3U.js} +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -3
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +2 -2
- package/src/GraphComputeLayout.test.ts +24 -0
- package/src/GraphComputeLayout.ts +4 -2
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +16 -0
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +83 -0
- package/src/GraphGenomeView/components/usePaneGestures.test.tsx +82 -5
- package/src/GraphGenomeView/components/usePaneGestures.ts +10 -4
- package/src/GraphGenomeView/graphLayers.ts +14 -1
- package/src/GraphGenomeView/legendSections.ts +32 -0
- package/src/GraphGenomeView/model.test.ts +307 -7
- package/src/GraphGenomeView/model.ts +3 -10
- package/src/GraphGenomeView/nodeColor.test.ts +27 -1
- package/src/GraphGenomeView/nodeColor.ts +38 -29
- package/src/GraphGenomeView/nodeSize.test.ts +21 -5
- package/src/GraphGenomeView/nodeSize.ts +24 -8
- package/src/GraphGenomeView/pane/fitViews.ts +5 -1
- package/src/GraphGenomeView/pane/graphViews.ts +102 -20
- package/src/GraphGenomeView/pane/launchActions.ts +55 -11
- package/src/GraphGenomeView/pane/launchViews.ts +46 -0
- package/src/GraphGenomeView/pane/loadActions.ts +85 -25
- package/src/GraphGenomeView/pane/morph.test.ts +25 -1
- package/src/GraphGenomeView/pane/morph.ts +3 -1
- package/src/GraphGenomeView/pane/paneBase.ts +5 -0
- package/src/GraphGenomeView/pane/renderingActions.ts +61 -2
- package/src/GraphGenomeView/pane/settingActions.ts +65 -5
- package/src/GraphGenomeView/pane/walkRowViews.ts +17 -6
- package/src/GraphGenomeView/retiredNodeColor.test.ts +46 -0
- package/src/GraphGenomeView/retiredNodeColor.ts +76 -0
- package/src/GraphGenomeView/tubeMapPanels.ts +2 -1
- package/src/GraphGenomeView/viewModel.ts +16 -9
- package/src/GraphGenomeView/walkRowBlocks.ts +50 -0
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +97 -10
- package/src/LinearGraphDisplay/components/GraphStatusChrome.test.tsx +18 -0
- package/src/LinearGraphDisplay/components/GraphStatusChrome.tsx +4 -2
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.test.tsx +41 -1
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.tsx +33 -19
- package/src/LinearGraphDisplay/configSchema.ts +18 -10
- package/src/LinearGraphDisplay/model.ts +4 -2
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +2 -9
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +3 -6
- package/src/hoverSync/graphViewHighlights.test.ts +53 -0
- package/src/hoverSync/graphViewHighlights.ts +25 -0
- package/src/hoverSync/lgvHover.test.ts +100 -1
- package/src/hoverSync/lgvHover.ts +77 -6
- package/src/launchFromGraph/contributors.test.ts +52 -0
- package/src/launchFromGraph/contributors.ts +105 -1
- package/src/launchFromGraph/launchFromGraph.ts +2 -2
- package/src/panSNTabix.test.ts +28 -0
- package/src/panSNTabix.ts +26 -8
- package/src/version.ts +1 -1
- package/dist/chunks/GraphTrackSettingsDialog-EVIIGC5G.js +0 -6
- package/dist/chunks/GraphTrackSettingsDialog-EVIIGC5G.js.map +0 -7
- package/dist/chunks/LinearGraphDisplay-ZC4BQ27J.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-XSOIEWBK.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-XSOIEWBK.js.map +0 -7
- package/dist/chunks/WalkTabixSyntenyAdapter-3WRVNDLB.js +0 -2
- package/dist/chunks/chunk-6VNU7SD4.js +0 -2
- package/dist/chunks/chunk-GRTN25B6.js +0 -3
- package/dist/chunks/chunk-GRTN25B6.js.map +0 -7
- package/dist/chunks/chunk-HCDKP3IP.js +0 -2
- package/dist/chunks/chunk-LE43I4PT.js +0 -4
- package/dist/chunks/chunk-LE43I4PT.js.map +0 -7
- package/dist/chunks/chunk-QV7SSOMJ.js +0 -2
- package/dist/chunks/chunk-S333HOG5.js +0 -2
- package/dist/chunks/chunk-VZQ6VOUP.js +0 -2
- package/dist/chunks/chunk-VZQ6VOUP.js.map +0 -7
- package/dist/chunks/chunk-WLRRI35A.js +0 -3
- package/dist/chunks/chunk-WLRRI35A.js.map +0 -7
- package/src/launchFromGraph/launchTracks.test.ts +0 -95
- package/src/launchFromGraph/launchTracks.ts +0 -49
- /package/dist/chunks/{GraphGenomeView-SEAPDRXS.js.map → GraphGenomeView-GQW4YC23.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-QAYWLO5I.js.map → HighlightColorDialog-PD3KMF2U.js.map} +0 -0
- /package/dist/chunks/{MinigraphBubbleAdapter-3QM4XV47.js.map → MinigraphBubbleAdapter-L6JSQIOL.js.map} +0 -0
- /package/dist/chunks/{WalkFormsAdapter-FT2SW4H6.js.map → WalkFormsAdapter-NX36GP35.js.map} +0 -0
- /package/dist/chunks/{chunk-QV7SSOMJ.js.map → chunk-3AC33AXB.js.map} +0 -0
- /package/dist/chunks/{chunk-HF4H6LQI.js.map → chunk-4PHM6JNU.js.map} +0 -0
- /package/dist/chunks/{chunk-OJWBLPPH.js.map → chunk-OQHN2GWX.js.map} +0 -0
- /package/dist/chunks/{chunk-MUONTMYZ.js.map → chunk-XSDGE4RF.js.map} +0 -0
- /package/dist/chunks/{renderSvg-ZIUOPWJQ.js.map → renderSvg-NIWFCY3U.js.map} +0 -0
|
@@ -314,7 +314,8 @@ export function motionAllowed() {
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
// Calls `blend` with the eased progress once a frame for MORPH_MS while
|
|
317
|
-
// `isLive` holds, then `onEnd`. `finish` jumps to the last frame
|
|
317
|
+
// `isLive` holds, then `onEnd`. `finish` jumps to the last frame, as does a
|
|
318
|
+
// morph cut short, since the layout cache keeps the positions it blends.
|
|
318
319
|
export function runMorph(
|
|
319
320
|
blend: (t: number) => void,
|
|
320
321
|
isLive: () => boolean,
|
|
@@ -330,6 +331,7 @@ export function runMorph(
|
|
|
330
331
|
let last = performance.now()
|
|
331
332
|
const step = () => {
|
|
332
333
|
if (!isLive()) {
|
|
334
|
+
blend(1)
|
|
333
335
|
onEnd()
|
|
334
336
|
return
|
|
335
337
|
}
|
|
@@ -95,6 +95,8 @@ export const VIEWPORT_DEBOUNCE_MS = 150
|
|
|
95
95
|
export const VIEWPORT_PANES_BUILT = 1
|
|
96
96
|
// How many walk rows read their haplotype's genes, the first rows down
|
|
97
97
|
export const WALK_GENE_ROWS = 40
|
|
98
|
+
// How many off-reference contigs read their haplotype's genes
|
|
99
|
+
export const OWN_GENE_CONTIGS = 40
|
|
98
100
|
|
|
99
101
|
// What the canvas draws under the tube map, whose ink is all TubeMapOverlay's
|
|
100
102
|
export type TubeMapRoutes = 'each' | 'grouped' | 'bundled'
|
|
@@ -359,6 +361,9 @@ export const paneBase = types
|
|
|
359
361
|
// each walk row's genes, read from its haplotype's own assembly, by walk
|
|
360
362
|
// name
|
|
361
363
|
walkGeneFeatures: undefined as Map<string, GeneModel[]> | undefined,
|
|
364
|
+
// each loaded haplotype's genes on its own nodes off the reference, under
|
|
365
|
+
// those nodes' stable names (ownGeneReads)
|
|
366
|
+
ownGeneFeatures: undefined as GeneModel[] | undefined,
|
|
362
367
|
// the source track's samples TSV, which walk rows group by
|
|
363
368
|
walkRowSampleTable: undefined as SampleRow[] | undefined,
|
|
364
369
|
// the tandem repeat arrays over the cut window, from the repeat track
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sameBubble } from '@jbrowse/bandage-core/bubbles/bubbleLine'
|
|
1
2
|
import { contains } from '@jbrowse/bandage-core/viewport'
|
|
2
3
|
import { getSession } from '@jbrowse/core/util'
|
|
3
4
|
import { addDisposer } from '@jbrowse/mobx-state-tree'
|
|
@@ -11,11 +12,16 @@ import {
|
|
|
11
12
|
paneViewportOf,
|
|
12
13
|
} from './paneBase'
|
|
13
14
|
import {
|
|
15
|
+
bubbleForLgvHover,
|
|
16
|
+
hoverFromPair,
|
|
14
17
|
hoverInRegion,
|
|
15
18
|
nodeForLgvHover,
|
|
16
19
|
readLgvHover,
|
|
20
|
+
readSelectedFeature,
|
|
17
21
|
} from '../../hoverSync/lgvHover'
|
|
18
22
|
import { walksForRow } from '../../hoverSync/rowWalks'
|
|
23
|
+
import { isRecord } from '../../isRecord'
|
|
24
|
+
import { withRows } from '../../launchFromGraph/linearViewTarget'
|
|
19
25
|
import { trackedDpr } from '../trackedDpr'
|
|
20
26
|
|
|
21
27
|
import type { LayoutEngineKind } from '@jbrowse/bandage-core/layoutEngines'
|
|
@@ -88,6 +94,23 @@ export const withRenderingActions = withLoadActions
|
|
|
88
94
|
},
|
|
89
95
|
),
|
|
90
96
|
)
|
|
97
|
+
// and the haplotypes' own genes on their nodes off the reference
|
|
98
|
+
addDisposer(
|
|
99
|
+
self,
|
|
100
|
+
reaction(
|
|
101
|
+
() => ({
|
|
102
|
+
graph: self.graph,
|
|
103
|
+
reads: JSON.stringify(self.ownGeneReads),
|
|
104
|
+
}),
|
|
105
|
+
() => {
|
|
106
|
+
void self.loadOwnGenes()
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
fireImmediately: true,
|
|
110
|
+
equals: (a, b) => a.graph === b.graph && a.reads === b.reads,
|
|
111
|
+
},
|
|
112
|
+
),
|
|
113
|
+
)
|
|
91
114
|
// Walk rows and tube map panels read the source track's samples
|
|
92
115
|
// TSV, to group by its columns, again for a graph after a dropped one
|
|
93
116
|
addDisposer(
|
|
@@ -145,8 +168,16 @@ export const withRenderingActions = withLoadActions
|
|
|
145
168
|
untracked(() => {
|
|
146
169
|
const region = self.graphRegion
|
|
147
170
|
const graph = self.graph
|
|
171
|
+
const liveViewIds = new Set(
|
|
172
|
+
withRows([...getSession(self).views]).flatMap(v =>
|
|
173
|
+
isRecord(v) && typeof v.id === 'string' ? [v.id] : [],
|
|
174
|
+
),
|
|
175
|
+
)
|
|
148
176
|
const inRegion =
|
|
149
|
-
hover &&
|
|
177
|
+
hover &&
|
|
178
|
+
region &&
|
|
179
|
+
hoverInRegion(hover, region) &&
|
|
180
|
+
hoverFromPair(hover, self.pairedViewId, liveViewIds)
|
|
150
181
|
? hover
|
|
151
182
|
: undefined
|
|
152
183
|
// a row naming a walk is the hover, not the node at its bp,
|
|
@@ -155,9 +186,14 @@ export const withRenderingActions = withLoadActions
|
|
|
155
186
|
inRegion?.featureName && graph?.paths
|
|
156
187
|
? walksForRow(inRegion.featureName, graph.paths)
|
|
157
188
|
: []
|
|
189
|
+
// a bubble track's feature, or a graph-derived VCF record,
|
|
190
|
+
// names a whole bubble, which lights whatever the setting
|
|
191
|
+
const bubble =
|
|
192
|
+
inRegion && bubbleForLgvHover(inRegion, self.bubbles)
|
|
158
193
|
if (region && graph && !self.pointerInPane) {
|
|
194
|
+
self.setHoveredBubble(bubble ?? null)
|
|
159
195
|
self.setHoveredNode(
|
|
160
|
-
inRegion && lightsNode && rowWalks.length === 0
|
|
196
|
+
inRegion && lightsNode && rowWalks.length === 0 && !bubble
|
|
161
197
|
? nodeForLgvHover({ hover: inRegion, nodes: graph.nodes })
|
|
162
198
|
: null,
|
|
163
199
|
)
|
|
@@ -167,6 +203,29 @@ export const withRenderingActions = withLoadActions
|
|
|
167
203
|
}),
|
|
168
204
|
)
|
|
169
205
|
|
|
206
|
+
// A click on a bubble feature or graph-derived variant in a linear
|
|
207
|
+
// view opens its bubble here, as clicking its label would
|
|
208
|
+
addDisposer(
|
|
209
|
+
self,
|
|
210
|
+
reaction(
|
|
211
|
+
() => readSelectedFeature(getSession(self).selection),
|
|
212
|
+
selected => {
|
|
213
|
+
const region = self.graphRegion
|
|
214
|
+
const bubble =
|
|
215
|
+
selected &&
|
|
216
|
+
region &&
|
|
217
|
+
hoverInRegion(selected, region) &&
|
|
218
|
+
bubbleForLgvHover(selected, self.bubbles)
|
|
219
|
+
if (
|
|
220
|
+
bubble &&
|
|
221
|
+
!self.openBubbles.some(open => sameBubble(open, bubble))
|
|
222
|
+
) {
|
|
223
|
+
self.toggleBubble(bubble)
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
|
|
170
229
|
// Reaction: drop a hover the pointer cannot still be over.
|
|
171
230
|
//
|
|
172
231
|
// A hover is set by a mousemove on the canvas, and here the drawing
|
|
@@ -20,7 +20,7 @@ import { nodeOwnLocation } from '../../launchFromGraph/contributors'
|
|
|
20
20
|
import { withRows } from '../../launchFromGraph/linearViewTarget'
|
|
21
21
|
import { withLayer } from '../graphLayers'
|
|
22
22
|
import { colorOfScheme } from '../nodeColor'
|
|
23
|
-
import {
|
|
23
|
+
import { sizeOf } from '../nodeSize'
|
|
24
24
|
|
|
25
25
|
import type { GraphGrammar } from './graphViews'
|
|
26
26
|
import type { ScreenTransform } from './keepInView'
|
|
@@ -32,6 +32,7 @@ import type { WalkRowGroupBy } from '../walkRowGroups'
|
|
|
32
32
|
import type { BubbleSpread } from '@jbrowse/bandage-core/bubbleSpreads'
|
|
33
33
|
import type { MinigraphBubble } from '@jbrowse/bandage-core/bubbles/bubbleLine'
|
|
34
34
|
import type { ColorScheme } from '@jbrowse/bandage-core/colorSchemes'
|
|
35
|
+
import type { EdgeWidth } from '@jbrowse/bandage-core/edgeWidths'
|
|
35
36
|
import type { FacetSetting } from '@jbrowse/bandage-core/facetGrid'
|
|
36
37
|
import type { LayoutEngineKind } from '@jbrowse/bandage-core/layoutEngines'
|
|
37
38
|
import type { LayoutModeValue } from '@jbrowse/bandage-core/layoutModes'
|
|
@@ -44,6 +45,8 @@ import type {
|
|
|
44
45
|
WalkLayer,
|
|
45
46
|
} from '@jbrowse/bandage-core/walkEncoding'
|
|
46
47
|
|
|
48
|
+
const ROW_HOVER_SETTLE_MS = 80
|
|
49
|
+
|
|
47
50
|
export const withSettingActions = withTubeMapViews
|
|
48
51
|
.actions(() => ({
|
|
49
52
|
// each host overrides: a view assigns its props, a track display writes
|
|
@@ -85,7 +88,9 @@ export const withSettingActions = withTubeMapViews
|
|
|
85
88
|
) {
|
|
86
89
|
keptDomain = self.statedColorDomain
|
|
87
90
|
}
|
|
88
|
-
self.writeGrammar({
|
|
91
|
+
self.writeGrammar({
|
|
92
|
+
encoding: { color: colorOfScheme(scheme, keptDomain) },
|
|
93
|
+
})
|
|
89
94
|
},
|
|
90
95
|
}
|
|
91
96
|
})
|
|
@@ -166,7 +171,20 @@ export const withSettingActions = withTubeMapViews
|
|
|
166
171
|
},
|
|
167
172
|
setNodeWidth(width: NodeWidth) {
|
|
168
173
|
self.writeGrammar({
|
|
169
|
-
size:
|
|
174
|
+
size: sizeOf({
|
|
175
|
+
width,
|
|
176
|
+
px: self.contigThickness,
|
|
177
|
+
links: self.linkWidth,
|
|
178
|
+
}),
|
|
179
|
+
})
|
|
180
|
+
},
|
|
181
|
+
setLinkWidth(links: EdgeWidth) {
|
|
182
|
+
self.writeGrammar({
|
|
183
|
+
size: sizeOf({
|
|
184
|
+
width: self.nodeWidth,
|
|
185
|
+
px: self.contigThickness,
|
|
186
|
+
links,
|
|
187
|
+
}),
|
|
170
188
|
})
|
|
171
189
|
},
|
|
172
190
|
setTubeMapFold(bp: number) {
|
|
@@ -237,7 +255,19 @@ export const withSettingActions = withTubeMapViews
|
|
|
237
255
|
)
|
|
238
256
|
},
|
|
239
257
|
setWalkRowSamples(samples: string[] | undefined) {
|
|
240
|
-
|
|
258
|
+
const order = self.walkRowOrder
|
|
259
|
+
self.writeGrammar({
|
|
260
|
+
rows: {
|
|
261
|
+
...(samples ? { kept: samples } : {}),
|
|
262
|
+
...(order ? { order } : {}),
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
},
|
|
266
|
+
setWalkRowOrder(order: 'shared' | undefined) {
|
|
267
|
+
const kept = self.walkRowSamples
|
|
268
|
+
self.writeGrammar({
|
|
269
|
+
rows: { ...(kept ? { kept } : {}), ...(order ? { order } : {}) },
|
|
270
|
+
})
|
|
241
271
|
},
|
|
242
272
|
setWalkRowGroupBy(groupBy: WalkRowGroupBy | undefined) {
|
|
243
273
|
if (groupBy || self.walkRowGroupBy) {
|
|
@@ -276,7 +306,7 @@ export const withSettingActions = withTubeMapViews
|
|
|
276
306
|
self.hoveredTube = tube
|
|
277
307
|
}
|
|
278
308
|
},
|
|
279
|
-
|
|
309
|
+
applyHoveredRowWalks(walks: string[]) {
|
|
280
310
|
if (walks.join('\n') !== self.hoveredRowWalks.join('\n')) {
|
|
281
311
|
self.hoveredRowWalks = walks
|
|
282
312
|
}
|
|
@@ -469,6 +499,36 @@ export const withSettingActions = withTubeMapViews
|
|
|
469
499
|
self.builtViewport = undefined
|
|
470
500
|
},
|
|
471
501
|
}))
|
|
502
|
+
.actions(self => {
|
|
503
|
+
let settle: ReturnType<typeof setTimeout> | undefined
|
|
504
|
+
let pending: string | undefined
|
|
505
|
+
return {
|
|
506
|
+
// A lane row's lift rebuilds the drawing (43 ms at 10k nodes), and a
|
|
507
|
+
// sweep down a lane crosses a row every few px, so a move from one row
|
|
508
|
+
// to the next lifts once the pointer has stayed on a row a moment.
|
|
509
|
+
// Entering and leaving the rows apply at once.
|
|
510
|
+
setHoveredRowWalks(walks: string[]) {
|
|
511
|
+
const key = walks.join('\n')
|
|
512
|
+
if (key === pending) {
|
|
513
|
+
return
|
|
514
|
+
}
|
|
515
|
+
clearTimeout(settle)
|
|
516
|
+
pending = undefined
|
|
517
|
+
if (walks.length > 0 && self.hoveredRowWalks.length > 0) {
|
|
518
|
+
pending = key
|
|
519
|
+
settle = setTimeout(() => {
|
|
520
|
+
pending = undefined
|
|
521
|
+
// a hover cleared meanwhile, by a pan, stays cleared
|
|
522
|
+
if (isAlive(self) && self.hoveredRowWalks.length > 0) {
|
|
523
|
+
self.applyHoveredRowWalks(walks)
|
|
524
|
+
}
|
|
525
|
+
}, ROW_HOVER_SETTLE_MS)
|
|
526
|
+
} else {
|
|
527
|
+
self.applyHoveredRowWalks(walks)
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
}
|
|
531
|
+
})
|
|
472
532
|
.actions(self => ({
|
|
473
533
|
// The frame clock. Unclamped, since clampZoom's floor is there to keep a
|
|
474
534
|
// scale finite and 1 / bpPerPx already is; clamped, a whole-chromosome
|
|
@@ -18,6 +18,7 @@ import { resolveLocationAssembly } from '../../launchFromGraph/contributors'
|
|
|
18
18
|
|
|
19
19
|
import type { SubgraphRegion } from '../../GetSubgraph'
|
|
20
20
|
import type { WalkAxis, WalkRow } from '@jbrowse/bandage-core/layout/walkRows'
|
|
21
|
+
import type { LayoutModeValue } from '@jbrowse/bandage-core/layoutModes'
|
|
21
22
|
|
|
22
23
|
export const withWalkRowViews = withLaunchViews
|
|
23
24
|
.views(self => ({
|
|
@@ -47,13 +48,23 @@ export const withWalkRowViews = withLaunchViews
|
|
|
47
48
|
}
|
|
48
49
|
return walkStripFrame(bars, { width: self.paneWidth })
|
|
49
50
|
},
|
|
50
|
-
// Whether a cut must follow every snarl a walk leaves the
|
|
51
|
-
// walks come back whole: walk rows measure them, and so does
|
|
51
|
+
// Whether a cut under `mode` must follow every snarl a walk leaves the
|
|
52
|
+
// window by, so walks come back whole: walk rows measure them, and so does
|
|
53
|
+
// the strip under a layout that draws nodes. Only a GBZ cut follows
|
|
54
|
+
// snarls; a walk file cuts the same either way, so asking would re-fetch
|
|
55
|
+
// an identical graph.
|
|
56
|
+
cutsWholeWalksIn(mode: LayoutModeValue, walkStrip = self.walkStrip) {
|
|
57
|
+
const m = layoutModeByValue(mode)
|
|
58
|
+
return (
|
|
59
|
+
self.sourceAdapter?.type === 'GbzBaseSyntenyAdapter' &&
|
|
60
|
+
wholeWalksRule(m, {
|
|
61
|
+
walkStrip: walkStrip && m.drawsNodes,
|
|
62
|
+
host: !!self.host,
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
},
|
|
52
66
|
get cutsWholeWalks() {
|
|
53
|
-
return
|
|
54
|
-
walkStrip: self.walkStrip,
|
|
55
|
-
host: !!self.host,
|
|
56
|
-
})
|
|
67
|
+
return this.cutsWholeWalksIn(self.chosenLayoutMode)
|
|
57
68
|
},
|
|
58
69
|
// Where each walk passes the hovered node, or the selected one
|
|
59
70
|
get walkStripMarks() {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { liftFlatNodeColor } from './retiredNodeColor'
|
|
2
|
+
|
|
3
|
+
test('a bare constant becomes a value', () => {
|
|
4
|
+
expect(liftFlatNodeColor('grey')).toEqual({ value: 'grey' })
|
|
5
|
+
expect(liftFlatNodeColor('uniform')).toEqual({ value: 'uniform' })
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test('categorical becomes an ordinal scale', () => {
|
|
9
|
+
expect(liftFlatNodeColor({ field: 'id', scale: 'categorical' })).toEqual({
|
|
10
|
+
field: 'id',
|
|
11
|
+
scale: { type: 'ordinal' },
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('scheme and the domain move under scale', () => {
|
|
16
|
+
expect(liftFlatNodeColor({ field: 'id', scheme: 'rainbow' })).toEqual({
|
|
17
|
+
field: 'id',
|
|
18
|
+
scale: { scheme: 'rainbow' },
|
|
19
|
+
})
|
|
20
|
+
expect(
|
|
21
|
+
liftFlatNodeColor({ field: 'position', domainMin: 5, domainMax: 50 }),
|
|
22
|
+
).toEqual({ field: 'position', scale: { domainMin: 5, domainMax: 50 } })
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('labels and title move to scale.names and legend.title', () => {
|
|
26
|
+
expect(
|
|
27
|
+
liftFlatNodeColor({ field: 'id', labels: ['a'], title: 'Node' }),
|
|
28
|
+
).toEqual({ field: 'id', scale: { names: ['a'] }, legend: { title: 'Node' } })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('a member the channel object already nests wins over its flat twin', () => {
|
|
32
|
+
expect(
|
|
33
|
+
liftFlatNodeColor({
|
|
34
|
+
field: 'position',
|
|
35
|
+
domainMin: 1,
|
|
36
|
+
scale: { domainMin: 7 },
|
|
37
|
+
}),
|
|
38
|
+
).toEqual({ field: 'position', scale: { domainMin: 7 } })
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('a channel object is the object it was', () => {
|
|
42
|
+
const color = { field: 'position', scale: { domainMin: 1, domainMax: 2 } }
|
|
43
|
+
expect(liftFlatNodeColor(color)).toBe(color)
|
|
44
|
+
expect(liftFlatNodeColor({ field: 'depth' })).toEqual({ field: 'depth' })
|
|
45
|
+
expect(liftFlatNodeColor(undefined)).toBeUndefined()
|
|
46
|
+
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { isRecord } from '../isRecord'
|
|
2
|
+
|
|
3
|
+
// Where each member of the flat color object sits in the channel object, as
|
|
4
|
+
// display-kit's `liftFlatColor` places it
|
|
5
|
+
const FLAT_MEMBERS: Readonly<
|
|
6
|
+
Record<string, readonly ['scale' | 'legend', string]>
|
|
7
|
+
> = {
|
|
8
|
+
scale: ['scale', 'type'],
|
|
9
|
+
domain: ['scale', 'domain'],
|
|
10
|
+
range: ['scale', 'range'],
|
|
11
|
+
labels: ['scale', 'names'],
|
|
12
|
+
scheme: ['scale', 'scheme'],
|
|
13
|
+
reverse: ['scale', 'reverse'],
|
|
14
|
+
domainMin: ['scale', 'domainMin'],
|
|
15
|
+
domainMax: ['scale', 'domainMax'],
|
|
16
|
+
domainMid: ['scale', 'domainMid'],
|
|
17
|
+
domainQuantile: ['scale', 'domainQuantile'],
|
|
18
|
+
unknown: ['scale', 'unknown'],
|
|
19
|
+
title: ['legend', 'title'],
|
|
20
|
+
descending: ['legend', 'descending'],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isFlat(color: Record<string, unknown>) {
|
|
24
|
+
return Object.entries(color).some(
|
|
25
|
+
([key, value]) =>
|
|
26
|
+
key in FLAT_MEMBERS && !(key === 'scale' && isRecord(value)),
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated The node color through 7.5.0, a bare `'grey'` or `'uniform'` or a
|
|
32
|
+
* flat object (`scale: 'categorical'`, `scheme`, `domainMin`, `domainMax`
|
|
33
|
+
* beside `field`), lifted to the channel object: the constants become
|
|
34
|
+
* `value`, scale members go under `scale` and `categorical` becomes
|
|
35
|
+
* `ordinal`. Covers a LinearGraphDisplay's `color` and a GraphGenomeView's
|
|
36
|
+
* saved `color`. Goes once no saved session or hosted config predates `encoding.color`.
|
|
37
|
+
*/
|
|
38
|
+
export function liftFlatNodeColor(color: unknown): unknown {
|
|
39
|
+
if (typeof color === 'string') {
|
|
40
|
+
return { value: color }
|
|
41
|
+
}
|
|
42
|
+
if (!isRecord(color) || !isFlat(color)) {
|
|
43
|
+
return color
|
|
44
|
+
}
|
|
45
|
+
const out: Record<string, unknown> = {}
|
|
46
|
+
const parts: Record<'scale' | 'legend', Record<string, unknown>> = {
|
|
47
|
+
scale: {},
|
|
48
|
+
legend: {},
|
|
49
|
+
}
|
|
50
|
+
for (const [key, value] of Object.entries(color)) {
|
|
51
|
+
const at = FLAT_MEMBERS[key]
|
|
52
|
+
if (!at || (key === 'scale' && isRecord(value))) {
|
|
53
|
+
out[key] = value
|
|
54
|
+
} else if (value !== undefined) {
|
|
55
|
+
parts[at[0]][at[1]] =
|
|
56
|
+
key === 'scale' && value === 'categorical' ? 'ordinal' : value
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const part of ['scale', 'legend'] as const) {
|
|
60
|
+
if (Object.keys(parts[part]).length > 0) {
|
|
61
|
+
const nested = out[part]
|
|
62
|
+
out[part] = { ...parts[part], ...(isRecord(nested) ? nested : {}) }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @deprecated A LinearGraphDisplay's flat `color` as `encoding.color`, for
|
|
70
|
+
* the schema's `retired`. Goes with `liftFlatNodeColor`.
|
|
71
|
+
*/
|
|
72
|
+
export const retiredGraphColor = {
|
|
73
|
+
color: (color: unknown) => ({
|
|
74
|
+
encoding: { color: liftFlatNodeColor(color) },
|
|
75
|
+
}),
|
|
76
|
+
}
|
|
@@ -215,9 +215,10 @@ export function walksThrough(graph: Graph, box: string) {
|
|
|
215
215
|
for (const p of graph.paths ?? []) {
|
|
216
216
|
const walk = walkOf(p.name)
|
|
217
217
|
if (walk !== graph.referencePath) {
|
|
218
|
+
const through = p.nodeIds.includes(box)
|
|
218
219
|
for (const member of p.members ?? [walk]) {
|
|
219
220
|
all.add(member)
|
|
220
|
-
if (
|
|
221
|
+
if (through) {
|
|
221
222
|
here.add(member)
|
|
222
223
|
}
|
|
223
224
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LAYOUT_MODE_VALUES,
|
|
3
|
-
layoutModeByValue,
|
|
4
|
-
} from '@jbrowse/bandage-core/layoutModes'
|
|
1
|
+
import { LAYOUT_MODE_VALUES } from '@jbrowse/bandage-core/layoutModes'
|
|
5
2
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
|
|
6
3
|
import { getSession } from '@jbrowse/core/util'
|
|
7
4
|
import { addDisposer, isAlive, types } from '@jbrowse/mobx-state-tree'
|
|
@@ -10,6 +7,7 @@ import { reaction } from 'mobx'
|
|
|
10
7
|
import { HOVER_HIGHLIGHT_VALUES } from './hoverHighlight'
|
|
11
8
|
import { lenientMaybeEnum } from './lenientEnum'
|
|
12
9
|
import { GraphPane, fileName, formatSpanBp } from './model'
|
|
10
|
+
import { liftFlatNodeColor } from './retiredNodeColor'
|
|
13
11
|
import { canonicalRegion } from '../launchFromGraph/launchFromGraph'
|
|
14
12
|
|
|
15
13
|
import type { GraphLayers } from './graphLayers'
|
|
@@ -51,11 +49,16 @@ export default function stateModelFactory() {
|
|
|
51
49
|
hover: lenientMaybeEnum(HOVER_HIGHLIGHT_VALUES),
|
|
52
50
|
}),
|
|
53
51
|
)
|
|
52
|
+
.preProcessSnapshot(snap =>
|
|
53
|
+
snap.color === undefined
|
|
54
|
+
? snap
|
|
55
|
+
: { ...snap, color: liftFlatNodeColor(snap.color) as NodeColor },
|
|
56
|
+
)
|
|
54
57
|
.views(self => ({
|
|
55
58
|
get grammar(): GraphGrammar {
|
|
56
59
|
return {
|
|
57
60
|
layoutMode: self.layoutMode,
|
|
58
|
-
color: self.color,
|
|
61
|
+
encoding: { color: self.color },
|
|
59
62
|
size: self.size,
|
|
60
63
|
layers: self.layers,
|
|
61
64
|
facet: self.facet,
|
|
@@ -65,8 +68,11 @@ export default function stateModelFactory() {
|
|
|
65
68
|
},
|
|
66
69
|
}))
|
|
67
70
|
.actions(self => ({
|
|
68
|
-
writeGrammar(settings: GraphGrammar) {
|
|
69
|
-
Object.assign(
|
|
71
|
+
writeGrammar({ encoding, ...settings }: GraphGrammar) {
|
|
72
|
+
Object.assign(
|
|
73
|
+
self,
|
|
74
|
+
encoding ? { ...settings, color: encoding.color } : settings,
|
|
75
|
+
)
|
|
70
76
|
},
|
|
71
77
|
}))
|
|
72
78
|
.volatile(() => ({
|
|
@@ -222,10 +228,11 @@ export default function stateModelFactory() {
|
|
|
222
228
|
self,
|
|
223
229
|
reaction(
|
|
224
230
|
() => self.walkStrip,
|
|
225
|
-
|
|
231
|
+
on => {
|
|
226
232
|
if (
|
|
227
233
|
self.loadedTrackId &&
|
|
228
|
-
|
|
234
|
+
self.cutsWholeWalks !==
|
|
235
|
+
self.cutsWholeWalksIn(self.chosenLayoutMode, !on)
|
|
229
236
|
) {
|
|
230
237
|
void self.load()
|
|
231
238
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { WalkRowBlock } from '@jbrowse/bandage-core/layout/walkRowLinks'
|
|
2
|
+
|
|
3
|
+
// ggbandage's: the three biggest blocks in colour, the rest a pale grey that
|
|
4
|
+
// still carries its letter
|
|
5
|
+
const BLOCK_COLORS = ['#d4735e', '#d8a94a', '#c98aa4']
|
|
6
|
+
const OTHER_BLOCK = '#e6e6e6'
|
|
7
|
+
const BLOCK_EDGE = '#555'
|
|
8
|
+
const LETTER_FONT = 'bold 11px sans-serif'
|
|
9
|
+
const MIN_LETTER_PX = 12
|
|
10
|
+
const MIN_LETTER_BAR_PX = 9
|
|
11
|
+
|
|
12
|
+
export const blockFill = (block: number) => BLOCK_COLORS[block] ?? OTHER_BLOCK
|
|
13
|
+
|
|
14
|
+
// A, B, ... Z, then AA, AB, ...
|
|
15
|
+
export function blockLetter(block: number): string {
|
|
16
|
+
const letter = String.fromCharCode(65 + (block % 26))
|
|
17
|
+
return block < 26 ? letter : blockLetter(Math.floor(block / 26) - 1) + letter
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// How many blocks get a colour of their own, for the key
|
|
21
|
+
export const COLORED_BLOCKS = BLOCK_COLORS.length
|
|
22
|
+
|
|
23
|
+
// Each stretch as a box over its row's bar, lettered where it is wide enough
|
|
24
|
+
export function paintWalkRowBlocks(
|
|
25
|
+
ctx: CanvasRenderingContext2D,
|
|
26
|
+
blocks: WalkRowBlock[],
|
|
27
|
+
f: {
|
|
28
|
+
x: (offset: number) => number
|
|
29
|
+
rowCentre: (row: number) => number
|
|
30
|
+
barPx: number
|
|
31
|
+
},
|
|
32
|
+
) {
|
|
33
|
+
ctx.font = LETTER_FONT
|
|
34
|
+
ctx.textAlign = 'center'
|
|
35
|
+
ctx.textBaseline = 'middle'
|
|
36
|
+
for (const { row, start, bp, block } of blocks) {
|
|
37
|
+
const left = f.x(start)
|
|
38
|
+
const width = f.x(start + bp) - left
|
|
39
|
+
const top = f.rowCentre(row) - f.barPx / 2
|
|
40
|
+
ctx.fillStyle = blockFill(block)
|
|
41
|
+
ctx.fillRect(left, top, width, f.barPx)
|
|
42
|
+
ctx.strokeStyle = BLOCK_EDGE
|
|
43
|
+
ctx.lineWidth = 1
|
|
44
|
+
ctx.strokeRect(left + 0.5, top + 0.5, width - 1, f.barPx - 1)
|
|
45
|
+
if (width >= MIN_LETTER_PX && f.barPx >= MIN_LETTER_BAR_PX) {
|
|
46
|
+
ctx.fillStyle = '#111'
|
|
47
|
+
ctx.fillText(blockLetter(block), left + width / 2, f.rowCentre(row))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|