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/dist/model.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ElementId, FileLocation } from '@jbrowse/core/util/types/mst';
|
|
|
4
4
|
import { addDisposer, cast, types } from '@jbrowse/mobx-state-tree';
|
|
5
5
|
import { colord } from 'colord';
|
|
6
6
|
import { autorun, transaction } from 'mobx';
|
|
7
|
-
import { generateNodeIds,
|
|
7
|
+
import { generateNodeIds, gffToAnnotations, interProScanToAnnotations, parseEmfTree, parseGFF, parseMSA, parseNewick, } from 'msa-parsers';
|
|
8
8
|
import { calculateBlocks } from './calculateBlocks.js';
|
|
9
9
|
import { clustalXColumnColors } from './clustalX.js';
|
|
10
10
|
import colorSchemes from './colorSchemes.js';
|
|
@@ -15,7 +15,7 @@ import { defaultAllowedGappyness, defaultColWidth, defaultColorSchemeName, defau
|
|
|
15
15
|
import { createPaletteMap } from './createPaletteMap.js';
|
|
16
16
|
import { fetchTextWithProgress, isAbortError } from './fetchUtils.js';
|
|
17
17
|
import { flatToTree } from './flatToTree.js';
|
|
18
|
-
import { calcDepthToLeaf, clusterLayout, collapse,
|
|
18
|
+
import { calcDepthToLeaf, clusterLayout, collapse, collapsedSubtreeMaxLength, find, findMaxBranchLen, forEachDescendant, hierarchy, leaves, links, maxLength, setBrLength, sort, sum as hierarchySum, } from './hierarchy.js';
|
|
19
19
|
import { measureTextCanvas } from './measureTextCanvas.js';
|
|
20
20
|
import { DataModelF } from './model/DataModel.js';
|
|
21
21
|
import { DialogQueueSessionMixin } from './model/DialogQueue.js';
|
|
@@ -24,7 +24,7 @@ import { TreeModelF } from './model/treeModel.js';
|
|
|
24
24
|
import { calculateNeighborJoiningTree } from './neighborJoining.js';
|
|
25
25
|
import { parseAsn1 } from './parseAsn1.js';
|
|
26
26
|
import { calculatePropertyConservation } from './propertyConservation.js';
|
|
27
|
-
import { globalColToVisibleCol, visibleColToGlobalCol, visibleColToSeqPosForRow, } from './rowCoordinateCalculations.js';
|
|
27
|
+
import { globalColToVisibleCol, visibleColToGlobalCol, visibleColToSeqPosForRow, visibleColsBefore, } from './rowCoordinateCalculations.js';
|
|
28
28
|
import { buildSeqPosIndex } from './seqPosToGlobalCol.js';
|
|
29
29
|
import { stripDefault } from './stripDefault.js';
|
|
30
30
|
import { computeRowInsertions, len, skipBlanks, transform } from './util.js';
|
|
@@ -39,7 +39,7 @@ function parseTreeText(text) {
|
|
|
39
39
|
* #stateModel MsaView
|
|
40
40
|
*
|
|
41
41
|
* The main MSAView state model. Holds the loaded alignment, tree, and optional
|
|
42
|
-
*
|
|
42
|
+
* overlay annotations, plus all display state (color scheme, zoom,
|
|
43
43
|
* scroll, collapsed clades). It composes in members from `DialogQueueSessionMixin`,
|
|
44
44
|
* `Tree`, and `MSAModel` (see Inherited members below). Data is loaded reactively
|
|
45
45
|
* from the `msaFilehandle` / `treeFilehandle` / `gffFilehandle` properties, or set
|
|
@@ -145,7 +145,7 @@ function stateModelFactory() {
|
|
|
145
145
|
treeMetadataFilehandle: types.maybe(FileLocation),
|
|
146
146
|
/**
|
|
147
147
|
* #property
|
|
148
|
-
* filehandle object for
|
|
148
|
+
* filehandle object for a GFF file of overlay annotations
|
|
149
149
|
*/
|
|
150
150
|
gffFilehandle: types.maybe(FileLocation),
|
|
151
151
|
/**
|
|
@@ -283,8 +283,12 @@ function stateModelFactory() {
|
|
|
283
283
|
error: undefined,
|
|
284
284
|
/**
|
|
285
285
|
* #volatile
|
|
286
|
+
* overlay annotations drawn on the alignment, whatever their source.
|
|
287
|
+
* Every source -- InterProScan, GFF, a user upload -- converts to this
|
|
288
|
+
* flat list before it reaches the model, so nothing downstream of here
|
|
289
|
+
* knows which one it came from
|
|
286
290
|
*/
|
|
287
|
-
|
|
291
|
+
annotations: [],
|
|
288
292
|
}))
|
|
289
293
|
.actions(self => ({
|
|
290
294
|
/**
|
|
@@ -373,7 +377,7 @@ function stateModelFactory() {
|
|
|
373
377
|
},
|
|
374
378
|
/**
|
|
375
379
|
* #action
|
|
376
|
-
* toggle the
|
|
380
|
+
* toggle the annotation overlay on the alignment
|
|
377
381
|
*/
|
|
378
382
|
setShowDomains(arg) {
|
|
379
383
|
self.showDomains = arg;
|
|
@@ -413,13 +417,6 @@ function stateModelFactory() {
|
|
|
413
417
|
setColWidth(n) {
|
|
414
418
|
self.colWidth = n;
|
|
415
419
|
},
|
|
416
|
-
/**
|
|
417
|
-
* #action
|
|
418
|
-
* set scroll Y-offset (px)
|
|
419
|
-
*/
|
|
420
|
-
setScrollY(n) {
|
|
421
|
-
self.scrollY = n;
|
|
422
|
-
},
|
|
423
420
|
/**
|
|
424
421
|
* #action
|
|
425
422
|
*
|
|
@@ -511,7 +508,7 @@ function stateModelFactory() {
|
|
|
511
508
|
* #getter
|
|
512
509
|
*/
|
|
513
510
|
get actuallyShowDomains() {
|
|
514
|
-
return self.showDomains &&
|
|
511
|
+
return self.showDomains && self.annotations.length > 0;
|
|
515
512
|
},
|
|
516
513
|
get viewInitialized() {
|
|
517
514
|
return self.volatileWidth !== undefined;
|
|
@@ -562,7 +559,7 @@ function stateModelFactory() {
|
|
|
562
559
|
return !!this.tree.noTree;
|
|
563
560
|
},
|
|
564
561
|
get noDomains() {
|
|
565
|
-
return
|
|
562
|
+
return self.annotations.length === 0;
|
|
566
563
|
},
|
|
567
564
|
menuItems() {
|
|
568
565
|
return [];
|
|
@@ -742,12 +739,6 @@ function stateModelFactory() {
|
|
|
742
739
|
}
|
|
743
740
|
return blanks;
|
|
744
741
|
},
|
|
745
|
-
/**
|
|
746
|
-
* #getter
|
|
747
|
-
*/
|
|
748
|
-
get blanksSet() {
|
|
749
|
-
return new Set(this.blanks);
|
|
750
|
-
},
|
|
751
742
|
/**
|
|
752
743
|
* #getter
|
|
753
744
|
* Returns a map of row name to array of insertions with display position and letters
|
|
@@ -828,13 +819,6 @@ function stateModelFactory() {
|
|
|
828
819
|
get colStats() {
|
|
829
820
|
return columnCountsFromRows(this.columns2d);
|
|
830
821
|
},
|
|
831
|
-
/**
|
|
832
|
-
* #getter
|
|
833
|
-
* per-column residue totals (gaps included)
|
|
834
|
-
*/
|
|
835
|
-
get colStatsSums() {
|
|
836
|
-
return this.colStats.totals;
|
|
837
|
-
},
|
|
838
822
|
/**
|
|
839
823
|
* #getter
|
|
840
824
|
* Detects sequence type based on letters present in the alignment.
|
|
@@ -935,14 +919,13 @@ function stateModelFactory() {
|
|
|
935
919
|
const max = maxLength(r);
|
|
936
920
|
const k = max ? self.treeWidth / max : 0;
|
|
937
921
|
setBrLength(r, 0, k);
|
|
938
|
-
// for each collapsed clade, record the pixel x-
|
|
939
|
-
//
|
|
940
|
-
//
|
|
922
|
+
// for each collapsed clade, record the pixel x-position of its farthest
|
|
923
|
+
// tip so the renderer can draw a triangle spanning the branch-length
|
|
924
|
+
// extent of the hidden subtree
|
|
941
925
|
forEachDescendant(r, node => {
|
|
942
926
|
if (node._children) {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
node.collapsedTipXFar = (node.len ?? 0) + mx * k;
|
|
927
|
+
node.collapsedTipXFar =
|
|
928
|
+
(node.len ?? 0) + collapsedSubtreeMaxLength(node) * k;
|
|
946
929
|
}
|
|
947
930
|
});
|
|
948
931
|
return r;
|
|
@@ -1089,18 +1072,24 @@ function stateModelFactory() {
|
|
|
1089
1072
|
* set hovered tree node and its descendants
|
|
1090
1073
|
*/
|
|
1091
1074
|
setHoveredTreeNode(nodeId) {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
}
|
|
1099
|
-
: undefined;
|
|
1075
|
+
// the tree's mousemove handler calls this on every event, and both the
|
|
1076
|
+
// lookup and the write are expensive: `find` walks the whole hierarchy,
|
|
1077
|
+
// and a fresh object here invalidates hoveredRowIndices and redraws the
|
|
1078
|
+
// tree and MSA overlays. Re-hovering the same node is the common case
|
|
1079
|
+
if (nodeId === self.hoveredTreeNode?.nodeId) {
|
|
1080
|
+
return;
|
|
1100
1081
|
}
|
|
1101
|
-
|
|
1082
|
+
if (!nodeId) {
|
|
1102
1083
|
self.hoveredTreeNode = undefined;
|
|
1084
|
+
return;
|
|
1103
1085
|
}
|
|
1086
|
+
const node = find(self.hierarchy, n => n.data.id === nodeId);
|
|
1087
|
+
self.hoveredTreeNode = node
|
|
1088
|
+
? {
|
|
1089
|
+
nodeId,
|
|
1090
|
+
descendantNames: leaves(node).map(leaf => leaf.data.name),
|
|
1091
|
+
}
|
|
1092
|
+
: undefined;
|
|
1104
1093
|
},
|
|
1105
1094
|
/**
|
|
1106
1095
|
* #action
|
|
@@ -1196,21 +1185,36 @@ function stateModelFactory() {
|
|
|
1196
1185
|
* #action
|
|
1197
1186
|
*/
|
|
1198
1187
|
doScrollY(deltaY) {
|
|
1199
|
-
|
|
1188
|
+
this.setScrollY(self.scrollY + deltaY);
|
|
1200
1189
|
},
|
|
1201
1190
|
/**
|
|
1202
1191
|
* #action
|
|
1203
|
-
*
|
|
1204
|
-
|
|
1205
|
-
|
|
1192
|
+
* set scroll Y-offset (px), clamped to keep the alignment in view
|
|
1193
|
+
*/
|
|
1194
|
+
setScrollY(n) {
|
|
1195
|
+
self.scrollY = clamp(n, self.maxScrollY, 0);
|
|
1196
|
+
},
|
|
1197
|
+
/**
|
|
1198
|
+
* #action
|
|
1199
|
+
* Set the overlay annotations and reveal the overlay in a single step (an
|
|
1200
|
+
* empty list clears both). Every source funnels through here after its
|
|
1201
|
+
* own adapter has flattened it: InterProScan, GFF, user uploads, NCBI CDD.
|
|
1202
|
+
*/
|
|
1203
|
+
setAnnotations(annotations) {
|
|
1204
|
+
self.annotations = annotations;
|
|
1205
|
+
self.setShowDomains(annotations.length > 0);
|
|
1206
|
+
},
|
|
1207
|
+
/**
|
|
1208
|
+
* #action
|
|
1209
|
+
* set the overlay from raw InterProScan results keyed by row name. Kept
|
|
1210
|
+
* for downstream plugins that hold the EBI wire format; new code should
|
|
1211
|
+
* adapt to Annotation[] and call setAnnotations.
|
|
1206
1212
|
*/
|
|
1207
1213
|
setDomains(data) {
|
|
1208
|
-
|
|
1209
|
-
self.setShowDomains(!!data);
|
|
1214
|
+
this.setAnnotations(data ? interProScanToAnnotations(data) : []);
|
|
1210
1215
|
},
|
|
1211
1216
|
applyGFFText(gffText) {
|
|
1212
|
-
|
|
1213
|
-
this.setDomains(gffToInterProResults(gffRecords));
|
|
1217
|
+
this.setAnnotations(gffToAnnotations(parseGFF(gffText)));
|
|
1214
1218
|
},
|
|
1215
1219
|
/**
|
|
1216
1220
|
* #action
|
|
@@ -1247,17 +1251,20 @@ function stateModelFactory() {
|
|
|
1247
1251
|
* #getter
|
|
1248
1252
|
*/
|
|
1249
1253
|
get labelWidthMap() {
|
|
1250
|
-
const {
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
+
const { showTreeText, leaves, treeMetadata, fontSize } = self;
|
|
1255
|
+
// gated on the same condition the renderer draws labels under, so the
|
|
1256
|
+
// gutter labelsWidth reserves and the labels actually drawn cannot
|
|
1257
|
+
// disagree -- and so turning labels off hands their space to the tree
|
|
1258
|
+
return showTreeText
|
|
1259
|
+
? new Map(leaves.map(node => {
|
|
1254
1260
|
const { name } = node.data;
|
|
1255
1261
|
// `||`, matching renderTreeLabels: an empty genome falls back
|
|
1256
1262
|
// to the row name, and measuring '' would size the gutter (and
|
|
1257
1263
|
// the label's click target) to nothing
|
|
1258
1264
|
const displayName = treeMetadata[name]?.genome || name;
|
|
1259
1265
|
return [name, measureTextCanvas(displayName, fontSize)];
|
|
1260
|
-
}))
|
|
1266
|
+
}))
|
|
1267
|
+
: new Map();
|
|
1261
1268
|
},
|
|
1262
1269
|
get labelsWidth() {
|
|
1263
1270
|
// a loop, not Math.max(...widths.values()): spreading a map of every
|
|
@@ -1419,8 +1426,8 @@ function stateModelFactory() {
|
|
|
1419
1426
|
return 0;
|
|
1420
1427
|
}
|
|
1421
1428
|
const col = self.seqPosGlobalColIndex.get(rowName)?.[seqPos];
|
|
1422
|
-
// past the end of the ungapped sequence: 0 for the degenerate
|
|
1423
|
-
//
|
|
1429
|
+
// past the end of the ungapped sequence: 0 for the degenerate all-gap
|
|
1430
|
+
// row, otherwise one past the last column
|
|
1424
1431
|
return col ?? (seqPos === 0 ? 0 : seq.length);
|
|
1425
1432
|
},
|
|
1426
1433
|
/**
|
|
@@ -1454,35 +1461,27 @@ function stateModelFactory() {
|
|
|
1454
1461
|
get totalTrackAreaHeight() {
|
|
1455
1462
|
return sum(self.turnedOnTracks.map(r => r.model.height));
|
|
1456
1463
|
},
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
start,
|
|
1473
|
-
end,
|
|
1474
|
-
strand,
|
|
1475
|
-
}))
|
|
1476
|
-
: [];
|
|
1477
|
-
}))
|
|
1478
|
-
.toSorted((a, b) => len(b) - len(a))
|
|
1479
|
-
: [];
|
|
1464
|
+
/**
|
|
1465
|
+
* one representative annotation per accession, which is what the legend,
|
|
1466
|
+
* the filter dialog and the palettes key off
|
|
1467
|
+
*/
|
|
1468
|
+
get annotationTypes() {
|
|
1469
|
+
// first occurrence wins. The representative supplies only the name,
|
|
1470
|
+
// description and -- for ordinal segments -- the start that orders
|
|
1471
|
+
// them, and those agree across an accession's instances
|
|
1472
|
+
const types = new Map();
|
|
1473
|
+
for (const annot of self.annotations) {
|
|
1474
|
+
if (!types.has(annot.accession)) {
|
|
1475
|
+
types.set(annot.accession, annot);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return types;
|
|
1480
1479
|
},
|
|
1481
|
-
get
|
|
1482
|
-
return
|
|
1480
|
+
get filteredAnnotations() {
|
|
1481
|
+
return self.annotations.filter(r => self.featureFilters.get(r.accession));
|
|
1483
1482
|
},
|
|
1484
|
-
get
|
|
1485
|
-
return groupBy(this.
|
|
1483
|
+
get annotationsByRow() {
|
|
1484
|
+
return groupBy(this.filteredAnnotations, r => r.id);
|
|
1486
1485
|
},
|
|
1487
1486
|
}))
|
|
1488
1487
|
.views(self => ({
|
|
@@ -1500,6 +1499,15 @@ function stateModelFactory() {
|
|
|
1500
1499
|
get verticalScrollbarWidth() {
|
|
1501
1500
|
return self.showVerticalScrollbar ? 20 : 0;
|
|
1502
1501
|
},
|
|
1502
|
+
/**
|
|
1503
|
+
* #getter
|
|
1504
|
+
* width of the alignment canvas itself: the msa area less the vertical
|
|
1505
|
+
* scrollbar sitting in it. Not usable from showHorizontalScrollbar, which
|
|
1506
|
+
* feeds msaAreaHeight -> showVerticalScrollbar and would close a cycle
|
|
1507
|
+
*/
|
|
1508
|
+
get msaCanvasWidth() {
|
|
1509
|
+
return self.msaAreaWidth - this.verticalScrollbarWidth;
|
|
1510
|
+
},
|
|
1503
1511
|
/**
|
|
1504
1512
|
* #getter
|
|
1505
1513
|
* ordinal segment types (exons etc.), ordered by sequence position so
|
|
@@ -1507,7 +1515,7 @@ function stateModelFactory() {
|
|
|
1507
1515
|
* labeled by number rather than each getting a distinct hue + legend row
|
|
1508
1516
|
*/
|
|
1509
1517
|
get segmentDomainTypes() {
|
|
1510
|
-
return [...self.
|
|
1518
|
+
return [...self.annotationTypes.values()]
|
|
1511
1519
|
.filter(d => segmentFeatureTypes.has(d.featureType ?? ''))
|
|
1512
1520
|
.toSorted((a, b) => a.start - b.start);
|
|
1513
1521
|
},
|
|
@@ -1517,7 +1525,7 @@ function stateModelFactory() {
|
|
|
1517
1525
|
* their own color and a legend entry
|
|
1518
1526
|
*/
|
|
1519
1527
|
get categoricalDomainTypes() {
|
|
1520
|
-
return [...self.
|
|
1528
|
+
return [...self.annotationTypes.values()].filter(d => !segmentFeatureTypes.has(d.featureType ?? ''));
|
|
1521
1529
|
},
|
|
1522
1530
|
get fillPalette() {
|
|
1523
1531
|
const segments = Object.fromEntries(this.segmentDomainTypes.map((d, i) => [
|
|
@@ -1558,29 +1566,39 @@ function stateModelFactory() {
|
|
|
1558
1566
|
},
|
|
1559
1567
|
/**
|
|
1560
1568
|
* #getter
|
|
1561
|
-
* every filtered-on
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1569
|
+
* every filtered-on annotation resolved to the visible column span it is
|
|
1570
|
+
* drawn across, keyed by row name. Each row is ordered longest-first so a
|
|
1571
|
+
* short domain nested inside a long one draws on top of it rather than
|
|
1572
|
+
* under it. Resolving these once here rather than inside each canvas
|
|
1573
|
+
* block removes a per-feature, per-block sequence position conversion
|
|
1574
|
+
* from every redraw, and gives the letter renderer the band colors it
|
|
1575
|
+
* needs to keep residues readable on top of the boxes.
|
|
1567
1576
|
*/
|
|
1568
1577
|
get domainBands() {
|
|
1569
1578
|
const bands = new Map();
|
|
1570
|
-
for (const [name, annotations] of Object.entries(self.
|
|
1579
|
+
for (const [name, annotations] of Object.entries(self.annotationsByRow)) {
|
|
1580
|
+
const { blanks } = self;
|
|
1571
1581
|
const rowBands = annotations
|
|
1572
|
-
.
|
|
1573
|
-
|
|
1574
|
-
//
|
|
1575
|
-
//
|
|
1576
|
-
//
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1582
|
+
.toSorted((a, b) => len(b) - len(a))
|
|
1583
|
+
.map(annotation => {
|
|
1584
|
+
// annotation positions are 1-based and inclusive. Both ends count
|
|
1585
|
+
// the visible columns in front of a global column, so endCol is
|
|
1586
|
+
// the exclusive column after the last residue's own column --
|
|
1587
|
+
// the band stops there rather than stretching across a following
|
|
1588
|
+
// gap run -- and a residue whose column is itself hidden
|
|
1589
|
+
// collapses onto the neighbouring boundary instead of dropping
|
|
1590
|
+
// the band. A band whose every column is hidden spans nothing
|
|
1591
|
+
// and is left out.
|
|
1592
|
+
const startCol = visibleColsBefore(blanks, self.seqPosToGlobalCol(name, annotation.start - 1));
|
|
1593
|
+
const endCol = visibleColsBefore(blanks, self.seqPosToGlobalCol(name, annotation.end - 1) + 1);
|
|
1594
|
+
return endCol > startCol
|
|
1595
|
+
? { annotation, startCol, endCol }
|
|
1581
1596
|
: undefined;
|
|
1582
1597
|
})
|
|
1583
|
-
.filter(notEmpty)
|
|
1598
|
+
.filter(notEmpty)
|
|
1599
|
+
// numbered after the drop, so a band that resolved to nothing does
|
|
1600
|
+
// not leave an empty sub-row behind it
|
|
1601
|
+
.map((band, stackIndex) => ({ ...band, stackIndex }));
|
|
1584
1602
|
if (rowBands.length > 0) {
|
|
1585
1603
|
bands.set(name, rowBands);
|
|
1586
1604
|
}
|
|
@@ -1730,7 +1748,7 @@ function stateModelFactory() {
|
|
|
1730
1748
|
self.setTreeFilehandle(undefined);
|
|
1731
1749
|
self.setMSAFilehandle(undefined);
|
|
1732
1750
|
self.setGFFFilehandle(undefined);
|
|
1733
|
-
self.
|
|
1751
|
+
self.setAnnotations([]);
|
|
1734
1752
|
},
|
|
1735
1753
|
/**
|
|
1736
1754
|
* #action
|
|
@@ -1770,7 +1788,10 @@ function stateModelFactory() {
|
|
|
1770
1788
|
*/
|
|
1771
1789
|
fitHorizontally() {
|
|
1772
1790
|
if (self.numColumns > 0) {
|
|
1773
|
-
|
|
1791
|
+
// fitting to msaAreaWidth instead left the last ~20px of columns off
|
|
1792
|
+
// the right edge -- and short of the width that shows a minimap, so
|
|
1793
|
+
// nothing on screen said they were there
|
|
1794
|
+
self.colWidth = clamp(self.msaCanvasWidth / self.numColumns, minColWidth, maxCellSize);
|
|
1774
1795
|
}
|
|
1775
1796
|
self.scrollX = 0;
|
|
1776
1797
|
},
|
|
@@ -1782,7 +1803,7 @@ function stateModelFactory() {
|
|
|
1782
1803
|
self.setHighlightedColumns(self.highlightColumns);
|
|
1783
1804
|
}
|
|
1784
1805
|
addDisposer(self, autorun(() => {
|
|
1785
|
-
for (const key of self.
|
|
1806
|
+
for (const key of self.annotationTypes.keys()) {
|
|
1786
1807
|
this.initFilter(key);
|
|
1787
1808
|
}
|
|
1788
1809
|
}));
|
|
@@ -1805,37 +1826,51 @@ function stateModelFactory() {
|
|
|
1805
1826
|
mql.removeEventListener('change', onChange);
|
|
1806
1827
|
});
|
|
1807
1828
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1829
|
+
/**
|
|
1830
|
+
* Fetch a filehandle whenever it changes, and hand the text to
|
|
1831
|
+
* `onLoad`.
|
|
1832
|
+
*
|
|
1833
|
+
* Every loader carries a generation guard: the filehandle can change
|
|
1834
|
+
* mid-fetch (a second file picked while the first is still in flight),
|
|
1835
|
+
* and the slower earlier request must not clobber the data, status, or
|
|
1836
|
+
* loading flag belonging to the newer one.
|
|
1837
|
+
*
|
|
1838
|
+
* `clearFilehandle` serves two purposes for the loaders that pass it.
|
|
1839
|
+
* A local file has no URL to refetch from, so the handle is dropped
|
|
1840
|
+
* once its bytes are in the model; and a fetch the user cancels drops
|
|
1841
|
+
* it too, returning the view to the import form rather than leaving a
|
|
1842
|
+
* stuck spinner.
|
|
1843
|
+
*/
|
|
1844
|
+
const loadOnFilehandleChange = ({ getFilehandle, onLoad, setLoading, clearFilehandle, }) => {
|
|
1845
|
+
let generation = 0;
|
|
1846
|
+
addDisposer(self, autorun(async () => {
|
|
1847
|
+
const filehandle = getFilehandle();
|
|
1848
|
+
if (!filehandle) {
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
const current = ++generation;
|
|
1852
|
+
const isCurrent = () => current === generation;
|
|
1816
1853
|
try {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1854
|
+
setLoading?.(true);
|
|
1855
|
+
self.setError(undefined);
|
|
1856
|
+
const text = await fetchTextWithProgress(openLocation(filehandle), status => {
|
|
1857
|
+
if (isCurrent()) {
|
|
1820
1858
|
self.setStatus(status);
|
|
1821
1859
|
}
|
|
1822
1860
|
});
|
|
1823
|
-
if (
|
|
1861
|
+
if (isCurrent()) {
|
|
1824
1862
|
transaction(() => {
|
|
1825
|
-
|
|
1826
|
-
if (
|
|
1827
|
-
|
|
1828
|
-
self.setTreeFilehandle(undefined);
|
|
1863
|
+
onLoad(text);
|
|
1864
|
+
if (filehandle.locationType === 'BlobLocation') {
|
|
1865
|
+
clearFilehandle?.();
|
|
1829
1866
|
}
|
|
1830
1867
|
});
|
|
1831
1868
|
}
|
|
1832
1869
|
}
|
|
1833
1870
|
catch (e) {
|
|
1834
|
-
if (
|
|
1871
|
+
if (isCurrent()) {
|
|
1835
1872
|
if (isAbortError(e)) {
|
|
1836
|
-
|
|
1837
|
-
// returns to the import form instead of a stuck spinner
|
|
1838
|
-
self.setTreeFilehandle(undefined);
|
|
1873
|
+
clearFilehandle?.();
|
|
1839
1874
|
}
|
|
1840
1875
|
else {
|
|
1841
1876
|
console.error(e);
|
|
@@ -1844,30 +1879,32 @@ function stateModelFactory() {
|
|
|
1844
1879
|
}
|
|
1845
1880
|
}
|
|
1846
1881
|
finally {
|
|
1847
|
-
if (
|
|
1848
|
-
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
}));
|
|
1853
|
-
// autorun opens treeMetadataFilehandle
|
|
1854
|
-
addDisposer(self, autorun(async () => {
|
|
1855
|
-
const { treeMetadataFilehandle } = self;
|
|
1856
|
-
if (treeMetadataFilehandle) {
|
|
1857
|
-
try {
|
|
1858
|
-
self.setTreeMetadata(await fetchTextWithProgress(openLocation(treeMetadataFilehandle), status => {
|
|
1859
|
-
self.setStatus(status);
|
|
1860
|
-
}));
|
|
1861
|
-
}
|
|
1862
|
-
catch (e) {
|
|
1863
|
-
// ignore user cancel (isAbortError); treeMetadata is optional
|
|
1864
|
-
if (!isAbortError(e)) {
|
|
1865
|
-
console.error(e);
|
|
1866
|
-
self.setError(e);
|
|
1882
|
+
if (isCurrent()) {
|
|
1883
|
+
setLoading?.(false);
|
|
1867
1884
|
}
|
|
1868
1885
|
}
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1886
|
+
}));
|
|
1887
|
+
};
|
|
1888
|
+
loadOnFilehandleChange({
|
|
1889
|
+
getFilehandle: () => self.treeFilehandle,
|
|
1890
|
+
onLoad: text => {
|
|
1891
|
+
self.setTree(text);
|
|
1892
|
+
},
|
|
1893
|
+
setLoading: arg => {
|
|
1894
|
+
self.setLoadingTree(arg);
|
|
1895
|
+
},
|
|
1896
|
+
clearFilehandle: () => {
|
|
1897
|
+
self.setTreeFilehandle(undefined);
|
|
1898
|
+
},
|
|
1899
|
+
});
|
|
1900
|
+
// treeMetadata is decorative and has no import-form step of its own, so
|
|
1901
|
+
// it keeps no loading flag and nothing to return to on cancel
|
|
1902
|
+
loadOnFilehandleChange({
|
|
1903
|
+
getFilehandle: () => self.treeMetadataFilehandle,
|
|
1904
|
+
onLoad: text => {
|
|
1905
|
+
self.setTreeMetadata(text);
|
|
1906
|
+
},
|
|
1907
|
+
});
|
|
1871
1908
|
// autorun parses inline gff text from data.gff
|
|
1872
1909
|
addDisposer(self, autorun(() => {
|
|
1873
1910
|
const gffText = self.data.gff;
|
|
@@ -1881,75 +1918,28 @@ function stateModelFactory() {
|
|
|
1881
1918
|
}
|
|
1882
1919
|
}
|
|
1883
1920
|
}));
|
|
1884
|
-
//
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
}));
|
|
1909
|
-
// autorun opens msaFilehandle. generation guard: see treeFilehandle
|
|
1910
|
-
let msaGeneration = 0;
|
|
1911
|
-
addDisposer(self, autorun(async () => {
|
|
1912
|
-
const { msaFilehandle } = self;
|
|
1913
|
-
if (msaFilehandle) {
|
|
1914
|
-
const generation = ++msaGeneration;
|
|
1915
|
-
try {
|
|
1916
|
-
self.setLoadingMSA(true);
|
|
1917
|
-
self.setError(undefined);
|
|
1918
|
-
const txt = await fetchTextWithProgress(openLocation(msaFilehandle), status => {
|
|
1919
|
-
if (generation === msaGeneration) {
|
|
1920
|
-
self.setStatus(status);
|
|
1921
|
-
}
|
|
1922
|
-
});
|
|
1923
|
-
if (generation === msaGeneration) {
|
|
1924
|
-
transaction(() => {
|
|
1925
|
-
self.setMSA(txt);
|
|
1926
|
-
if (msaFilehandle.locationType === 'BlobLocation') {
|
|
1927
|
-
// clear filehandle after loading if from a local file
|
|
1928
|
-
self.setMSAFilehandle(undefined);
|
|
1929
|
-
}
|
|
1930
|
-
});
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
catch (e) {
|
|
1934
|
-
if (generation === msaGeneration) {
|
|
1935
|
-
if (isAbortError(e)) {
|
|
1936
|
-
// cancelled by the user: drop the filehandle so the view
|
|
1937
|
-
// returns to the import form instead of a stuck spinner
|
|
1938
|
-
self.setMSAFilehandle(undefined);
|
|
1939
|
-
}
|
|
1940
|
-
else {
|
|
1941
|
-
console.error(e);
|
|
1942
|
-
self.setError(e);
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
finally {
|
|
1947
|
-
if (generation === msaGeneration) {
|
|
1948
|
-
self.setLoadingMSA(false);
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
}));
|
|
1921
|
+
// gffFilehandle carries overlay annotations
|
|
1922
|
+
loadOnFilehandleChange({
|
|
1923
|
+
getFilehandle: () => self.gffFilehandle,
|
|
1924
|
+
onLoad: text => {
|
|
1925
|
+
self.applyGFFText(text);
|
|
1926
|
+
},
|
|
1927
|
+
clearFilehandle: () => {
|
|
1928
|
+
self.setGFFFilehandle(undefined);
|
|
1929
|
+
},
|
|
1930
|
+
});
|
|
1931
|
+
loadOnFilehandleChange({
|
|
1932
|
+
getFilehandle: () => self.msaFilehandle,
|
|
1933
|
+
onLoad: text => {
|
|
1934
|
+
self.setMSA(text);
|
|
1935
|
+
},
|
|
1936
|
+
setLoading: arg => {
|
|
1937
|
+
self.setLoadingMSA(arg);
|
|
1938
|
+
},
|
|
1939
|
+
clearFilehandle: () => {
|
|
1940
|
+
self.setMSAFilehandle(undefined);
|
|
1941
|
+
},
|
|
1942
|
+
});
|
|
1953
1943
|
// Keep the parse chain warm: reading self.columns transitively holds
|
|
1954
1944
|
// self.MSA (parseMSA) computed alive, so it is parsed once per data
|
|
1955
1945
|
// change rather than re-parsed on every non-reactive access. Do not
|