jbrowse-plugin-graphgenomeviewer 4.2.0 → 4.3.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/{GraphGenomeView-QOBHGMLE.js → GraphGenomeView-ZB4N6Q3H.js} +2 -2
- package/dist/chunks/{GraphTrackSettingsDialog-RTSJTM6A.js → GraphTrackSettingsDialog-LXYUYPZS.js} +2 -2
- package/dist/chunks/{LinearGraphDisplay-TPXW6POY.js → LinearGraphDisplay-BCTYOYQ5.js} +2 -2
- package/dist/chunks/{chunk-4C4CMI6K.js → chunk-CNFN6EVE.js} +2 -2
- package/dist/chunks/chunk-PPYB6N2Q.js +2 -0
- package/dist/chunks/chunk-PPYB6N2Q.js.map +7 -0
- package/dist/chunks/chunk-PSOLXUFN.js +3 -0
- package/dist/chunks/chunk-PSOLXUFN.js.map +7 -0
- package/dist/chunks/chunk-UTWFU4X6.js +2 -0
- package/dist/chunks/chunk-UTWFU4X6.js.map +7 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +5 -5
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +2 -2
- package/src/GraphGenomeView/components/GraphCanvas.tsx +6 -53
- package/src/GraphGenomeView/components/WalkRowContextMenu.tsx +63 -0
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +1 -1
- package/src/GraphGenomeView/components/WalkStrip.tsx +207 -0
- package/src/GraphGenomeView/components/WalkStripLocator.tsx +49 -0
- package/src/GraphGenomeView/components/usePaneGestures.ts +1 -0
- package/src/GraphGenomeView/model.test.ts +42 -3
- package/src/GraphGenomeView/model.ts +139 -13
- package/src/GraphGenomeView/viewModel.ts +23 -7
- package/src/version.ts +1 -1
- package/dist/chunks/chunk-ECKLYJWP.js +0 -2
- package/dist/chunks/chunk-ECKLYJWP.js.map +0 -7
- package/dist/chunks/chunk-HW55QCJG.js +0 -3
- package/dist/chunks/chunk-HW55QCJG.js.map +0 -7
- package/dist/chunks/chunk-SIP5IHWK.js +0 -2
- package/dist/chunks/chunk-SIP5IHWK.js.map +0 -7
- /package/dist/chunks/{GraphGenomeView-QOBHGMLE.js.map → GraphGenomeView-ZB4N6Q3H.js.map} +0 -0
- /package/dist/chunks/{GraphTrackSettingsDialog-RTSJTM6A.js.map → GraphTrackSettingsDialog-LXYUYPZS.js.map} +0 -0
- /package/dist/chunks/{LinearGraphDisplay-TPXW6POY.js.map → LinearGraphDisplay-BCTYOYQ5.js.map} +0 -0
- /package/dist/chunks/{chunk-4C4CMI6K.js.map → chunk-CNFN6EVE.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jbrowse-plugin-graphgenomeviewer",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.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": {
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"typescript-eslint": "^8.70.1",
|
|
77
77
|
"typescript7": "npm:typescript@^7.0.2",
|
|
78
78
|
"vitest": "^5.0.2",
|
|
79
|
-
"@jbrowse/bandage-core": "4.
|
|
79
|
+
"@jbrowse/bandage-core": "4.3.0"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"react": ">=18.0.0",
|
|
@@ -11,7 +11,6 @@ import { ErrorBanner, LoadingOverlay, Menu } from '@jbrowse/core/ui'
|
|
|
11
11
|
import { isAlive } from '@jbrowse/mobx-state-tree'
|
|
12
12
|
import { useRenderingBackend } from '@jbrowse/render-core/useRenderingBackend'
|
|
13
13
|
import InfoIcon from '@mui/icons-material/Info'
|
|
14
|
-
import LineStyleIcon from '@mui/icons-material/LineStyle'
|
|
15
14
|
import { observer } from 'mobx-react'
|
|
16
15
|
|
|
17
16
|
import BubbleHalos, { HaloLegend } from './BubbleHalos'
|
|
@@ -25,7 +24,10 @@ import ReferenceStripOverlay, {
|
|
|
25
24
|
import TubeMapOverlay, { TubeMapLegend } from './TubeMapOverlay'
|
|
26
25
|
import UnpopButton from './UnpopButton'
|
|
27
26
|
import WalkKey, { walkSwatchStyle } from './WalkKey'
|
|
27
|
+
import { WalkRowContextMenu } from './WalkRowContextMenu'
|
|
28
28
|
import WalkRowsOverlay, { WalkRowsLegend } from './WalkRowsOverlay'
|
|
29
|
+
import WalkStrip from './WalkStrip'
|
|
30
|
+
import WalkStripLocator from './WalkStripLocator'
|
|
29
31
|
import { legendBoxStyle, legendRowStyle } from './legendStyles'
|
|
30
32
|
import { usePaneGestures, useWheelZoom } from './usePaneGestures'
|
|
31
33
|
import { locLabel, nodeOwnLocation } from '../../launchFromGraph/contributors'
|
|
@@ -552,58 +554,6 @@ const NodeContextMenu = observer(function NodeContextMenu({
|
|
|
552
554
|
)
|
|
553
555
|
})
|
|
554
556
|
|
|
555
|
-
// A walk row's bar stands for a span of its haplotype's own contig, so the
|
|
556
|
-
// question a right-click asks is answered by a linear view on that assembly,
|
|
557
|
-
// with its genes
|
|
558
|
-
const WalkRowContextMenu = observer(function WalkRowContextMenu({
|
|
559
|
-
model,
|
|
560
|
-
row,
|
|
561
|
-
top,
|
|
562
|
-
left,
|
|
563
|
-
onClose,
|
|
564
|
-
}: {
|
|
565
|
-
model: GraphPaneModel
|
|
566
|
-
row: number
|
|
567
|
-
top: number
|
|
568
|
-
left: number
|
|
569
|
-
onClose: () => void
|
|
570
|
-
}) {
|
|
571
|
-
const target = model.walkRowLaunchTarget(row)
|
|
572
|
-
const assembly = target?.assembly
|
|
573
|
-
return (
|
|
574
|
-
<Menu
|
|
575
|
-
open
|
|
576
|
-
anchorReference="anchorPosition"
|
|
577
|
-
anchorPosition={{ top, left }}
|
|
578
|
-
onClose={() => {
|
|
579
|
-
onClose()
|
|
580
|
-
}}
|
|
581
|
-
onMenuItemClick={callback => {
|
|
582
|
-
callback()
|
|
583
|
-
}}
|
|
584
|
-
menuItems={[
|
|
585
|
-
target && assembly
|
|
586
|
-
? {
|
|
587
|
-
label: `Linear genome view — ${assembly} ${locLabel(target.location)}`,
|
|
588
|
-
icon: LineStyleIcon,
|
|
589
|
-
onClick: () => {
|
|
590
|
-
model.showInLinearView({ location: target.location, assembly })
|
|
591
|
-
},
|
|
592
|
-
}
|
|
593
|
-
: {
|
|
594
|
-
label: 'Linear genome view',
|
|
595
|
-
icon: LineStyleIcon,
|
|
596
|
-
disabled: true,
|
|
597
|
-
disabledHelpText: target
|
|
598
|
-
? `No assembly in this session is ${target.location.haplotype ?? target.label}`
|
|
599
|
-
: 'This walk states no contig coordinates',
|
|
600
|
-
onClick: () => {},
|
|
601
|
-
},
|
|
602
|
-
]}
|
|
603
|
-
/>
|
|
604
|
-
)
|
|
605
|
-
})
|
|
606
|
-
|
|
607
557
|
// `ownChrome` false for a pane inside a track, whose controls, loading state
|
|
608
558
|
// and errors are the track's. A hosted pane whose x the host places takes no pan or wheel of its
|
|
609
559
|
// own: those are the linear view's, as on any other track. One drawing its
|
|
@@ -676,6 +626,7 @@ const GraphCanvas = observer(function GraphCanvas({
|
|
|
676
626
|
<GenePins model={model} />
|
|
677
627
|
<LabelLayer model={model} />
|
|
678
628
|
<WalkRowsOverlay model={model} />
|
|
629
|
+
<WalkStripLocator model={model} />
|
|
679
630
|
<Legends model={model} />
|
|
680
631
|
</>
|
|
681
632
|
)}
|
|
@@ -699,6 +650,8 @@ const GraphCanvas = observer(function GraphCanvas({
|
|
|
699
650
|
) : null}
|
|
700
651
|
</div>
|
|
701
652
|
|
|
653
|
+
{model.walkStripShown ? <WalkStrip model={model} /> : null}
|
|
654
|
+
|
|
702
655
|
<HoverTooltips model={model} />
|
|
703
656
|
|
|
704
657
|
{contextNode ? (
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Menu } from '@jbrowse/core/ui'
|
|
2
|
+
import LineStyleIcon from '@mui/icons-material/LineStyle'
|
|
3
|
+
import { observer } from 'mobx-react'
|
|
4
|
+
|
|
5
|
+
import { locLabel } from '../../launchFromGraph/contributors'
|
|
6
|
+
|
|
7
|
+
import type { GraphPaneModel } from '../model'
|
|
8
|
+
import type { WalkRows } from '@jbrowse/bandage-core/layout/walkRows'
|
|
9
|
+
|
|
10
|
+
// A walk row's bar stands for a span of its haplotype's own contig, so the
|
|
11
|
+
// question a right-click asks is answered by a linear view on that assembly,
|
|
12
|
+
// with its genes
|
|
13
|
+
export const WalkRowContextMenu = observer(function WalkRowContextMenu({
|
|
14
|
+
model,
|
|
15
|
+
row,
|
|
16
|
+
bars,
|
|
17
|
+
top,
|
|
18
|
+
left,
|
|
19
|
+
onClose,
|
|
20
|
+
}: {
|
|
21
|
+
model: GraphPaneModel
|
|
22
|
+
row: number
|
|
23
|
+
// the rows `row` indexes, walk rows' own unless a strip's
|
|
24
|
+
bars?: WalkRows
|
|
25
|
+
top: number
|
|
26
|
+
left: number
|
|
27
|
+
onClose: () => void
|
|
28
|
+
}) {
|
|
29
|
+
const target = model.walkRowLaunchTarget(row, bars ?? model.walkRowBars)
|
|
30
|
+
const assembly = target?.assembly
|
|
31
|
+
return (
|
|
32
|
+
<Menu
|
|
33
|
+
open
|
|
34
|
+
anchorReference="anchorPosition"
|
|
35
|
+
anchorPosition={{ top, left }}
|
|
36
|
+
onClose={() => {
|
|
37
|
+
onClose()
|
|
38
|
+
}}
|
|
39
|
+
onMenuItemClick={callback => {
|
|
40
|
+
callback()
|
|
41
|
+
}}
|
|
42
|
+
menuItems={[
|
|
43
|
+
target && assembly
|
|
44
|
+
? {
|
|
45
|
+
label: `Linear genome view — ${assembly} ${locLabel(target.location)}`,
|
|
46
|
+
icon: LineStyleIcon,
|
|
47
|
+
onClick: () => {
|
|
48
|
+
model.showInLinearView({ location: target.location, assembly })
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
: {
|
|
52
|
+
label: 'Linear genome view',
|
|
53
|
+
icon: LineStyleIcon,
|
|
54
|
+
disabled: true,
|
|
55
|
+
disabledHelpText: target
|
|
56
|
+
? `No assembly in this session is ${target.location.haplotype ?? target.label}`
|
|
57
|
+
: 'This walk states no contig coordinates',
|
|
58
|
+
onClick: () => {},
|
|
59
|
+
},
|
|
60
|
+
]}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
})
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { useId, useMemo, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
walkRowReadout,
|
|
5
|
+
walkRowsKey,
|
|
6
|
+
walkRowsTree,
|
|
7
|
+
} from '@jbrowse/bandage-core/layout/walkRowDraw'
|
|
8
|
+
import {
|
|
9
|
+
stripRowAt,
|
|
10
|
+
walkMarksTree,
|
|
11
|
+
} from '@jbrowse/bandage-core/layout/walkStrip'
|
|
12
|
+
import { RAMP_GRADIENT_CSS } from '@jbrowse/bandage-core/referenceRampCss'
|
|
13
|
+
import { isAlive } from '@jbrowse/mobx-state-tree'
|
|
14
|
+
import { observer } from 'mobx-react'
|
|
15
|
+
|
|
16
|
+
import ElTree from './ElTree'
|
|
17
|
+
import { WalkRowContextMenu } from './WalkRowContextMenu'
|
|
18
|
+
import { Swatch } from './WalkRowsOverlay'
|
|
19
|
+
import { legendRowStyle } from './legendStyles'
|
|
20
|
+
|
|
21
|
+
import type { GraphPaneModel } from '../model'
|
|
22
|
+
import type { WalkRows } from '@jbrowse/bandage-core/layout/walkRows'
|
|
23
|
+
import type { StripFrame } from '@jbrowse/bandage-core/layout/walkStrip'
|
|
24
|
+
|
|
25
|
+
// Walk rows under a layout that draws nodes, each haplotype's walk on its own
|
|
26
|
+
// bp, linked to the drawing above: the hovered node ticks every bar where its
|
|
27
|
+
// walk passes it, and a point on a bar lights its node, ringed, above. The
|
|
28
|
+
// bars share the drawing's colours, so under the reference-position ramp a hue
|
|
29
|
+
// in the drawing is the same hue on each bar. Every row is shown; rows too
|
|
30
|
+
// thin to letter say who they are on hover.
|
|
31
|
+
|
|
32
|
+
const keyStyle = {
|
|
33
|
+
display: 'flex',
|
|
34
|
+
flexWrap: 'wrap' as const,
|
|
35
|
+
gap: '2px 14px',
|
|
36
|
+
padding: '2px 8px 6px',
|
|
37
|
+
fontSize: 11,
|
|
38
|
+
color: '#333',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const tipStyle = {
|
|
42
|
+
position: 'absolute' as const,
|
|
43
|
+
pointerEvents: 'none' as const,
|
|
44
|
+
background: 'rgba(255,255,255,0.92)',
|
|
45
|
+
border: '1px solid #ccc',
|
|
46
|
+
borderRadius: 3,
|
|
47
|
+
padding: '1px 6px',
|
|
48
|
+
fontSize: 11,
|
|
49
|
+
whiteSpace: 'nowrap' as const,
|
|
50
|
+
zIndex: 4,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The bars, built once per rows, frame and colouring rather than per hover
|
|
54
|
+
const StripBars = observer(function StripBars({
|
|
55
|
+
bars,
|
|
56
|
+
frame,
|
|
57
|
+
ramp,
|
|
58
|
+
}: {
|
|
59
|
+
bars: WalkRows
|
|
60
|
+
frame: StripFrame
|
|
61
|
+
ramp: { start: number; end: number } | undefined
|
|
62
|
+
}) {
|
|
63
|
+
const idPrefix = useId().replace(/[^\w-]/g, '')
|
|
64
|
+
const tree = useMemo(
|
|
65
|
+
() => walkRowsTree(bars, frame, { ramp, idPrefix }),
|
|
66
|
+
[bars, frame, ramp, idPrefix],
|
|
67
|
+
)
|
|
68
|
+
return <ElTree el={tree} />
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const WalkStrip = observer(function WalkStrip({
|
|
72
|
+
model,
|
|
73
|
+
}: {
|
|
74
|
+
model: GraphPaneModel
|
|
75
|
+
}) {
|
|
76
|
+
const bars = model.walkStripRows
|
|
77
|
+
const frame = model.walkStripFrame
|
|
78
|
+
const [menu, setMenu] = useState<
|
|
79
|
+
{ row: number; top: number; left: number } | undefined
|
|
80
|
+
>(undefined)
|
|
81
|
+
const [tip, setTip] = useState<
|
|
82
|
+
{ text: string; x: number; y: number } | undefined
|
|
83
|
+
>(undefined)
|
|
84
|
+
if (!bars || !frame) {
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
const rows = [bars.reference, ...bars.rows]
|
|
88
|
+
const hit = (e: React.MouseEvent<SVGSVGElement>) => {
|
|
89
|
+
const box = e.currentTarget.getBoundingClientRect()
|
|
90
|
+
const x = e.clientX - box.left
|
|
91
|
+
const y = e.clientY - box.top
|
|
92
|
+
return { ...stripRowAt(bars, frame, x, y), x, y }
|
|
93
|
+
}
|
|
94
|
+
const key = walkRowsKey(bars, {
|
|
95
|
+
ramp: model.referenceRampDomain,
|
|
96
|
+
rampCss: RAMP_GRADIENT_CSS,
|
|
97
|
+
})
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
data-testid="graph-walk-strip"
|
|
101
|
+
style={{ position: 'relative', borderTop: '1px solid #ddd' }}
|
|
102
|
+
onMouseEnter={() => {
|
|
103
|
+
model.setPointerInPane(true)
|
|
104
|
+
}}
|
|
105
|
+
onMouseLeave={() => {
|
|
106
|
+
if (isAlive(model)) {
|
|
107
|
+
model.setPointerInPane(false)
|
|
108
|
+
model.setStripHover(null)
|
|
109
|
+
}
|
|
110
|
+
setTip(undefined)
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<svg
|
|
114
|
+
width={frame.width}
|
|
115
|
+
height={frame.height}
|
|
116
|
+
style={{ display: 'block', cursor: 'pointer' }}
|
|
117
|
+
onMouseMove={e => {
|
|
118
|
+
const at = hit(e)
|
|
119
|
+
if (at.row) {
|
|
120
|
+
model.setStripHover({ row: at.row.name, offset: at.offset! })
|
|
121
|
+
setTip(
|
|
122
|
+
frame.labelled
|
|
123
|
+
? undefined
|
|
124
|
+
: {
|
|
125
|
+
text: `${at.row.label} · ${walkRowReadout(
|
|
126
|
+
at.row,
|
|
127
|
+
at.index === 0 ? undefined : bars.reference,
|
|
128
|
+
)}`,
|
|
129
|
+
x: at.x,
|
|
130
|
+
y: at.y,
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
} else {
|
|
134
|
+
model.setStripHover(null)
|
|
135
|
+
setTip(undefined)
|
|
136
|
+
}
|
|
137
|
+
}}
|
|
138
|
+
onClick={e => {
|
|
139
|
+
const at = hit(e)
|
|
140
|
+
if (at.row && at.index! > 0) {
|
|
141
|
+
model.toggleWalk(at.row.name)
|
|
142
|
+
}
|
|
143
|
+
}}
|
|
144
|
+
onContextMenu={e => {
|
|
145
|
+
const at = hit(e)
|
|
146
|
+
if (at.row) {
|
|
147
|
+
e.preventDefault()
|
|
148
|
+
setMenu({ row: at.index!, top: e.clientY, left: e.clientX })
|
|
149
|
+
}
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
<StripBars bars={bars} frame={frame} ramp={model.referenceRampDomain} />
|
|
153
|
+
<ElTree el={walkMarksTree(bars, frame, model.walkStripMarks)} />
|
|
154
|
+
{frame.labelled
|
|
155
|
+
? rows.map((row, i) => (
|
|
156
|
+
<text
|
|
157
|
+
key={row.name}
|
|
158
|
+
x={6}
|
|
159
|
+
y={i * frame.rowPx + frame.translateY + 4}
|
|
160
|
+
fontSize={11}
|
|
161
|
+
fontFamily="sans-serif"
|
|
162
|
+
fill="#333"
|
|
163
|
+
fontWeight={
|
|
164
|
+
model.walkLayers.some(l => l.walk === row.name) ? 600 : 400
|
|
165
|
+
}
|
|
166
|
+
>
|
|
167
|
+
{row.label}
|
|
168
|
+
</text>
|
|
169
|
+
))
|
|
170
|
+
: null}
|
|
171
|
+
</svg>
|
|
172
|
+
{tip ? (
|
|
173
|
+
<div style={{ ...tipStyle, left: tip.x + 12, top: tip.y - 18 }}>
|
|
174
|
+
{tip.text}
|
|
175
|
+
</div>
|
|
176
|
+
) : null}
|
|
177
|
+
<div style={keyStyle} data-testid="graph-walk-strip-key">
|
|
178
|
+
{key.map(entry => (
|
|
179
|
+
<div key={entry.label} style={legendRowStyle}>
|
|
180
|
+
<Swatch swatch={entry.swatch} />
|
|
181
|
+
<span>{entry.label}</span>
|
|
182
|
+
</div>
|
|
183
|
+
))}
|
|
184
|
+
<div style={legendRowStyle}>
|
|
185
|
+
<span>
|
|
186
|
+
▮ ticks: where each walk passes the node under the pointer · click a
|
|
187
|
+
bar to lift its walk
|
|
188
|
+
</span>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
{menu ? (
|
|
192
|
+
<WalkRowContextMenu
|
|
193
|
+
model={model}
|
|
194
|
+
row={menu.row}
|
|
195
|
+
bars={bars}
|
|
196
|
+
top={menu.top}
|
|
197
|
+
left={menu.left}
|
|
198
|
+
onClose={() => {
|
|
199
|
+
setMenu(undefined)
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
) : null}
|
|
203
|
+
</div>
|
|
204
|
+
)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
export default WalkStrip
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { observer } from 'mobx-react'
|
|
2
|
+
|
|
3
|
+
import type { GraphPaneModel } from '../model'
|
|
4
|
+
|
|
5
|
+
const svgStyle = {
|
|
6
|
+
position: 'absolute' as const,
|
|
7
|
+
left: 0,
|
|
8
|
+
top: 0,
|
|
9
|
+
pointerEvents: 'none' as const,
|
|
10
|
+
overflow: 'visible' as const,
|
|
11
|
+
zIndex: 4,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// A ring round the node a point on the walk strip lights, so a one-bp node
|
|
15
|
+
// can be found in a hairball
|
|
16
|
+
const WalkStripLocator = observer(function WalkStripLocator({
|
|
17
|
+
model,
|
|
18
|
+
}: {
|
|
19
|
+
model: GraphPaneModel
|
|
20
|
+
}) {
|
|
21
|
+
const at = model.walkStripLocator
|
|
22
|
+
return at ? (
|
|
23
|
+
<svg
|
|
24
|
+
style={svgStyle}
|
|
25
|
+
width={model.paneWidth}
|
|
26
|
+
height={model.canvasHeight}
|
|
27
|
+
data-testid="graph-walk-strip-locator"
|
|
28
|
+
>
|
|
29
|
+
<circle
|
|
30
|
+
cx={at.x}
|
|
31
|
+
cy={at.y}
|
|
32
|
+
r={11}
|
|
33
|
+
fill="none"
|
|
34
|
+
stroke="white"
|
|
35
|
+
strokeWidth={4}
|
|
36
|
+
/>
|
|
37
|
+
<circle
|
|
38
|
+
cx={at.x}
|
|
39
|
+
cy={at.y}
|
|
40
|
+
r={11}
|
|
41
|
+
fill="none"
|
|
42
|
+
stroke="#111"
|
|
43
|
+
strokeWidth={1.75}
|
|
44
|
+
/>
|
|
45
|
+
</svg>
|
|
46
|
+
) : null
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export default WalkStripLocator
|
|
@@ -2335,12 +2335,11 @@ describe('what the row axis draws, in pixels', () => {
|
|
|
2335
2335
|
})
|
|
2336
2336
|
|
|
2337
2337
|
test('a deletion edge draws only in a view that shows it', async () => {
|
|
2338
|
-
const hidden = await fitted()
|
|
2339
|
-
expect(hidden.showDeletionEdges).toBe(false)
|
|
2338
|
+
const hidden = await fitted({ showDeletionEdges: false })
|
|
2340
2339
|
expect([...hidden.hiddenEdgeIndexes]).toEqual(
|
|
2341
2340
|
hidden.deletions.map(d => d.edgeIndex),
|
|
2342
2341
|
)
|
|
2343
|
-
const shown = await fitted(
|
|
2342
|
+
const shown = await fitted()
|
|
2344
2343
|
expect(getSnapshot(shown).showDeletionEdges).toBe(true)
|
|
2345
2344
|
expect(shown.hiddenEdgeIndexes.size).toBe(0)
|
|
2346
2345
|
|
|
@@ -2654,6 +2653,46 @@ describe('walk rows', () => {
|
|
|
2654
2653
|
'',
|
|
2655
2654
|
].join('\n')
|
|
2656
2655
|
|
|
2656
|
+
test('the walk strip shows under a node layout, and links bars and nodes both ways', async () => {
|
|
2657
|
+
rpcRespond()
|
|
2658
|
+
const model = stateModelFactory().create({
|
|
2659
|
+
type: 'GraphGenomeView',
|
|
2660
|
+
layoutMode: 'ordered',
|
|
2661
|
+
walkStrip: true,
|
|
2662
|
+
referencePath: 'GRCh38',
|
|
2663
|
+
})
|
|
2664
|
+
await model.loadGFA(WALKS_GFA, 'walks')
|
|
2665
|
+
model.setWidth(800)
|
|
2666
|
+
expect(model.walkStripShown).toBe(true)
|
|
2667
|
+
// the layout keeps its own drawing: walk rows' bars are not the strip's
|
|
2668
|
+
expect(model.walkRowBars).toBeUndefined()
|
|
2669
|
+
const bars = model.walkStripRows!
|
|
2670
|
+
expect(bars.rows.map(r => r.label).sort()).toEqual(['A#1', 'A#2', 'B#1'])
|
|
2671
|
+
const two = model.graph!.nodes.find(n => n.name === '2')!
|
|
2672
|
+
// a point on B#1's bar inside node 2 lights it
|
|
2673
|
+
model.setStripHover({ row: 'B#1#ctg', offset: 6 })
|
|
2674
|
+
expect(model.hoveredNode).toBe(two.id)
|
|
2675
|
+
// and node 2 ticks the bars of the walks through it, not A#2's
|
|
2676
|
+
const marked = new Set(model.walkStripMarks.map(m => m.row))
|
|
2677
|
+
expect([...marked].sort()).toEqual(['A#1#ctg', 'B#1#ctg'])
|
|
2678
|
+
model.setStripHover(null)
|
|
2679
|
+
expect(model.hoveredNode).toBeNull()
|
|
2680
|
+
model.setWalkStrip(false)
|
|
2681
|
+
expect(model.walkStripShown).toBe(false)
|
|
2682
|
+
expect(model.walkStripRows).toBeUndefined()
|
|
2683
|
+
})
|
|
2684
|
+
|
|
2685
|
+
test('walk rows and tube maps take no strip', async () => {
|
|
2686
|
+
rpcRespond()
|
|
2687
|
+
const model = stateModelFactory().create({
|
|
2688
|
+
type: 'GraphGenomeView',
|
|
2689
|
+
layoutMode: 'walkrows',
|
|
2690
|
+
walkStrip: true,
|
|
2691
|
+
})
|
|
2692
|
+
await model.loadGFA(WALKS_GFA, 'walks')
|
|
2693
|
+
expect(model.walkStripShown).toBe(false)
|
|
2694
|
+
})
|
|
2695
|
+
|
|
2657
2696
|
test('a sample filter keeps its walks, paired in the order named, and labels follow', async () => {
|
|
2658
2697
|
rpcRespond()
|
|
2659
2698
|
const model = stateModelFactory().create({
|