react-msaview 5.10.0 → 6.1.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/bundle/index.js +99 -99
- package/bundle/index.js.map +4 -4
- package/dist/clustalX.js +22 -22
- package/dist/clustalX.js.map +1 -1
- package/dist/columnCounts.d.ts +7 -0
- package/dist/columnCounts.js +18 -0
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/CopyButton.d.ts +9 -0
- package/dist/components/CopyButton.js +23 -0
- package/dist/components/CopyButton.js.map +1 -0
- package/dist/components/DragHandle.d.ts +25 -0
- package/dist/components/DragHandle.js +49 -0
- package/dist/components/DragHandle.js.map +1 -0
- package/dist/components/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +2 -2
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/ResizeHandles.d.ts +4 -2
- package/dist/components/ResizeHandles.js +35 -54
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/SequenceTextArea.js +3 -10
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +11 -25
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/FeatureDialog.js +3 -3
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/dialogs/InterProScanDialog.js +52 -129
- package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
- package/dist/components/dialogs/{UserProvidedDomainsDialog.d.ts → InterProScanFileDialog.d.ts} +2 -2
- package/dist/components/dialogs/{UserProvidedDomainsDialog.js → InterProScanFileDialog.js} +6 -10
- package/dist/components/dialogs/InterProScanFileDialog.js.map +1 -0
- package/dist/components/dialogs/TrackInfoDialog.d.ts +2 -4
- package/dist/components/dialogs/TrackInfoDialog.js +8 -14
- package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
- package/dist/components/header/FileMenu.js +3 -3
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/GappynessSlider.js +1 -1
- package/dist/components/header/GappynessSlider.js.map +1 -1
- package/dist/components/header/{getDomainsMenu.d.ts → getAnnotationsMenu.d.ts} +1 -1
- package/dist/components/header/{getDomainsMenu.js → getAnnotationsMenu.js} +10 -9
- package/dist/components/header/getAnnotationsMenu.js.map +1 -0
- package/dist/components/header/settingsMenuItems.js +15 -0
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/ImportForm.js +1 -1
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +66 -57
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/minimap/Minimap.js +40 -23
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.d.ts +2 -1
- package/dist/components/minimap/MinimapSVG.js +3 -4
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/minimap/minimapLayout.d.ts +10 -1
- package/dist/components/minimap/minimapLayout.js +15 -10
- package/dist/components/minimap/minimapLayout.js.map +1 -1
- package/dist/components/msa/{DomainLegend.d.ts → AnnotationLegend.d.ts} +2 -2
- package/dist/components/msa/{DomainLegend.js → AnnotationLegend.js} +4 -4
- package/dist/components/msa/AnnotationLegend.js.map +1 -0
- package/dist/components/msa/MSACanvas.js +9 -3
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +12 -3
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +1 -2
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/MSAPanel.js +2 -2
- package/dist/components/msa/MSAPanel.js.map +1 -1
- package/dist/components/msa/domainBandCursor.d.ts +17 -0
- package/dist/components/msa/domainBandCursor.js +39 -0
- package/dist/components/msa/domainBandCursor.js.map +1 -0
- package/dist/components/msa/msaRaster.d.ts +63 -0
- package/dist/components/msa/msaRaster.js +252 -0
- package/dist/components/msa/msaRaster.js.map +1 -0
- package/dist/components/msa/renderMSABlock.d.ts +2 -1
- package/dist/components/msa/renderMSABlock.js +27 -41
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +1 -2
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/msa/tileColor.d.ts +6 -0
- package/dist/components/msa/tileColor.js +35 -0
- package/dist/components/msa/tileColor.js.map +1 -0
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/tracks/TrackBlocks.d.ts +14 -0
- package/dist/components/tracks/TrackBlocks.js +65 -0
- package/dist/components/tracks/TrackBlocks.js.map +1 -0
- package/dist/components/tracks/{renderTracksSvg.d.ts → drawTracks.d.ts} +22 -10
- package/dist/components/tracks/drawTracks.js +193 -0
- package/dist/components/tracks/drawTracks.js.map +1 -0
- package/dist/components/tree/TreeCanvas.js +8 -2
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +7 -4
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeRuler.js +5 -2
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +5 -2
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -1
- package/dist/ggplotPalettes.d.ts +0 -1
- package/dist/ggplotPalettes.js +0 -3
- package/dist/ggplotPalettes.js.map +1 -1
- package/dist/hierarchy.d.ts +2 -6
- package/dist/hierarchy.js +4 -6
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/launchInterProScan.d.ts +0 -23
- package/dist/launchInterProScan.js +2 -5
- package/dist/launchInterProScan.js.map +1 -1
- package/dist/measureTextCanvas.js +21 -6
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/DataModel.d.ts +1 -4
- package/dist/model/DataModel.js +8 -10
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model.d.ts +86 -107
- package/dist/model.js +238 -162
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +34 -18
- package/dist/neighborJoining.js.map +1 -1
- package/dist/renderTestEnv.d.ts +43 -0
- package/dist/renderTestEnv.js +88 -0
- package/dist/renderTestEnv.js.map +1 -0
- package/dist/renderToSvg.js +3 -3
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +13 -17
- package/dist/rowCoordinateCalculations.js +45 -39
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/sequenceLogo.d.ts +28 -0
- package/dist/sequenceLogo.js +47 -0
- package/dist/sequenceLogo.js.map +1 -0
- package/dist/types.d.ts +6 -11
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -5
- package/src/clustalX.ts +22 -22
- package/src/colSpace.test.ts +27 -0
- package/src/columnCounts.test.ts +15 -0
- package/src/columnCounts.ts +19 -0
- package/src/components/CopyButton.tsx +44 -0
- package/src/components/DragHandle.tsx +81 -0
- package/src/components/Loading.tsx +10 -1
- package/src/components/MSAView.tsx +2 -3
- package/src/components/ResizeHandles.tsx +70 -87
- package/src/components/SequenceTextArea.tsx +3 -16
- package/src/components/VerticalScrollbar.tsx +20 -33
- package/src/components/dialogs/FeatureDialog.tsx +3 -4
- package/src/components/dialogs/InterProScanDialog.tsx +54 -131
- package/src/components/dialogs/{UserProvidedDomainsDialog.tsx → InterProScanFileDialog.tsx} +6 -14
- package/src/components/dialogs/TrackInfoDialog.tsx +18 -20
- package/src/components/header/FileMenu.tsx +3 -3
- package/src/components/header/GappynessSlider.tsx +4 -1
- package/src/components/header/{getDomainsMenu.ts → getAnnotationsMenu.ts} +10 -9
- package/src/components/header/settingsMenuItems.ts +18 -0
- package/src/components/import/ImportForm.tsx +1 -1
- package/src/components/import/ImportFormExamples.tsx +82 -133
- package/src/components/minimap/Minimap.tsx +67 -32
- package/src/components/minimap/MinimapSVG.tsx +32 -31
- package/src/components/minimap/minimapLayout.test.ts +60 -0
- package/src/components/minimap/minimapLayout.ts +15 -10
- package/src/components/msa/{DomainLegend.tsx → AnnotationLegend.tsx} +7 -3
- package/src/components/msa/MSACanvas.tsx +24 -11
- package/src/components/msa/MSACanvasBlock.tsx +13 -3
- package/src/components/msa/MSAMouseoverCanvas.tsx +1 -3
- package/src/components/msa/MSAPanel.tsx +2 -2
- package/src/components/msa/domainBandCursor.ts +41 -0
- package/src/components/msa/msaRaster.test.ts +145 -0
- package/src/components/msa/msaRaster.ts +350 -0
- package/src/components/msa/renderMSABlock.ts +34 -43
- package/src/components/msa/renderMSAMouseover.ts +1 -3
- package/src/components/msa/tileColor.ts +37 -0
- package/src/components/renderCtx.ts +2 -0
- package/src/components/tracks/TrackBlocks.tsx +111 -0
- package/src/components/tracks/drawTracks.ts +323 -0
- package/src/components/tree/TreeCanvas.tsx +16 -4
- package/src/components/tree/TreeCanvasBlock.tsx +18 -11
- package/src/components/tree/TreeRuler.tsx +7 -2
- package/src/components/tree/renderTreeCanvas.test.ts +15 -7
- package/src/components/tree/renderTreeCanvas.ts +5 -1
- package/src/constants.ts +10 -0
- package/src/domainBands.test.ts +24 -0
- package/src/domainLetterColor.test.ts +80 -0
- package/src/ggplotPalettes.ts +0 -4
- package/src/hierarchy.test.ts +7 -7
- package/src/hierarchy.ts +6 -9
- package/src/impgRender.test.tsx +2 -57
- package/src/index.ts +4 -1
- package/src/largeTree.test.ts +39 -4
- package/src/launchInterProScan.ts +3 -30
- package/src/loadErrorDisplay.test.tsx +102 -0
- package/src/measureTextCanvas.ts +22 -6
- package/src/model/DataModel.ts +11 -11
- package/src/model.ts +262 -172
- package/src/neighborJoining.ts +36 -18
- package/src/renderDomainsSvg.test.tsx +2 -57
- package/src/renderMinimapSvg.test.tsx +55 -0
- package/src/renderSequenceLogoSvg.test.tsx +176 -0
- package/src/renderTestEnv.ts +96 -0
- package/src/renderToSvg.tsx +3 -3
- package/src/rowCoordinateCalculations.ts +48 -44
- package/src/sequenceLogo.test.ts +88 -0
- package/src/sequenceLogo.ts +63 -0
- package/src/types.ts +17 -16
- package/src/version.ts +1 -1
- package/src/viewChrome.test.tsx +134 -0
- package/dist/components/ConservationTrack.d.ts +0 -8
- package/dist/components/ConservationTrack.js +0 -49
- package/dist/components/ConservationTrack.js.map +0 -1
- package/dist/components/TextTrack.d.ts +0 -8
- package/dist/components/TextTrack.js +0 -58
- package/dist/components/TextTrack.js.map +0 -1
- package/dist/components/dialogs/DomainDialog.d.ts +0 -6
- package/dist/components/dialogs/DomainDialog.js +0 -19
- package/dist/components/dialogs/DomainDialog.js.map +0 -1
- package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +0 -1
- package/dist/components/header/SettingsMenu.d.ts +0 -6
- package/dist/components/header/SettingsMenu.js +0 -40
- package/dist/components/header/SettingsMenu.js.map +0 -1
- package/dist/components/header/getDomainsMenu.js.map +0 -1
- package/dist/components/msa/DomainLegend.js.map +0 -1
- package/dist/components/tracks/renderTracksSvg.js +0 -126
- package/dist/components/tracks/renderTracksSvg.js.map +0 -1
- package/src/components/ConservationTrack.tsx +0 -99
- package/src/components/TextTrack.tsx +0 -103
- package/src/components/dialogs/DomainDialog.tsx +0 -45
- package/src/components/header/SettingsMenu.tsx +0 -57
- package/src/components/tracks/renderTracksSvg.ts +0 -247
package/src/model.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { colord } from 'colord'
|
|
|
6
6
|
import { autorun, transaction } from 'mobx'
|
|
7
7
|
import {
|
|
8
8
|
generateNodeIds,
|
|
9
|
-
|
|
9
|
+
gffToAnnotations,
|
|
10
|
+
interProScanToAnnotations,
|
|
10
11
|
parseEmfTree,
|
|
11
12
|
parseGFF,
|
|
12
13
|
parseMSA,
|
|
@@ -17,8 +18,7 @@ import { calculateBlocks } from './calculateBlocks.ts'
|
|
|
17
18
|
import { clustalXColumnColors } from './clustalX.ts'
|
|
18
19
|
import colorSchemes from './colorSchemes.ts'
|
|
19
20
|
import { columnCountsFromRows, letterOfResidueSlot } from './columnCounts.ts'
|
|
20
|
-
import
|
|
21
|
-
import TextTrack from './components/TextTrack.tsx'
|
|
21
|
+
import TrackBlocks from './components/tracks/TrackBlocks.tsx'
|
|
22
22
|
import {
|
|
23
23
|
defaultAllowedGappyness,
|
|
24
24
|
defaultColWidth,
|
|
@@ -34,8 +34,11 @@ import {
|
|
|
34
34
|
defaultShowDomainLegend,
|
|
35
35
|
defaultShowDomains,
|
|
36
36
|
defaultSubFeatureRows,
|
|
37
|
+
labelReferenceFontSize,
|
|
37
38
|
maxCellSize,
|
|
38
39
|
minColWidth,
|
|
40
|
+
minLetterColWidth,
|
|
41
|
+
minLetterRowHeight,
|
|
39
42
|
minRowHeight,
|
|
40
43
|
segmentFeatureTypes,
|
|
41
44
|
segmentShades,
|
|
@@ -47,7 +50,7 @@ import {
|
|
|
47
50
|
calcDepthToLeaf,
|
|
48
51
|
clusterLayout,
|
|
49
52
|
collapse,
|
|
50
|
-
|
|
53
|
+
collapsedSubtreeMaxLength,
|
|
51
54
|
find,
|
|
52
55
|
findMaxBranchLen,
|
|
53
56
|
forEachDescendant,
|
|
@@ -71,15 +74,17 @@ import {
|
|
|
71
74
|
globalColToVisibleCol,
|
|
72
75
|
visibleColToGlobalCol,
|
|
73
76
|
visibleColToSeqPosForRow,
|
|
77
|
+
visibleColsBefore,
|
|
74
78
|
} from './rowCoordinateCalculations.ts'
|
|
75
79
|
import { buildSeqPosIndex } from './seqPosToGlobalCol.ts'
|
|
80
|
+
import { maxBitsFor } from './sequenceLogo.ts'
|
|
76
81
|
import { stripDefault } from './stripDefault.ts'
|
|
77
82
|
import { computeRowInsertions, len, skipBlanks, transform } from './util.ts'
|
|
78
83
|
import { saveAs } from './vendor/fileSaver.ts'
|
|
79
84
|
|
|
80
85
|
import type { HierarchyNode } from './hierarchy.ts'
|
|
81
|
-
import type { InterProScanResults } from './launchInterProScan.ts'
|
|
82
86
|
import type {
|
|
87
|
+
Annotation,
|
|
83
88
|
BasicTrack,
|
|
84
89
|
DomainBand,
|
|
85
90
|
NodeWithIds,
|
|
@@ -88,6 +93,7 @@ import type {
|
|
|
88
93
|
import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
|
|
89
94
|
import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
90
95
|
import type { Theme } from '@mui/material'
|
|
96
|
+
import type { InterProScanResults } from 'msa-parsers'
|
|
91
97
|
|
|
92
98
|
function parseTreeText(text: string) {
|
|
93
99
|
if (text.startsWith('BioTreeContainer')) {
|
|
@@ -96,11 +102,27 @@ function parseTreeText(text: string) {
|
|
|
96
102
|
return parseNewick(text.startsWith('SEQ') ? parseEmfTree(text).tree : text)
|
|
97
103
|
}
|
|
98
104
|
|
|
105
|
+
// Tracks that start hidden. The sequence logo answers a narrower question than
|
|
106
|
+
// conservation does and costs three times the vertical space, so it waits to be
|
|
107
|
+
// asked for.
|
|
108
|
+
const defaultOffTracks = new Set(['sequence-logo'])
|
|
109
|
+
|
|
110
|
+
// `turnedOffTracks` records the user's explicit choices only: an id is absent
|
|
111
|
+
// until they touch that track, and then its value is whether the track is OFF.
|
|
112
|
+
// Reading the default through this is what lets a track ship hidden without
|
|
113
|
+
// writing an entry into every snapshot and shared URL.
|
|
114
|
+
function trackIsOff(
|
|
115
|
+
turnedOffTracks: { get: (id: string) => boolean | undefined },
|
|
116
|
+
id: string,
|
|
117
|
+
) {
|
|
118
|
+
return turnedOffTracks.get(id) ?? defaultOffTracks.has(id)
|
|
119
|
+
}
|
|
120
|
+
|
|
99
121
|
/**
|
|
100
122
|
* #stateModel MsaView
|
|
101
123
|
*
|
|
102
124
|
* The main MSAView state model. Holds the loaded alignment, tree, and optional
|
|
103
|
-
*
|
|
125
|
+
* overlay annotations, plus all display state (color scheme, zoom,
|
|
104
126
|
* scroll, collapsed clades). It composes in members from `DialogQueueSessionMixin`,
|
|
105
127
|
* `Tree`, and `MSAModel` (see Inherited members below). Data is loaded reactively
|
|
106
128
|
* from the `msaFilehandle` / `treeFilehandle` / `gffFilehandle` properties, or set
|
|
@@ -223,7 +245,7 @@ function stateModelFactory() {
|
|
|
223
245
|
|
|
224
246
|
/**
|
|
225
247
|
* #property
|
|
226
|
-
* filehandle object for
|
|
248
|
+
* filehandle object for a GFF file of overlay annotations
|
|
227
249
|
*/
|
|
228
250
|
gffFilehandle: types.maybe(FileLocation),
|
|
229
251
|
|
|
@@ -246,7 +268,10 @@ function stateModelFactory() {
|
|
|
246
268
|
showOnly: types.maybe(types.string),
|
|
247
269
|
/**
|
|
248
270
|
* #property
|
|
249
|
-
*
|
|
271
|
+
* the user's explicit show/hide choice per track id, keyed by id with
|
|
272
|
+
* the value meaning "off". A track the user has never touched is absent
|
|
273
|
+
* and falls back to its own default (see `defaultOffTracks`), so a
|
|
274
|
+
* hidden-by-default track adds nothing to the shared URL.
|
|
250
275
|
*/
|
|
251
276
|
turnedOffTracks: stripDefault(types.map(types.boolean), {}),
|
|
252
277
|
|
|
@@ -372,6 +397,14 @@ function stateModelFactory() {
|
|
|
372
397
|
*/
|
|
373
398
|
conservationTrackHeight: 40,
|
|
374
399
|
|
|
400
|
+
/**
|
|
401
|
+
* #volatile
|
|
402
|
+
* taller than the conservation track by default: the logo spends its
|
|
403
|
+
* height on stacked glyphs, and a 40px stack of four residues leaves each
|
|
404
|
+
* one too short to identify
|
|
405
|
+
*/
|
|
406
|
+
sequenceLogoTrackHeight: 80,
|
|
407
|
+
|
|
375
408
|
/**
|
|
376
409
|
* #volatile
|
|
377
410
|
*/
|
|
@@ -385,10 +418,12 @@ function stateModelFactory() {
|
|
|
385
418
|
|
|
386
419
|
/**
|
|
387
420
|
* #volatile
|
|
421
|
+
* overlay annotations drawn on the alignment, whatever their source.
|
|
422
|
+
* Every source -- InterProScan, GFF, a user upload -- converts to this
|
|
423
|
+
* flat list before it reaches the model, so nothing downstream of here
|
|
424
|
+
* knows which one it came from
|
|
388
425
|
*/
|
|
389
|
-
|
|
390
|
-
| undefined
|
|
391
|
-
| Record<string, InterProScanResults>,
|
|
426
|
+
annotations: [] as Annotation[],
|
|
392
427
|
}))
|
|
393
428
|
.actions(self => ({
|
|
394
429
|
/**
|
|
@@ -480,7 +515,7 @@ function stateModelFactory() {
|
|
|
480
515
|
},
|
|
481
516
|
/**
|
|
482
517
|
* #action
|
|
483
|
-
* toggle the
|
|
518
|
+
* toggle the annotation overlay on the alignment
|
|
484
519
|
*/
|
|
485
520
|
setShowDomains(arg: boolean) {
|
|
486
521
|
self.showDomains = arg
|
|
@@ -523,14 +558,6 @@ function stateModelFactory() {
|
|
|
523
558
|
self.colWidth = n
|
|
524
559
|
},
|
|
525
560
|
|
|
526
|
-
/**
|
|
527
|
-
* #action
|
|
528
|
-
* set scroll Y-offset (px)
|
|
529
|
-
*/
|
|
530
|
-
setScrollY(n: number) {
|
|
531
|
-
self.scrollY = n
|
|
532
|
-
},
|
|
533
|
-
|
|
534
561
|
/**
|
|
535
562
|
* #action
|
|
536
563
|
*
|
|
@@ -638,7 +665,7 @@ function stateModelFactory() {
|
|
|
638
665
|
* #getter
|
|
639
666
|
*/
|
|
640
667
|
get actuallyShowDomains() {
|
|
641
|
-
return self.showDomains &&
|
|
668
|
+
return self.showDomains && self.annotations.length > 0
|
|
642
669
|
},
|
|
643
670
|
get viewInitialized() {
|
|
644
671
|
return self.volatileWidth !== undefined
|
|
@@ -693,7 +720,7 @@ function stateModelFactory() {
|
|
|
693
720
|
return !!this.tree.noTree
|
|
694
721
|
},
|
|
695
722
|
get noDomains() {
|
|
696
|
-
return
|
|
723
|
+
return self.annotations.length === 0
|
|
697
724
|
},
|
|
698
725
|
menuItems() {
|
|
699
726
|
return []
|
|
@@ -883,12 +910,6 @@ function stateModelFactory() {
|
|
|
883
910
|
}
|
|
884
911
|
return blanks
|
|
885
912
|
},
|
|
886
|
-
/**
|
|
887
|
-
* #getter
|
|
888
|
-
*/
|
|
889
|
-
get blanksSet() {
|
|
890
|
-
return new Set(this.blanks)
|
|
891
|
-
},
|
|
892
913
|
/**
|
|
893
914
|
* #getter
|
|
894
915
|
* Returns a map of row name to array of insertions with display position and letters
|
|
@@ -981,14 +1002,6 @@ function stateModelFactory() {
|
|
|
981
1002
|
return columnCountsFromRows(this.columns2d)
|
|
982
1003
|
},
|
|
983
1004
|
|
|
984
|
-
/**
|
|
985
|
-
* #getter
|
|
986
|
-
* per-column residue totals (gaps included)
|
|
987
|
-
*/
|
|
988
|
-
get colStatsSums() {
|
|
989
|
-
return this.colStats.totals
|
|
990
|
-
},
|
|
991
|
-
|
|
992
1005
|
/**
|
|
993
1006
|
* #getter
|
|
994
1007
|
* Detects sequence type based on letters present in the alignment.
|
|
@@ -1053,27 +1066,25 @@ function stateModelFactory() {
|
|
|
1053
1066
|
* Returns values 0-1 where 1 = fully conserved, 0 = no conservation.
|
|
1054
1067
|
*/
|
|
1055
1068
|
get conservation() {
|
|
1056
|
-
const { colStats,
|
|
1057
|
-
const alphabetSize = sequenceType === 'amino' ? 20 : 4
|
|
1058
|
-
const maxEntropy = Math.log2(alphabetSize)
|
|
1059
|
-
|
|
1069
|
+
const { colStats, alphabetMaxBits } = this
|
|
1060
1070
|
return Array.from({ length: colStats.numColumns }, (_, col) => {
|
|
1061
1071
|
const total = colStats.total(col)
|
|
1062
1072
|
const gapCount = colStats.gapCount(col)
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
colStats.forEachResidue(col, (_slot, count) => {
|
|
1068
|
-
const freq = count / nonGapTotal
|
|
1069
|
-
entropy -= freq * Math.log2(freq)
|
|
1070
|
-
})
|
|
1071
|
-
score =
|
|
1072
|
-
Math.max(0, 1 - entropy / maxEntropy) * (1 - gapCount / total)
|
|
1073
|
-
}
|
|
1074
|
-
return score
|
|
1073
|
+
return total > gapCount
|
|
1074
|
+
? Math.max(0, 1 - colStats.entropy(col) / alphabetMaxBits) *
|
|
1075
|
+
(1 - gapCount / total)
|
|
1076
|
+
: 0
|
|
1075
1077
|
})
|
|
1076
1078
|
},
|
|
1079
|
+
/**
|
|
1080
|
+
* #getter
|
|
1081
|
+
* The information content of a fully conserved column, in bits, which
|
|
1082
|
+
* depends on the alphabet. Both the entropy ceiling `conservation`
|
|
1083
|
+
* normalizes against and the y-axis ceiling of the sequence logo track.
|
|
1084
|
+
*/
|
|
1085
|
+
get alphabetMaxBits() {
|
|
1086
|
+
return maxBitsFor(this.sequenceType)
|
|
1087
|
+
},
|
|
1077
1088
|
/**
|
|
1078
1089
|
* #getter
|
|
1079
1090
|
* Per-column conservation of physicochemical property class (amino acids
|
|
@@ -1096,14 +1107,13 @@ function stateModelFactory() {
|
|
|
1096
1107
|
const max = maxLength(r)
|
|
1097
1108
|
const k = max ? self.treeWidth / max : 0
|
|
1098
1109
|
setBrLength(r, 0, k)
|
|
1099
|
-
// for each collapsed clade, record the pixel x-
|
|
1100
|
-
//
|
|
1101
|
-
//
|
|
1110
|
+
// for each collapsed clade, record the pixel x-position of its farthest
|
|
1111
|
+
// tip so the renderer can draw a triangle spanning the branch-length
|
|
1112
|
+
// extent of the hidden subtree
|
|
1102
1113
|
forEachDescendant(r, node => {
|
|
1103
1114
|
if (node._children) {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
node.collapsedTipXFar = (node.len ?? 0) + mx * k
|
|
1115
|
+
node.collapsedTipXFar =
|
|
1116
|
+
(node.len ?? 0) + collapsedSubtreeMaxLength(node) * k
|
|
1107
1117
|
}
|
|
1108
1118
|
})
|
|
1109
1119
|
return r as HierarchyNode<NodeWithIdsAndLength>
|
|
@@ -1186,12 +1196,24 @@ function stateModelFactory() {
|
|
|
1186
1196
|
*/
|
|
1187
1197
|
get blocksY() {
|
|
1188
1198
|
return calculateBlocks({
|
|
1189
|
-
viewportSize:
|
|
1199
|
+
viewportSize: this.visibleMsaHeight,
|
|
1190
1200
|
viewportPos: -self.scrollY,
|
|
1191
1201
|
blockSize: self.blockSize,
|
|
1192
1202
|
mapSize: self.totalHeight,
|
|
1193
1203
|
})
|
|
1194
1204
|
},
|
|
1205
|
+
/**
|
|
1206
|
+
* #getter
|
|
1207
|
+
* height of the alignment viewport, px. The same subtraction as
|
|
1208
|
+
* msaAreaHeight, which is defined later in the views chain
|
|
1209
|
+
*/
|
|
1210
|
+
get visibleMsaHeight() {
|
|
1211
|
+
return (
|
|
1212
|
+
self.height -
|
|
1213
|
+
self.headerHeight -
|
|
1214
|
+
(self.msaAreaWidth < self.totalWidth ? self.minimapHeight : 0)
|
|
1215
|
+
)
|
|
1216
|
+
},
|
|
1195
1217
|
}))
|
|
1196
1218
|
.views(self => ({
|
|
1197
1219
|
/**
|
|
@@ -1218,15 +1240,10 @@ function stateModelFactory() {
|
|
|
1218
1240
|
/**
|
|
1219
1241
|
* #getter
|
|
1220
1242
|
* most-negative allowed scrollY, keeping the last row in view rather than
|
|
1221
|
-
* letting the whole alignment scroll off the top.
|
|
1222
|
-
* computed inline here because msaAreaHeight is defined later in the chain.
|
|
1243
|
+
* letting the whole alignment scroll off the top.
|
|
1223
1244
|
*/
|
|
1224
1245
|
get maxScrollY() {
|
|
1225
|
-
|
|
1226
|
-
self.height -
|
|
1227
|
-
self.headerHeight -
|
|
1228
|
-
(self.msaAreaWidth < self.totalWidth ? self.minimapHeight : 0)
|
|
1229
|
-
return Math.min(-self.totalHeight + visibleHeight, 0)
|
|
1246
|
+
return Math.min(-self.totalHeight + self.visibleMsaHeight, 0)
|
|
1230
1247
|
},
|
|
1231
1248
|
/**
|
|
1232
1249
|
* #getter
|
|
@@ -1234,7 +1251,8 @@ function stateModelFactory() {
|
|
|
1234
1251
|
get showMsaLetters() {
|
|
1235
1252
|
return (
|
|
1236
1253
|
self.drawMsaLetters &&
|
|
1237
|
-
self.rowHeight >=
|
|
1254
|
+
self.rowHeight >= minLetterRowHeight &&
|
|
1255
|
+
self.colWidth >= minLetterColWidth &&
|
|
1238
1256
|
self.colWidth > self.rowHeight / 2
|
|
1239
1257
|
)
|
|
1240
1258
|
},
|
|
@@ -1242,7 +1260,7 @@ function stateModelFactory() {
|
|
|
1242
1260
|
* #getter
|
|
1243
1261
|
*/
|
|
1244
1262
|
get showTreeText() {
|
|
1245
|
-
return self.drawLabels && self.rowHeight >=
|
|
1263
|
+
return self.drawLabels && self.rowHeight >= minLetterRowHeight
|
|
1246
1264
|
},
|
|
1247
1265
|
}))
|
|
1248
1266
|
.actions(self => ({
|
|
@@ -1265,17 +1283,24 @@ function stateModelFactory() {
|
|
|
1265
1283
|
* set hovered tree node and its descendants
|
|
1266
1284
|
*/
|
|
1267
1285
|
setHoveredTreeNode(nodeId?: string) {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
} else {
|
|
1286
|
+
// the tree's mousemove handler calls this on every event, and both the
|
|
1287
|
+
// lookup and the write are expensive: `find` walks the whole hierarchy,
|
|
1288
|
+
// and a fresh object here invalidates hoveredRowIndices and redraws the
|
|
1289
|
+
// tree and MSA overlays. Re-hovering the same node is the common case
|
|
1290
|
+
if (nodeId === self.hoveredTreeNode?.nodeId) {
|
|
1291
|
+
return
|
|
1292
|
+
}
|
|
1293
|
+
if (!nodeId) {
|
|
1277
1294
|
self.hoveredTreeNode = undefined
|
|
1295
|
+
return
|
|
1278
1296
|
}
|
|
1297
|
+
const node = find(self.hierarchy, n => n.data.id === nodeId)
|
|
1298
|
+
self.hoveredTreeNode = node
|
|
1299
|
+
? {
|
|
1300
|
+
nodeId,
|
|
1301
|
+
descendantNames: leaves(node).map(leaf => leaf.data.name),
|
|
1302
|
+
}
|
|
1303
|
+
: undefined
|
|
1279
1304
|
},
|
|
1280
1305
|
|
|
1281
1306
|
/**
|
|
@@ -1394,23 +1419,40 @@ function stateModelFactory() {
|
|
|
1394
1419
|
* #action
|
|
1395
1420
|
*/
|
|
1396
1421
|
doScrollY(deltaY: number) {
|
|
1397
|
-
|
|
1422
|
+
this.setScrollY(self.scrollY + deltaY)
|
|
1398
1423
|
},
|
|
1399
1424
|
|
|
1400
1425
|
/**
|
|
1401
1426
|
* #action
|
|
1402
|
-
*
|
|
1403
|
-
|
|
1404
|
-
|
|
1427
|
+
* set scroll Y-offset (px), clamped to keep the alignment in view
|
|
1428
|
+
*/
|
|
1429
|
+
setScrollY(n: number) {
|
|
1430
|
+
self.scrollY = clamp(n, self.maxScrollY, 0)
|
|
1431
|
+
},
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* #action
|
|
1435
|
+
* Set the overlay annotations and reveal the overlay in a single step (an
|
|
1436
|
+
* empty list clears both). Every source funnels through here after its
|
|
1437
|
+
* own adapter has flattened it: InterProScan, GFF, user uploads, NCBI CDD.
|
|
1438
|
+
*/
|
|
1439
|
+
setAnnotations(annotations: Annotation[]) {
|
|
1440
|
+
self.annotations = annotations
|
|
1441
|
+
self.setShowDomains(annotations.length > 0)
|
|
1442
|
+
},
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* #action
|
|
1446
|
+
* set the overlay from raw InterProScan results keyed by row name. Kept
|
|
1447
|
+
* for downstream plugins that hold the EBI wire format; new code should
|
|
1448
|
+
* adapt to Annotation[] and call setAnnotations.
|
|
1405
1449
|
*/
|
|
1406
1450
|
setDomains(data?: Record<string, InterProScanResults>) {
|
|
1407
|
-
|
|
1408
|
-
self.setShowDomains(!!data)
|
|
1451
|
+
this.setAnnotations(data ? interProScanToAnnotations(data) : [])
|
|
1409
1452
|
},
|
|
1410
1453
|
|
|
1411
1454
|
applyGFFText(gffText: string) {
|
|
1412
|
-
|
|
1413
|
-
this.setDomains(gffToInterProResults(gffRecords))
|
|
1455
|
+
this.setAnnotations(gffToAnnotations(parseGFF(gffText)))
|
|
1414
1456
|
},
|
|
1415
1457
|
|
|
1416
1458
|
/**
|
|
@@ -1431,11 +1473,9 @@ function stateModelFactory() {
|
|
|
1431
1473
|
* #action
|
|
1432
1474
|
*/
|
|
1433
1475
|
toggleTrack(id: string) {
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
self.turnedOffTracks.set(id, true)
|
|
1438
|
-
}
|
|
1476
|
+
// the stored value is "is off", so the current shown state is exactly
|
|
1477
|
+
// what the flipped entry should hold
|
|
1478
|
+
self.turnedOffTracks.set(id, !trackIsOff(self.turnedOffTracks, id))
|
|
1439
1479
|
},
|
|
1440
1480
|
/**
|
|
1441
1481
|
* #action
|
|
@@ -1449,19 +1489,37 @@ function stateModelFactory() {
|
|
|
1449
1489
|
* #getter
|
|
1450
1490
|
*/
|
|
1451
1491
|
get labelWidthMap() {
|
|
1452
|
-
const {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1492
|
+
const { showTreeText, leaves, treeMetadata } = self
|
|
1493
|
+
// gated on the same condition the renderer draws labels under, so the
|
|
1494
|
+
// gutter labelsWidth reserves and the labels actually drawn cannot
|
|
1495
|
+
// disagree -- and so turning labels off hands their space to the tree.
|
|
1496
|
+
// Measured at a fixed reference size and scaled by labelWidthScale:
|
|
1497
|
+
// re-measuring every leaf on every vertical-zoom frame cost ~200ms on a
|
|
1498
|
+
// 50k-leaf tree
|
|
1499
|
+
return showTreeText
|
|
1500
|
+
? new Map(
|
|
1456
1501
|
leaves.map(node => {
|
|
1457
1502
|
const { name } = node.data
|
|
1458
1503
|
// `||`, matching renderTreeLabels: an empty genome falls back
|
|
1459
1504
|
// to the row name, and measuring '' would size the gutter (and
|
|
1460
1505
|
// the label's click target) to nothing
|
|
1461
1506
|
const displayName = treeMetadata[name]?.genome || name
|
|
1462
|
-
return [
|
|
1507
|
+
return [
|
|
1508
|
+
name,
|
|
1509
|
+
measureTextCanvas(displayName, labelReferenceFontSize),
|
|
1510
|
+
] as const
|
|
1463
1511
|
}),
|
|
1464
1512
|
)
|
|
1513
|
+
: new Map<string, number>()
|
|
1514
|
+
},
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* #getter
|
|
1518
|
+
* factor turning a labelWidthMap entry into its width at the current
|
|
1519
|
+
* font size
|
|
1520
|
+
*/
|
|
1521
|
+
get labelWidthScale() {
|
|
1522
|
+
return self.fontSize / labelReferenceFontSize
|
|
1465
1523
|
},
|
|
1466
1524
|
|
|
1467
1525
|
get labelsWidth() {
|
|
@@ -1476,7 +1534,7 @@ function stateModelFactory() {
|
|
|
1476
1534
|
max = width
|
|
1477
1535
|
}
|
|
1478
1536
|
}
|
|
1479
|
-
return max
|
|
1537
|
+
return max * this.labelWidthScale
|
|
1480
1538
|
},
|
|
1481
1539
|
|
|
1482
1540
|
/**
|
|
@@ -1504,10 +1562,11 @@ function stateModelFactory() {
|
|
|
1504
1562
|
.map(t => ({
|
|
1505
1563
|
model: {
|
|
1506
1564
|
...t,
|
|
1565
|
+
kind: 'text' as const,
|
|
1507
1566
|
data: hideGapsEffective ? skipBlanks(blanks, t.data!) : t.data,
|
|
1508
1567
|
height: rowHeight,
|
|
1509
1568
|
},
|
|
1510
|
-
ReactComponent:
|
|
1569
|
+
ReactComponent: TrackBlocks,
|
|
1511
1570
|
}))
|
|
1512
1571
|
},
|
|
1513
1572
|
|
|
@@ -1515,28 +1574,35 @@ function stateModelFactory() {
|
|
|
1515
1574
|
* #getter
|
|
1516
1575
|
*/
|
|
1517
1576
|
get tracks(): BasicTrack[] {
|
|
1518
|
-
const conservationTrack
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
},
|
|
1525
|
-
ReactComponent: ConservationTrack,
|
|
1577
|
+
const conservationTrack = {
|
|
1578
|
+
id: 'conservation',
|
|
1579
|
+
name: 'Conservation',
|
|
1580
|
+
kind: 'bar' as const,
|
|
1581
|
+
height: self.conservationTrackHeight,
|
|
1582
|
+
barColor: 'gray',
|
|
1526
1583
|
}
|
|
1527
|
-
const propertyConservationTrack
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1584
|
+
const propertyConservationTrack = {
|
|
1585
|
+
id: 'property-conservation',
|
|
1586
|
+
name: 'Property conservation',
|
|
1587
|
+
kind: 'bar' as const,
|
|
1588
|
+
height: self.conservationTrackHeight,
|
|
1589
|
+
barColor: '#6a51a3',
|
|
1590
|
+
}
|
|
1591
|
+
const sequenceLogoTrack = {
|
|
1592
|
+
id: 'sequence-logo',
|
|
1593
|
+
name: 'Sequence logo',
|
|
1594
|
+
kind: 'logo' as const,
|
|
1595
|
+
height: self.sequenceLogoTrackHeight,
|
|
1535
1596
|
}
|
|
1536
1597
|
return [
|
|
1537
1598
|
...this.adapterTrackModels,
|
|
1538
|
-
|
|
1539
|
-
|
|
1599
|
+
...[
|
|
1600
|
+
conservationTrack,
|
|
1601
|
+
...(self.sequenceType === 'amino'
|
|
1602
|
+
? [propertyConservationTrack]
|
|
1603
|
+
: []),
|
|
1604
|
+
sequenceLogoTrack,
|
|
1605
|
+
].map(model => ({ model, ReactComponent: TrackBlocks })),
|
|
1540
1606
|
]
|
|
1541
1607
|
},
|
|
1542
1608
|
|
|
@@ -1544,7 +1610,9 @@ function stateModelFactory() {
|
|
|
1544
1610
|
* #getter
|
|
1545
1611
|
*/
|
|
1546
1612
|
get turnedOnTracks() {
|
|
1547
|
-
return this.tracks.filter(
|
|
1613
|
+
return this.tracks.filter(
|
|
1614
|
+
f => !trackIsOff(self.turnedOffTracks, f.model.id),
|
|
1615
|
+
)
|
|
1548
1616
|
},
|
|
1549
1617
|
|
|
1550
1618
|
/**
|
|
@@ -1674,41 +1742,29 @@ function stateModelFactory() {
|
|
|
1674
1742
|
get totalTrackAreaHeight() {
|
|
1675
1743
|
return sum(self.turnedOnTracks.map(r => r.model.height))
|
|
1676
1744
|
},
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
accession: entry.accession,
|
|
1693
|
-
description: entry.description,
|
|
1694
|
-
featureType: entry.featureType,
|
|
1695
|
-
start,
|
|
1696
|
-
end,
|
|
1697
|
-
strand,
|
|
1698
|
-
}))
|
|
1699
|
-
: []
|
|
1700
|
-
}),
|
|
1701
|
-
)
|
|
1702
|
-
.toSorted((a, b) => len(b) - len(a))
|
|
1703
|
-
: []
|
|
1745
|
+
/**
|
|
1746
|
+
* one representative annotation per accession, which is what the legend,
|
|
1747
|
+
* the filter dialog and the palettes key off
|
|
1748
|
+
*/
|
|
1749
|
+
get annotationTypes() {
|
|
1750
|
+
// first occurrence wins. The representative supplies only the name,
|
|
1751
|
+
// description and -- for ordinal segments -- the start that orders
|
|
1752
|
+
// them, and those agree across an accession's instances
|
|
1753
|
+
const types = new Map<string, Annotation>()
|
|
1754
|
+
for (const annot of self.annotations) {
|
|
1755
|
+
if (!types.has(annot.accession)) {
|
|
1756
|
+
types.set(annot.accession, annot)
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return types
|
|
1704
1760
|
},
|
|
1705
|
-
get
|
|
1706
|
-
return
|
|
1761
|
+
get filteredAnnotations() {
|
|
1762
|
+
return self.annotations.filter(r =>
|
|
1707
1763
|
self.featureFilters.get(r.accession),
|
|
1708
1764
|
)
|
|
1709
1765
|
},
|
|
1710
|
-
get
|
|
1711
|
-
return groupBy(this.
|
|
1766
|
+
get annotationsByRow() {
|
|
1767
|
+
return groupBy(this.filteredAnnotations, r => r.id)
|
|
1712
1768
|
},
|
|
1713
1769
|
}))
|
|
1714
1770
|
.views(self => ({
|
|
@@ -1726,6 +1782,15 @@ function stateModelFactory() {
|
|
|
1726
1782
|
get verticalScrollbarWidth() {
|
|
1727
1783
|
return self.showVerticalScrollbar ? 20 : 0
|
|
1728
1784
|
},
|
|
1785
|
+
/**
|
|
1786
|
+
* #getter
|
|
1787
|
+
* width of the alignment canvas itself: the msa area less the vertical
|
|
1788
|
+
* scrollbar sitting in it. Not usable from showHorizontalScrollbar, which
|
|
1789
|
+
* feeds msaAreaHeight -> showVerticalScrollbar and would close a cycle
|
|
1790
|
+
*/
|
|
1791
|
+
get msaCanvasWidth() {
|
|
1792
|
+
return self.msaAreaWidth - this.verticalScrollbarWidth
|
|
1793
|
+
},
|
|
1729
1794
|
/**
|
|
1730
1795
|
* #getter
|
|
1731
1796
|
* ordinal segment types (exons etc.), ordered by sequence position so
|
|
@@ -1733,7 +1798,7 @@ function stateModelFactory() {
|
|
|
1733
1798
|
* labeled by number rather than each getting a distinct hue + legend row
|
|
1734
1799
|
*/
|
|
1735
1800
|
get segmentDomainTypes() {
|
|
1736
|
-
return [...self.
|
|
1801
|
+
return [...self.annotationTypes.values()]
|
|
1737
1802
|
.filter(d => segmentFeatureTypes.has(d.featureType ?? ''))
|
|
1738
1803
|
.toSorted((a, b) => a.start - b.start)
|
|
1739
1804
|
},
|
|
@@ -1743,7 +1808,7 @@ function stateModelFactory() {
|
|
|
1743
1808
|
* their own color and a legend entry
|
|
1744
1809
|
*/
|
|
1745
1810
|
get categoricalDomainTypes() {
|
|
1746
|
-
return [...self.
|
|
1811
|
+
return [...self.annotationTypes.values()].filter(
|
|
1747
1812
|
d => !segmentFeatureTypes.has(d.featureType ?? ''),
|
|
1748
1813
|
)
|
|
1749
1814
|
},
|
|
@@ -1795,34 +1860,47 @@ function stateModelFactory() {
|
|
|
1795
1860
|
|
|
1796
1861
|
/**
|
|
1797
1862
|
* #getter
|
|
1798
|
-
* every filtered-on
|
|
1799
|
-
*
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
1863
|
+
* every filtered-on annotation resolved to the visible column span it is
|
|
1864
|
+
* drawn across, keyed by row name. Each row is ordered longest-first so a
|
|
1865
|
+
* short domain nested inside a long one draws on top of it rather than
|
|
1866
|
+
* under it. Resolving these once here rather than inside each canvas
|
|
1867
|
+
* block removes a per-feature, per-block sequence position conversion
|
|
1868
|
+
* from every redraw, and gives the letter renderer the band colors it
|
|
1869
|
+
* needs to keep residues readable on top of the boxes.
|
|
1804
1870
|
*/
|
|
1805
1871
|
get domainBands() {
|
|
1872
|
+
const { blanks } = self
|
|
1806
1873
|
const bands = new Map<string, DomainBand[]>()
|
|
1807
1874
|
for (const [name, annotations] of Object.entries(
|
|
1808
|
-
self.
|
|
1875
|
+
self.annotationsByRow,
|
|
1809
1876
|
)) {
|
|
1810
1877
|
const rowBands = annotations
|
|
1811
|
-
.
|
|
1812
|
-
|
|
1813
|
-
//
|
|
1814
|
-
//
|
|
1815
|
-
//
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1878
|
+
.toSorted((a, b) => len(b) - len(a))
|
|
1879
|
+
.map(annotation => {
|
|
1880
|
+
// annotation positions are 1-based and inclusive. Both ends count
|
|
1881
|
+
// the visible columns in front of a global column, so endCol is
|
|
1882
|
+
// the exclusive column after the last residue's own column --
|
|
1883
|
+
// the band stops there rather than stretching across a following
|
|
1884
|
+
// gap run -- and a residue whose column is itself hidden
|
|
1885
|
+
// collapses onto the neighbouring boundary instead of dropping
|
|
1886
|
+
// the band. A band whose every column is hidden spans nothing
|
|
1887
|
+
// and is left out.
|
|
1888
|
+
const startCol = visibleColsBefore(
|
|
1889
|
+
blanks,
|
|
1890
|
+
self.seqPosToGlobalCol(name, annotation.start - 1),
|
|
1819
1891
|
)
|
|
1820
|
-
const
|
|
1821
|
-
|
|
1822
|
-
|
|
1892
|
+
const endCol = visibleColsBefore(
|
|
1893
|
+
blanks,
|
|
1894
|
+
self.seqPosToGlobalCol(name, annotation.end - 1) + 1,
|
|
1895
|
+
)
|
|
1896
|
+
return endCol > startCol
|
|
1897
|
+
? { annotation, startCol, endCol }
|
|
1823
1898
|
: undefined
|
|
1824
1899
|
})
|
|
1825
1900
|
.filter(notEmpty)
|
|
1901
|
+
// numbered after the drop, so a band that resolved to nothing does
|
|
1902
|
+
// not leave an empty sub-row behind it
|
|
1903
|
+
.map((band, stackIndex) => ({ ...band, stackIndex }))
|
|
1826
1904
|
if (rowBands.length > 0) {
|
|
1827
1905
|
bands.set(name, rowBands)
|
|
1828
1906
|
}
|
|
@@ -1923,8 +2001,11 @@ function stateModelFactory() {
|
|
|
1923
2001
|
return undefined
|
|
1924
2002
|
}
|
|
1925
2003
|
const { colStats } = self
|
|
2004
|
+
if (mouseCol >= colStats.numColumns) {
|
|
2005
|
+
return undefined
|
|
2006
|
+
}
|
|
1926
2007
|
const total = colStats.total(mouseCol)
|
|
1927
|
-
if (
|
|
2008
|
+
if (!total) {
|
|
1928
2009
|
return undefined
|
|
1929
2010
|
}
|
|
1930
2011
|
const gaps = colStats.gapCount(mouseCol)
|
|
@@ -1969,6 +2050,12 @@ function stateModelFactory() {
|
|
|
1969
2050
|
setConservationTrackHeight(arg: number) {
|
|
1970
2051
|
self.conservationTrackHeight = arg
|
|
1971
2052
|
},
|
|
2053
|
+
/**
|
|
2054
|
+
* #action
|
|
2055
|
+
*/
|
|
2056
|
+
setSequenceLogoTrackHeight(arg: number) {
|
|
2057
|
+
self.sequenceLogoTrackHeight = arg
|
|
2058
|
+
},
|
|
1972
2059
|
/**
|
|
1973
2060
|
* #action
|
|
1974
2061
|
*/
|
|
@@ -1985,7 +2072,7 @@ function stateModelFactory() {
|
|
|
1985
2072
|
self.setTreeFilehandle(undefined)
|
|
1986
2073
|
self.setMSAFilehandle(undefined)
|
|
1987
2074
|
self.setGFFFilehandle(undefined)
|
|
1988
|
-
self.
|
|
2075
|
+
self.setAnnotations([])
|
|
1989
2076
|
},
|
|
1990
2077
|
/**
|
|
1991
2078
|
* #action
|
|
@@ -2035,8 +2122,11 @@ function stateModelFactory() {
|
|
|
2035
2122
|
*/
|
|
2036
2123
|
fitHorizontally() {
|
|
2037
2124
|
if (self.numColumns > 0) {
|
|
2125
|
+
// fitting to msaAreaWidth instead left the last ~20px of columns off
|
|
2126
|
+
// the right edge -- and short of the width that shows a minimap, so
|
|
2127
|
+
// nothing on screen said they were there
|
|
2038
2128
|
self.colWidth = clamp(
|
|
2039
|
-
self.
|
|
2129
|
+
self.msaCanvasWidth / self.numColumns,
|
|
2040
2130
|
minColWidth,
|
|
2041
2131
|
maxCellSize,
|
|
2042
2132
|
)
|
|
@@ -2055,7 +2145,7 @@ function stateModelFactory() {
|
|
|
2055
2145
|
addDisposer(
|
|
2056
2146
|
self,
|
|
2057
2147
|
autorun(() => {
|
|
2058
|
-
for (const key of self.
|
|
2148
|
+
for (const key of self.annotationTypes.keys()) {
|
|
2059
2149
|
this.initFilter(key)
|
|
2060
2150
|
}
|
|
2061
2151
|
}),
|
|
@@ -2195,7 +2285,7 @@ function stateModelFactory() {
|
|
|
2195
2285
|
}),
|
|
2196
2286
|
)
|
|
2197
2287
|
|
|
2198
|
-
// gffFilehandle carries
|
|
2288
|
+
// gffFilehandle carries overlay annotations
|
|
2199
2289
|
loadOnFilehandleChange({
|
|
2200
2290
|
getFilehandle: () => self.gffFilehandle,
|
|
2201
2291
|
onLoad: text => {
|