jbrowse-plugin-graphgenomeviewer 5.0.1 → 5.2.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.
Files changed (59) hide show
  1. package/README.md +3 -4
  2. package/dist/chunks/GbzBaseSyntenyAdapter-TQVDD3OC.js +8 -0
  3. package/dist/chunks/GbzBaseSyntenyAdapter-TQVDD3OC.js.map +7 -0
  4. package/dist/chunks/{GraphGenomeView-YYP4IFSI.js → GraphGenomeView-AY2FO4P2.js} +2 -2
  5. package/dist/chunks/{GraphTrackSettingsDialog-2WPJRAJC.js → GraphTrackSettingsDialog-QJXPMWYG.js} +2 -2
  6. package/dist/chunks/LinearGraphDisplay-GKVG72J4.js +2 -0
  7. package/dist/chunks/LinearGraphDisplay-GKVG72J4.js.map +7 -0
  8. package/dist/chunks/chunk-2V27PV25.js +3 -0
  9. package/dist/chunks/chunk-2V27PV25.js.map +7 -0
  10. package/dist/chunks/chunk-FNHKEAKB.js +5 -0
  11. package/dist/chunks/{chunk-LGFKHSVJ.js.map → chunk-FNHKEAKB.js.map} +3 -3
  12. package/dist/chunks/chunk-K5Z47YVI.js +2 -0
  13. package/dist/chunks/chunk-K5Z47YVI.js.map +7 -0
  14. package/dist/chunks/{chunk-SJNG2XK7.js → chunk-LJZDHT2C.js} +2 -2
  15. package/dist/chunks/chunk-NWJN4C4V.js +2 -0
  16. package/dist/chunks/chunk-NWJN4C4V.js.map +7 -0
  17. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +5 -5
  18. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
  19. package/package.json +2 -2
  20. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +20 -38
  21. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +14 -65
  22. package/src/GraphGenomeView/components/GenePins.tsx +25 -80
  23. package/src/GraphGenomeView/components/usePaneGestures.ts +1 -0
  24. package/src/GraphGenomeView/genes/geneFeatures.ts +46 -7
  25. package/src/GraphGenomeView/genes/genes.test.ts +88 -1
  26. package/src/GraphGenomeView/model.test.ts +1 -1
  27. package/src/GraphGenomeView/pane/fitViews.ts +1 -0
  28. package/src/GraphGenomeView/pane/graphViews.ts +39 -5
  29. package/src/GraphGenomeView/pane/loadActions.ts +5 -5
  30. package/src/GraphGenomeView/pane/paneBase.ts +3 -2
  31. package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +160 -92
  32. package/src/LinearGraphDisplay/components/LinearGraphDisplay.tsx +2 -12
  33. package/src/LinearGraphDisplay/denseWindow.ts +40 -0
  34. package/src/LinearGraphDisplay/model.ts +34 -94
  35. package/src/index.ts +0 -2
  36. package/src/version.ts +1 -1
  37. package/dist/chunks/GbzBaseSyntenyAdapter-CKKVHC2R.js +0 -4
  38. package/dist/chunks/GbzBaseSyntenyAdapter-CKKVHC2R.js.map +0 -7
  39. package/dist/chunks/LinearGraphDisplay-7YE624D5.js +0 -2
  40. package/dist/chunks/LinearGraphDisplay-7YE624D5.js.map +0 -7
  41. package/dist/chunks/chunk-2H7GDQ3R.js +0 -6
  42. package/dist/chunks/chunk-2H7GDQ3R.js.map +0 -7
  43. package/dist/chunks/chunk-BZOIENU5.js +0 -2
  44. package/dist/chunks/chunk-BZOIENU5.js.map +0 -7
  45. package/dist/chunks/chunk-H54W6OQQ.js +0 -2
  46. package/dist/chunks/chunk-H54W6OQQ.js.map +0 -7
  47. package/dist/chunks/chunk-LGFKHSVJ.js +0 -5
  48. package/dist/chunks/chunk-O4XV3K35.js +0 -2
  49. package/dist/chunks/chunk-O4XV3K35.js.map +0 -7
  50. package/dist/chunks/chunk-WKVVIDCS.js +0 -3
  51. package/dist/chunks/chunk-WKVVIDCS.js.map +0 -7
  52. package/src/GetHaplotypeOverview.ts +0 -77
  53. package/src/HaplotypeOverview/HaplotypeOverviewCanvas.tsx +0 -203
  54. package/src/HaplotypeOverview/draw.test.ts +0 -136
  55. package/src/HaplotypeOverview/draw.ts +0 -433
  56. package/src/HaplotypeOverview/model.ts +0 -163
  57. /package/dist/chunks/{GraphGenomeView-YYP4IFSI.js.map → GraphGenomeView-AY2FO4P2.js.map} +0 -0
  58. /package/dist/chunks/{GraphTrackSettingsDialog-2WPJRAJC.js.map → GraphTrackSettingsDialog-QJXPMWYG.js.map} +0 -0
  59. /package/dist/chunks/{chunk-SJNG2XK7.js.map → chunk-LJZDHT2C.js.map} +0 -0
