jbrowse-plugin-graphgenomeviewer 7.4.3 → 7.5.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 +10 -7
- package/dist/chunks/{AddTrackWorkflow-LK25EC3H.js → AddTrackWorkflow-NLSZYK56.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-EKLUEECW.js → GbzBaseSyntenyAdapter-4KVWVUN6.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-EKLUEECW.js.map → GbzBaseSyntenyAdapter-4KVWVUN6.js.map} +1 -1
- package/dist/chunks/{GraphGenomeView-P3XMSKUQ.js → GraphGenomeView-KI4FQ5A7.js} +2 -2
- package/dist/chunks/GraphTrackSettingsDialog-UQNMDXTE.js +4 -0
- package/dist/chunks/GraphTrackSettingsDialog-UQNMDXTE.js.map +7 -0
- package/dist/chunks/{HighlightColorDialog-G6AFRYUD.js → HighlightColorDialog-D4RCUCUF.js} +2 -2
- package/dist/chunks/LinearGraphDisplay-RPZUZSM2.js +2 -0
- package/dist/chunks/{LinearGraphDisplay-O4SQSXJJ.js.map → LinearGraphDisplay-RPZUZSM2.js.map} +2 -2
- package/dist/chunks/MinigraphBubbleAdapter-TJYI7KEN.js +2 -0
- package/dist/chunks/{MinigraphBubbleAdapter-3NJ2VLN2.js.map → MinigraphBubbleAdapter-TJYI7KEN.js.map} +1 -1
- package/dist/chunks/RgfaTabixAdapter-5ZMJLFZP.js +2 -0
- package/dist/chunks/RgfaTabixAdapter-5ZMJLFZP.js.map +7 -0
- package/dist/chunks/WalkFormsAdapter-CAJOWOEX.js +2 -0
- package/dist/chunks/WalkFormsAdapter-CAJOWOEX.js.map +7 -0
- package/dist/chunks/WalkTabixSyntenyAdapter-VI3KA2GX.js +2 -0
- package/dist/chunks/{WalkTabixSyntenyAdapter-NHNSZZBX.js.map → WalkTabixSyntenyAdapter-VI3KA2GX.js.map} +2 -2
- package/dist/chunks/{chunk-MCE6STZR.js → chunk-22ANI2KL.js} +2 -2
- package/dist/chunks/{chunk-MCE6STZR.js.map → chunk-22ANI2KL.js.map} +1 -1
- package/dist/chunks/chunk-42KD4AVY.js +2 -0
- package/dist/chunks/{chunk-OKRGO7XA.js.map → chunk-42KD4AVY.js.map} +4 -4
- package/dist/chunks/chunk-4NIWMRKH.js +2 -0
- package/dist/chunks/chunk-4NIWMRKH.js.map +7 -0
- package/dist/chunks/chunk-BCOXRUNQ.js +3 -0
- package/dist/chunks/chunk-BCOXRUNQ.js.map +7 -0
- package/dist/chunks/chunk-DAPI7M4M.js +4 -0
- package/dist/chunks/chunk-DAPI7M4M.js.map +7 -0
- package/dist/chunks/chunk-DEWZOJSC.js +2 -0
- package/dist/chunks/{chunk-6OXXT2YP.js.map → chunk-DEWZOJSC.js.map} +4 -4
- package/dist/chunks/{chunk-K4TFAHGC.js → chunk-FCMXEFJA.js} +1 -1
- package/dist/chunks/{chunk-K4TFAHGC.js.map → chunk-FCMXEFJA.js.map} +1 -1
- package/dist/chunks/{chunk-UOFWYS6T.js → chunk-GYY6LF5L.js} +1 -1
- package/dist/chunks/{chunk-UOFWYS6T.js.map → chunk-GYY6LF5L.js.map} +1 -1
- package/dist/chunks/{chunk-F2DMEJEK.js → chunk-HHMJNAA3.js} +2 -2
- package/dist/chunks/{chunk-NPV7SRJD.js → chunk-HTTONSO4.js} +2 -2
- package/dist/chunks/{chunk-NPV7SRJD.js.map → chunk-HTTONSO4.js.map} +1 -1
- package/dist/chunks/{chunk-MVVXRZXV.js → chunk-JRJHXLGP.js} +2 -2
- package/dist/chunks/{chunk-MVVXRZXV.js.map → chunk-JRJHXLGP.js.map} +2 -2
- package/dist/chunks/chunk-K4JL57NR.js +2 -0
- package/dist/chunks/chunk-K4JL57NR.js.map +7 -0
- package/dist/chunks/chunk-KOC23YNN.js +4 -0
- package/dist/chunks/chunk-KOC23YNN.js.map +7 -0
- package/dist/chunks/{chunk-MNCPMOHJ.js → chunk-LTNFVL2F.js} +2 -2
- package/dist/chunks/chunk-OZEKEYT3.js +2 -0
- package/dist/chunks/{chunk-ZAYC5OMQ.js.map → chunk-OZEKEYT3.js.map} +4 -4
- package/dist/chunks/{chunk-DUPBXBYE.js → chunk-POUV7OPY.js} +2 -2
- package/dist/chunks/{chunk-DUPBXBYE.js.map → chunk-POUV7OPY.js.map} +1 -1
- package/dist/chunks/{chunk-CNNZDE6I.js → chunk-SPL3UO2D.js} +2 -2
- package/dist/chunks/{chunk-CNNZDE6I.js.map → chunk-SPL3UO2D.js.map} +1 -1
- package/dist/chunks/{chunk-QJ43NDYS.js → chunk-TYV6UXJA.js} +2 -2
- package/dist/chunks/chunk-UR2MJJUP.js +2 -0
- package/dist/chunks/chunk-VTMJQFN5.js +2 -0
- package/dist/chunks/chunk-VTMJQFN5.js.map +7 -0
- package/dist/chunks/{chunk-CXOMX2I6.js → chunk-XZGYJ3JC.js} +1 -1
- package/dist/chunks/{chunk-CXOMX2I6.js.map → chunk-XZGYJ3JC.js.map} +1 -1
- package/dist/chunks/{chunk-TGSDXZXI.js → chunk-Z3IKZAYC.js} +2 -2
- package/dist/chunks/{chunk-TGSDXZXI.js.map → chunk-Z3IKZAYC.js.map} +1 -1
- package/dist/chunks/chunk-ZV5FNOHS.js +3 -0
- package/dist/chunks/chunk-ZV5FNOHS.js.map +7 -0
- package/dist/chunks/{renderSvg-7G6NWBXF.js → renderSvg-3PPAEMBT.js} +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -4
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +3 -3
- 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 +294 -113
- package/src/GraphGenomeView/model.ts +3 -10
- package/src/GraphGenomeView/nodeSize.test.ts +21 -5
- package/src/GraphGenomeView/nodeSize.ts +24 -8
- package/src/GraphGenomeView/pane/fitViews.ts +5 -110
- package/src/GraphGenomeView/pane/graphViews.ts +96 -20
- package/src/GraphGenomeView/pane/launchActions.ts +55 -33
- 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 -7
- package/src/GraphGenomeView/pane/renderingActions.ts +61 -2
- package/src/GraphGenomeView/pane/settingActions.ts +62 -4
- package/src/GraphGenomeView/pane/walkRowViews.ts +17 -6
- package/src/GraphGenomeView/tubeMapPanels.ts +2 -1
- package/src/GraphGenomeView/viewModel.ts +4 -9
- package/src/GraphGenomeView/walkRowBlocks.ts +50 -0
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +35 -5
- 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 +4 -3
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +2 -9
- package/src/RgfaTabixAdapter/configSchema.ts +3 -3
- package/src/RgfaTabixAdapter/walkFiles.test.ts +6 -6
- package/src/WalkFormsAdapter/WalkFormsAdapter.test.ts +204 -0
- package/src/WalkFormsAdapter/WalkFormsAdapter.ts +232 -0
- package/src/WalkFormsAdapter/configSchema.ts +127 -0
- package/src/WalkFormsAdapter/formRows.test.ts +258 -0
- package/src/WalkFormsAdapter/formRows.ts +231 -0
- package/src/WalkFormsAdapter/formsMenu.test.ts +237 -0
- package/src/WalkFormsAdapter/formsMenu.ts +202 -0
- package/src/WalkFormsAdapter/index.ts +21 -0
- package/src/WalkFormsAdapter/pinned.ts +33 -0
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.test.ts +5 -5
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +3 -6
- package/src/WalkTabixSyntenyAdapter/configSchema.ts +2 -2
- 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/index.ts +4 -0
- package/src/launchFromGraph/contributors.test.ts +52 -0
- package/src/launchFromGraph/contributors.ts +105 -1
- package/src/launchFromGraph/launchFromGraph.ts +2 -2
- package/src/panSNAliases/panSNAliases.test.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-UTYH6HEC.js +0 -6
- package/dist/chunks/GraphTrackSettingsDialog-UTYH6HEC.js.map +0 -7
- package/dist/chunks/LinearGraphDisplay-O4SQSXJJ.js +0 -2
- package/dist/chunks/MinigraphBubbleAdapter-3NJ2VLN2.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-55DGDID2.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-55DGDID2.js.map +0 -7
- package/dist/chunks/WalkTabixSyntenyAdapter-NHNSZZBX.js +0 -2
- package/dist/chunks/chunk-4I3K4RHE.js +0 -2
- package/dist/chunks/chunk-4I3K4RHE.js.map +0 -7
- package/dist/chunks/chunk-6OXXT2YP.js +0 -2
- package/dist/chunks/chunk-NFH2U74B.js +0 -2
- package/dist/chunks/chunk-OKRGO7XA.js +0 -2
- package/dist/chunks/chunk-P7VMOTUM.js +0 -3
- package/dist/chunks/chunk-P7VMOTUM.js.map +0 -7
- package/dist/chunks/chunk-RU4WSKBO.js +0 -3
- package/dist/chunks/chunk-RU4WSKBO.js.map +0 -7
- package/dist/chunks/chunk-WGTLNVST.js +0 -2
- package/dist/chunks/chunk-WGTLNVST.js.map +0 -7
- package/dist/chunks/chunk-XQFBLJU5.js +0 -4
- package/dist/chunks/chunk-XQFBLJU5.js.map +0 -7
- package/dist/chunks/chunk-Y5DNBFDY.js +0 -3
- package/dist/chunks/chunk-Y5DNBFDY.js.map +0 -7
- package/dist/chunks/chunk-ZAYC5OMQ.js +0 -2
- package/src/launchFromGraph/launchTracks.test.ts +0 -95
- package/src/launchFromGraph/launchTracks.ts +0 -49
- /package/dist/chunks/{AddTrackWorkflow-LK25EC3H.js.map → AddTrackWorkflow-NLSZYK56.js.map} +0 -0
- /package/dist/chunks/{GraphGenomeView-P3XMSKUQ.js.map → GraphGenomeView-KI4FQ5A7.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-G6AFRYUD.js.map → HighlightColorDialog-D4RCUCUF.js.map} +0 -0
- /package/dist/chunks/{chunk-F2DMEJEK.js.map → chunk-HHMJNAA3.js.map} +0 -0
- /package/dist/chunks/{chunk-MNCPMOHJ.js.map → chunk-LTNFVL2F.js.map} +0 -0
- /package/dist/chunks/{chunk-QJ43NDYS.js.map → chunk-TYV6UXJA.js.map} +0 -0
- /package/dist/chunks/{chunk-NFH2U74B.js.map → chunk-UR2MJJUP.js.map} +0 -0
- /package/dist/chunks/{renderSvg-7G6NWBXF.js.map → renderSvg-3PPAEMBT.js.map} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_follow.GRCh38.walks.bed.gz → walks5_follow.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_follow.GRCh38.walks.bed.gz.tbi → walks5_follow.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_header.GRCh38.walks.bed.gz → walks5_header.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_header.GRCh38.walks.bed.gz.tbi → walks5_header.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_inversion.GRCh38.walks.bed.gz → walks5_inversion.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_inversion.GRCh38.walks.bed.gz.tbi → walks5_inversion.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_lanepairs.GRCh38.walks.bed.gz → walks5_lanepairs.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_lanepairs.GRCh38.walks.bed.gz.tbi → walks5_lanepairs.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_maxnode.GRCh38.walks.bed.gz → walks5_maxnode.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_maxnode.GRCh38.walks.bed.gz.tbi → walks5_maxnode.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_rejoin.GRCh38.walks.bed.gz → walks5_rejoin.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_rejoin.GRCh38.walks.bed.gz.tbi → walks5_rejoin.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_twopart.GRCh38.walks.bed.gz → walks5_twopart.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_twopart.GRCh38.walks.bed.gz.tbi → walks5_twopart.GRCh38.walks.bed.gz.tbi} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jbrowse-plugin-graphgenomeviewer",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
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": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@jbrowse/core": "5.0.0-beta.11",
|
|
33
33
|
"@jbrowse/display-kit": "5.0.0-beta.11",
|
|
34
34
|
"@jbrowse/display-ui": "5.0.0-beta.11",
|
|
35
|
-
"@jbrowse/graph-walks": "^4.
|
|
35
|
+
"@jbrowse/graph-walks": "^4.2.0",
|
|
36
36
|
"@jbrowse/render-core": "5.0.0-beta.11",
|
|
37
37
|
"@jbrowse/synteny-core": "5.0.0-beta.11"
|
|
38
38
|
},
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@emotion/react": "^11.14.0",
|
|
41
41
|
"@emotion/styled": "^11.14.1",
|
|
42
42
|
"@eslint/js": "^10.0.1",
|
|
43
|
-
"@jbrowse/bandage-core": "^11.
|
|
43
|
+
"@jbrowse/bandage-core": "^11.11.0",
|
|
44
44
|
"@jbrowse/cli": "^4.3.0",
|
|
45
45
|
"@jbrowse/mobx-state-tree": "^6.9.0",
|
|
46
46
|
"@jbrowse/plugin-linear-genome-view": "5.0.0-beta.11",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { layoutEngine } from '@jbrowse/bandage-core/layoutEngines'
|
|
2
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
3
|
+
|
|
4
|
+
import GraphComputeLayout from './GraphComputeLayout'
|
|
5
|
+
|
|
6
|
+
vi.mock('@jbrowse/bandage-core/layoutEngines', () => ({
|
|
7
|
+
layoutEngine: vi.fn(() => Promise.resolve({ result: {}, duration: 0 })),
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
// A layout the user moved on from while it waited behind another in the
|
|
11
|
+
// worker's queue
|
|
12
|
+
test('a layout aborted before it starts never reaches the engine', async () => {
|
|
13
|
+
const method = new GraphComputeLayout(new PluginManager([]))
|
|
14
|
+
const gone = new AbortController()
|
|
15
|
+
gone.abort()
|
|
16
|
+
await expect(
|
|
17
|
+
method.execute({
|
|
18
|
+
graph: { name: 'g', nodes: [], edges: [] },
|
|
19
|
+
options: {},
|
|
20
|
+
signal: gone.signal,
|
|
21
|
+
} as never),
|
|
22
|
+
).rejects.toMatchObject({ name: 'AbortError' })
|
|
23
|
+
expect(layoutEngine).not.toHaveBeenCalled()
|
|
24
|
+
})
|
|
@@ -24,8 +24,10 @@ export default class GraphComputeLayout extends RpcMethodType<'GraphComputeLayou
|
|
|
24
24
|
name = 'GraphComputeLayout' as const
|
|
25
25
|
|
|
26
26
|
async execute(args: RpcExecuteArgs<'GraphComputeLayout'>) {
|
|
27
|
-
const { graph, options, statusCallback } = args
|
|
28
|
-
|
|
27
|
+
const { graph, options, signal, statusCallback } = args
|
|
28
|
+
// The engine takes no signal, so a layout superseded while it queued
|
|
29
|
+
// behind another is dropped here rather than holding up the next cut
|
|
30
|
+
signal?.throwIfAborted()
|
|
29
31
|
statusCallback?.('Computing layout')
|
|
30
32
|
return layoutEngine({ graph, options })
|
|
31
33
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
|
|
3
3
|
import { BUBBLE_SPREADS } from '@jbrowse/bandage-core/bubbleSpreads'
|
|
4
|
+
import { EDGE_WIDTHS } from '@jbrowse/bandage-core/edgeWidths'
|
|
4
5
|
import { LAYOUT_ENGINES } from '@jbrowse/bandage-core/layoutEngines'
|
|
5
6
|
import { LAYOUT_QUALITIES } from '@jbrowse/bandage-core/layoutQualities'
|
|
6
7
|
import { NODE_WIDTHS } from '@jbrowse/bandage-core/nodeWidths'
|
|
@@ -227,6 +228,21 @@ const GraphSettingsDialog = observer(function GraphSettingsDialog(props: {
|
|
|
227
228
|
</Caption>
|
|
228
229
|
</SettingSelect>
|
|
229
230
|
|
|
231
|
+
<SettingSelect
|
|
232
|
+
label="Link width"
|
|
233
|
+
value={model.linkWidth}
|
|
234
|
+
options={EDGE_WIDTHS}
|
|
235
|
+
testId="graph-link-width-select"
|
|
236
|
+
onChange={links => {
|
|
237
|
+
model.setLinkWidth(links)
|
|
238
|
+
}}
|
|
239
|
+
>
|
|
240
|
+
<Caption>
|
|
241
|
+
Walks as width, so a common route reads heavy and a private one
|
|
242
|
+
thin.
|
|
243
|
+
</Caption>
|
|
244
|
+
</SettingSelect>
|
|
245
|
+
|
|
230
246
|
<SettingSwitch
|
|
231
247
|
label="Draw paths"
|
|
232
248
|
checked={model.drawPaths}
|
|
@@ -5,6 +5,7 @@ import { observer } from 'mobx-react'
|
|
|
5
5
|
|
|
6
6
|
import ElTree, { useSvgId } from './ElTree'
|
|
7
7
|
import { PaneOverlayCanvas, PaneSvgLayer } from './PaneLayer'
|
|
8
|
+
import { paintWalkRowBlocks } from '../walkRowBlocks'
|
|
8
9
|
import { SECTION_HEADER_PX, walkRowTrees } from '../walkRowGroups'
|
|
9
10
|
import { paintWalkRowSpans, walkRowSpans } from '../walkRowSpans'
|
|
10
11
|
|
|
@@ -83,6 +84,82 @@ function WalkRowSpansCanvas({ model }: { model: GraphPaneModel }) {
|
|
|
83
84
|
)
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
const RIBBON_FORWARD = 'rgba(160, 196, 232, 0.55)'
|
|
88
|
+
const RIBBON_REVERSED = 'rgba(160, 140, 220, 0.55)'
|
|
89
|
+
|
|
90
|
+
// Under the bars, a ribbon from each stretch of a row to where the row below
|
|
91
|
+
// walks the same nodes, lavender where it walks them backwards, as ggbandage
|
|
92
|
+
// draws them: neighbours that share a copy, an inversion or a duplication
|
|
93
|
+
// read off the ribbons' shape
|
|
94
|
+
function WalkRowRibbonsCanvas({ model }: { model: GraphPaneModel }) {
|
|
95
|
+
const paint = useCallback(
|
|
96
|
+
(ctx: CanvasRenderingContext2D) => {
|
|
97
|
+
const links = model.walkRowRibbons
|
|
98
|
+
const bars = model.walkRowBars
|
|
99
|
+
const place = model.walkRowPlacement
|
|
100
|
+
const pitch = model.walkRowPitch
|
|
101
|
+
if (!links || !bars || !place || !pitch) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
const { scaleX, scaleY, translateX, translateY } = model.screenTransform
|
|
105
|
+
const x = (offset: number) => (bars.origin + offset) * scaleX + translateX
|
|
106
|
+
const half = pitch.barPx / 2
|
|
107
|
+
for (const { row, a0, a1, b0, b1 } of links) {
|
|
108
|
+
const top = place.rowY(row) * scaleY + translateY + half
|
|
109
|
+
const bottom = place.rowY(row + 1) * scaleY + translateY - half
|
|
110
|
+
ctx.fillStyle = b0 > b1 ? RIBBON_REVERSED : RIBBON_FORWARD
|
|
111
|
+
ctx.beginPath()
|
|
112
|
+
ctx.moveTo(x(a0), top)
|
|
113
|
+
ctx.lineTo(x(a1), top)
|
|
114
|
+
ctx.lineTo(x(b1), bottom)
|
|
115
|
+
ctx.lineTo(x(b0), bottom)
|
|
116
|
+
ctx.closePath()
|
|
117
|
+
ctx.fill()
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[model],
|
|
121
|
+
)
|
|
122
|
+
return (
|
|
123
|
+
<PaneOverlayCanvas
|
|
124
|
+
model={model}
|
|
125
|
+
zIndex={2}
|
|
126
|
+
testId="graph-walk-rows-ribbons"
|
|
127
|
+
paint={paint}
|
|
128
|
+
/>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Over the bars, the lettered blocks: copy A on two rows, and C where another
|
|
133
|
+
// row has A, read at a glance
|
|
134
|
+
function WalkRowBlocksCanvas({ model }: { model: GraphPaneModel }) {
|
|
135
|
+
const paint = useCallback(
|
|
136
|
+
(ctx: CanvasRenderingContext2D) => {
|
|
137
|
+
const blocks = model.sharedBlocks
|
|
138
|
+
const bars = model.walkRowBars
|
|
139
|
+
const place = model.walkRowPlacement
|
|
140
|
+
const pitch = model.walkRowPitch
|
|
141
|
+
if (!blocks || !bars || !place || !pitch) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
const { scaleX, scaleY, translateX, translateY } = model.screenTransform
|
|
145
|
+
paintWalkRowBlocks(ctx, blocks, {
|
|
146
|
+
x: offset => (bars.origin + offset) * scaleX + translateX,
|
|
147
|
+
rowCentre: row => place.rowY(row) * scaleY + translateY,
|
|
148
|
+
barPx: pitch.barPx,
|
|
149
|
+
})
|
|
150
|
+
},
|
|
151
|
+
[model],
|
|
152
|
+
)
|
|
153
|
+
return (
|
|
154
|
+
<PaneOverlayCanvas
|
|
155
|
+
model={model}
|
|
156
|
+
zIndex={3}
|
|
157
|
+
testId="graph-walk-rows-blocks"
|
|
158
|
+
paint={paint}
|
|
159
|
+
/>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
86
163
|
// Its own observer, so moving between rows redraws this rect and not every row
|
|
87
164
|
const HoveredWalkRow = observer(function HoveredWalkRow({
|
|
88
165
|
model,
|
|
@@ -142,7 +219,13 @@ const WalkRowsOverlay = observer(function WalkRowsOverlay({
|
|
|
142
219
|
}
|
|
143
220
|
return (
|
|
144
221
|
<>
|
|
222
|
+
{vector || !model.walkRowRibbons ? null : (
|
|
223
|
+
<WalkRowRibbonsCanvas model={model} />
|
|
224
|
+
)}
|
|
145
225
|
{vector ? null : <WalkRowSpansCanvas model={model} />}
|
|
226
|
+
{vector || !model.sharedBlocks ? null : (
|
|
227
|
+
<WalkRowBlocksCanvas model={model} />
|
|
228
|
+
)}
|
|
146
229
|
<PaneSvgLayer
|
|
147
230
|
model={model}
|
|
148
231
|
zIndex={3}
|
|
@@ -136,7 +136,7 @@ test('a hosted pane that pans itself claims presses from the linear view; one th
|
|
|
136
136
|
})
|
|
137
137
|
|
|
138
138
|
// A pane mid-drag, whose transform moves as the model's would
|
|
139
|
-
function panModel() {
|
|
139
|
+
function panModel(state: Record<string, unknown> = {}) {
|
|
140
140
|
const model: Record<string, unknown> = fakeModel({
|
|
141
141
|
isPanning: false,
|
|
142
142
|
draggingNode: null,
|
|
@@ -155,6 +155,7 @@ function panModel() {
|
|
|
155
155
|
model.isPanning = false
|
|
156
156
|
},
|
|
157
157
|
clearHover: vi.fn(),
|
|
158
|
+
...state,
|
|
158
159
|
})
|
|
159
160
|
return model
|
|
160
161
|
}
|
|
@@ -382,11 +383,12 @@ describe('a press on a bubble label', () => {
|
|
|
382
383
|
window.dispatchEvent(e)
|
|
383
384
|
}
|
|
384
385
|
|
|
385
|
-
function pressLabel(
|
|
386
|
-
|
|
387
|
-
|
|
386
|
+
function pressLabel(
|
|
387
|
+
model = panModel(),
|
|
388
|
+
{ result } = renderHook(() =>
|
|
388
389
|
usePaneGestures(model as unknown as GraphPaneModel),
|
|
389
|
-
)
|
|
390
|
+
),
|
|
391
|
+
) {
|
|
390
392
|
const layer = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
391
393
|
layer.setAttribute(PANS_WITH_DRAWING, '')
|
|
392
394
|
const chip = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
|
@@ -402,6 +404,7 @@ describe('a press on a bubble label', () => {
|
|
|
402
404
|
const click = { stopPropagation: vi.fn() }
|
|
403
405
|
return {
|
|
404
406
|
model,
|
|
407
|
+
result,
|
|
405
408
|
click: () => {
|
|
406
409
|
onClickCapture(click as unknown as React.MouseEvent)
|
|
407
410
|
return click.stopPropagation.mock.calls.length === 0
|
|
@@ -428,6 +431,75 @@ describe('a press on a bubble label', () => {
|
|
|
428
431
|
expect(click()).toBe(false)
|
|
429
432
|
})
|
|
430
433
|
|
|
434
|
+
// The pointer leaves a 3px tick at once, so the canvas under it sees the
|
|
435
|
+
// same moves the window listener pans by
|
|
436
|
+
test('pans once, not again through the canvas under the pointer', () => {
|
|
437
|
+
const frames: FrameRequestCallback[] = []
|
|
438
|
+
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) =>
|
|
439
|
+
frames.push(cb),
|
|
440
|
+
)
|
|
441
|
+
const { model, result } = pressLabel()
|
|
442
|
+
act(() => {
|
|
443
|
+
pointer('pointermove', 110)
|
|
444
|
+
result.current.handlers.onPointerMove(at(110, 20))
|
|
445
|
+
pointer('pointermove', 140)
|
|
446
|
+
result.current.handlers.onPointerMove(at(140, 20))
|
|
447
|
+
frames.splice(0).forEach(f => {
|
|
448
|
+
f(0)
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
expect(model.translateX).toBe(40)
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
test('after a drag on the reference strip still pans the graph', () => {
|
|
455
|
+
const frames: FrameRequestCallback[] = []
|
|
456
|
+
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) =>
|
|
457
|
+
frames.push(cb),
|
|
458
|
+
)
|
|
459
|
+
const horizontalScroll = vi.fn()
|
|
460
|
+
const model = panModel({
|
|
461
|
+
referenceStripZonePx: 30,
|
|
462
|
+
host: { horizontalScroll },
|
|
463
|
+
followsHostView: false,
|
|
464
|
+
})
|
|
465
|
+
const hook = renderHook(() =>
|
|
466
|
+
usePaneGestures(model as unknown as GraphPaneModel),
|
|
467
|
+
)
|
|
468
|
+
const canvas = Object.assign(document.createElement('canvas'), {
|
|
469
|
+
setPointerCapture: vi.fn(),
|
|
470
|
+
getBoundingClientRect: () => ({ left: 0, top: 0 }),
|
|
471
|
+
})
|
|
472
|
+
const press = (x: number) =>
|
|
473
|
+
({
|
|
474
|
+
clientX: x,
|
|
475
|
+
clientY: 10,
|
|
476
|
+
button: 0,
|
|
477
|
+
pointerId: 1,
|
|
478
|
+
pointerType: 'mouse',
|
|
479
|
+
currentTarget: canvas,
|
|
480
|
+
}) as unknown as React.PointerEvent
|
|
481
|
+
const h = hook.result.current.handlers
|
|
482
|
+
act(() => {
|
|
483
|
+
h.onPointerDown(press(50))
|
|
484
|
+
h.onPointerMove(press(80))
|
|
485
|
+
frames.splice(0).forEach(f => {
|
|
486
|
+
f(0)
|
|
487
|
+
})
|
|
488
|
+
h.onPointerUp(press(80))
|
|
489
|
+
})
|
|
490
|
+
horizontalScroll.mockClear()
|
|
491
|
+
pressLabel(model, hook)
|
|
492
|
+
act(() => {
|
|
493
|
+
pointer('pointermove', 140)
|
|
494
|
+
frames.splice(0).forEach(f => {
|
|
495
|
+
f(0)
|
|
496
|
+
})
|
|
497
|
+
pointer('pointerup', 140)
|
|
498
|
+
})
|
|
499
|
+
expect(horizontalScroll).not.toHaveBeenCalled()
|
|
500
|
+
expect(model.translateX).toBe(40)
|
|
501
|
+
})
|
|
502
|
+
|
|
431
503
|
test('that stays put is a click on the label', () => {
|
|
432
504
|
const { model, click } = pressLabel()
|
|
433
505
|
act(() => {
|
|
@@ -439,6 +511,11 @@ describe('a press on a bubble label', () => {
|
|
|
439
511
|
})
|
|
440
512
|
})
|
|
441
513
|
|
|
514
|
+
test('a frame that began before the wheel event never zooms backwards', () => {
|
|
515
|
+
expect(easeZoom(0.2, -5)).toBeGreaterThanOrEqual(0)
|
|
516
|
+
expect(easeZoom(-0.2, -5)).toBeLessThanOrEqual(0)
|
|
517
|
+
})
|
|
518
|
+
|
|
442
519
|
test('a wheel notch eases in over a few frames and lands the whole zoom', () => {
|
|
443
520
|
const owed = Math.log(1.21)
|
|
444
521
|
let left = owed
|
|
@@ -27,9 +27,11 @@ const ZOOM_ACTIVE_MS = 100
|
|
|
27
27
|
const ZOOM_EASE_MS = 24
|
|
28
28
|
const MAX_ZOOM_BACKLOG = Math.log(16)
|
|
29
29
|
|
|
30
|
-
//
|
|
30
|
+
// How much of the owed zoom, in log(scale), to apply this frame. A frame's
|
|
31
|
+
// timestamp is when it began, so a wheel handled late in one leaves elapsedMs
|
|
32
|
+
// negative, which would ease the zoom backwards.
|
|
31
33
|
export function easeZoom(backlog: number, elapsedMs: number) {
|
|
32
|
-
const rest = backlog * Math.exp(-elapsedMs / ZOOM_EASE_MS)
|
|
34
|
+
const rest = backlog * Math.exp(-Math.max(0, elapsedMs) / ZOOM_EASE_MS)
|
|
33
35
|
return Math.abs(rest) < 1e-3 ? backlog : backlog - rest
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -173,6 +175,9 @@ export function usePaneGestures(model: GraphPaneModel) {
|
|
|
173
175
|
const dragCanvasRef = useRef<Element | null>(null)
|
|
174
176
|
const pinchRef = useRef<ReturnType<typeof pinchOf> | null>(null)
|
|
175
177
|
const stripPanRef = useRef(false)
|
|
178
|
+
// a press on a bubble label pans through window listeners, so the canvas
|
|
179
|
+
// under the pointer leaves those moves alone
|
|
180
|
+
const labelPanRef = useRef<{ end: () => void; moved: boolean } | null>(null)
|
|
176
181
|
const verticalPanRef = useRef(false)
|
|
177
182
|
// A pan and a hover are applied once per frame, not once per mousemove:
|
|
178
183
|
// mousemove fires in bursts well above the frame rate, and each pan step
|
|
@@ -428,7 +433,7 @@ export function usePaneGestures(model: GraphPaneModel) {
|
|
|
428
433
|
scheduleFrame()
|
|
429
434
|
return
|
|
430
435
|
}
|
|
431
|
-
if (isOtherPointer(e)) {
|
|
436
|
+
if (isOtherPointer(e) || labelPanRef.current) {
|
|
432
437
|
return
|
|
433
438
|
}
|
|
434
439
|
const dx = e.clientX - lastMouseRef.current.x
|
|
@@ -542,7 +547,6 @@ export function usePaneGestures(model: GraphPaneModel) {
|
|
|
542
547
|
// the drawing does, rather than leaving a patch of the graph that will not
|
|
543
548
|
// drag; one that stays put is a click on the label. The click that ends a
|
|
544
549
|
// pan is swallowed before the label sees it.
|
|
545
|
-
const labelPanRef = useRef<{ end: () => void; moved: boolean } | null>(null)
|
|
546
550
|
useEffect(
|
|
547
551
|
() => () => {
|
|
548
552
|
labelPanRef.current?.end()
|
|
@@ -560,6 +564,8 @@ export function usePaneGestures(model: GraphPaneModel) {
|
|
|
560
564
|
return
|
|
561
565
|
}
|
|
562
566
|
labelPanRef.current?.end()
|
|
567
|
+
stripPanRef.current = false
|
|
568
|
+
verticalPanRef.current = false
|
|
563
569
|
const id = e.pointerId
|
|
564
570
|
const start = pointOf(e)
|
|
565
571
|
let last = start
|
|
@@ -9,8 +9,19 @@ import { isRecord } from '../isRecord'
|
|
|
9
9
|
// - `referenceStrip`: reference segments at their bp, over a drawing in its
|
|
10
10
|
// own coordinates inside a linear view
|
|
11
11
|
// - `walkStrip`: walk rows under a node layout, linked to the drawing
|
|
12
|
+
// - `walkRibbons`: between neighbouring walk rows, a ribbon joining the
|
|
13
|
+
// stretches both walk through the same nodes
|
|
14
|
+
// - `walkBlocks`: on walk rows, a lettered box round each stretch of nodes off
|
|
15
|
+
// the reference's path that two or more rows walk
|
|
12
16
|
export type GraphLayer =
|
|
13
|
-
|
|
17
|
+
| 'paths'
|
|
18
|
+
| 'bubbles'
|
|
19
|
+
| 'deletions'
|
|
20
|
+
| 'genes'
|
|
21
|
+
| 'referenceStrip'
|
|
22
|
+
| 'walkStrip'
|
|
23
|
+
| 'walkRibbons'
|
|
24
|
+
| 'walkBlocks'
|
|
14
25
|
|
|
15
26
|
const DEFAULTS: Record<GraphLayer, boolean> = {
|
|
16
27
|
paths: false,
|
|
@@ -19,6 +30,8 @@ const DEFAULTS: Record<GraphLayer, boolean> = {
|
|
|
19
30
|
genes: true,
|
|
20
31
|
referenceStrip: true,
|
|
21
32
|
walkStrip: false,
|
|
33
|
+
walkRibbons: false,
|
|
34
|
+
walkBlocks: false,
|
|
22
35
|
}
|
|
23
36
|
|
|
24
37
|
// A config or session states only what differs from the defaults, as
|
|
@@ -28,6 +28,7 @@ import { walkKey } from '@jbrowse/bandage-core/walkKey'
|
|
|
28
28
|
|
|
29
29
|
import { CALL_TOLERANCE } from './repeats/walkCalls'
|
|
30
30
|
import { PANEL_REFERENCE_TUBE } from './tubeMapPanels'
|
|
31
|
+
import { COLORED_BLOCKS, blockFill, blockLetter } from './walkRowBlocks'
|
|
31
32
|
|
|
32
33
|
import type { GraphPaneModel } from './model'
|
|
33
34
|
import type { KeySwatch } from '@jbrowse/bandage-core/layout/walkRowDraw'
|
|
@@ -399,6 +400,36 @@ export function walkRowSwatch(swatch: KeySwatch): LegendSwatch {
|
|
|
399
400
|
}
|
|
400
401
|
}
|
|
401
402
|
|
|
403
|
+
// The shared blocks the rows draw: each coloured one by its letter, then one
|
|
404
|
+
// row for the grey rest
|
|
405
|
+
function blockRows(model: GraphPaneModel): LegendRow[] {
|
|
406
|
+
const present = new Set(model.sharedBlocks?.map(b => b.block))
|
|
407
|
+
const swatch = (block: number) => ({
|
|
408
|
+
kind: 'fill' as const,
|
|
409
|
+
stops: flat(blockFill(block)),
|
|
410
|
+
height: WALK_ROW_SWATCH_PX,
|
|
411
|
+
})
|
|
412
|
+
return [
|
|
413
|
+
...[...present]
|
|
414
|
+
.filter(block => block < COLORED_BLOCKS)
|
|
415
|
+
.sort((a, b) => a - b)
|
|
416
|
+
.map(block => ({
|
|
417
|
+
key: `block-${block}`,
|
|
418
|
+
label: `${blockLetter(block)}: off the reference, walked by several rows`,
|
|
419
|
+
swatch: swatch(block),
|
|
420
|
+
})),
|
|
421
|
+
...([...present].some(block => block >= COLORED_BLOCKS)
|
|
422
|
+
? [
|
|
423
|
+
{
|
|
424
|
+
key: 'block-more',
|
|
425
|
+
label: 'more shared blocks',
|
|
426
|
+
swatch: swatch(COLORED_BLOCKS),
|
|
427
|
+
},
|
|
428
|
+
]
|
|
429
|
+
: []),
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
|
|
402
433
|
// The walk rows' bar colours, genes and ticks; each tick row appears once
|
|
403
434
|
// the rows hold one, so a catalogue with no genotypes keeps the plain key
|
|
404
435
|
function walkRowsRows(model: GraphPaneModel): LegendRow[] {
|
|
@@ -421,6 +452,7 @@ function walkRowsRows(model: GraphPaneModel): LegendRow[] {
|
|
|
421
452
|
label: entry.label,
|
|
422
453
|
swatch: entry.note ? undefined : walkRowSwatch(entry.swatch),
|
|
423
454
|
})),
|
|
455
|
+
...blockRows(model),
|
|
424
456
|
...(calls.some(call => call.spanningReads !== 0)
|
|
425
457
|
? [tick('call', CALL_TICK, 'allele length called for this walk')]
|
|
426
458
|
: []),
|