react-msaview 5.2.0 → 5.2.1
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/README.md +41 -8
- package/bundle/index.js +17 -17
- package/bundle/index.js.LICENSE.txt +1 -1
- package/bundle/index.js.map +1 -1
- package/dist/components/ConservationTrack.js +2 -13
- package/dist/components/ConservationTrack.js.map +1 -1
- package/dist/components/MSAView.js +8 -1
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/ResizeHandles.d.ts +3 -0
- package/dist/components/ResizeHandles.js +60 -105
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/TextTrack.js +2 -1
- package/dist/components/TextTrack.js.map +1 -1
- package/dist/components/Track.js +1 -1
- package/dist/components/Track.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +2 -2
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/UserProvidedDomainsDialog.js +1 -2
- package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +1 -1
- package/dist/components/header/ColorSchemeMenu.js +1 -1
- package/dist/components/header/ColorSchemeMenu.js.map +1 -1
- package/dist/components/header/FileMenu.js +1 -1
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/HeaderInfoArea.js +28 -8
- package/dist/components/header/HeaderInfoArea.js.map +1 -1
- package/dist/components/header/MSASettingsMenu.js +1 -1
- package/dist/components/header/MSASettingsMenu.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.js +1 -1
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/ZoomControls.js +7 -2
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/ZoomMenu.js +9 -0
- package/dist/components/header/ZoomMenu.js.map +1 -1
- package/dist/components/minimap/Minimap.js +6 -12
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.js +7 -13
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/minimap/minimapLayout.d.ts +9 -0
- package/dist/components/minimap/minimapLayout.js +15 -0
- package/dist/components/minimap/minimapLayout.js.map +1 -0
- package/dist/components/msa/MSACanvas.js +6 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +5 -1
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +5 -7
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +3 -2
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/tracks/renderTracksSvg.js +2 -1
- package/dist/components/tracks/renderTracksSvg.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +2 -2
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +7 -0
- package/dist/constants.js.map +1 -1
- package/dist/flatToTree.js +6 -0
- package/dist/flatToTree.js.map +1 -1
- package/dist/hierarchy.d.ts +1 -1
- package/dist/hierarchy.js +136 -102
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/launchInterProScan.js +22 -9
- package/dist/launchInterProScan.js.map +1 -1
- package/dist/measureTextCanvas.js +2 -1
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model.d.ts +212 -150
- package/dist/model.js +239 -205
- package/dist/model.js.map +1 -1
- package/dist/parseAsn1.js +8 -2
- package/dist/parseAsn1.js.map +1 -1
- package/dist/renderToSvg.js +27 -24
- package/dist/renderToSvg.js.map +1 -1
- package/dist/setFontSize.d.ts +3 -0
- package/dist/setFontSize.js +14 -0
- package/dist/setFontSize.js.map +1 -0
- package/dist/useDevicePixelRatio.d.ts +1 -0
- package/dist/useDevicePixelRatio.js +27 -0
- package/dist/useDevicePixelRatio.js.map +1 -0
- package/dist/useWheelScroll.d.ts +3 -1
- package/dist/useWheelScroll.js +64 -22
- package/dist/useWheelScroll.js.map +1 -1
- package/dist/util.d.ts +4 -0
- package/dist/util.js +36 -0
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/webpack.js +0 -1
- package/dist/webpack.js.map +1 -1
- package/dist/wheelZoom.d.ts +4 -0
- package/dist/wheelZoom.js +43 -0
- package/dist/wheelZoom.js.map +1 -0
- package/package.json +5 -5
- package/src/components/ConservationTrack.tsx +2 -18
- package/src/components/MSAView.tsx +10 -1
- package/src/components/ResizeHandles.tsx +101 -113
- package/src/components/TextTrack.tsx +2 -1
- package/src/components/Track.tsx +1 -1
- package/src/components/VerticalScrollbar.tsx +2 -2
- package/src/components/dialogs/UserProvidedDomainsDialog.tsx +1 -2
- package/src/components/header/ColorSchemeMenu.tsx +1 -0
- package/src/components/header/FileMenu.tsx +1 -0
- package/src/components/header/HeaderInfoArea.tsx +32 -7
- package/src/components/header/MSASettingsMenu.tsx +1 -0
- package/src/components/header/TreeSettingsMenu.tsx +1 -0
- package/src/components/header/ZoomControls.tsx +14 -1
- package/src/components/header/ZoomMenu.tsx +10 -0
- package/src/components/minimap/Minimap.tsx +6 -15
- package/src/components/minimap/MinimapSVG.tsx +9 -22
- package/src/components/minimap/minimapLayout.ts +17 -0
- package/src/components/msa/MSACanvas.tsx +9 -0
- package/src/components/msa/MSACanvasBlock.tsx +7 -1
- package/src/components/msa/MSAMouseoverCanvas.tsx +4 -3
- package/src/components/msa/renderMSABlock.ts +6 -9
- package/src/components/msa/renderMSAMouseover.ts +3 -1
- package/src/components/tracks/renderTracksSvg.ts +2 -1
- package/src/components/tree/renderTreeCanvas.ts +2 -2
- package/src/constants.ts +8 -0
- package/src/flatToTree.test.ts +30 -0
- package/src/flatToTree.ts +10 -1
- package/src/hierarchy.test.ts +53 -1
- package/src/hierarchy.ts +139 -113
- package/src/index.ts +1 -0
- package/src/launchInterProScan.ts +33 -11
- package/src/measureTextCanvas.ts +3 -1
- package/src/model.ts +312 -242
- package/src/parseAsn1.test.ts +6 -0
- package/src/parseAsn1.ts +11 -3
- package/src/renderToSvg.tsx +79 -67
- package/src/setFontSize.test.ts +33 -0
- package/src/setFontSize.ts +20 -0
- package/src/useDevicePixelRatio.ts +29 -0
- package/src/useWheelScroll.ts +79 -21
- package/src/util.ts +35 -0
- package/src/version.ts +1 -1
- package/src/webpack.ts +0 -1
- package/src/wheelZoom.test.ts +46 -0
- package/src/wheelZoom.ts +47 -0
package/dist/model.js
CHANGED
|
@@ -10,7 +10,7 @@ import { clustalXColumnColors } from "./clustalX.js";
|
|
|
10
10
|
import colorSchemes from "./colorSchemes.js";
|
|
11
11
|
import ConservationTrack from "./components/ConservationTrack.js";
|
|
12
12
|
import TextTrack from "./components/TextTrack.js";
|
|
13
|
-
import { defaultAllowedGappyness, defaultBgColor, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawLabels, defaultDrawMsaLetters, defaultDrawNodeBubbles, defaultDrawTree, defaultHeight, defaultHideGaps, defaultLabelsAlignRight, defaultRowHeight, defaultScrollX, defaultScrollY, defaultShowBranchLen, defaultShowDomains, defaultSubFeatureRows, defaultTreeAreaWidth, defaultTreeWidth, } from "./constants.js";
|
|
13
|
+
import { defaultAllowedGappyness, defaultBgColor, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawLabels, defaultDrawMsaLetters, defaultDrawNodeBubbles, defaultDrawTree, defaultHeight, defaultHideGaps, defaultLabelsAlignRight, defaultRowHeight, defaultScrollX, defaultScrollY, defaultScrollZoom, defaultShowBranchLen, defaultShowDomains, defaultSubFeatureRows, defaultTreeAreaWidth, defaultTreeWidth, maxCellSize, minColWidth, minRowHeight, } from "./constants.js";
|
|
14
14
|
import { createPaletteMap } from "./createPaletteMap.js";
|
|
15
15
|
import { flatToTree } from "./flatToTree.js";
|
|
16
16
|
import { calcDepthToLeaf, clusterLayout, collapse, find, findMaxBranchLen, hierarchy, leaves, links, maxLength, setBrLength, sort, sum as hierarchySum, } from "./hierarchy.js";
|
|
@@ -23,14 +23,25 @@ import { calculateNeighborJoiningTree } from "./neighborJoining.js";
|
|
|
23
23
|
import { parseAsn1 } from "./parseAsn1.js";
|
|
24
24
|
import { globalColToVisibleCol, visibleColToGlobalCol, visibleColToSeqPosForRow, } from "./rowCoordinateCalculations.js";
|
|
25
25
|
import { seqPosToGlobalCol } from "./seqPosToGlobalCol.js";
|
|
26
|
-
import { len, skipBlanks } from "./util.js";
|
|
26
|
+
import { computeRowInsertions, len, skipBlanks, transform } from "./util.js";
|
|
27
27
|
import { saveAs } from "./vendor/fileSaver.js";
|
|
28
|
+
function parseTreeText(text) {
|
|
29
|
+
if (text.startsWith('BioTreeContainer')) {
|
|
30
|
+
return flatToTree(parseAsn1(text));
|
|
31
|
+
}
|
|
32
|
+
return parseNewick(text.startsWith('SEQ') ? parseEmfTree(text).tree : text);
|
|
33
|
+
}
|
|
28
34
|
/**
|
|
29
35
|
* #stateModel MsaView
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* -
|
|
33
|
-
* -
|
|
36
|
+
* #example
|
|
37
|
+
* ```js
|
|
38
|
+
* import { MSAModelF } from 'react-msaview'
|
|
39
|
+
* import { types } from '@jbrowse/mobx-state-tree'
|
|
40
|
+
*
|
|
41
|
+
* const RootModel = types.model({ view: types.optional(MSAModelF(), {}) })
|
|
42
|
+
* const root = RootModel.create({})
|
|
43
|
+
* root.view.setData({ msa: '>seq1\nACGT\n>seq2\nACGT' })
|
|
44
|
+
* ```
|
|
34
45
|
*/
|
|
35
46
|
function stateModelFactory() {
|
|
36
47
|
return types
|
|
@@ -65,6 +76,11 @@ function stateModelFactory() {
|
|
|
65
76
|
* #property
|
|
66
77
|
*/
|
|
67
78
|
drawMsaLetters: defaultDrawMsaLetters,
|
|
79
|
+
/**
|
|
80
|
+
* #property
|
|
81
|
+
* zoom in/out on plain mouse-wheel without holding ctrl
|
|
82
|
+
*/
|
|
83
|
+
scrollZoom: defaultScrollZoom,
|
|
68
84
|
/**
|
|
69
85
|
* #property
|
|
70
86
|
* height of the div containing the view, px
|
|
@@ -162,9 +178,10 @@ function stateModelFactory() {
|
|
|
162
178
|
/**
|
|
163
179
|
* #volatile
|
|
164
180
|
* high resolution scale factor, helps make canvas look better on hi-dpi
|
|
165
|
-
* screens
|
|
181
|
+
* screens. derived from the device pixel ratio so canvases are crisp on
|
|
182
|
+
* retina/4k displays and not needlessly oversized on standard ones
|
|
166
183
|
*/
|
|
167
|
-
highResScaleFactor:
|
|
184
|
+
highResScaleFactor: typeof window === 'undefined' ? 1 : window.devicePixelRatio,
|
|
168
185
|
/**
|
|
169
186
|
* #volatile
|
|
170
187
|
*/
|
|
@@ -276,6 +293,14 @@ function stateModelFactory() {
|
|
|
276
293
|
setWidth(arg) {
|
|
277
294
|
self.volatileWidth = arg;
|
|
278
295
|
},
|
|
296
|
+
/**
|
|
297
|
+
* #action
|
|
298
|
+
* high-res scale factor, tracks the device pixel ratio so canvases stay
|
|
299
|
+
* crisp when the window moves between monitors or the browser zooms
|
|
300
|
+
*/
|
|
301
|
+
setHighResScaleFactor(arg) {
|
|
302
|
+
self.highResScaleFactor = arg;
|
|
303
|
+
},
|
|
279
304
|
/**
|
|
280
305
|
* #action
|
|
281
306
|
* set the height of the view in px
|
|
@@ -302,23 +327,6 @@ function stateModelFactory() {
|
|
|
302
327
|
self.mouseCol = col;
|
|
303
328
|
self.mouseRow = row;
|
|
304
329
|
},
|
|
305
|
-
/**
|
|
306
|
-
* #action
|
|
307
|
-
* set hovered tree node and its descendants
|
|
308
|
-
*/
|
|
309
|
-
setHoveredTreeNode(nodeId) {
|
|
310
|
-
if (!nodeId) {
|
|
311
|
-
self.hoveredTreeNode = undefined;
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const node = find(self.hierarchy, n => n.data.id === nodeId);
|
|
315
|
-
if (!node) {
|
|
316
|
-
self.hoveredTreeNode = undefined;
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
const descendantNames = leaves(node).map(leaf => leaf.data.name);
|
|
320
|
-
self.hoveredTreeNode = { nodeId, descendantNames };
|
|
321
|
-
},
|
|
322
330
|
/**
|
|
323
331
|
* #action
|
|
324
332
|
* set highlighted columns
|
|
@@ -484,13 +492,16 @@ function stateModelFactory() {
|
|
|
484
492
|
* #getter
|
|
485
493
|
*/
|
|
486
494
|
get colorScheme() {
|
|
487
|
-
|
|
495
|
+
// colorSchemeName is a free string (menus, snapshots, URL params); fall
|
|
496
|
+
// back to the default rather than returning undefined on a stale name
|
|
497
|
+
return (colorSchemes[self.colorSchemeName] ??
|
|
498
|
+
colorSchemes[defaultColorSchemeName]);
|
|
488
499
|
},
|
|
489
500
|
/**
|
|
490
501
|
* #getter
|
|
491
502
|
*/
|
|
492
503
|
get header() {
|
|
493
|
-
return
|
|
504
|
+
return this.MSA?.getHeader() ?? {};
|
|
494
505
|
},
|
|
495
506
|
/**
|
|
496
507
|
* #getter
|
|
@@ -537,19 +548,14 @@ function stateModelFactory() {
|
|
|
537
548
|
get tree() {
|
|
538
549
|
const text = self.data.tree;
|
|
539
550
|
return text
|
|
540
|
-
? generateNodeIds(text
|
|
541
|
-
|
|
542
|
-
: parseNewick(text.startsWith('SEQ') ? parseEmfTree(text).tree : text))
|
|
543
|
-
: this.MSA?.getTree() || {
|
|
551
|
+
? generateNodeIds(parseTreeText(text))
|
|
552
|
+
: (this.MSA?.getTree() ?? {
|
|
544
553
|
noTree: true,
|
|
545
554
|
children: [],
|
|
546
555
|
id: 'empty',
|
|
547
556
|
name: 'empty',
|
|
548
|
-
};
|
|
557
|
+
});
|
|
549
558
|
},
|
|
550
|
-
/**
|
|
551
|
-
* #getter
|
|
552
|
-
*/
|
|
553
559
|
/**
|
|
554
560
|
* #getter
|
|
555
561
|
* Returns the list of row (sequence) names in display order.
|
|
@@ -562,11 +568,7 @@ function stateModelFactory() {
|
|
|
562
568
|
* #getter
|
|
563
569
|
*/
|
|
564
570
|
get rowNamesSet() {
|
|
565
|
-
|
|
566
|
-
this.leaves.forEach((leaf, index) => {
|
|
567
|
-
map.set(leaf.data.name, index);
|
|
568
|
-
});
|
|
569
|
-
return map;
|
|
571
|
+
return new Map(this.leaves.map((leaf, index) => [leaf.data.name, index]));
|
|
570
572
|
},
|
|
571
573
|
/**
|
|
572
574
|
* #getter
|
|
@@ -583,24 +585,16 @@ function stateModelFactory() {
|
|
|
583
585
|
*/
|
|
584
586
|
get hoveredInsertion() {
|
|
585
587
|
const { mouseCol, mouseRow } = self;
|
|
586
|
-
if (mouseCol
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
const insertion = insertions.find(ins => ins.pos === mouseCol);
|
|
598
|
-
if (insertion) {
|
|
599
|
-
return {
|
|
600
|
-
rowName,
|
|
601
|
-
col: mouseCol,
|
|
602
|
-
letters: insertion.letters,
|
|
603
|
-
};
|
|
588
|
+
if (mouseCol !== undefined && mouseRow !== undefined) {
|
|
589
|
+
const rowName = this.leaves[mouseRow]?.data.name;
|
|
590
|
+
if (rowName) {
|
|
591
|
+
const insertion = this.insertionPositions
|
|
592
|
+
.get(rowName)
|
|
593
|
+
?.find(ins => ins.pos === mouseCol);
|
|
594
|
+
if (insertion) {
|
|
595
|
+
return { rowName, col: mouseCol, letters: insertion.letters };
|
|
596
|
+
}
|
|
597
|
+
}
|
|
604
598
|
}
|
|
605
599
|
return undefined;
|
|
606
600
|
},
|
|
@@ -619,14 +613,13 @@ function stateModelFactory() {
|
|
|
619
613
|
}
|
|
620
614
|
for (const collapsedId of self.collapsed) {
|
|
621
615
|
const node = find(hier, n => n.data.id === collapsedId);
|
|
622
|
-
if (
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
node.parent.children = node.parent.children.filter(c => c.data.id !== collapsedId);
|
|
616
|
+
if (node) {
|
|
617
|
+
if (node.children) {
|
|
618
|
+
collapse(node);
|
|
619
|
+
}
|
|
620
|
+
else if (node.parent?.children) {
|
|
621
|
+
node.parent.children = node.parent.children.filter(c => c.data.id !== collapsedId);
|
|
622
|
+
}
|
|
630
623
|
}
|
|
631
624
|
}
|
|
632
625
|
return hier;
|
|
@@ -636,7 +629,7 @@ function stateModelFactory() {
|
|
|
636
629
|
* widget width minus the tree area gives the space for the MSA
|
|
637
630
|
*/
|
|
638
631
|
get msaAreaWidth() {
|
|
639
|
-
return self.width - self.treeAreaWidth;
|
|
632
|
+
return self.width - self.treeAreaWidth - self.resizeHandleWidth;
|
|
640
633
|
},
|
|
641
634
|
/**
|
|
642
635
|
* #getter
|
|
@@ -690,51 +683,13 @@ function stateModelFactory() {
|
|
|
690
683
|
* Returns a map of row name to array of insertions with display position and letters
|
|
691
684
|
*/
|
|
692
685
|
get insertionPositions() {
|
|
693
|
-
const { hideGapsEffective } = self;
|
|
694
686
|
const { blanks, rows } = this;
|
|
695
|
-
|
|
696
|
-
if (blanksLen === 0 || !hideGapsEffective) {
|
|
687
|
+
if (blanks.length === 0 || !self.hideGapsEffective) {
|
|
697
688
|
return new Map();
|
|
698
689
|
}
|
|
699
690
|
const result = new Map();
|
|
700
691
|
for (const [name, seq] of rows) {
|
|
701
|
-
const insertions =
|
|
702
|
-
let displayPos = 0;
|
|
703
|
-
let blankIdx = 0;
|
|
704
|
-
let currentInsertPos = -1;
|
|
705
|
-
let letterChars = [];
|
|
706
|
-
const seqLen = seq.length;
|
|
707
|
-
for (let i = 0; i < seqLen; i++) {
|
|
708
|
-
if (blankIdx < blanksLen && blanks[blankIdx] === i) {
|
|
709
|
-
// bit trick: (code - 45) >>> 0 <= 1 checks for '-' (45) or '.' (46)
|
|
710
|
-
const code = seq.charCodeAt(i);
|
|
711
|
-
if (!((code - 45) >>> 0 <= 1)) {
|
|
712
|
-
if (currentInsertPos === displayPos) {
|
|
713
|
-
letterChars.push(seq[i]);
|
|
714
|
-
}
|
|
715
|
-
else {
|
|
716
|
-
if (letterChars.length > 0) {
|
|
717
|
-
insertions.push({
|
|
718
|
-
pos: currentInsertPos,
|
|
719
|
-
letters: letterChars.join(''),
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
currentInsertPos = displayPos;
|
|
723
|
-
letterChars = [seq[i]];
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
blankIdx++;
|
|
727
|
-
}
|
|
728
|
-
else {
|
|
729
|
-
displayPos++;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
if (letterChars.length > 0) {
|
|
733
|
-
insertions.push({
|
|
734
|
-
pos: currentInsertPos,
|
|
735
|
-
letters: letterChars.join(''),
|
|
736
|
-
});
|
|
737
|
-
}
|
|
692
|
+
const insertions = computeRowInsertions(blanks, seq);
|
|
738
693
|
if (insertions.length > 0) {
|
|
739
694
|
result.set(name, insertions);
|
|
740
695
|
}
|
|
@@ -766,16 +721,15 @@ function stateModelFactory() {
|
|
|
766
721
|
* #getter
|
|
767
722
|
*/
|
|
768
723
|
get columns() {
|
|
769
|
-
|
|
724
|
+
const columns2d = this.columns2d;
|
|
725
|
+
return new Map(this.rows.map((row, index) => [row[0], columns2d[index]]));
|
|
770
726
|
},
|
|
771
727
|
/**
|
|
772
728
|
* #getter
|
|
773
729
|
*/
|
|
774
730
|
get columns2d() {
|
|
775
731
|
const { hideGapsEffective } = self;
|
|
776
|
-
return this.rows
|
|
777
|
-
.map(r => r[1])
|
|
778
|
-
.map(str => (hideGapsEffective
|
|
732
|
+
return this.rows.map(([, str]) => (hideGapsEffective
|
|
779
733
|
? skipBlanks(this.blanks, str)
|
|
780
734
|
: str).toUpperCase());
|
|
781
735
|
},
|
|
@@ -804,13 +758,7 @@ function stateModelFactory() {
|
|
|
804
758
|
* #getter
|
|
805
759
|
*/
|
|
806
760
|
get colStatsSums() {
|
|
807
|
-
return this.colStats.map(col =>
|
|
808
|
-
let s = 0;
|
|
809
|
-
for (const k in col) {
|
|
810
|
-
s += col[k];
|
|
811
|
-
}
|
|
812
|
-
return s;
|
|
813
|
-
});
|
|
761
|
+
return this.colStats.map(col => sum(Object.values(col)));
|
|
814
762
|
},
|
|
815
763
|
/**
|
|
816
764
|
* #getter
|
|
@@ -826,17 +774,13 @@ function stateModelFactory() {
|
|
|
826
774
|
}
|
|
827
775
|
}
|
|
828
776
|
}
|
|
777
|
+
// isDna already excludes U (not in the DNA set) and isRna excludes T,
|
|
778
|
+
// so the set membership alone disambiguates the two
|
|
829
779
|
const dna = new Set(['A', 'C', 'G', 'T', 'N']);
|
|
830
780
|
const rna = new Set(['A', 'C', 'G', 'U', 'N']);
|
|
831
|
-
const isDna = [...letters].every(l => dna.has(l));
|
|
832
|
-
const isRna = [...letters].every(l => rna.has(l));
|
|
833
|
-
|
|
834
|
-
return 'dna';
|
|
835
|
-
}
|
|
836
|
-
if (isRna && !letters.has('T')) {
|
|
837
|
-
return 'rna';
|
|
838
|
-
}
|
|
839
|
-
return 'amino';
|
|
781
|
+
const isDna = letters.size > 0 && [...letters].every(l => dna.has(l));
|
|
782
|
+
const isRna = letters.size > 0 && [...letters].every(l => rna.has(l));
|
|
783
|
+
return isDna ? 'dna' : isRna ? 'rna' : 'amino';
|
|
840
784
|
},
|
|
841
785
|
/**
|
|
842
786
|
* #getter
|
|
@@ -849,8 +793,7 @@ function stateModelFactory() {
|
|
|
849
793
|
const total = colStatsSums[i];
|
|
850
794
|
let maxCount = 0;
|
|
851
795
|
let letter = '';
|
|
852
|
-
for (const key
|
|
853
|
-
const val = stats[key];
|
|
796
|
+
for (const [key, val] of Object.entries(stats)) {
|
|
854
797
|
if (val > maxCount && key !== '-' && key !== '.') {
|
|
855
798
|
maxCount = val;
|
|
856
799
|
letter = key;
|
|
@@ -896,12 +839,9 @@ function stateModelFactory() {
|
|
|
896
839
|
return 0;
|
|
897
840
|
}
|
|
898
841
|
let entropy = 0;
|
|
899
|
-
for (const letter of Object.
|
|
900
|
-
if (letter
|
|
901
|
-
|
|
902
|
-
}
|
|
903
|
-
const freq = stats[letter] / nonGapTotal;
|
|
904
|
-
if (freq > 0) {
|
|
842
|
+
for (const [letter, count] of Object.entries(stats)) {
|
|
843
|
+
if (letter !== '-' && letter !== '.') {
|
|
844
|
+
const freq = count / nonGapTotal;
|
|
905
845
|
entropy -= freq * Math.log2(freq);
|
|
906
846
|
}
|
|
907
847
|
}
|
|
@@ -918,7 +858,8 @@ function stateModelFactory() {
|
|
|
918
858
|
const r = this.root;
|
|
919
859
|
clusterLayout(r, this.totalHeight, self.treeWidth);
|
|
920
860
|
r.data.length = 0;
|
|
921
|
-
|
|
861
|
+
const max = maxLength(r);
|
|
862
|
+
setBrLength(r, 0, max ? self.treeWidth / max : 0);
|
|
922
863
|
return r;
|
|
923
864
|
},
|
|
924
865
|
/**
|
|
@@ -974,7 +915,7 @@ function stateModelFactory() {
|
|
|
974
915
|
* #getter
|
|
975
916
|
*/
|
|
976
917
|
get dataInitialized() {
|
|
977
|
-
return (self.data.msa || self.data.tree) && !self.error;
|
|
918
|
+
return (self.data.msa !== '' || self.data.tree !== '') && !self.error;
|
|
978
919
|
},
|
|
979
920
|
/**
|
|
980
921
|
* #getter
|
|
@@ -1018,6 +959,18 @@ function stateModelFactory() {
|
|
|
1018
959
|
get maxScrollX() {
|
|
1019
960
|
return Math.min(-self.totalWidth + (self.msaAreaWidth - 100), 0);
|
|
1020
961
|
},
|
|
962
|
+
/**
|
|
963
|
+
* #getter
|
|
964
|
+
* most-negative allowed scrollY, keeping the last row in view rather than
|
|
965
|
+
* letting the whole alignment scroll off the top. visible MSA height is
|
|
966
|
+
* computed inline here because msaAreaHeight is defined later in the chain.
|
|
967
|
+
*/
|
|
968
|
+
get maxScrollY() {
|
|
969
|
+
const visibleHeight = self.height -
|
|
970
|
+
self.headerHeight -
|
|
971
|
+
(self.msaAreaWidth < self.totalWidth ? self.minimapHeight : 0);
|
|
972
|
+
return Math.min(-self.totalHeight + visibleHeight, 0);
|
|
973
|
+
},
|
|
1021
974
|
/**
|
|
1022
975
|
* #getter
|
|
1023
976
|
*/
|
|
@@ -1040,6 +993,30 @@ function stateModelFactory() {
|
|
|
1040
993
|
setDrawMsaLetters(arg) {
|
|
1041
994
|
self.drawMsaLetters = arg;
|
|
1042
995
|
},
|
|
996
|
+
/**
|
|
997
|
+
* #action
|
|
998
|
+
*/
|
|
999
|
+
setScrollZoom(arg) {
|
|
1000
|
+
self.scrollZoom = arg;
|
|
1001
|
+
},
|
|
1002
|
+
/**
|
|
1003
|
+
* #action
|
|
1004
|
+
* set hovered tree node and its descendants
|
|
1005
|
+
*/
|
|
1006
|
+
setHoveredTreeNode(nodeId) {
|
|
1007
|
+
if (nodeId) {
|
|
1008
|
+
const node = find(self.hierarchy, n => n.data.id === nodeId);
|
|
1009
|
+
self.hoveredTreeNode = node
|
|
1010
|
+
? {
|
|
1011
|
+
nodeId,
|
|
1012
|
+
descendantNames: leaves(node).map(leaf => leaf.data.name),
|
|
1013
|
+
}
|
|
1014
|
+
: undefined;
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
self.hoveredTreeNode = undefined;
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1043
1020
|
/**
|
|
1044
1021
|
* #action
|
|
1045
1022
|
* Calculate a neighbor joining tree from the current MSA using BLOSUM62 distances
|
|
@@ -1062,35 +1039,35 @@ function stateModelFactory() {
|
|
|
1062
1039
|
* #action
|
|
1063
1040
|
*/
|
|
1064
1041
|
zoomOutHorizontal() {
|
|
1065
|
-
self.colWidth = Math.max(
|
|
1042
|
+
self.colWidth = Math.max(minColWidth, Math.floor(self.colWidth * 0.75));
|
|
1066
1043
|
self.scrollX = clamp(self.scrollX, self.maxScrollX, 0);
|
|
1067
1044
|
},
|
|
1068
1045
|
/**
|
|
1069
1046
|
* #action
|
|
1070
1047
|
*/
|
|
1071
1048
|
zoomInHorizontal() {
|
|
1072
|
-
self.colWidth = Math.ceil(self.colWidth * 1.5);
|
|
1049
|
+
self.colWidth = Math.min(maxCellSize, Math.ceil(self.colWidth * 1.5));
|
|
1073
1050
|
self.scrollX = clamp(self.scrollX, self.maxScrollX, 0);
|
|
1074
1051
|
},
|
|
1075
1052
|
/**
|
|
1076
1053
|
* #action
|
|
1077
1054
|
*/
|
|
1078
1055
|
zoomInVertical() {
|
|
1079
|
-
self.rowHeight = Math.ceil(self.rowHeight * 1.5);
|
|
1056
|
+
self.rowHeight = Math.min(maxCellSize, Math.ceil(self.rowHeight * 1.5));
|
|
1080
1057
|
},
|
|
1081
1058
|
/**
|
|
1082
1059
|
* #action
|
|
1083
1060
|
*/
|
|
1084
1061
|
zoomOutVertical() {
|
|
1085
|
-
self.rowHeight = Math.max(
|
|
1062
|
+
self.rowHeight = Math.max(minRowHeight, Math.floor(self.rowHeight * 0.75));
|
|
1086
1063
|
},
|
|
1087
1064
|
/**
|
|
1088
1065
|
* #action
|
|
1089
1066
|
*/
|
|
1090
1067
|
zoomIn() {
|
|
1091
1068
|
transaction(() => {
|
|
1092
|
-
self.colWidth = Math.ceil(self.colWidth * 1.5);
|
|
1093
|
-
self.rowHeight = Math.ceil(self.rowHeight * 1.5);
|
|
1069
|
+
self.colWidth = Math.min(maxCellSize, Math.ceil(self.colWidth * 1.5));
|
|
1070
|
+
self.rowHeight = Math.min(maxCellSize, Math.ceil(self.rowHeight * 1.5));
|
|
1094
1071
|
self.scrollX = clamp(self.scrollX, self.maxScrollX, 0);
|
|
1095
1072
|
});
|
|
1096
1073
|
},
|
|
@@ -1099,25 +1076,53 @@ function stateModelFactory() {
|
|
|
1099
1076
|
*/
|
|
1100
1077
|
zoomOut() {
|
|
1101
1078
|
transaction(() => {
|
|
1102
|
-
self.colWidth = Math.max(
|
|
1103
|
-
self.rowHeight = Math.max(
|
|
1079
|
+
self.colWidth = Math.max(minColWidth, Math.floor(self.colWidth * 0.75));
|
|
1080
|
+
self.rowHeight = Math.max(minRowHeight, Math.floor(self.rowHeight * 0.75));
|
|
1104
1081
|
self.scrollX = clamp(self.scrollX, self.maxScrollX, 0);
|
|
1105
1082
|
});
|
|
1106
1083
|
},
|
|
1084
|
+
/**
|
|
1085
|
+
* #action
|
|
1086
|
+
* Smoothly zoom by a continuous scaleFactor. The column under the cursor
|
|
1087
|
+
* (offsetX/offsetY, px relative to the MSA area) stays anchored
|
|
1088
|
+
* horizontally. Vertically the anchor is biased toward the top: when the
|
|
1089
|
+
* alignment nearly fits the viewport, snap to y=0 rather than pinning a
|
|
1090
|
+
* random row under the cursor, with the bias fading out as the alignment
|
|
1091
|
+
* grows taller than the viewport (where cursor-anchoring is useful).
|
|
1092
|
+
* Drives wheel/trackpad-pinch zoom.
|
|
1093
|
+
*/
|
|
1094
|
+
zoomToPos(scaleFactor, offsetX, offsetY) {
|
|
1095
|
+
transaction(() => {
|
|
1096
|
+
const colInView = (-self.scrollX + offsetX) / self.colWidth;
|
|
1097
|
+
const rowInView = (-self.scrollY + offsetY) / self.rowHeight;
|
|
1098
|
+
self.colWidth = clamp(self.colWidth * scaleFactor, minColWidth, maxCellSize);
|
|
1099
|
+
self.rowHeight = clamp(self.rowHeight * scaleFactor, minRowHeight, maxCellSize);
|
|
1100
|
+
self.scrollX = clamp(offsetX - colInView * self.colWidth, self.maxScrollX, 0);
|
|
1101
|
+
const anchoredScrollY = offsetY - rowInView * self.rowHeight;
|
|
1102
|
+
const overflow = Math.max(0, self.totalHeight - self.height);
|
|
1103
|
+
const topBias = self.height > 0 ? clamp(1 - overflow / self.height, 0, 1) : 0;
|
|
1104
|
+
self.scrollY = clamp(anchoredScrollY * (1 - topBias), self.maxScrollY, 0);
|
|
1105
|
+
});
|
|
1106
|
+
},
|
|
1107
1107
|
/**
|
|
1108
1108
|
* #action
|
|
1109
1109
|
*/
|
|
1110
1110
|
doScrollY(deltaY) {
|
|
1111
|
-
self.scrollY = clamp(self.scrollY + deltaY,
|
|
1111
|
+
self.scrollY = clamp(self.scrollY + deltaY, self.maxScrollY, 0);
|
|
1112
1112
|
},
|
|
1113
|
-
|
|
1113
|
+
/**
|
|
1114
|
+
* #action
|
|
1115
|
+
* Set domain annotations and reveal the overlay in a single step (or clear
|
|
1116
|
+
* both when passed undefined). Shared by every domain source: InterProScan,
|
|
1117
|
+
* GFF, user-provided uploads, and NCBI CDD.
|
|
1118
|
+
*/
|
|
1119
|
+
setDomains(data) {
|
|
1114
1120
|
self.interProAnnotations = data;
|
|
1121
|
+
self.setShowDomains(!!data);
|
|
1115
1122
|
},
|
|
1116
1123
|
applyGFFText(gffText) {
|
|
1117
1124
|
const gffRecords = parseGFF(gffText);
|
|
1118
|
-
|
|
1119
|
-
self.interProAnnotations = interProResults;
|
|
1120
|
-
self.setShowDomains(true);
|
|
1125
|
+
this.setDomains(gffToInterProResults(gffRecords));
|
|
1121
1126
|
},
|
|
1122
1127
|
/**
|
|
1123
1128
|
* #action
|
|
@@ -1155,14 +1160,13 @@ function stateModelFactory() {
|
|
|
1155
1160
|
*/
|
|
1156
1161
|
get labelWidthMap() {
|
|
1157
1162
|
const { rowHeight, leaves, treeMetadata, fontSize } = self;
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
}));
|
|
1163
|
+
return rowHeight <= 5
|
|
1164
|
+
? new Map()
|
|
1165
|
+
: new Map(leaves.map(node => {
|
|
1166
|
+
const { name } = node.data;
|
|
1167
|
+
const displayName = treeMetadata[name]?.genome ?? name;
|
|
1168
|
+
return [name, measureTextCanvas(displayName, fontSize)];
|
|
1169
|
+
}));
|
|
1166
1170
|
},
|
|
1167
1171
|
get labelsWidth() {
|
|
1168
1172
|
const widths = this.labelWidthMap;
|
|
@@ -1185,15 +1189,13 @@ function stateModelFactory() {
|
|
|
1185
1189
|
*/
|
|
1186
1190
|
get adapterTrackModels() {
|
|
1187
1191
|
const { rowHeight, MSA, hideGapsEffective, blanks } = self;
|
|
1188
|
-
const tracks =
|
|
1192
|
+
const tracks = MSA?.tracks ?? [];
|
|
1189
1193
|
return tracks
|
|
1190
1194
|
.filter(t => !!t.data)
|
|
1191
1195
|
.map(t => ({
|
|
1192
1196
|
model: {
|
|
1193
1197
|
...t,
|
|
1194
|
-
data: hideGapsEffective
|
|
1195
|
-
? skipBlanks(blanks, t.data)
|
|
1196
|
-
: t.data,
|
|
1198
|
+
data: hideGapsEffective ? skipBlanks(blanks, t.data) : t.data,
|
|
1197
1199
|
height: rowHeight,
|
|
1198
1200
|
},
|
|
1199
1201
|
ReactComponent: TextTrack,
|
|
@@ -1335,22 +1337,23 @@ function stateModelFactory() {
|
|
|
1335
1337
|
return new Map(this.tidyInterProAnnotations.map(annot => [annot.accession, annot]));
|
|
1336
1338
|
},
|
|
1337
1339
|
get tidyInterProAnnotations() {
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1340
|
+
return self.interProAnnotations
|
|
1341
|
+
? Object.entries(self.interProAnnotations)
|
|
1342
|
+
.flatMap(([id, val]) => val.matches.flatMap(({ signature, locations }) => {
|
|
1343
|
+
const { entry } = signature;
|
|
1344
|
+
return entry
|
|
1345
|
+
? locations.map(({ start, end }) => ({
|
|
1346
|
+
id,
|
|
1347
|
+
name: entry.name,
|
|
1348
|
+
accession: entry.accession,
|
|
1349
|
+
description: entry.description,
|
|
1350
|
+
start,
|
|
1351
|
+
end,
|
|
1352
|
+
}))
|
|
1353
|
+
: [];
|
|
1351
1354
|
}))
|
|
1352
|
-
|
|
1353
|
-
|
|
1355
|
+
.toSorted((a, b) => len(b) - len(a))
|
|
1356
|
+
: [];
|
|
1354
1357
|
},
|
|
1355
1358
|
get tidyFilteredInterProAnnotations() {
|
|
1356
1359
|
return this.tidyInterProAnnotations.filter(r => self.featureFilters.get(r.accession));
|
|
@@ -1378,10 +1381,10 @@ function stateModelFactory() {
|
|
|
1378
1381
|
return createPaletteMap([...self.tidyInterProAnnotationTypes.keys()]);
|
|
1379
1382
|
},
|
|
1380
1383
|
get strokePalette() {
|
|
1381
|
-
return
|
|
1384
|
+
return transform(this.fillPalette, ([key, val]) => [
|
|
1382
1385
|
key,
|
|
1383
1386
|
colord(val).darken(0.1).toHex(),
|
|
1384
|
-
])
|
|
1387
|
+
]);
|
|
1385
1388
|
},
|
|
1386
1389
|
/**
|
|
1387
1390
|
* #method
|
|
@@ -1422,8 +1425,7 @@ function stateModelFactory() {
|
|
|
1422
1425
|
self.setTreeFilehandle(undefined);
|
|
1423
1426
|
self.setMSAFilehandle(undefined);
|
|
1424
1427
|
self.setGFFFilehandle(undefined);
|
|
1425
|
-
self.
|
|
1426
|
-
self.setShowDomains(false);
|
|
1428
|
+
self.setDomains(undefined);
|
|
1427
1429
|
},
|
|
1428
1430
|
/**
|
|
1429
1431
|
* #action
|
|
@@ -1446,8 +1448,12 @@ function stateModelFactory() {
|
|
|
1446
1448
|
* #action
|
|
1447
1449
|
*/
|
|
1448
1450
|
fit() {
|
|
1449
|
-
self.
|
|
1450
|
-
|
|
1451
|
+
if (self.numRows > 0) {
|
|
1452
|
+
self.rowHeight = clamp(self.msaAreaHeight / self.numRows, minRowHeight, maxCellSize);
|
|
1453
|
+
}
|
|
1454
|
+
if (self.numColumns > 0) {
|
|
1455
|
+
self.colWidth = clamp(self.msaAreaWidth / self.numColumns, minColWidth, maxCellSize);
|
|
1456
|
+
}
|
|
1451
1457
|
self.scrollX = 0;
|
|
1452
1458
|
self.scrollY = 0;
|
|
1453
1459
|
},
|
|
@@ -1455,14 +1461,18 @@ function stateModelFactory() {
|
|
|
1455
1461
|
* #action
|
|
1456
1462
|
*/
|
|
1457
1463
|
fitVertically() {
|
|
1458
|
-
self.
|
|
1464
|
+
if (self.numRows > 0) {
|
|
1465
|
+
self.rowHeight = clamp(self.msaAreaHeight / self.numRows, minRowHeight, maxCellSize);
|
|
1466
|
+
}
|
|
1459
1467
|
self.scrollY = 0;
|
|
1460
1468
|
},
|
|
1461
1469
|
/**
|
|
1462
1470
|
* #action
|
|
1463
1471
|
*/
|
|
1464
1472
|
fitHorizontally() {
|
|
1465
|
-
self.
|
|
1473
|
+
if (self.numColumns > 0) {
|
|
1474
|
+
self.colWidth = clamp(self.msaAreaWidth / self.numColumns, minColWidth, maxCellSize);
|
|
1475
|
+
}
|
|
1466
1476
|
self.scrollX = 0;
|
|
1467
1477
|
},
|
|
1468
1478
|
afterCreate() {
|
|
@@ -1471,24 +1481,37 @@ function stateModelFactory() {
|
|
|
1471
1481
|
this.initFilter(key);
|
|
1472
1482
|
}
|
|
1473
1483
|
}));
|
|
1474
|
-
// autorun opens treeFilehandle
|
|
1484
|
+
// autorun opens treeFilehandle. generation guard: if the filehandle
|
|
1485
|
+
// changes mid-fetch, a slower earlier request must not clobber the data
|
|
1486
|
+
// (or loading flag) set by the newer one
|
|
1487
|
+
let treeGeneration = 0;
|
|
1475
1488
|
addDisposer(self, autorun(async () => {
|
|
1476
1489
|
const { treeFilehandle } = self;
|
|
1477
1490
|
if (treeFilehandle) {
|
|
1491
|
+
const generation = ++treeGeneration;
|
|
1478
1492
|
try {
|
|
1479
1493
|
self.setLoadingTree(true);
|
|
1480
|
-
|
|
1481
|
-
if (
|
|
1482
|
-
|
|
1483
|
-
|
|
1494
|
+
const text = await fetchAndMaybeUnzipText(openLocation(treeFilehandle));
|
|
1495
|
+
if (generation === treeGeneration) {
|
|
1496
|
+
transaction(() => {
|
|
1497
|
+
self.setTree(text);
|
|
1498
|
+
if (treeFilehandle.locationType === 'BlobLocation') {
|
|
1499
|
+
// clear filehandle after loading if from a local file
|
|
1500
|
+
self.setTreeFilehandle(undefined);
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1484
1503
|
}
|
|
1485
1504
|
}
|
|
1486
1505
|
catch (e) {
|
|
1487
|
-
|
|
1488
|
-
|
|
1506
|
+
if (generation === treeGeneration) {
|
|
1507
|
+
console.error(e);
|
|
1508
|
+
self.setError(e);
|
|
1509
|
+
}
|
|
1489
1510
|
}
|
|
1490
1511
|
finally {
|
|
1491
|
-
|
|
1512
|
+
if (generation === treeGeneration) {
|
|
1513
|
+
self.setLoadingTree(false);
|
|
1514
|
+
}
|
|
1492
1515
|
}
|
|
1493
1516
|
}
|
|
1494
1517
|
}));
|
|
@@ -1535,32 +1558,43 @@ function stateModelFactory() {
|
|
|
1535
1558
|
}
|
|
1536
1559
|
}
|
|
1537
1560
|
}));
|
|
1538
|
-
// autorun opens msaFilehandle
|
|
1561
|
+
// autorun opens msaFilehandle. generation guard: see treeFilehandle
|
|
1562
|
+
let msaGeneration = 0;
|
|
1539
1563
|
addDisposer(self, autorun(async () => {
|
|
1540
1564
|
const { msaFilehandle } = self;
|
|
1541
1565
|
if (msaFilehandle) {
|
|
1566
|
+
const generation = ++msaGeneration;
|
|
1542
1567
|
try {
|
|
1543
1568
|
self.setLoadingMSA(true);
|
|
1544
1569
|
self.setError(undefined);
|
|
1545
1570
|
const txt = await fetchAndMaybeUnzipText(openLocation(msaFilehandle));
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1571
|
+
if (generation === msaGeneration) {
|
|
1572
|
+
transaction(() => {
|
|
1573
|
+
self.setMSA(txt);
|
|
1574
|
+
if (msaFilehandle.locationType === 'BlobLocation') {
|
|
1575
|
+
// clear filehandle after loading if from a local file
|
|
1576
|
+
self.setMSAFilehandle(undefined);
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1553
1580
|
}
|
|
1554
1581
|
catch (e) {
|
|
1555
|
-
|
|
1556
|
-
|
|
1582
|
+
if (generation === msaGeneration) {
|
|
1583
|
+
console.error(e);
|
|
1584
|
+
self.setError(e);
|
|
1585
|
+
}
|
|
1557
1586
|
}
|
|
1558
1587
|
finally {
|
|
1559
|
-
|
|
1588
|
+
if (generation === msaGeneration) {
|
|
1589
|
+
self.setLoadingMSA(false);
|
|
1590
|
+
}
|
|
1560
1591
|
}
|
|
1561
1592
|
}
|
|
1562
1593
|
}));
|
|
1563
|
-
//
|
|
1594
|
+
// Keep the parse chain warm: reading self.columns transitively holds
|
|
1595
|
+
// self.MSA (parseMSA) computed alive, so it is parsed once per data
|
|
1596
|
+
// change rather than re-parsed on every non-reactive access. colStats is
|
|
1597
|
+
// additionally held for dynamic color schemes. Do not remove.
|
|
1564
1598
|
// xref solution https://github.com/mobxjs/mobx/issues/266#issuecomment-222007278
|
|
1565
1599
|
// xref problem https://github.com/GMOD/react-msaview/issues/75
|
|
1566
1600
|
addDisposer(self, autorun(() => {
|