@@ -1,433 +0,0 @@
1
- import {
2
- OVERVIEW_ABSENT,
3
- OVERVIEW_PARTIAL,
4
- OVERVIEW_REFERENCE,
5
- OVERVIEW_VARIANT,
6
- } from '@gmod/gbz-base'
7
-
8
- import type { HaplotypeOverviewData } from '../GetHaplotypeOverview'
9
-
10
- type Rgb = readonly [number, number, number]
11
-
12
- // The variant track's colours: reference grey, so the two tracks read alike,
13
- // and variant cells deepening with how many variant marks the bin holds
14
- export const PALETTE = {
15
- absent: [244, 244, 244],
16
- reference: [200, 200, 200],
17
- partial: [224, 204, 150],
18
- variant: [
19
- [132, 166, 200],
20
- [64, 116, 168],
21
- [33, 82, 132],
22
- [14, 46, 86],
23
- ],
24
- } as const satisfies Record<string, Rgb | readonly Rgb[]>
25
-
26
- export const DENSITY_PX = 40
27
- const GAP_PX = 4
28
- const PIN_GAP_PX = 3
29
- const PIN_ROW_PX = 14
30
- const LABEL_MIN_PX = 9
31
- // Past 16 kb bins nearly every bin holds a 50 bp excursion for most
32
- // haplotypes, so the density row counts variant excursions instead of
33
- // haplotypes per class
34
- export const FINE_BIN_BP = 16_384
35
-
36
- // "Diverges" rather than "variant": only excursions of 50 bp or more and
37
- // rearrangements count, so a haplotype full of SNPs is reference-like
38
- const CLASS_NAMES = [
39
- 'absent: no contig covers the bin',
40
- 'reference-like',
41
- 'partial: contigs cover under 90% of the bin',
42
- 'diverges',
43
- ]
44
- const MARK_BUCKETS = [
45
- '1 excursion',
46
- '2–3 excursions',
47
- '4–15 excursions',
48
- '16+ excursions',
49
- ]
50
- export const NOT_SHOWN = 'SNPs and indels under 50 bp are not shown'
51
-
52
- export function cellColor(cell: number): Rgb {
53
- const klass = cell & 3
54
- return klass === OVERVIEW_ABSENT
55
- ? PALETTE.absent
56
- : klass === OVERVIEW_REFERENCE
57
- ? PALETTE.reference
58
- : klass === OVERVIEW_PARTIAL
59
- ? PALETTE.partial
60
- : PALETTE.variant[cell >> 2]!
61
- }
62
-
63
- export function cellLabel(cell: number) {
64
- const klass = cell & 3
65
- return klass === OVERVIEW_VARIANT
66
- ? `diverges ≥50 bp, ${MARK_BUCKETS[cell >> 2]}`
67
- : CLASS_NAMES[klass]!
68
- }
69
-
70
- export interface OverviewLayout {
71
- rows: number[]
72
- pinnedCount: number
73
- pinnedHeight: number
74
- restHeight: number
75
- rowsTop: number
76
- restTop: number
77
- }
78
-
79
- // The track's lanes first, at a height their labels fit when there is room,
80
- // then every other haplotype unless only the lanes are wanted, sharing what
81
- // is left. The reference sample's rows never draw.
82
- export function overviewLayout(
83
- data: Pick<HaplotypeOverviewData, 'rows' | 'pinned' | 'reference'>,
84
- allRows: boolean,
85
- height: number,
86
- ): OverviewLayout {
87
- const skip = new Set([...data.reference, ...data.pinned])
88
- const pinned = data.pinned.filter(row => !data.reference.includes(row))
89
- const rest = allRows
90
- ? data.rows.flatMap((_, row) => (skip.has(row) ? [] : [row]))
91
- : []
92
- const rowsTop = DENSITY_PX + GAP_PX
93
- const available = Math.max(0, height - rowsTop)
94
- const gap = pinned.length > 0 && rest.length > 0 ? PIN_GAP_PX : 0
95
- const pinnedHeight =
96
- pinned.length === 0
97
- ? 0
98
- : Math.min(
99
- PIN_ROW_PX,
100
- (rest.length > 0 ? available * 0.4 : available) / pinned.length,
101
- )
102
- const restTop = rowsTop + pinned.length * pinnedHeight + gap
103
- return {
104
- rows: [...pinned, ...rest],
105
- pinnedCount: pinned.length,
106
- pinnedHeight,
107
- restHeight:
108
- rest.length > 0 ? Math.max(0, height - restTop) / rest.length : 0,
109
- rowsTop,
110
- restTop,
111
- }
112
- }
113
-
114
- export function rowTop(layout: OverviewLayout, index: number) {
115
- return index < layout.pinnedCount
116
- ? layout.rowsTop + index * layout.pinnedHeight
117
- : layout.restTop + (index - layout.pinnedCount) * layout.restHeight
118
- }
119
-
120
- // The haplotype row under y, or undefined over the density row or a gap
121
- export function rowAt(layout: OverviewLayout, y: number) {
122
- const { pinnedCount, pinnedHeight, restHeight, rowsTop, restTop } = layout
123
- const pinnedEnd = rowsTop + pinnedCount * pinnedHeight
124
- const index =
125
- y >= rowsTop && y < pinnedEnd
126
- ? Math.floor((y - rowsTop) / pinnedHeight)
127
- : y >= restTop && restHeight > 0
128
- ? pinnedCount + Math.floor((y - restTop) / restHeight)
129
- : undefined
130
- return index !== undefined && index < layout.rows.length
131
- ? layout.rows[index]
132
- : undefined
133
- }
134
-
135
- // The bin holding bp, or undefined between fragments and past either end
136
- export function binAt(data: Pick<HaplotypeOverviewData, 'bins'>, bp: number) {
137
- const { bins } = data
138
- let lo = 0
139
- let hi = bins.length - 1
140
- while (lo < hi) {
141
- const mid = (lo + hi + 1) >> 1
142
- if (bins[mid]!.start <= bp) {
143
- lo = mid
144
- } else {
145
- hi = mid - 1
146
- }
147
- }
148
- const bin = bins[lo]
149
- return bin && bin.start <= bp && bp < bin.end ? lo : undefined
150
- }
151
-
152
- // Per bin, the haplotypes that are not the reference counted by what the
153
- // density row stacks: absent, partial, and variant in each mark bucket
154
- export function densityCounts(data: HaplotypeOverviewData) {
155
- const height = data.rows.length
156
- const reference = new Set(data.reference)
157
- return data.bins.map((_, b) => {
158
- const counts = [0, 0, 0, 0, 0, 0]
159
- for (let row = 0; row < height; row++) {
160
- if (!reference.has(row)) {
161
- const cell = data.cells[b * height + row]!
162
- const klass = cell & 3
163
- if (klass === OVERVIEW_VARIANT) {
164
- counts[2 + (cell >> 2)]! += 1
165
- } else if (klass === OVERVIEW_ABSENT) {
166
- counts[0]! += 1
167
- } else if (klass === OVERVIEW_PARTIAL) {
168
- counts[1]! += 1
169
- }
170
- }
171
- }
172
- return counts
173
- })
174
- }
175
-
176
- export function describeBin(data: HaplotypeOverviewData, b: number) {
177
- const bin = data.bins[b]!
178
- const [absent, reference, partial, variant] = bin.classes
179
- return {
180
- span: `${bin.start.toLocaleString()}–${bin.end.toLocaleString()}`,
181
- classes: `${reference} reference-like, ${variant} diverge ≥50 bp, ${partial} partial, ${absent} absent`,
182
- excursions: `${bin.excursions.toLocaleString()} excursions, ${bin.variants.toLocaleString()} of 50 bp or more, longest ${bin.longestExcursion.toLocaleString()} bp`,
183
- }
184
- }
185
-
186
- interface Frame {
187
- // screen x of a bp on the overview's contig
188
- xOf: (bp: number) => number
189
- width: number
190
- height: number
191
- dpr: number
192
- }
193
-
194
- function makeCanvas(width: number, height: number) {
195
- const canvas = document.createElement('canvas')
196
- canvas.width = Math.max(1, width)
197
- canvas.height = Math.max(1, height)
198
- return canvas
199
- }
200
-
201
- // Consecutive bins with no gap between them, which one image can stretch over
202
- function contiguousRuns(data: HaplotypeOverviewData) {
203
- const runs: [number, number][] = []
204
- let first = 0
205
- data.bins.forEach((bin, b) => {
206
- if (b > 0 && bin.start !== data.bins[b - 1]!.end) {
207
- runs.push([first, b])
208
- first = b
209
- }
210
- })
211
- if (data.bins.length > 0) {
212
- runs.push([first, data.bins.length])
213
- }
214
- return runs
215
- }
216
-
217
- export interface RowImage {
218
- image: HTMLCanvasElement
219
- top: number
220
- height: number
221
- }
222
-
223
- // One image per group of rows, built once per overview and layout: a pixel
224
- // per cell, scaled to the group's device height with smoothing only when
225
- // rows outnumber pixels
226
- function rowImage(
227
- data: HaplotypeOverviewData,
228
- rows: number[],
229
- top: number,
230
- height: number,
231
- dpr: number,
232
- ): RowImage | undefined {
233
- const cols = data.bins.length
234
- if (rows.length === 0 || cols === 0 || height <= 0) {
235
- return undefined
236
- }
237
- const cells = makeCanvas(cols, rows.length)
238
- const cellsCtx = cells.getContext('2d')!
239
- const pixels = cellsCtx.createImageData(cols, rows.length)
240
- const stride = data.rows.length
241
- rows.forEach((row, y) => {
242
- for (let b = 0; b < cols; b++) {
243
- const [r, g, bl] = cellColor(data.cells[b * stride + row]!)
244
- const o = (y * cols + b) * 4
245
- pixels.data[o] = r
246
- pixels.data[o + 1] = g
247
- pixels.data[o + 2] = bl
248
- pixels.data[o + 3] = 255
249
- }
250
- })
251
- cellsCtx.putImageData(pixels, 0, 0)
252
- const devHeight = Math.max(1, Math.round(height * dpr))
253
- const image = makeCanvas(cols, devHeight)
254
- const ctx = image.getContext('2d')!
255
- ctx.imageSmoothingEnabled = rows.length > devHeight
256
- ctx.imageSmoothingQuality = 'high'
257
- ctx.drawImage(cells, 0, 0, cols, devHeight)
258
- return { image, top, height }
259
- }
260
-
261
- export function rowImages(
262
- data: HaplotypeOverviewData,
263
- layout: OverviewLayout,
264
- dpr: number,
265
- ) {
266
- const { rows, pinnedCount, pinnedHeight, restHeight } = layout
267
- return [
268
- rowImage(
269
- data,
270
- rows.slice(0, pinnedCount),
271
- layout.rowsTop,
272
- pinnedCount * pinnedHeight,
273
- dpr,
274
- ),
275
- rowImage(
276
- data,
277
- rows.slice(pinnedCount),
278
- layout.restTop,
279
- (rows.length - pinnedCount) * restHeight,
280
- dpr,
281
- ),
282
- ].filter(image => image !== undefined)
283
- }
284
-
285
- // The screen span of bp [start, end), which runs right to left on a
286
- // reversed block
287
- function screenSpan(frame: Frame, start: number, end: number) {
288
- const a = frame.xOf(start)
289
- const b = frame.xOf(end)
290
- return { left: Math.min(a, b), width: Math.abs(b - a), reversed: b < a }
291
- }
292
-
293
- // Each run of bins stretched to its bp, smoothed only when bins outnumber
294
- // pixels, and mirrored on a reversed block
295
- function blitRows(
296
- ctx: CanvasRenderingContext2D,
297
- data: HaplotypeOverviewData,
298
- images: RowImage[],
299
- frame: Frame,
300
- ) {
301
- for (const [first, end] of contiguousRuns(data)) {
302
- const { left, width, reversed } = screenSpan(
303
- frame,
304
- data.bins[first]!.start,
305
- data.bins[end - 1]!.end,
306
- )
307
- if (left + width < 0 || left > frame.width) {
308
- continue
309
- }
310
- ctx.imageSmoothingEnabled = end - first > width * frame.dpr
311
- for (const { image, top, height } of images) {
312
- ctx.save()
313
- if (reversed) {
314
- ctx.translate(2 * left + width, 0)
315
- ctx.scale(-1, 1)
316
- }
317
- ctx.drawImage(
318
- image,
319
- first,
320
- 0,
321
- end - first,
322
- image.height,
323
- left,
324
- top,
325
- width,
326
- height,
327
- )
328
- ctx.restore()
329
- }
330
- }
331
- }
332
-
333
- function drawDensity(
334
- ctx: CanvasRenderingContext2D,
335
- data: HaplotypeOverviewData,
336
- counts: number[][] | undefined,
337
- frame: Frame,
338
- ) {
339
- const total = data.rows.length - data.reference.length
340
- const most = Math.max(1, ...data.bins.map(bin => bin.variants))
341
- const stack: Rgb[] = [[175, 175, 175], PALETTE.partial, ...PALETTE.variant]
342
- data.bins.forEach((bin, b) => {
343
- const span = screenSpan(frame, bin.start, bin.end)
344
- const { left } = span
345
- const width = Math.max(span.width, 1 / frame.dpr)
346
- if (left + width < 0 || left > frame.width) {
347
- return
348
- }
349
- if (counts) {
350
- let y = DENSITY_PX
351
- for (const i of [5, 4, 3, 2, 1, 0]) {
352
- const h = (DENSITY_PX * counts[b]![i]!) / Math.max(1, total)
353
- if (h > 0) {
354
- const [r, g, bl] = stack[i]!
355
- ctx.fillStyle = `rgb(${r},${g},${bl})`
356
- ctx.fillRect(left, y - h, width, h)
357
- y -= h
358
- }
359
- }
360
- } else {
361
- const h = (DENSITY_PX * bin.variants) / most
362
- const [r, g, bl] = PALETTE.variant[1]
363
- ctx.fillStyle = `rgb(${r},${g},${bl})`
364
- ctx.fillRect(left, DENSITY_PX - h, width, h)
365
- }
366
- })
367
- }
368
-
369
- function drawLabels(
370
- ctx: CanvasRenderingContext2D,
371
- data: HaplotypeOverviewData,
372
- layout: OverviewLayout,
373
- ) {
374
- if (layout.pinnedHeight < LABEL_MIN_PX) {
375
- return
376
- }
377
- ctx.font = `${Math.min(11, layout.pinnedHeight - 2)}px sans-serif`
378
- ctx.textBaseline = 'middle'
379
- for (let i = 0; i < layout.pinnedCount; i++) {
380
- const label = data.rows[layout.rows[i]!]!
381
- const y = rowTop(layout, i)
382
- const w = ctx.measureText(label).width
383
- ctx.fillStyle = 'rgba(255,255,255,0.8)'
384
- ctx.fillRect(2, y + 1, w + 6, layout.pinnedHeight - 2)
385
- ctx.fillStyle = '#222'
386
- ctx.fillText(label, 5, y + layout.pinnedHeight / 2)
387
- }
388
- }
389
-
390
- function formatBp(bp: number) {
391
- return bp >= 1_000_000
392
- ? `${+(bp / 1_000_000).toFixed(1)} Mb`
393
- : `${+(bp / 1000).toFixed(1)} kb`
394
- }
395
-
396
- // What the density band counts, which changes with the bin size
397
- export function densityLabel(data: HaplotypeOverviewData) {
398
- const haplotypes = data.rows.length - data.reference.length
399
- return data.bin <= FINE_BIN_BP
400
- ? `haplotypes diverging ≥50 bp, of ${haplotypes} · ${formatBp(data.bin)} bins`
401
- : `excursions ≥50 bp per ${formatBp(data.bin)} bin, all ${haplotypes} haplotypes`
402
- }
403
-
404
- function drawDensityLabel(
405
- ctx: CanvasRenderingContext2D,
406
- data: HaplotypeOverviewData,
407
- ) {
408
- const label = densityLabel(data)
409
- ctx.font = '10px sans-serif'
410
- ctx.textBaseline = 'top'
411
- const w = ctx.measureText(label).width
412
- ctx.fillStyle = 'rgba(255,255,255,0.8)'
413
- ctx.fillRect(2, 1, w + 6, 13)
414
- ctx.fillStyle = '#444'
415
- ctx.fillText(label, 5, 2)
416
- }
417
-
418
- // The density band from per-class counts at bins up to FINE_BIN_BP, from
419
- // excursion counts past them (undefined counts)
420
- export function drawOverview(
421
- ctx: CanvasRenderingContext2D,
422
- data: HaplotypeOverviewData,
423
- counts: number[][] | undefined,
424
- layout: OverviewLayout,
425
- images: RowImage[],
426
- frame: Frame,
427
- ) {
428
- ctx.clearRect(0, 0, frame.width, frame.height)
429
- drawDensity(ctx, data, counts, frame)
430
- blitRows(ctx, data, images, frame)
431
- drawLabels(ctx, data, layout)
432
- drawDensityLabel(ctx, data)
433
- }
@@ -1,163 +0,0 @@
1
- import { getRpcSessionId, getSession } from '@jbrowse/core/util'
2
- import { flow, types } from '@jbrowse/mobx-state-tree'
3
-
4
- import { FINE_BIN_BP, densityCounts } from './draw'
5
- import { cutHolds, hostCut } from '../GraphGenomeView/host'
6
-
7
- import type { HaplotypeOverviewData } from '../GetHaplotypeOverview'
8
- import type { SubgraphRegion } from '../GetSubgraph'
9
- import type { HostWindow } from '../GraphGenomeView/host'
10
-
11
- // A cut refused for its node count: the GBZ adapter's own error, or
12
- // gbz-base's when it reaches the display unwrapped
13
- export function isNodeLimitError(error: unknown) {
14
- return (
15
- error instanceof Error &&
16
- (error.name === 'NodeLimitError' ||
17
- /reads more than nodeLimit|^Subgraph size limit of \d+ nodes exceeded/.test(
18
- error.message,
19
- ))
20
- )
21
- }
22
-
23
- // Where a cut came back over its node limit. The graph is as dense as that
24
- // only near it, so it stands for windows as wide or wider on the same contig
25
- // within one window-width of it.
26
- export interface DenseWindow {
27
- refName: string
28
- assemblyName: string
29
- start: number
30
- end: number
31
- }
32
-
33
- export function denseCovers(dense: DenseWindow | undefined, seen: HostWindow) {
34
- if (
35
- dense?.refName !== seen.refName ||
36
- dense.assemblyName !== seen.assemblyName
37
- ) {
38
- return false
39
- }
40
- const span = dense.end - dense.start
41
- return (
42
- seen.end - seen.start >= span &&
43
- seen.end > dense.start - span &&
44
- seen.start < dense.end + span
45
- )
46
- }
47
-
48
- export class NoOverviewError extends Error {
49
- override name = 'NoOverviewError'
50
-
51
- constructor() {
52
- super(
53
- 'this window is too large to cut, and there is no overview to draw instead: either the haplotype index has none (rebuild it with gbz-haplotype-index 0.3 or later) or the reference has no path by this name. Zoom in to see the graph',
54
- )
55
- }
56
- }
57
-
58
- // What a GBZ track draws once its window is too large to cut: the haplotype
59
- // index's overview of the window plus one window-width each side, refetched
60
- // once the window leaves it or the zoom moves a level.
61
- export function HaplotypeOverviewMixin() {
62
- return types
63
- .model({
64
- // every haplotype as a row, or only the lanes the track names; unset
65
- // is every haplotype, and keeps the key out of snapshots
66
- overviewRowsChoice: types.maybe(types.boolean),
67
- })
68
- .volatile(() => ({
69
- overview: undefined as HaplotypeOverviewData | undefined,
70
- overviewRegion: undefined as SubgraphRegion | undefined,
71
- overviewBpPerPx: 0,
72
- overviewLoading: false,
73
- overviewError: undefined as Error | undefined,
74
- overviewPainted: undefined as HaplotypeOverviewData | undefined,
75
- overviewController: undefined as AbortController | undefined,
76
- dense: undefined as DenseWindow | undefined,
77
- }))
78
- .views(self => ({
79
- get overviewAllRows() {
80
- return self.overviewRowsChoice ?? true
81
- },
82
- // per bin, the density band's class counts; past FINE_BIN_BP the band
83
- // reads the bins' excursion counts instead
84
- get overviewCounts() {
85
- return self.overview && self.overview.bin <= FINE_BIN_BP
86
- ? densityCounts(self.overview)
87
- : undefined
88
- },
89
- overviewHolds(seen: HostWindow) {
90
- return (
91
- self.overview !== undefined &&
92
- self.overviewError === undefined &&
93
- cutHolds(self.overviewRegion, seen) &&
94
- seen.bpPerPx >= self.overviewBpPerPx / 2 &&
95
- seen.bpPerPx <= self.overviewBpPerPx * 2
96
- )
97
- },
98
- }))
99
- .actions(self => ({
100
- setOverviewAllRows(all: boolean) {
101
- self.overviewRowsChoice = all
102
- },
103
- beforeDestroy() {
104
- self.overviewController?.abort()
105
- },
106
- setOverviewPainted(data: HaplotypeOverviewData) {
107
- self.overviewPainted = data
108
- },
109
- setDense(window: DenseWindow | undefined) {
110
- self.dense = window
111
- },
112
- forgetOverview() {
113
- self.overviewController?.abort()
114
- self.overview = undefined
115
- self.overviewRegion = undefined
116
- self.overviewLoading = false
117
- },
118
- fetchOverview: flow(function* (
119
- adapterConfig: Record<string, unknown>,
120
- seen: HostWindow,
121
- haplotypes: string[] | undefined,
122
- ) {
123
- self.overviewController?.abort()
124
- const controller = new AbortController()
125
- self.overviewController = controller
126
- self.overviewLoading = true
127
- self.overviewError = undefined
128
- const region = hostCut(seen, Infinity)
129
- try {
130
- const data = (yield getSession(self).rpcManager.call(
131
- getRpcSessionId(self),
132
- 'GetHaplotypeOverview',
133
- {
134
- adapterConfig,
135
- region,
136
- // a level coarser than one bin per pixel, so a row image is
137
- // at most a few times the window's width
138
- bpPerPx: seen.bpPerPx * 2,
139
- haplotypes,
140
- signal: controller.signal,
141
- },
142
- )) as HaplotypeOverviewData | undefined
143
- if (!controller.signal.aborted) {
144
- self.overview = data
145
- self.overviewRegion = region
146
- self.overviewBpPerPx = seen.bpPerPx
147
- if (!data) {
148
- self.overviewError = new NoOverviewError()
149
- }
150
- }
151
- } catch (e) {
152
- if (!controller.signal.aborted) {
153
- console.error('[HaplotypeOverview]', e)
154
- self.overviewError = e instanceof Error ? e : new Error(String(e))
155
- }
156
- } finally {
157
- if (self.overviewController === controller) {
158
- self.overviewLoading = false
159
- }
160
- }
161
- }),
162
- }))
163
- }