jbrowse-plugin-graphgenomeviewer 4.0.0 → 4.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jbrowse-plugin-graphgenomeviewer",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
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": {
@@ -343,6 +343,48 @@ test('the track is as tall as its rows, up to the configured height', async () =
343
343
  expect(display.height).toBe(pane.canvasHeight)
344
344
  })
345
345
 
346
+ test("a launch names the pane's props without its type, and opens in that layout", async () => {
347
+ const { view, cuts } = createEnvironment()
348
+ view.zoomTo(60_000 / WIDTH_PX)
349
+ view.scrollTo(1_000_000 / view.bpPerPx)
350
+ view.showTrack(
351
+ 'graph',
352
+ {},
353
+ {
354
+ type: 'LinearGraphDisplay',
355
+ pane: { layoutMode: 'force', colorScheme: 'uniform' },
356
+ },
357
+ )
358
+ const display = view.tracks[0]!.displays[0] as LinearGraphDisplayModel
359
+ display.pane.startRenderingBackend(fakeRenderer())
360
+ await wait(SETTLE_MS)
361
+ expect(cuts).toHaveLength(1)
362
+ expect(display.pane.layoutMode).toBe('force')
363
+ expect(display.pane.colorScheme).toBe('uniform')
364
+ expect(display.pane.hostPlacesX).toBe(false)
365
+ })
366
+
367
+ test('a launch that states one pane prop takes the rest from the config', async () => {
368
+ const { view, cuts } = createEnvironment()
369
+ view.zoomTo(60_000 / WIDTH_PX)
370
+ view.scrollTo(1_000_000 / view.bpPerPx)
371
+ view.showTrack(
372
+ 'graph',
373
+ {},
374
+ {
375
+ type: 'LinearGraphDisplay',
376
+ pane: { colorScheme: 'uniform' },
377
+ },
378
+ )
379
+ const display = view.tracks[0]!.displays[0] as LinearGraphDisplayModel
380
+ display.pane.startRenderingBackend(fakeRenderer())
381
+ await wait(SETTLE_MS)
382
+ expect(cuts).toHaveLength(1)
383
+ expect(display.pane.colorScheme).toBe('uniform')
384
+ expect(display.pane.layoutMode).toBe('auto')
385
+ expect(display.pane.hostPlacesX).toBe(true)
386
+ })
387
+
346
388
  test('the track menu offers the layouts, colours and the settings dialog', async () => {
347
389
  const { display } = await shownGraph()
348
390
  const labels = display
@@ -25,6 +25,10 @@ const LinearGraphDisplay = observer(function LinearGraphDisplay({
25
25
  return (
26
26
  <div
27
27
  data-testid="linear-graph-display"
28
+ data-layout={pane.layoutMode}
29
+ data-cut-tier={pane.cutTier}
30
+ data-recuts={pane.recuts}
31
+ data-node-count={pane.hasGraph ? pane.nodeCount : undefined}
28
32
  style={{
29
33
  position: 'relative',
30
34
  width: pane.width,
@@ -21,206 +21,247 @@ const GraphSettingsDialog = lazy(
21
21
  () => import('../GraphGenomeView/components/GraphSettingsDialog'),
22
22
  )
23
23
 
24
+ type ConfiguredSlot = 'layoutMode' | 'colorScheme' | 'height'
25
+
24
26
  export function stateModelFactory(configSchema: LinearGraphDisplayConfigModel) {
25
27
  const Pane = paneModelFactory()
26
- return types
27
- .compose(
28
- 'LinearGraphDisplay',
29
- BaseDisplay,
30
- TrackHeightMixin(),
31
- types.model({
32
- type: types.literal('LinearGraphDisplay'),
33
- configuration: ConfigurationReference(configSchema),
34
- // The graph pane, the same model the standalone view is. Here the
35
- // linear view above is its host: x is that view's window for a layout
36
- // whose x is reference bp, and the cut is re-made when the window
37
- // leaves it (pane.host).
38
- pane: types.optional(Pane, () => ({
39
- type: 'GraphGenomeView' as const,
40
- })),
41
- // The config's layout and colour reach the pane once, so a choice made
42
- // in the track menu survives a reload.
43
- configured: types.optional(types.boolean, false),
44
- }),
45
- )
46
- .views(self => ({
47
- get host() {
48
- let view: unknown
49
- try {
50
- view = getContainingView(self)
51
- } catch {
52
- return undefined
28
+ return (
29
+ types
30
+ .compose(
31
+ 'LinearGraphDisplay',
32
+ BaseDisplay,
33
+ TrackHeightMixin(),
34
+ types.model({
35
+ type: types.literal('LinearGraphDisplay'),
36
+ configuration: ConfigurationReference(configSchema),
37
+ // The graph pane, the same model the standalone view is. Here the
38
+ // linear view above is its host: x is that view's window for a layout
39
+ // whose x is reference bp, and the cut is re-made when the window
40
+ // leaves it (pane.host).
41
+ pane: types.optional(Pane, () => ({
42
+ type: 'GraphGenomeView' as const,
43
+ })),
44
+ // Which of the config's slots have reached the pane. Each reaches it
45
+ // once, so a choice made in the track menu survives a reload, and a
46
+ // launch that states a pane prop keeps it over the config's.
47
+ configured: types.optional(
48
+ types.frozen<Partial<Record<ConfiguredSlot, boolean>>>(),
49
+ {},
50
+ ),
51
+ }),
52
+ )
53
+ // A launch names the pane's props without the pane's own type, and what
54
+ // it states is what the config does not set.
55
+ .preProcessSnapshot(snapshot => {
56
+ const { pane, configured } = snapshot as {
57
+ pane?: Record<string, unknown>
58
+ configured?: Partial<Record<ConfiguredSlot, boolean>>
53
59
  }
54
- return isLinearHost(view) ? view : undefined
55
- },
56
- get trackId(): string {
57
- return getConf(self.parentTrack, 'trackId')
58
- },
59
- }))
60
- .views(self => ({
61
- trackMenuItems(): MenuItem[] {
62
- const { pane } = self
63
- const { graph } = pane
64
- const walks = pane.walkChoices
65
- return [
66
- {
67
- label: 'Layout',
68
- subMenu: LAYOUT_MODES.map(mode => ({
69
- type: 'radio' as const,
70
- label: mode.label,
71
- checked: pane.layoutMode === mode.value,
72
- disabled: graph ? !mode.available(graph) : false,
73
- onClick: () => {
74
- void pane.switchLayout(mode.value)
75
- },
76
- })),
77
- },
78
- {
79
- label: 'Color',
80
- subMenu: COLOR_SCHEMES.map(scheme => ({
81
- type: 'radio' as const,
82
- label: scheme.label,
83
- checked: pane.colorScheme === scheme.value,
84
- onClick: () => {
85
- pane.setColorScheme(scheme.value)
60
+ return pane
61
+ ? ({
62
+ ...snapshot,
63
+ configured: {
64
+ layoutMode: pane.layoutMode !== undefined,
65
+ colorScheme: pane.colorScheme !== undefined,
66
+ height: pane.paneHeight !== undefined,
67
+ ...configured,
86
68
  },
87
- })),
88
- },
89
- ...(walks.length > 0
90
- ? [
91
- {
92
- label: 'Walk',
93
- subMenu: [
94
- {
95
- type: 'radio' as const,
96
- label: 'None',
97
- checked: pane.highlightedPath === '',
98
- onClick: () => {
99
- pane.setHighlightedPath('')
69
+ pane:
70
+ pane.type === undefined
71
+ ? { type: 'GraphGenomeView', ...pane }
72
+ : pane,
73
+ } as typeof snapshot)
74
+ : snapshot
75
+ })
76
+ .views(self => ({
77
+ get host() {
78
+ let view: unknown
79
+ try {
80
+ view = getContainingView(self)
81
+ } catch {
82
+ return undefined
83
+ }
84
+ return isLinearHost(view) ? view : undefined
85
+ },
86
+ get trackId(): string {
87
+ return getConf(self.parentTrack, 'trackId')
88
+ },
89
+ }))
90
+ .views(self => ({
91
+ trackMenuItems(): MenuItem[] {
92
+ const { pane } = self
93
+ const { graph } = pane
94
+ const walks = pane.walkChoices
95
+ return [
96
+ {
97
+ label: 'Layout',
98
+ subMenu: LAYOUT_MODES.map(mode => ({
99
+ type: 'radio' as const,
100
+ label: mode.label,
101
+ checked: pane.layoutMode === mode.value,
102
+ disabled: graph ? !mode.available(graph) : false,
103
+ onClick: () => {
104
+ void pane.switchLayout(mode.value)
105
+ },
106
+ })),
107
+ },
108
+ {
109
+ label: 'Color',
110
+ subMenu: COLOR_SCHEMES.map(scheme => ({
111
+ type: 'radio' as const,
112
+ label: scheme.label,
113
+ checked: pane.colorScheme === scheme.value,
114
+ onClick: () => {
115
+ pane.setColorScheme(scheme.value)
116
+ },
117
+ })),
118
+ },
119
+ ...(walks.length > 0
120
+ ? [
121
+ {
122
+ label: 'Walk',
123
+ subMenu: [
124
+ {
125
+ type: 'radio' as const,
126
+ label: 'None',
127
+ checked: pane.highlightedPath === '',
128
+ onClick: () => {
129
+ pane.setHighlightedPath('')
130
+ },
100
131
  },
132
+ ...walks.map(walk => ({
133
+ type: 'radio' as const,
134
+ label: walk.label,
135
+ checked: pane.highlightedPath === walk.name,
136
+ onClick: () => {
137
+ pane.setHighlightedPath(walk.name)
138
+ },
139
+ })),
140
+ ],
141
+ },
142
+ ]
143
+ : []),
144
+ ...(pane.hostPlacesX
145
+ ? []
146
+ : [
147
+ {
148
+ label: 'Zoom in',
149
+ onClick: () => {
150
+ pane.zoom(1.5, pane.width / 2, pane.canvasHeight / 2)
101
151
  },
102
- ...walks.map(walk => ({
103
- type: 'radio' as const,
104
- label: walk.label,
105
- checked: pane.highlightedPath === walk.name,
106
- onClick: () => {
107
- pane.setHighlightedPath(walk.name)
108
- },
109
- })),
110
- ],
111
- },
112
- ]
113
- : []),
114
- ...(pane.hostPlacesX
115
- ? []
116
- : [
117
- {
118
- label: 'Zoom in',
119
- onClick: () => {
120
- pane.zoom(1.5, pane.width / 2, pane.canvasHeight / 2)
121
152
  },
122
- },
123
- {
124
- label: 'Zoom out',
125
- onClick: () => {
126
- pane.zoom(1 / 1.5, pane.width / 2, pane.canvasHeight / 2)
153
+ {
154
+ label: 'Zoom out',
155
+ onClick: () => {
156
+ pane.zoom(1 / 1.5, pane.width / 2, pane.canvasHeight / 2)
157
+ },
127
158
  },
128
- },
129
- {
130
- label: 'Zoom to fit',
131
- onClick: () => {
132
- pane.zoomToFit()
159
+ {
160
+ label: 'Zoom to fit',
161
+ onClick: () => {
162
+ pane.zoomToFit()
163
+ },
133
164
  },
134
- },
135
- ]),
136
- {
137
- type: 'checkbox',
138
- label: 'Mark bubbles',
139
- checked: pane.showBubbles,
140
- onClick: () => {
141
- pane.setShowBubbles(!pane.showBubbles)
142
- },
143
- },
144
- {
145
- type: 'checkbox',
146
- label: 'Show deletion edges',
147
- checked: pane.showDeletionEdges,
148
- onClick: () => {
149
- pane.setShowDeletionEdges(!pane.showDeletionEdges)
150
- },
151
- },
152
- {
153
- type: 'checkbox',
154
- label: 'Genes on the backbone',
155
- checked: pane.showGenes,
156
- onClick: () => {
157
- pane.setShowGenes(!pane.showGenes)
165
+ ]),
166
+ {
167
+ type: 'checkbox',
168
+ label: 'Mark bubbles',
169
+ checked: pane.showBubbles,
170
+ onClick: () => {
171
+ pane.setShowBubbles(!pane.showBubbles)
172
+ },
158
173
  },
159
- },
160
- {
161
- label: 'Settings',
162
- icon: SettingsIcon,
163
- onClick: () => {
164
- getSession(self).queueDialog(onClose => [
165
- GraphSettingsDialog,
166
- { model: pane, open: true, onClose },
167
- ])
174
+ {
175
+ type: 'checkbox',
176
+ label: 'Show deletion edges',
177
+ checked: pane.showDeletionEdges,
178
+ onClick: () => {
179
+ pane.setShowDeletionEdges(!pane.showDeletionEdges)
180
+ },
168
181
  },
169
- },
170
- ]
171
- },
172
- }))
173
- .actions(self => ({
174
- setConfigured() {
175
- self.configured = true
176
- },
177
- // The track's height is the pane's ceiling: a layout shorter than it
178
- // takes only what it needs, and a drag on the handle moves the ceiling.
179
- resizeHeight(distance: number) {
180
- const before = self.height
181
- const target = Math.max(before + distance, 40)
182
- self.pane.setPaneHeight(target)
183
- return target - before
184
- },
185
- }))
186
- .actions(self => ({
187
- afterAttach() {
188
- const { pane } = self
189
- if (!self.configured) {
190
- pane.setLayoutMode(getConf(self, 'layoutMode'))
191
- pane.setColorScheme(getConf(self, 'colorScheme'))
192
- pane.setPaneHeight(getConf(self, 'height'))
193
- self.setConfigured()
194
- }
195
- pane.adoptTrack(self.trackId)
196
- pane.startHosting()
197
- addDisposer(
198
- self,
199
- autorun(
200
- () => {
201
- const { host } = self
202
- if (host) {
203
- pane.setWidth(host.width)
204
- pane.pairWithLinearView(host.id)
205
- }
182
+ {
183
+ type: 'checkbox',
184
+ label: 'Genes on the backbone',
185
+ checked: pane.showGenes,
186
+ onClick: () => {
187
+ pane.setShowGenes(!pane.showGenes)
188
+ },
206
189
  },
207
- { name: 'LinearGraphDisplayHost' },
208
- ),
209
- )
210
- addDisposer(
211
- self,
212
- autorun(
213
- () => {
214
- const height = pane.canvasHeight
215
- if (self.height !== height) {
216
- self.setHeight(height)
217
- }
190
+ {
191
+ label: 'Settings',
192
+ icon: SettingsIcon,
193
+ onClick: () => {
194
+ getSession(self).queueDialog(onClose => [
195
+ GraphSettingsDialog,
196
+ { model: pane, open: true, onClose },
197
+ ])
198
+ },
218
199
  },
219
- { name: 'LinearGraphDisplayHeight' },
220
- ),
221
- )
222
- },
223
- }))
200
+ ]
201
+ },
202
+ }))
203
+ .actions(self => ({
204
+ // The config's slots the pane has not yet taken, then all of them.
205
+ applyConfig() {
206
+ const { pane, configured } = self
207
+ if (!configured.layoutMode) {
208
+ pane.setLayoutMode(getConf(self, 'layoutMode'))
209
+ }
210
+ if (!configured.colorScheme) {
211
+ pane.setColorScheme(getConf(self, 'colorScheme'))
212
+ }
213
+ if (!configured.height) {
214
+ pane.setPaneHeight(getConf(self, 'height'))
215
+ }
216
+ self.configured = {
217
+ layoutMode: true,
218
+ colorScheme: true,
219
+ height: true,
220
+ }
221
+ },
222
+ // The track's height is the pane's ceiling: a layout shorter than it
223
+ // takes only what it needs, and a drag on the handle moves the ceiling.
224
+ resizeHeight(distance: number) {
225
+ const before = self.height
226
+ const target = Math.max(before + distance, 40)
227
+ self.pane.setPaneHeight(target)
228
+ return target - before
229
+ },
230
+ }))
231
+ .actions(self => ({
232
+ afterAttach() {
233
+ const { pane } = self
234
+ self.applyConfig()
235
+ pane.adoptTrack(self.trackId)
236
+ pane.startHosting()
237
+ addDisposer(
238
+ self,
239
+ autorun(
240
+ () => {
241
+ const { host } = self
242
+ if (host) {
243
+ pane.setWidth(host.width)
244
+ pane.pairWithLinearView(host.id)
245
+ }
246
+ },
247
+ { name: 'LinearGraphDisplayHost' },
248
+ ),
249
+ )
250
+ addDisposer(
251
+ self,
252
+ autorun(
253
+ () => {
254
+ const height = pane.canvasHeight
255
+ if (self.height !== height) {
256
+ self.setHeight(height)
257
+ }
258
+ },
259
+ { name: 'LinearGraphDisplayHeight' },
260
+ ),
261
+ )
262
+ },
263
+ }))
264
+ )
224
265
  }
225
266
 
226
267
  export type LinearGraphDisplayStateModel = ReturnType<typeof stateModelFactory>
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '4.0.0'
1
+ export const version = '4.0.2'
@@ -1,2 +0,0 @@
1
- import{a as e,b as i}from"./chunk-SDTCVLC3.js";import"./chunk-A4ZWFDQY.js";import"./chunk-ZNGDWZ6Z.js";import"./chunk-OHT6UX4C.js";import"./chunk-OP7IRVG6.js";import"./chunk-F4C3BEPV.js";import{a as r,b as s,c as h}from"./chunk-XEJ47DLD.js";import{b as t}from"./chunk-FY3BTKY4.js";var p=t(h(),1),n=t(s(),1);var o=t(r(),1),m={position:"absolute",left:8,top:4,zIndex:5,background:"rgba(255,255,255,0.82)",padding:"0 4px",borderRadius:3},y=(0,n.observer)(function({model:l}){let{pane:a,height:d}=l;return(0,o.jsxs)("div",{"data-testid":"linear-graph-display",style:{position:"relative",width:a.width,height:d,overflow:"hidden"},children:[(0,o.jsx)(e,{model:a,toolbar:!1}),a.hasGraph?null:(0,o.jsx)(i,{model:a}),a.cutNote?(0,o.jsx)(p.Typography,{variant:"caption",color:"warning.main",style:m,"data-testid":"graph-cut-note",children:a.cutNote}):null]})}),g=y;export{g as default};
2
- //# sourceMappingURL=LinearGraphDisplay-P7U6NCZF.js.map