jbrowse-plugin-graphgenomeviewer 4.0.4 → 4.0.6
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-BQYWWF6Z.js → GraphGenomeView-XPAI2P62.js} +2 -2
- package/dist/chunks/{LinearGraphDisplay-7O3VQHKU.js → LinearGraphDisplay-K4WU7WQ4.js} +2 -2
- package/dist/chunks/{chunk-5WJPRX4N.js → chunk-5T6MYZYN.js} +2 -2
- package/dist/chunks/{chunk-5WJPRX4N.js.map → chunk-5T6MYZYN.js.map} +2 -2
- package/dist/chunks/{chunk-RSAY56Y4.js → chunk-HG4BVF35.js} +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +3 -3
- package/package.json +1 -1
- package/src/GraphGenomeView/host.test.ts +41 -0
- package/src/GraphGenomeView/host.ts +13 -3
- package/src/GraphGenomeView/layoutModes.ts +11 -10
- package/src/GraphGenomeView/model.ts +1 -1
- package/src/hoverSync/graphViewHighlights.test.ts +21 -0
- package/src/hoverSync/graphViewHighlights.ts +26 -0
- package/src/version.ts +1 -1
- /package/dist/chunks/{GraphGenomeView-BQYWWF6Z.js.map → GraphGenomeView-XPAI2P62.js.map} +0 -0
- /package/dist/chunks/{LinearGraphDisplay-7O3VQHKU.js.map → LinearGraphDisplay-K4WU7WQ4.js.map} +0 -0
- /package/dist/chunks/{chunk-RSAY56Y4.js.map → chunk-HG4BVF35.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jbrowse-plugin-graphgenomeviewer",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.6",
|
|
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": {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cutHolds, hostCut } from './host'
|
|
2
|
+
|
|
3
|
+
import type { HostWindow } from './host'
|
|
4
|
+
|
|
5
|
+
const window: HostWindow = {
|
|
6
|
+
refName: 'chr1',
|
|
7
|
+
assemblyName: 'hg38',
|
|
8
|
+
start: 1_000_000.4,
|
|
9
|
+
end: 1_060_000.4,
|
|
10
|
+
bpPerPx: 60,
|
|
11
|
+
span: 60_000,
|
|
12
|
+
regionStart: 0,
|
|
13
|
+
regionEnd: 10_000_000,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('a cut with margins is the window plus a window-width each side, under the cap', () => {
|
|
17
|
+
expect(hostCut(window, Infinity)).toEqual({
|
|
18
|
+
refName: 'chr1',
|
|
19
|
+
assemblyName: 'hg38',
|
|
20
|
+
start: 940_000,
|
|
21
|
+
end: 1_120_001,
|
|
22
|
+
})
|
|
23
|
+
const capped = hostCut(window, 66_000)
|
|
24
|
+
expect(capped.end - capped.start).toBeLessThanOrEqual(66_000)
|
|
25
|
+
expect(cutHolds(capped, window)).toBe(true)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('a cut without margins is the window alone, and holds for that window only', () => {
|
|
29
|
+
const cut = hostCut(window, Infinity, false)
|
|
30
|
+
expect(cut).toMatchObject({ start: 1_000_000, end: 1_060_001 })
|
|
31
|
+
expect(cutHolds(cut, window, false)).toBe(true)
|
|
32
|
+
expect(
|
|
33
|
+
cutHolds(cut, { ...window, start: 1_010_000, end: 1_070_000 }, false),
|
|
34
|
+
).toBe(false)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('a cut trimmed to a cap the window fills still holds that window', () => {
|
|
38
|
+
const cut = hostCut(window, 60_000)
|
|
39
|
+
expect(cut.end - cut.start).toBeLessThanOrEqual(60_000)
|
|
40
|
+
expect(cutHolds(cut, window)).toBe(true)
|
|
41
|
+
})
|
|
@@ -114,11 +114,20 @@ export function hostCut(
|
|
|
114
114
|
const margin = margins
|
|
115
115
|
? Math.max(0, Math.min(window.span, (capBp - visible) / 2))
|
|
116
116
|
: 0
|
|
117
|
+
let start = Math.max(window.regionStart, Math.floor(window.start - margin))
|
|
118
|
+
let end = Math.min(window.regionEnd, Math.ceil(window.end + margin))
|
|
119
|
+
// Rounding outward can put a cut a base or two past the cap, which the load
|
|
120
|
+
// refuses whole; trim it back rather than lose the cut.
|
|
121
|
+
const over = end - start - capBp
|
|
122
|
+
if (over > 0) {
|
|
123
|
+
start += Math.floor(over / 2)
|
|
124
|
+
end -= Math.ceil(over / 2)
|
|
125
|
+
}
|
|
117
126
|
return {
|
|
118
127
|
refName: window.refName,
|
|
119
128
|
assemblyName: window.assemblyName,
|
|
120
|
-
start
|
|
121
|
-
end
|
|
129
|
+
start,
|
|
130
|
+
end,
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
133
|
|
|
@@ -135,8 +144,9 @@ export function cutHolds(
|
|
|
135
144
|
) {
|
|
136
145
|
return false
|
|
137
146
|
}
|
|
147
|
+
// to the base, since a cut trimmed to its cap can end a base inside the window
|
|
138
148
|
if (margins) {
|
|
139
|
-
return cut.start <= window.start && window.end <= cut.end
|
|
149
|
+
return cut.start <= window.start + 1 && window.end <= cut.end + 1
|
|
140
150
|
}
|
|
141
151
|
const exact = hostCut(window, Infinity, false)
|
|
142
152
|
return (
|
|
@@ -39,10 +39,11 @@ export interface LayoutMode {
|
|
|
39
39
|
// rest rather than hiding them, so the reason a mode is unavailable stays
|
|
40
40
|
// visible instead of the menu silently changing shape between graphs
|
|
41
41
|
available: (graph: Graph) => boolean
|
|
42
|
-
// whether
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
42
|
+
// whether a cut for this mode carries a window-width margin each side, to
|
|
43
|
+
// pan over before the next cut. A mode that draws a picture of the window
|
|
44
|
+
// — force, ordered, and walk rows, whose bars are lengths through it — is
|
|
45
|
+
// cut to the window alone.
|
|
46
|
+
cutMargins: boolean
|
|
46
47
|
// whether `run` draws this graph itself, as opposed to handing off to the
|
|
47
48
|
// engine. Not the same question as `available` and not derivable from it:
|
|
48
49
|
// 'force' is available for every graph and draws none of them itself. The two
|
|
@@ -67,7 +68,7 @@ export const LAYOUT_MODES = [
|
|
|
67
68
|
description:
|
|
68
69
|
'x is reference bp, one row per stable rank. Needs rGFA tags or a reference path.',
|
|
69
70
|
run: anchoredLayout,
|
|
70
|
-
|
|
71
|
+
cutMargins: true,
|
|
71
72
|
available: hasBackbone,
|
|
72
73
|
drawsLocally: hasBackbone,
|
|
73
74
|
},
|
|
@@ -77,7 +78,7 @@ export const LAYOUT_MODES = [
|
|
|
77
78
|
description:
|
|
78
79
|
'x is reference bp, one row per contributing assembly. Needs rGFA tags or a reference path.',
|
|
79
80
|
run: sampleRowLayout,
|
|
80
|
-
|
|
81
|
+
cutMargins: true,
|
|
81
82
|
available: hasAlleles,
|
|
82
83
|
drawsLocally: hasAlleles,
|
|
83
84
|
},
|
|
@@ -87,7 +88,7 @@ export const LAYOUT_MODES = [
|
|
|
87
88
|
description:
|
|
88
89
|
"x is each walk's own bp: one bar per haplotype, sequence the reference also carries in blue and sequence it does not in purple, so a repeat expansion reads as bar length. Needs W or P lines.",
|
|
89
90
|
run: walkRowLayout,
|
|
90
|
-
|
|
91
|
+
cutMargins: false,
|
|
91
92
|
available: hasWalks,
|
|
92
93
|
drawsLocally: hasWalks,
|
|
93
94
|
},
|
|
@@ -97,7 +98,7 @@ export const LAYOUT_MODES = [
|
|
|
97
98
|
description:
|
|
98
99
|
'x is reference order, not bp: every node gets room, bubbles read as lenses. Needs rGFA tags or a reference path.',
|
|
99
100
|
run: orderedLayout,
|
|
100
|
-
|
|
101
|
+
cutMargins: false,
|
|
101
102
|
available: hasBackbone,
|
|
102
103
|
drawsLocally: hasBackbone,
|
|
103
104
|
},
|
|
@@ -107,7 +108,7 @@ export const LAYOUT_MODES = [
|
|
|
107
108
|
description:
|
|
108
109
|
'The reference as a line with one typed glyph per bubble, from the bubble index or from the graph itself: SNP, indel, deletion, inversion, repeat array. Click a glyph to open the graph inside it.',
|
|
109
110
|
run: variantMapLayout,
|
|
110
|
-
|
|
111
|
+
cutMargins: true,
|
|
111
112
|
available: hasBackbone,
|
|
112
113
|
drawsLocally: hasBackbone,
|
|
113
114
|
},
|
|
@@ -116,7 +117,7 @@ export const LAYOUT_MODES = [
|
|
|
116
117
|
label: FORCE_LAYOUT_LABEL,
|
|
117
118
|
description: 'OGDF FMMM, via the external Bandage engine.',
|
|
118
119
|
run: () => undefined,
|
|
119
|
-
|
|
120
|
+
cutMargins: false,
|
|
120
121
|
available: () => true,
|
|
121
122
|
drawsLocally: () => false,
|
|
122
123
|
},
|
|
@@ -2508,7 +2508,7 @@ export default function stateModelFactory() {
|
|
|
2508
2508
|
// window is cut to the window alone; one the host places carries
|
|
2509
2509
|
// margins to pan over.
|
|
2510
2510
|
settleOn(seen: HostWindow) {
|
|
2511
|
-
const margins = layoutModeByValue(self.layoutMode).
|
|
2511
|
+
const margins = layoutModeByValue(self.layoutMode).cutMargins
|
|
2512
2512
|
const above = self.coarseAboveBpPerPx
|
|
2513
2513
|
const tier =
|
|
2514
2514
|
above !== undefined && seen.bpPerPx > above ? 'coarse' : 'fine'
|
|
@@ -22,6 +22,27 @@ test('a graph view launched from this linear view contributes its highlight', ()
|
|
|
22
22
|
|
|
23
23
|
const linearView = (id: string) => ({ id, type: 'LinearGenomeView' })
|
|
24
24
|
|
|
25
|
+
test("a graph pane inside this linear view's own track contributes its highlight", () => {
|
|
26
|
+
const view = {
|
|
27
|
+
...linearView('lgv1'),
|
|
28
|
+
tracks: [
|
|
29
|
+
{
|
|
30
|
+
displays: [
|
|
31
|
+
{
|
|
32
|
+
id: 'display1',
|
|
33
|
+
type: 'LinearGraphDisplay',
|
|
34
|
+
pane: { type: 'GraphGenomeView', hoverHighlight: HIGHLIGHT },
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
expect(graphViewHighlights([view], 'lgv1')).toEqual([
|
|
41
|
+
{ key: 'display1', region: HIGHLIGHT },
|
|
42
|
+
])
|
|
43
|
+
expect(graphViewHighlights([view], 'lgv2')).toEqual([])
|
|
44
|
+
})
|
|
45
|
+
|
|
25
46
|
test('a graph view launched from a different linear view is ignored', () => {
|
|
26
47
|
expect(
|
|
27
48
|
graphViewHighlights(
|
|
@@ -89,6 +89,21 @@ function isOnAssembly(
|
|
|
89
89
|
// The highlights a linear view should draw for the graph views connected to it.
|
|
90
90
|
// Reads `session.views` structurally: the members it needs are declared by
|
|
91
91
|
// GraphGenomeView, not by the AbstractViewModel the session array is typed as.
|
|
92
|
+
// The graph panes inside a linear view's own tracks, connected to it by where
|
|
93
|
+
// they sit.
|
|
94
|
+
function trackPanes(view: Record<string, unknown>) {
|
|
95
|
+
const tracks = Array.isArray(view.tracks) ? view.tracks : []
|
|
96
|
+
return tracks.flatMap((track: unknown) => {
|
|
97
|
+
const displays =
|
|
98
|
+
isRecord(track) && Array.isArray(track.displays) ? track.displays : []
|
|
99
|
+
return displays.flatMap((display: unknown) =>
|
|
100
|
+
isRecord(display) && isRecord(display.pane)
|
|
101
|
+
? [{ key: display.id, pane: display.pane }]
|
|
102
|
+
: [],
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
92
107
|
export function graphViewHighlights(
|
|
93
108
|
views: unknown[],
|
|
94
109
|
linearViewId: string,
|
|
@@ -101,6 +116,17 @@ export function graphViewHighlights(
|
|
|
101
116
|
withRows(views).map(view => (isRecord(view) ? view.id : undefined)),
|
|
102
117
|
)
|
|
103
118
|
for (const view of views) {
|
|
119
|
+
if (isRecord(view) && view.id === linearViewId) {
|
|
120
|
+
for (const { key, pane } of trackPanes(view)) {
|
|
121
|
+
const region = readRegion(pane.hoverHighlight)
|
|
122
|
+
if (region && isOnAssembly(region, linearAssemblyNames)) {
|
|
123
|
+
highlights.push({
|
|
124
|
+
key: typeof key === 'string' ? key : 'graph-track',
|
|
125
|
+
region,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
104
130
|
if (
|
|
105
131
|
isRecord(view) &&
|
|
106
132
|
view.type === 'GraphGenomeView' &&
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '4.0.
|
|
1
|
+
export const version = '4.0.6'
|
|
File without changes
|
/package/dist/chunks/{LinearGraphDisplay-7O3VQHKU.js.map → LinearGraphDisplay-K4WU7WQ4.js.map}
RENAMED
|
File without changes
|
|
File without changes
|