react-msaview 5.9.0 → 6.0.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/components/ConservationTrack.js +21 -16
- package/dist/components/ConservationTrack.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/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +18 -24
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/ResizeHandles.js +24 -40
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/ScrollbarThumb.d.ts +14 -0
- package/dist/components/ScrollbarThumb.js +28 -0
- package/dist/components/ScrollbarThumb.js.map +1 -0
- package/dist/components/SequenceTextArea.js +3 -10
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/TextTrack.js +26 -21
- package/dist/components/TextTrack.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/{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 +39 -72
- 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 +11 -22
- 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 +2 -2
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +19 -16
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +14 -8
- 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/renderBoxFeatureCanvasBlock.js +9 -6
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +14 -20
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +32 -33
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/overlayColors.d.ts +2 -0
- package/dist/components/overlayColors.js +5 -0
- package/dist/components/overlayColors.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +29 -24
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +19 -12
- 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.d.ts +0 -38
- package/dist/components/tree/renderTreeCanvas.js +17 -17
- package/dist/components/tree/renderTreeCanvas.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.d.ts +50 -100
- package/dist/model.js +209 -219
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +12 -19
- package/dist/neighborJoining.js.map +1 -1
- package/dist/parseAsn1.js +32 -43
- package/dist/parseAsn1.js.map +1 -1
- package/dist/renderToSvg.js +5 -2
- 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/seqPosToGlobalCol.d.ts +6 -19
- package/dist/seqPosToGlobalCol.js +6 -33
- package/dist/seqPosToGlobalCol.js.map +1 -1
- package/dist/types.d.ts +4 -11
- package/dist/useCanvasAutorun.d.ts +23 -1
- package/dist/useCanvasAutorun.js +20 -8
- package/dist/useCanvasAutorun.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -5
- package/src/clustalX.ts +22 -22
- package/src/colSpace.test.ts +27 -0
- package/src/components/ConservationTrack.tsx +7 -5
- package/src/components/CopyButton.tsx +44 -0
- package/src/components/Loading.tsx +10 -1
- package/src/components/MSAView.tsx +25 -32
- package/src/components/ResizeHandles.tsx +60 -69
- package/src/components/ScrollbarThumb.tsx +48 -0
- package/src/components/SequenceTextArea.tsx +3 -16
- package/src/components/TextTrack.tsx +7 -12
- package/src/components/VerticalScrollbar.tsx +19 -33
- package/src/components/dialogs/FeatureDialog.tsx +3 -4
- 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 +40 -72
- package/src/components/import/ImportForm.tsx +1 -1
- package/src/components/import/ImportFormExamples.tsx +82 -133
- package/src/components/minimap/Minimap.tsx +23 -31
- 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 +3 -10
- package/src/components/msa/MSACanvasBlock.tsx +16 -21
- package/src/components/msa/MSAMouseoverCanvas.tsx +11 -9
- package/src/components/msa/MSAPanel.tsx +2 -2
- package/src/components/msa/domainBandCursor.ts +41 -0
- package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +51 -0
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +13 -8
- package/src/components/msa/renderMSABlock.ts +19 -23
- package/src/components/msa/renderMSAMouseover.ts +38 -35
- package/src/components/overlayColors.ts +6 -0
- package/src/components/tree/TreeCanvas.tsx +6 -4
- package/src/components/tree/TreeCanvasBlock.tsx +11 -12
- package/src/components/tree/TreeRuler.tsx +7 -2
- package/src/components/tree/renderTreeCanvas.test.ts +15 -7
- package/src/components/tree/renderTreeCanvas.ts +20 -17
- 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/index.ts +4 -1
- package/src/largeTree.test.ts +23 -2
- package/src/launchInterProScan.ts +3 -30
- package/src/loadErrorDisplay.test.tsx +102 -0
- package/src/measureTextCanvas.ts +22 -6
- package/src/model.ts +233 -244
- package/src/modelFilehandleLoaders.test.ts +137 -0
- package/src/neighborJoining.test.ts +12 -0
- package/src/neighborJoining.ts +12 -20
- package/src/parseAsn1.ts +32 -40
- package/src/{renderDomainLegendSvg.test.tsx → renderDomainsSvg.test.tsx} +44 -20
- package/src/renderMinimapSvg.test.tsx +109 -0
- package/src/renderToSvg.tsx +5 -2
- package/src/rowCoordinateCalculations.ts +48 -44
- package/src/seqPosToGlobalCol.test.ts +39 -51
- package/src/seqPosToGlobalCol.ts +6 -41
- package/src/types.ts +10 -15
- package/src/useCanvasAutorun.test.tsx +98 -0
- package/src/useCanvasAutorun.ts +30 -11
- package/src/version.ts +1 -1
- package/src/viewChrome.test.tsx +134 -0
- 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/src/components/dialogs/DomainDialog.tsx +0 -45
- package/src/components/header/SettingsMenu.tsx +0 -57
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,
|
|
@@ -47,7 +48,7 @@ import {
|
|
|
47
48
|
calcDepthToLeaf,
|
|
48
49
|
clusterLayout,
|
|
49
50
|
collapse,
|
|
50
|
-
|
|
51
|
+
collapsedSubtreeMaxLength,
|
|
51
52
|
find,
|
|
52
53
|
findMaxBranchLen,
|
|
53
54
|
forEachDescendant,
|
|
@@ -71,6 +72,7 @@ import {
|
|
|
71
72
|
globalColToVisibleCol,
|
|
72
73
|
visibleColToGlobalCol,
|
|
73
74
|
visibleColToSeqPosForRow,
|
|
75
|
+
visibleColsBefore,
|
|
74
76
|
} from './rowCoordinateCalculations.ts'
|
|
75
77
|
import { buildSeqPosIndex } from './seqPosToGlobalCol.ts'
|
|
76
78
|
import { stripDefault } from './stripDefault.ts'
|
|
@@ -78,8 +80,8 @@ import { computeRowInsertions, len, skipBlanks, transform } from './util.ts'
|
|
|
78
80
|
import { saveAs } from './vendor/fileSaver.ts'
|
|
79
81
|
|
|
80
82
|
import type { HierarchyNode } from './hierarchy.ts'
|
|
81
|
-
import type { InterProScanResults } from './launchInterProScan.ts'
|
|
82
83
|
import type {
|
|
84
|
+
Annotation,
|
|
83
85
|
BasicTrack,
|
|
84
86
|
DomainBand,
|
|
85
87
|
NodeWithIds,
|
|
@@ -88,6 +90,7 @@ import type {
|
|
|
88
90
|
import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
|
|
89
91
|
import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
90
92
|
import type { Theme } from '@mui/material'
|
|
93
|
+
import type { InterProScanResults } from 'msa-parsers'
|
|
91
94
|
|
|
92
95
|
function parseTreeText(text: string) {
|
|
93
96
|
if (text.startsWith('BioTreeContainer')) {
|
|
@@ -100,7 +103,7 @@ function parseTreeText(text: string) {
|
|
|
100
103
|
* #stateModel MsaView
|
|
101
104
|
*
|
|
102
105
|
* The main MSAView state model. Holds the loaded alignment, tree, and optional
|
|
103
|
-
*
|
|
106
|
+
* overlay annotations, plus all display state (color scheme, zoom,
|
|
104
107
|
* scroll, collapsed clades). It composes in members from `DialogQueueSessionMixin`,
|
|
105
108
|
* `Tree`, and `MSAModel` (see Inherited members below). Data is loaded reactively
|
|
106
109
|
* from the `msaFilehandle` / `treeFilehandle` / `gffFilehandle` properties, or set
|
|
@@ -223,7 +226,7 @@ function stateModelFactory() {
|
|
|
223
226
|
|
|
224
227
|
/**
|
|
225
228
|
* #property
|
|
226
|
-
* filehandle object for
|
|
229
|
+
* filehandle object for a GFF file of overlay annotations
|
|
227
230
|
*/
|
|
228
231
|
gffFilehandle: types.maybe(FileLocation),
|
|
229
232
|
|
|
@@ -385,10 +388,12 @@ function stateModelFactory() {
|
|
|
385
388
|
|
|
386
389
|
/**
|
|
387
390
|
* #volatile
|
|
391
|
+
* overlay annotations drawn on the alignment, whatever their source.
|
|
392
|
+
* Every source -- InterProScan, GFF, a user upload -- converts to this
|
|
393
|
+
* flat list before it reaches the model, so nothing downstream of here
|
|
394
|
+
* knows which one it came from
|
|
388
395
|
*/
|
|
389
|
-
|
|
390
|
-
| undefined
|
|
391
|
-
| Record<string, InterProScanResults>,
|
|
396
|
+
annotations: [] as Annotation[],
|
|
392
397
|
}))
|
|
393
398
|
.actions(self => ({
|
|
394
399
|
/**
|
|
@@ -480,7 +485,7 @@ function stateModelFactory() {
|
|
|
480
485
|
},
|
|
481
486
|
/**
|
|
482
487
|
* #action
|
|
483
|
-
* toggle the
|
|
488
|
+
* toggle the annotation overlay on the alignment
|
|
484
489
|
*/
|
|
485
490
|
setShowDomains(arg: boolean) {
|
|
486
491
|
self.showDomains = arg
|
|
@@ -523,14 +528,6 @@ function stateModelFactory() {
|
|
|
523
528
|
self.colWidth = n
|
|
524
529
|
},
|
|
525
530
|
|
|
526
|
-
/**
|
|
527
|
-
* #action
|
|
528
|
-
* set scroll Y-offset (px)
|
|
529
|
-
*/
|
|
530
|
-
setScrollY(n: number) {
|
|
531
|
-
self.scrollY = n
|
|
532
|
-
},
|
|
533
|
-
|
|
534
531
|
/**
|
|
535
532
|
* #action
|
|
536
533
|
*
|
|
@@ -638,7 +635,7 @@ function stateModelFactory() {
|
|
|
638
635
|
* #getter
|
|
639
636
|
*/
|
|
640
637
|
get actuallyShowDomains() {
|
|
641
|
-
return self.showDomains &&
|
|
638
|
+
return self.showDomains && self.annotations.length > 0
|
|
642
639
|
},
|
|
643
640
|
get viewInitialized() {
|
|
644
641
|
return self.volatileWidth !== undefined
|
|
@@ -693,7 +690,7 @@ function stateModelFactory() {
|
|
|
693
690
|
return !!this.tree.noTree
|
|
694
691
|
},
|
|
695
692
|
get noDomains() {
|
|
696
|
-
return
|
|
693
|
+
return self.annotations.length === 0
|
|
697
694
|
},
|
|
698
695
|
menuItems() {
|
|
699
696
|
return []
|
|
@@ -883,12 +880,6 @@ function stateModelFactory() {
|
|
|
883
880
|
}
|
|
884
881
|
return blanks
|
|
885
882
|
},
|
|
886
|
-
/**
|
|
887
|
-
* #getter
|
|
888
|
-
*/
|
|
889
|
-
get blanksSet() {
|
|
890
|
-
return new Set(this.blanks)
|
|
891
|
-
},
|
|
892
883
|
/**
|
|
893
884
|
* #getter
|
|
894
885
|
* Returns a map of row name to array of insertions with display position and letters
|
|
@@ -981,14 +972,6 @@ function stateModelFactory() {
|
|
|
981
972
|
return columnCountsFromRows(this.columns2d)
|
|
982
973
|
},
|
|
983
974
|
|
|
984
|
-
/**
|
|
985
|
-
* #getter
|
|
986
|
-
* per-column residue totals (gaps included)
|
|
987
|
-
*/
|
|
988
|
-
get colStatsSums() {
|
|
989
|
-
return this.colStats.totals
|
|
990
|
-
},
|
|
991
|
-
|
|
992
975
|
/**
|
|
993
976
|
* #getter
|
|
994
977
|
* Detects sequence type based on letters present in the alignment.
|
|
@@ -1096,14 +1079,13 @@ function stateModelFactory() {
|
|
|
1096
1079
|
const max = maxLength(r)
|
|
1097
1080
|
const k = max ? self.treeWidth / max : 0
|
|
1098
1081
|
setBrLength(r, 0, k)
|
|
1099
|
-
// for each collapsed clade, record the pixel x-
|
|
1100
|
-
//
|
|
1101
|
-
//
|
|
1082
|
+
// for each collapsed clade, record the pixel x-position of its farthest
|
|
1083
|
+
// tip so the renderer can draw a triangle spanning the branch-length
|
|
1084
|
+
// extent of the hidden subtree
|
|
1102
1085
|
forEachDescendant(r, node => {
|
|
1103
1086
|
if (node._children) {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
node.collapsedTipXFar = (node.len ?? 0) + mx * k
|
|
1087
|
+
node.collapsedTipXFar =
|
|
1088
|
+
(node.len ?? 0) + collapsedSubtreeMaxLength(node) * k
|
|
1107
1089
|
}
|
|
1108
1090
|
})
|
|
1109
1091
|
return r as HierarchyNode<NodeWithIdsAndLength>
|
|
@@ -1265,17 +1247,24 @@ function stateModelFactory() {
|
|
|
1265
1247
|
* set hovered tree node and its descendants
|
|
1266
1248
|
*/
|
|
1267
1249
|
setHoveredTreeNode(nodeId?: string) {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
} else {
|
|
1250
|
+
// the tree's mousemove handler calls this on every event, and both the
|
|
1251
|
+
// lookup and the write are expensive: `find` walks the whole hierarchy,
|
|
1252
|
+
// and a fresh object here invalidates hoveredRowIndices and redraws the
|
|
1253
|
+
// tree and MSA overlays. Re-hovering the same node is the common case
|
|
1254
|
+
if (nodeId === self.hoveredTreeNode?.nodeId) {
|
|
1255
|
+
return
|
|
1256
|
+
}
|
|
1257
|
+
if (!nodeId) {
|
|
1277
1258
|
self.hoveredTreeNode = undefined
|
|
1259
|
+
return
|
|
1278
1260
|
}
|
|
1261
|
+
const node = find(self.hierarchy, n => n.data.id === nodeId)
|
|
1262
|
+
self.hoveredTreeNode = node
|
|
1263
|
+
? {
|
|
1264
|
+
nodeId,
|
|
1265
|
+
descendantNames: leaves(node).map(leaf => leaf.data.name),
|
|
1266
|
+
}
|
|
1267
|
+
: undefined
|
|
1279
1268
|
},
|
|
1280
1269
|
|
|
1281
1270
|
/**
|
|
@@ -1394,23 +1383,40 @@ function stateModelFactory() {
|
|
|
1394
1383
|
* #action
|
|
1395
1384
|
*/
|
|
1396
1385
|
doScrollY(deltaY: number) {
|
|
1397
|
-
|
|
1386
|
+
this.setScrollY(self.scrollY + deltaY)
|
|
1398
1387
|
},
|
|
1399
1388
|
|
|
1400
1389
|
/**
|
|
1401
1390
|
* #action
|
|
1402
|
-
*
|
|
1403
|
-
|
|
1404
|
-
|
|
1391
|
+
* set scroll Y-offset (px), clamped to keep the alignment in view
|
|
1392
|
+
*/
|
|
1393
|
+
setScrollY(n: number) {
|
|
1394
|
+
self.scrollY = clamp(n, self.maxScrollY, 0)
|
|
1395
|
+
},
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* #action
|
|
1399
|
+
* Set the overlay annotations and reveal the overlay in a single step (an
|
|
1400
|
+
* empty list clears both). Every source funnels through here after its
|
|
1401
|
+
* own adapter has flattened it: InterProScan, GFF, user uploads, NCBI CDD.
|
|
1402
|
+
*/
|
|
1403
|
+
setAnnotations(annotations: Annotation[]) {
|
|
1404
|
+
self.annotations = annotations
|
|
1405
|
+
self.setShowDomains(annotations.length > 0)
|
|
1406
|
+
},
|
|
1407
|
+
|
|
1408
|
+
/**
|
|
1409
|
+
* #action
|
|
1410
|
+
* set the overlay from raw InterProScan results keyed by row name. Kept
|
|
1411
|
+
* for downstream plugins that hold the EBI wire format; new code should
|
|
1412
|
+
* adapt to Annotation[] and call setAnnotations.
|
|
1405
1413
|
*/
|
|
1406
1414
|
setDomains(data?: Record<string, InterProScanResults>) {
|
|
1407
|
-
|
|
1408
|
-
self.setShowDomains(!!data)
|
|
1415
|
+
this.setAnnotations(data ? interProScanToAnnotations(data) : [])
|
|
1409
1416
|
},
|
|
1410
1417
|
|
|
1411
1418
|
applyGFFText(gffText: string) {
|
|
1412
|
-
|
|
1413
|
-
this.setDomains(gffToInterProResults(gffRecords))
|
|
1419
|
+
this.setAnnotations(gffToAnnotations(parseGFF(gffText)))
|
|
1414
1420
|
},
|
|
1415
1421
|
|
|
1416
1422
|
/**
|
|
@@ -1449,10 +1455,12 @@ function stateModelFactory() {
|
|
|
1449
1455
|
* #getter
|
|
1450
1456
|
*/
|
|
1451
1457
|
get labelWidthMap() {
|
|
1452
|
-
const {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1458
|
+
const { showTreeText, leaves, treeMetadata, fontSize } = self
|
|
1459
|
+
// gated on the same condition the renderer draws labels under, so the
|
|
1460
|
+
// gutter labelsWidth reserves and the labels actually drawn cannot
|
|
1461
|
+
// disagree -- and so turning labels off hands their space to the tree
|
|
1462
|
+
return showTreeText
|
|
1463
|
+
? new Map(
|
|
1456
1464
|
leaves.map(node => {
|
|
1457
1465
|
const { name } = node.data
|
|
1458
1466
|
// `||`, matching renderTreeLabels: an empty genome falls back
|
|
@@ -1462,6 +1470,7 @@ function stateModelFactory() {
|
|
|
1462
1470
|
return [name, measureTextCanvas(displayName, fontSize)] as const
|
|
1463
1471
|
}),
|
|
1464
1472
|
)
|
|
1473
|
+
: new Map<string, number>()
|
|
1465
1474
|
},
|
|
1466
1475
|
|
|
1467
1476
|
get labelsWidth() {
|
|
@@ -1635,8 +1644,8 @@ function stateModelFactory() {
|
|
|
1635
1644
|
return 0
|
|
1636
1645
|
}
|
|
1637
1646
|
const col = self.seqPosGlobalColIndex.get(rowName)?.[seqPos]
|
|
1638
|
-
// past the end of the ungapped sequence: 0 for the degenerate
|
|
1639
|
-
//
|
|
1647
|
+
// past the end of the ungapped sequence: 0 for the degenerate all-gap
|
|
1648
|
+
// row, otherwise one past the last column
|
|
1640
1649
|
return col ?? (seqPos === 0 ? 0 : seq.length)
|
|
1641
1650
|
},
|
|
1642
1651
|
|
|
@@ -1674,41 +1683,29 @@ function stateModelFactory() {
|
|
|
1674
1683
|
get totalTrackAreaHeight() {
|
|
1675
1684
|
return sum(self.turnedOnTracks.map(r => r.model.height))
|
|
1676
1685
|
},
|
|
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
|
-
: []
|
|
1686
|
+
/**
|
|
1687
|
+
* one representative annotation per accession, which is what the legend,
|
|
1688
|
+
* the filter dialog and the palettes key off
|
|
1689
|
+
*/
|
|
1690
|
+
get annotationTypes() {
|
|
1691
|
+
// first occurrence wins. The representative supplies only the name,
|
|
1692
|
+
// description and -- for ordinal segments -- the start that orders
|
|
1693
|
+
// them, and those agree across an accession's instances
|
|
1694
|
+
const types = new Map<string, Annotation>()
|
|
1695
|
+
for (const annot of self.annotations) {
|
|
1696
|
+
if (!types.has(annot.accession)) {
|
|
1697
|
+
types.set(annot.accession, annot)
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
return types
|
|
1704
1701
|
},
|
|
1705
|
-
get
|
|
1706
|
-
return
|
|
1702
|
+
get filteredAnnotations() {
|
|
1703
|
+
return self.annotations.filter(r =>
|
|
1707
1704
|
self.featureFilters.get(r.accession),
|
|
1708
1705
|
)
|
|
1709
1706
|
},
|
|
1710
|
-
get
|
|
1711
|
-
return groupBy(this.
|
|
1707
|
+
get annotationsByRow() {
|
|
1708
|
+
return groupBy(this.filteredAnnotations, r => r.id)
|
|
1712
1709
|
},
|
|
1713
1710
|
}))
|
|
1714
1711
|
.views(self => ({
|
|
@@ -1726,6 +1723,15 @@ function stateModelFactory() {
|
|
|
1726
1723
|
get verticalScrollbarWidth() {
|
|
1727
1724
|
return self.showVerticalScrollbar ? 20 : 0
|
|
1728
1725
|
},
|
|
1726
|
+
/**
|
|
1727
|
+
* #getter
|
|
1728
|
+
* width of the alignment canvas itself: the msa area less the vertical
|
|
1729
|
+
* scrollbar sitting in it. Not usable from showHorizontalScrollbar, which
|
|
1730
|
+
* feeds msaAreaHeight -> showVerticalScrollbar and would close a cycle
|
|
1731
|
+
*/
|
|
1732
|
+
get msaCanvasWidth() {
|
|
1733
|
+
return self.msaAreaWidth - this.verticalScrollbarWidth
|
|
1734
|
+
},
|
|
1729
1735
|
/**
|
|
1730
1736
|
* #getter
|
|
1731
1737
|
* ordinal segment types (exons etc.), ordered by sequence position so
|
|
@@ -1733,7 +1739,7 @@ function stateModelFactory() {
|
|
|
1733
1739
|
* labeled by number rather than each getting a distinct hue + legend row
|
|
1734
1740
|
*/
|
|
1735
1741
|
get segmentDomainTypes() {
|
|
1736
|
-
return [...self.
|
|
1742
|
+
return [...self.annotationTypes.values()]
|
|
1737
1743
|
.filter(d => segmentFeatureTypes.has(d.featureType ?? ''))
|
|
1738
1744
|
.toSorted((a, b) => a.start - b.start)
|
|
1739
1745
|
},
|
|
@@ -1743,7 +1749,7 @@ function stateModelFactory() {
|
|
|
1743
1749
|
* their own color and a legend entry
|
|
1744
1750
|
*/
|
|
1745
1751
|
get categoricalDomainTypes() {
|
|
1746
|
-
return [...self.
|
|
1752
|
+
return [...self.annotationTypes.values()].filter(
|
|
1747
1753
|
d => !segmentFeatureTypes.has(d.featureType ?? ''),
|
|
1748
1754
|
)
|
|
1749
1755
|
},
|
|
@@ -1795,34 +1801,47 @@ function stateModelFactory() {
|
|
|
1795
1801
|
|
|
1796
1802
|
/**
|
|
1797
1803
|
* #getter
|
|
1798
|
-
* every filtered-on
|
|
1799
|
-
*
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
1804
|
+
* every filtered-on annotation resolved to the visible column span it is
|
|
1805
|
+
* drawn across, keyed by row name. Each row is ordered longest-first so a
|
|
1806
|
+
* short domain nested inside a long one draws on top of it rather than
|
|
1807
|
+
* under it. Resolving these once here rather than inside each canvas
|
|
1808
|
+
* block removes a per-feature, per-block sequence position conversion
|
|
1809
|
+
* from every redraw, and gives the letter renderer the band colors it
|
|
1810
|
+
* needs to keep residues readable on top of the boxes.
|
|
1804
1811
|
*/
|
|
1805
1812
|
get domainBands() {
|
|
1806
1813
|
const bands = new Map<string, DomainBand[]>()
|
|
1807
1814
|
for (const [name, annotations] of Object.entries(
|
|
1808
|
-
self.
|
|
1815
|
+
self.annotationsByRow,
|
|
1809
1816
|
)) {
|
|
1817
|
+
const { blanks } = self
|
|
1810
1818
|
const rowBands = annotations
|
|
1811
|
-
.
|
|
1812
|
-
|
|
1813
|
-
//
|
|
1814
|
-
//
|
|
1815
|
-
//
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
+
.toSorted((a, b) => len(b) - len(a))
|
|
1820
|
+
.map(annotation => {
|
|
1821
|
+
// annotation positions are 1-based and inclusive. Both ends count
|
|
1822
|
+
// the visible columns in front of a global column, so endCol is
|
|
1823
|
+
// the exclusive column after the last residue's own column --
|
|
1824
|
+
// the band stops there rather than stretching across a following
|
|
1825
|
+
// gap run -- and a residue whose column is itself hidden
|
|
1826
|
+
// collapses onto the neighbouring boundary instead of dropping
|
|
1827
|
+
// the band. A band whose every column is hidden spans nothing
|
|
1828
|
+
// and is left out.
|
|
1829
|
+
const startCol = visibleColsBefore(
|
|
1830
|
+
blanks,
|
|
1831
|
+
self.seqPosToGlobalCol(name, annotation.start - 1),
|
|
1832
|
+
)
|
|
1833
|
+
const endCol = visibleColsBefore(
|
|
1834
|
+
blanks,
|
|
1835
|
+
self.seqPosToGlobalCol(name, annotation.end - 1) + 1,
|
|
1819
1836
|
)
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
? { annotation, startCol, endCol: lastCol + 1, stackIndex }
|
|
1837
|
+
return endCol > startCol
|
|
1838
|
+
? { annotation, startCol, endCol }
|
|
1823
1839
|
: undefined
|
|
1824
1840
|
})
|
|
1825
1841
|
.filter(notEmpty)
|
|
1842
|
+
// numbered after the drop, so a band that resolved to nothing does
|
|
1843
|
+
// not leave an empty sub-row behind it
|
|
1844
|
+
.map((band, stackIndex) => ({ ...band, stackIndex }))
|
|
1826
1845
|
if (rowBands.length > 0) {
|
|
1827
1846
|
bands.set(name, rowBands)
|
|
1828
1847
|
}
|
|
@@ -1985,7 +2004,7 @@ function stateModelFactory() {
|
|
|
1985
2004
|
self.setTreeFilehandle(undefined)
|
|
1986
2005
|
self.setMSAFilehandle(undefined)
|
|
1987
2006
|
self.setGFFFilehandle(undefined)
|
|
1988
|
-
self.
|
|
2007
|
+
self.setAnnotations([])
|
|
1989
2008
|
},
|
|
1990
2009
|
/**
|
|
1991
2010
|
* #action
|
|
@@ -2035,8 +2054,11 @@ function stateModelFactory() {
|
|
|
2035
2054
|
*/
|
|
2036
2055
|
fitHorizontally() {
|
|
2037
2056
|
if (self.numColumns > 0) {
|
|
2057
|
+
// fitting to msaAreaWidth instead left the last ~20px of columns off
|
|
2058
|
+
// the right edge -- and short of the width that shows a minimap, so
|
|
2059
|
+
// nothing on screen said they were there
|
|
2038
2060
|
self.colWidth = clamp(
|
|
2039
|
-
self.
|
|
2061
|
+
self.msaCanvasWidth / self.numColumns,
|
|
2040
2062
|
minColWidth,
|
|
2041
2063
|
maxCellSize,
|
|
2042
2064
|
)
|
|
@@ -2055,7 +2077,7 @@ function stateModelFactory() {
|
|
|
2055
2077
|
addDisposer(
|
|
2056
2078
|
self,
|
|
2057
2079
|
autorun(() => {
|
|
2058
|
-
for (const key of self.
|
|
2080
|
+
for (const key of self.annotationTypes.keys()) {
|
|
2059
2081
|
this.initFilter(key)
|
|
2060
2082
|
}
|
|
2061
2083
|
}),
|
|
@@ -2084,79 +2106,100 @@ function stateModelFactory() {
|
|
|
2084
2106
|
})
|
|
2085
2107
|
}
|
|
2086
2108
|
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2109
|
+
/**
|
|
2110
|
+
* Fetch a filehandle whenever it changes, and hand the text to
|
|
2111
|
+
* `onLoad`.
|
|
2112
|
+
*
|
|
2113
|
+
* Every loader carries a generation guard: the filehandle can change
|
|
2114
|
+
* mid-fetch (a second file picked while the first is still in flight),
|
|
2115
|
+
* and the slower earlier request must not clobber the data, status, or
|
|
2116
|
+
* loading flag belonging to the newer one.
|
|
2117
|
+
*
|
|
2118
|
+
* `clearFilehandle` serves two purposes for the loaders that pass it.
|
|
2119
|
+
* A local file has no URL to refetch from, so the handle is dropped
|
|
2120
|
+
* once its bytes are in the model; and a fetch the user cancels drops
|
|
2121
|
+
* it too, returning the view to the import form rather than leaving a
|
|
2122
|
+
* stuck spinner.
|
|
2123
|
+
*/
|
|
2124
|
+
const loadOnFilehandleChange = ({
|
|
2125
|
+
getFilehandle,
|
|
2126
|
+
onLoad,
|
|
2127
|
+
setLoading,
|
|
2128
|
+
clearFilehandle,
|
|
2129
|
+
}: {
|
|
2130
|
+
getFilehandle: () => FileLocationType | undefined
|
|
2131
|
+
onLoad: (text: string) => void
|
|
2132
|
+
setLoading?: (arg: boolean) => void
|
|
2133
|
+
clearFilehandle?: () => void
|
|
2134
|
+
}) => {
|
|
2135
|
+
let generation = 0
|
|
2136
|
+
addDisposer(
|
|
2137
|
+
self,
|
|
2138
|
+
autorun(async () => {
|
|
2139
|
+
const filehandle = getFilehandle()
|
|
2140
|
+
if (!filehandle) {
|
|
2141
|
+
return
|
|
2142
|
+
}
|
|
2143
|
+
const current = ++generation
|
|
2144
|
+
const isCurrent = () => current === generation
|
|
2097
2145
|
try {
|
|
2098
|
-
|
|
2146
|
+
setLoading?.(true)
|
|
2147
|
+
self.setError(undefined)
|
|
2099
2148
|
const text = await fetchTextWithProgress(
|
|
2100
|
-
openLocation(
|
|
2149
|
+
openLocation(filehandle),
|
|
2101
2150
|
status => {
|
|
2102
|
-
if (
|
|
2151
|
+
if (isCurrent()) {
|
|
2103
2152
|
self.setStatus(status)
|
|
2104
2153
|
}
|
|
2105
2154
|
},
|
|
2106
2155
|
)
|
|
2107
|
-
if (
|
|
2156
|
+
if (isCurrent()) {
|
|
2108
2157
|
transaction(() => {
|
|
2109
|
-
|
|
2110
|
-
if (
|
|
2111
|
-
|
|
2112
|
-
self.setTreeFilehandle(undefined)
|
|
2158
|
+
onLoad(text)
|
|
2159
|
+
if (filehandle.locationType === 'BlobLocation') {
|
|
2160
|
+
clearFilehandle?.()
|
|
2113
2161
|
}
|
|
2114
2162
|
})
|
|
2115
2163
|
}
|
|
2116
2164
|
} catch (e) {
|
|
2117
|
-
if (
|
|
2165
|
+
if (isCurrent()) {
|
|
2118
2166
|
if (isAbortError(e)) {
|
|
2119
|
-
|
|
2120
|
-
// returns to the import form instead of a stuck spinner
|
|
2121
|
-
self.setTreeFilehandle(undefined)
|
|
2167
|
+
clearFilehandle?.()
|
|
2122
2168
|
} else {
|
|
2123
2169
|
console.error(e)
|
|
2124
2170
|
self.setError(e)
|
|
2125
2171
|
}
|
|
2126
2172
|
}
|
|
2127
2173
|
} finally {
|
|
2128
|
-
if (
|
|
2129
|
-
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
}),
|
|
2134
|
-
)
|
|
2135
|
-
// autorun opens treeMetadataFilehandle
|
|
2136
|
-
addDisposer(
|
|
2137
|
-
self,
|
|
2138
|
-
autorun(async () => {
|
|
2139
|
-
const { treeMetadataFilehandle } = self
|
|
2140
|
-
if (treeMetadataFilehandle) {
|
|
2141
|
-
try {
|
|
2142
|
-
self.setTreeMetadata(
|
|
2143
|
-
await fetchTextWithProgress(
|
|
2144
|
-
openLocation(treeMetadataFilehandle),
|
|
2145
|
-
status => {
|
|
2146
|
-
self.setStatus(status)
|
|
2147
|
-
},
|
|
2148
|
-
),
|
|
2149
|
-
)
|
|
2150
|
-
} catch (e) {
|
|
2151
|
-
// ignore user cancel (isAbortError); treeMetadata is optional
|
|
2152
|
-
if (!isAbortError(e)) {
|
|
2153
|
-
console.error(e)
|
|
2154
|
-
self.setError(e)
|
|
2174
|
+
if (isCurrent()) {
|
|
2175
|
+
setLoading?.(false)
|
|
2155
2176
|
}
|
|
2156
2177
|
}
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2178
|
+
}),
|
|
2179
|
+
)
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
loadOnFilehandleChange({
|
|
2183
|
+
getFilehandle: () => self.treeFilehandle,
|
|
2184
|
+
onLoad: text => {
|
|
2185
|
+
self.setTree(text)
|
|
2186
|
+
},
|
|
2187
|
+
setLoading: arg => {
|
|
2188
|
+
self.setLoadingTree(arg)
|
|
2189
|
+
},
|
|
2190
|
+
clearFilehandle: () => {
|
|
2191
|
+
self.setTreeFilehandle(undefined)
|
|
2192
|
+
},
|
|
2193
|
+
})
|
|
2194
|
+
|
|
2195
|
+
// treeMetadata is decorative and has no import-form step of its own, so
|
|
2196
|
+
// it keeps no loading flag and nothing to return to on cancel
|
|
2197
|
+
loadOnFilehandleChange({
|
|
2198
|
+
getFilehandle: () => self.treeMetadataFilehandle,
|
|
2199
|
+
onLoad: text => {
|
|
2200
|
+
self.setTreeMetadata(text)
|
|
2201
|
+
},
|
|
2202
|
+
})
|
|
2160
2203
|
|
|
2161
2204
|
// autorun parses inline gff text from data.gff
|
|
2162
2205
|
addDisposer(
|
|
@@ -2174,83 +2217,29 @@ function stateModelFactory() {
|
|
|
2174
2217
|
}),
|
|
2175
2218
|
)
|
|
2176
2219
|
|
|
2177
|
-
//
|
|
2178
|
-
|
|
2179
|
-
self,
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
self.setStatus(status)
|
|
2188
|
-
},
|
|
2189
|
-
)
|
|
2190
|
-
self.applyGFFText(gffText)
|
|
2191
|
-
if (gffFilehandle.locationType === 'BlobLocation') {
|
|
2192
|
-
self.setGFFFilehandle(undefined)
|
|
2193
|
-
}
|
|
2194
|
-
} catch (e) {
|
|
2195
|
-
// on user cancel (isAbortError) drop the filehandle
|
|
2196
|
-
if (isAbortError(e)) {
|
|
2197
|
-
self.setGFFFilehandle(undefined)
|
|
2198
|
-
} else {
|
|
2199
|
-
console.error(e)
|
|
2200
|
-
self.setError(e)
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
}),
|
|
2205
|
-
)
|
|
2220
|
+
// gffFilehandle carries overlay annotations
|
|
2221
|
+
loadOnFilehandleChange({
|
|
2222
|
+
getFilehandle: () => self.gffFilehandle,
|
|
2223
|
+
onLoad: text => {
|
|
2224
|
+
self.applyGFFText(text)
|
|
2225
|
+
},
|
|
2226
|
+
clearFilehandle: () => {
|
|
2227
|
+
self.setGFFFilehandle(undefined)
|
|
2228
|
+
},
|
|
2229
|
+
})
|
|
2206
2230
|
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
openLocation(msaFilehandle),
|
|
2220
|
-
status => {
|
|
2221
|
-
if (generation === msaGeneration) {
|
|
2222
|
-
self.setStatus(status)
|
|
2223
|
-
}
|
|
2224
|
-
},
|
|
2225
|
-
)
|
|
2226
|
-
if (generation === msaGeneration) {
|
|
2227
|
-
transaction(() => {
|
|
2228
|
-
self.setMSA(txt)
|
|
2229
|
-
if (msaFilehandle.locationType === 'BlobLocation') {
|
|
2230
|
-
// clear filehandle after loading if from a local file
|
|
2231
|
-
self.setMSAFilehandle(undefined)
|
|
2232
|
-
}
|
|
2233
|
-
})
|
|
2234
|
-
}
|
|
2235
|
-
} catch (e) {
|
|
2236
|
-
if (generation === msaGeneration) {
|
|
2237
|
-
if (isAbortError(e)) {
|
|
2238
|
-
// cancelled by the user: drop the filehandle so the view
|
|
2239
|
-
// returns to the import form instead of a stuck spinner
|
|
2240
|
-
self.setMSAFilehandle(undefined)
|
|
2241
|
-
} else {
|
|
2242
|
-
console.error(e)
|
|
2243
|
-
self.setError(e)
|
|
2244
|
-
}
|
|
2245
|
-
}
|
|
2246
|
-
} finally {
|
|
2247
|
-
if (generation === msaGeneration) {
|
|
2248
|
-
self.setLoadingMSA(false)
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
}),
|
|
2253
|
-
)
|
|
2231
|
+
loadOnFilehandleChange({
|
|
2232
|
+
getFilehandle: () => self.msaFilehandle,
|
|
2233
|
+
onLoad: text => {
|
|
2234
|
+
self.setMSA(text)
|
|
2235
|
+
},
|
|
2236
|
+
setLoading: arg => {
|
|
2237
|
+
self.setLoadingMSA(arg)
|
|
2238
|
+
},
|
|
2239
|
+
clearFilehandle: () => {
|
|
2240
|
+
self.setMSAFilehandle(undefined)
|
|
2241
|
+
},
|
|
2242
|
+
})
|
|
2254
2243
|
|
|
2255
2244
|
// Keep the parse chain warm: reading self.columns transitively holds
|
|
2256
2245
|
// self.MSA (parseMSA) computed alive, so it is parsed once per data
|