react-msaview 4.4.6 → 4.6.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 +9 -9
- package/bundle/index.js.LICENSE.txt +8 -8
- package/bundle/index.js.map +1 -1
- package/dist/colorSchemes.d.ts +0 -6
- package/dist/colorSchemes.js +1 -119
- package/dist/colorSchemes.js.map +1 -1
- package/dist/components/ConservationTrack.d.ts +8 -0
- package/dist/components/ConservationTrack.js +54 -0
- package/dist/components/ConservationTrack.js.map +1 -0
- package/dist/components/Loading.js +14 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +36 -0
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/SequenceTextArea.js +3 -2
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/TextTrack.d.ts +3 -3
- package/dist/components/TextTrack.js +4 -1
- package/dist/components/TextTrack.js.map +1 -1
- package/dist/components/Track.js +21 -8
- package/dist/components/Track.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +19 -3
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/header/GappynessSlider.d.ts +6 -0
- package/dist/components/header/GappynessSlider.js +19 -0
- package/dist/components/header/GappynessSlider.js.map +1 -0
- package/dist/components/header/Header.js +3 -1
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderMenu.js +30 -14
- package/dist/components/header/HeaderMenu.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.js +4 -3
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +56 -42
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +71 -26
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +8 -1
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/tracks/renderTracksSvg.d.ts +29 -0
- package/dist/components/tracks/renderTracksSvg.js +83 -0
- package/dist/components/tracks/renderTracksSvg.js.map +1 -0
- package/dist/components/tree/TreeNodeMenu.js +2 -2
- package/dist/components/tree/TreeNodeMenu.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
- package/dist/components/tree/renderTreeCanvas.js +23 -24
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +22 -0
- package/dist/constants.js +26 -0
- package/dist/constants.js.map +1 -0
- package/dist/layout.js.map +1 -1
- package/dist/model/msaModel.js +3 -2
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.js +9 -8
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +271 -15
- package/dist/model.js +427 -128
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.d.ts +1 -0
- package/dist/neighborJoining.js +839 -0
- package/dist/neighborJoining.js.map +1 -0
- package/dist/neighborJoining.test.d.ts +1 -0
- package/dist/neighborJoining.test.js +110 -0
- package/dist/neighborJoining.test.js.map +1 -0
- package/dist/parsers/A3mMSA.d.ts +43 -0
- package/dist/parsers/A3mMSA.js +277 -0
- package/dist/parsers/A3mMSA.js.map +1 -0
- package/dist/parsers/A3mMSA.test.d.ts +1 -0
- package/dist/parsers/A3mMSA.test.js +138 -0
- package/dist/parsers/A3mMSA.test.js.map +1 -0
- package/dist/parsers/ClustalMSA.d.ts +4 -4
- package/dist/parsers/ClustalMSA.js +3 -1
- package/dist/parsers/ClustalMSA.js.map +1 -1
- package/dist/parsers/FastaMSA.js +17 -16
- package/dist/parsers/FastaMSA.js.map +1 -1
- package/dist/renderToSvg.d.ts +1 -0
- package/dist/renderToSvg.js +48 -18
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.js +2 -0
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/types.d.ts +2 -3
- package/dist/util.js +17 -9
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -6
- package/src/colorSchemes.ts +1 -179
- package/src/components/ConservationTrack.tsx +104 -0
- package/src/components/Loading.tsx +44 -2
- package/src/components/MSAView.tsx +68 -0
- package/src/components/SequenceTextArea.tsx +3 -2
- package/src/components/TextTrack.tsx +7 -4
- package/src/components/Track.tsx +25 -9
- package/src/components/dialogs/ExportSVGDialog.tsx +25 -1
- package/src/components/header/GappynessSlider.tsx +35 -0
- package/src/components/header/Header.tsx +3 -1
- package/src/components/header/HeaderMenu.tsx +36 -15
- package/src/components/minimap/MinimapSVG.tsx +6 -3
- package/src/components/msa/MSACanvasBlock.tsx +66 -48
- package/src/components/msa/renderMSABlock.ts +103 -40
- package/src/components/msa/renderMSAMouseover.ts +9 -0
- package/src/components/tracks/renderTracksSvg.ts +157 -0
- package/src/components/tree/TreeNodeMenu.tsx +2 -2
- package/src/components/tree/renderTreeCanvas.ts +25 -34
- package/src/constants.ts +27 -0
- package/src/layout.ts +1 -6
- package/src/model/msaModel.ts +4 -2
- package/src/model/treeModel.ts +19 -8
- package/src/model.ts +517 -140
- package/src/neighborJoining.test.ts +129 -0
- package/src/neighborJoining.ts +885 -0
- package/src/parsers/A3mMSA.test.ts +164 -0
- package/src/parsers/A3mMSA.ts +321 -0
- package/src/parsers/ClustalMSA.ts +7 -5
- package/src/parsers/FastaMSA.ts +17 -17
- package/src/renderToSvg.tsx +105 -26
- package/src/rowCoordinateCalculations.ts +2 -0
- package/src/types.ts +2 -4
- package/src/util.ts +21 -8
- package/src/version.ts +1 -1
- package/dist/components/dialogs/TracklistDialog.d.ts +0 -7
- package/dist/components/dialogs/TracklistDialog.js +0 -23
- package/dist/components/dialogs/TracklistDialog.js.map +0 -1
- package/src/components/dialogs/TracklistDialog.tsx +0 -73
package/dist/model.js
CHANGED
|
@@ -11,7 +11,9 @@ import { addDisposer, cast, types } from 'mobx-state-tree';
|
|
|
11
11
|
import Stockholm from 'stockholm-js';
|
|
12
12
|
import { blocksX, blocksY } from './calculateBlocks';
|
|
13
13
|
import colorSchemes from './colorSchemes';
|
|
14
|
+
import ConservationTrack from './components/ConservationTrack';
|
|
14
15
|
import TextTrack from './components/TextTrack';
|
|
16
|
+
import { defaultAllowedGappyness, defaultBgColor, defaultColWidth, defaultColorSchemeName, defaultContrastLettering, defaultCurrentAlignment, defaultDrawLabels, defaultDrawMsaLetters, defaultDrawNodeBubbles, defaultDrawTree, defaultHeight, defaultHideGaps, defaultLabelsAlignRight, defaultRowHeight, defaultScrollX, defaultScrollY, defaultShowBranchLen, defaultShowDomains, defaultSubFeatureRows, defaultTreeAreaWidth, defaultTreeWidth, defaultTreeWidthMatchesArea, } from './constants';
|
|
15
17
|
import { flatToTree } from './flatToTree';
|
|
16
18
|
import palettes from './ggplotPalettes';
|
|
17
19
|
import { measureTextCanvas } from './measureTextCanvas';
|
|
@@ -19,8 +21,10 @@ import { DataModelF } from './model/DataModel';
|
|
|
19
21
|
import { DialogQueueSessionMixin } from './model/DialogQueue';
|
|
20
22
|
import { MSAModelF } from './model/msaModel';
|
|
21
23
|
import { TreeModelF } from './model/treeModel';
|
|
24
|
+
import { calculateNeighborJoiningTree } from './neighborJoining';
|
|
22
25
|
import { parseAsn1 } from './parseAsn1';
|
|
23
26
|
import parseNewick from './parseNewick';
|
|
27
|
+
import A3mMSA from './parsers/A3mMSA';
|
|
24
28
|
import ClustalMSA from './parsers/ClustalMSA';
|
|
25
29
|
import EmfMSA from './parsers/EmfMSA';
|
|
26
30
|
import FastaMSA from './parsers/FastaMSA';
|
|
@@ -28,9 +32,7 @@ import StockholmMSA from './parsers/StockholmMSA';
|
|
|
28
32
|
import { reparseTree } from './reparseTree';
|
|
29
33
|
import { mouseOverCoordToGapRemovedRowCoord, mouseOverCoordToGlobalCoord, } from './rowCoordinateCalculations';
|
|
30
34
|
import { seqCoordToRowSpecificGlobalCoord } from './seqCoordToRowSpecificGlobalCoord';
|
|
31
|
-
import { collapse, generateNodeIds,
|
|
32
|
-
const defaultRowHeight = 16;
|
|
33
|
-
const defaultColWidth = 12;
|
|
35
|
+
import { collapse, generateNodeIds, len, maxLength, setBrLength, skipBlanks, } from './util';
|
|
34
36
|
const showZoomStarKey = 'msa-showZoomStar';
|
|
35
37
|
/**
|
|
36
38
|
* #stateModel MsaView
|
|
@@ -50,23 +52,23 @@ function stateModelFactory() {
|
|
|
50
52
|
/**
|
|
51
53
|
* #property
|
|
52
54
|
*/
|
|
53
|
-
showDomains:
|
|
55
|
+
showDomains: defaultShowDomains,
|
|
54
56
|
/**
|
|
55
57
|
* #property
|
|
56
58
|
*/
|
|
57
|
-
hideGaps:
|
|
59
|
+
hideGaps: defaultHideGaps,
|
|
58
60
|
/**
|
|
59
61
|
* #property
|
|
60
62
|
*/
|
|
61
|
-
allowedGappyness:
|
|
63
|
+
allowedGappyness: defaultAllowedGappyness,
|
|
62
64
|
/**
|
|
63
65
|
* #property
|
|
64
66
|
*/
|
|
65
|
-
contrastLettering:
|
|
67
|
+
contrastLettering: defaultContrastLettering,
|
|
66
68
|
/**
|
|
67
69
|
* #property
|
|
68
70
|
*/
|
|
69
|
-
subFeatureRows:
|
|
71
|
+
subFeatureRows: defaultSubFeatureRows,
|
|
70
72
|
/**
|
|
71
73
|
* #property
|
|
72
74
|
* hardcoded view type
|
|
@@ -75,12 +77,12 @@ function stateModelFactory() {
|
|
|
75
77
|
/**
|
|
76
78
|
* #property
|
|
77
79
|
*/
|
|
78
|
-
drawMsaLetters:
|
|
80
|
+
drawMsaLetters: defaultDrawMsaLetters,
|
|
79
81
|
/**
|
|
80
82
|
* #property
|
|
81
83
|
* height of the div containing the view, px
|
|
82
84
|
*/
|
|
83
|
-
height: types.optional(types.number,
|
|
85
|
+
height: types.optional(types.number, defaultHeight),
|
|
84
86
|
/**
|
|
85
87
|
* #property
|
|
86
88
|
* height of each row, px
|
|
@@ -90,12 +92,12 @@ function stateModelFactory() {
|
|
|
90
92
|
* #property
|
|
91
93
|
* scroll position, Y-offset, px
|
|
92
94
|
*/
|
|
93
|
-
scrollY:
|
|
95
|
+
scrollY: defaultScrollY,
|
|
94
96
|
/**
|
|
95
97
|
* #property
|
|
96
98
|
* scroll position, X-offset, px
|
|
97
99
|
*/
|
|
98
|
-
scrollX:
|
|
100
|
+
scrollX: defaultScrollX,
|
|
99
101
|
/**
|
|
100
102
|
* #property
|
|
101
103
|
* width of columns, px
|
|
@@ -121,7 +123,7 @@ function stateModelFactory() {
|
|
|
121
123
|
* #property
|
|
122
124
|
*
|
|
123
125
|
*/
|
|
124
|
-
currentAlignment:
|
|
126
|
+
currentAlignment: defaultCurrentAlignment,
|
|
125
127
|
/**
|
|
126
128
|
* #property
|
|
127
129
|
* array of tree parent nodes that are 'collapsed' (all children are
|
|
@@ -230,6 +232,11 @@ function stateModelFactory() {
|
|
|
230
232
|
* the currently hovered tree node ID and its descendant leaf names
|
|
231
233
|
*/
|
|
232
234
|
hoveredTreeNode: undefined,
|
|
235
|
+
/**
|
|
236
|
+
* #volatile
|
|
237
|
+
* array of column indices to highlight
|
|
238
|
+
*/
|
|
239
|
+
highlightedColumns: undefined,
|
|
233
240
|
/**
|
|
234
241
|
* #volatile
|
|
235
242
|
* a dummy variable that is incremented when ref changes so autorun for
|
|
@@ -289,7 +296,7 @@ function stateModelFactory() {
|
|
|
289
296
|
self.loadingMSA = arg;
|
|
290
297
|
},
|
|
291
298
|
/**
|
|
292
|
-
* #
|
|
299
|
+
* #action
|
|
293
300
|
*/
|
|
294
301
|
setShowZoomStar(arg) {
|
|
295
302
|
self.showZoomStar = arg;
|
|
@@ -338,7 +345,7 @@ function stateModelFactory() {
|
|
|
338
345
|
return;
|
|
339
346
|
}
|
|
340
347
|
// Find the node in the hierarchy
|
|
341
|
-
const node = self.hierarchy.find(
|
|
348
|
+
const node = self.hierarchy.find(n => n.data.id === nodeId);
|
|
342
349
|
if (!node) {
|
|
343
350
|
self.hoveredTreeNode = undefined;
|
|
344
351
|
return;
|
|
@@ -347,6 +354,13 @@ function stateModelFactory() {
|
|
|
347
354
|
const descendantNames = node.leaves().map((leaf) => leaf.data.name);
|
|
348
355
|
self.hoveredTreeNode = { nodeId, descendantNames };
|
|
349
356
|
},
|
|
357
|
+
/**
|
|
358
|
+
* #action
|
|
359
|
+
* set highlighted columns
|
|
360
|
+
*/
|
|
361
|
+
setHighlightedColumns(columns) {
|
|
362
|
+
self.highlightedColumns = columns;
|
|
363
|
+
},
|
|
350
364
|
/**
|
|
351
365
|
* #action
|
|
352
366
|
*/
|
|
@@ -409,7 +423,7 @@ function stateModelFactory() {
|
|
|
409
423
|
/**
|
|
410
424
|
* #action
|
|
411
425
|
*/
|
|
412
|
-
|
|
426
|
+
toggleCollapsedLeaf(node) {
|
|
413
427
|
if (self.collapsedLeaves.includes(node)) {
|
|
414
428
|
self.collapsedLeaves.remove(node);
|
|
415
429
|
}
|
|
@@ -461,11 +475,21 @@ function stateModelFactory() {
|
|
|
461
475
|
},
|
|
462
476
|
}))
|
|
463
477
|
.views(self => ({
|
|
478
|
+
/**
|
|
479
|
+
* #getter
|
|
480
|
+
* hideGaps takes effect when there are collapsed rows or allowedGappyness < 100
|
|
481
|
+
*/
|
|
482
|
+
get hideGapsEffective() {
|
|
483
|
+
return (self.hideGaps &&
|
|
484
|
+
(self.collapsed.length > 0 ||
|
|
485
|
+
self.collapsedLeaves.length > 0 ||
|
|
486
|
+
self.allowedGappyness < 100));
|
|
487
|
+
},
|
|
464
488
|
/**
|
|
465
489
|
* #getter
|
|
466
490
|
*/
|
|
467
491
|
get realAllowedGappyness() {
|
|
468
|
-
return
|
|
492
|
+
return this.hideGapsEffective ? self.allowedGappyness : 100;
|
|
469
493
|
},
|
|
470
494
|
/**
|
|
471
495
|
* #getter
|
|
@@ -548,6 +572,9 @@ function stateModelFactory() {
|
|
|
548
572
|
if (Stockholm.sniff(text)) {
|
|
549
573
|
return new StockholmMSA(text, self.currentAlignment);
|
|
550
574
|
}
|
|
575
|
+
else if (A3mMSA.sniff(text)) {
|
|
576
|
+
return new A3mMSA(text);
|
|
577
|
+
}
|
|
551
578
|
else if (text.startsWith('>')) {
|
|
552
579
|
return new FastaMSA(text);
|
|
553
580
|
}
|
|
@@ -595,6 +622,33 @@ function stateModelFactory() {
|
|
|
595
622
|
const { mouseRow } = self;
|
|
596
623
|
return mouseRow === undefined ? undefined : this.rowNames[mouseRow];
|
|
597
624
|
},
|
|
625
|
+
/**
|
|
626
|
+
* #getter
|
|
627
|
+
* Returns insertion info if mouse is hovering over an insertion indicator
|
|
628
|
+
*/
|
|
629
|
+
get hoveredInsertion() {
|
|
630
|
+
const { mouseCol, mouseRow } = self;
|
|
631
|
+
if (mouseCol === undefined || mouseRow === undefined) {
|
|
632
|
+
return undefined;
|
|
633
|
+
}
|
|
634
|
+
const rowName = this.rowNames[mouseRow];
|
|
635
|
+
if (!rowName) {
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
const insertions = this.insertionPositions.get(rowName);
|
|
639
|
+
if (!insertions) {
|
|
640
|
+
return undefined;
|
|
641
|
+
}
|
|
642
|
+
const insertion = insertions.find(ins => ins.pos === mouseCol);
|
|
643
|
+
if (insertion) {
|
|
644
|
+
return {
|
|
645
|
+
rowName,
|
|
646
|
+
col: mouseCol,
|
|
647
|
+
letters: insertion.letters,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
return undefined;
|
|
651
|
+
},
|
|
598
652
|
/**
|
|
599
653
|
* #getter
|
|
600
654
|
*/
|
|
@@ -615,7 +669,7 @@ function stateModelFactory() {
|
|
|
615
669
|
[...self.collapsed, ...self.collapsedLeaves]
|
|
616
670
|
.map(collapsedId => hier.find(node => node.data.id === collapsedId))
|
|
617
671
|
.filter(notEmpty)
|
|
618
|
-
.
|
|
672
|
+
.forEach(node => {
|
|
619
673
|
collapse(node);
|
|
620
674
|
});
|
|
621
675
|
return hier;
|
|
@@ -637,28 +691,35 @@ function stateModelFactory() {
|
|
|
637
691
|
* #getter
|
|
638
692
|
*/
|
|
639
693
|
get blanks() {
|
|
640
|
-
const {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
694
|
+
const { hideGapsEffective, realAllowedGappyness } = self;
|
|
695
|
+
if (!hideGapsEffective) {
|
|
696
|
+
return [];
|
|
697
|
+
}
|
|
698
|
+
const strs = this.leaves
|
|
699
|
+
.map(leaf => this.MSA?.getRow(leaf.data.name))
|
|
700
|
+
.filter(notEmpty);
|
|
701
|
+
if (strs.length === 0) {
|
|
702
|
+
return [];
|
|
703
|
+
}
|
|
704
|
+
const numCols = strs[0].length;
|
|
705
|
+
const numRows = strs.length;
|
|
706
|
+
const threshold = Math.ceil((realAllowedGappyness / 100) * numRows);
|
|
707
|
+
const blankCounts = new Uint16Array(numCols);
|
|
708
|
+
for (let j = 0; j < numRows; j++) {
|
|
709
|
+
const str = strs[j];
|
|
710
|
+
for (let i = 0; i < numCols; i++) {
|
|
711
|
+
// bit trick: (code - 45) >>> 0 <= 1 checks for '-' (45) or '.' (46)
|
|
712
|
+
if ((str.charCodeAt(i) - 45) >>> 0 <= 1) {
|
|
713
|
+
blankCounts[i]++;
|
|
659
714
|
}
|
|
660
715
|
}
|
|
661
716
|
}
|
|
717
|
+
const blanks = [];
|
|
718
|
+
for (let i = 0; i < numCols; i++) {
|
|
719
|
+
if (blankCounts[i] >= threshold) {
|
|
720
|
+
blanks.push(i);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
662
723
|
return blanks;
|
|
663
724
|
},
|
|
664
725
|
/**
|
|
@@ -667,6 +728,62 @@ function stateModelFactory() {
|
|
|
667
728
|
get blanksSet() {
|
|
668
729
|
return new Set(this.blanks);
|
|
669
730
|
},
|
|
731
|
+
/**
|
|
732
|
+
* #getter
|
|
733
|
+
* Returns a map of row name to array of insertions with display position and letters
|
|
734
|
+
*/
|
|
735
|
+
get insertionPositions() {
|
|
736
|
+
const { hideGapsEffective } = self;
|
|
737
|
+
const { blanks, rows } = this;
|
|
738
|
+
const blanksLen = blanks.length;
|
|
739
|
+
if (blanksLen === 0 || !hideGapsEffective) {
|
|
740
|
+
return new Map();
|
|
741
|
+
}
|
|
742
|
+
const result = new Map();
|
|
743
|
+
for (const [name, seq] of rows) {
|
|
744
|
+
const insertions = [];
|
|
745
|
+
let displayPos = 0;
|
|
746
|
+
let blankIdx = 0;
|
|
747
|
+
let currentInsertPos = -1;
|
|
748
|
+
let letterChars = [];
|
|
749
|
+
const seqLen = seq.length;
|
|
750
|
+
for (let i = 0; i < seqLen; i++) {
|
|
751
|
+
if (blankIdx < blanksLen && blanks[blankIdx] === i) {
|
|
752
|
+
// bit trick: (code - 45) >>> 0 <= 1 checks for '-' (45) or '.' (46)
|
|
753
|
+
const code = seq.charCodeAt(i);
|
|
754
|
+
if (!((code - 45) >>> 0 <= 1)) {
|
|
755
|
+
if (currentInsertPos === displayPos) {
|
|
756
|
+
letterChars.push(seq[i]);
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
if (letterChars.length > 0) {
|
|
760
|
+
insertions.push({
|
|
761
|
+
pos: currentInsertPos,
|
|
762
|
+
letters: letterChars.join(''),
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
currentInsertPos = displayPos;
|
|
766
|
+
letterChars = [seq[i]];
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
blankIdx++;
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
displayPos++;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (letterChars.length > 0) {
|
|
776
|
+
insertions.push({
|
|
777
|
+
pos: currentInsertPos,
|
|
778
|
+
letters: letterChars.join(''),
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
if (insertions.length > 0) {
|
|
782
|
+
result.set(name, insertions);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return result;
|
|
786
|
+
},
|
|
670
787
|
/**
|
|
671
788
|
* #getter
|
|
672
789
|
*/
|
|
@@ -698,10 +815,10 @@ function stateModelFactory() {
|
|
|
698
815
|
* #getter
|
|
699
816
|
*/
|
|
700
817
|
get columns2d() {
|
|
701
|
-
const {
|
|
818
|
+
const { hideGapsEffective } = self;
|
|
702
819
|
return this.rows
|
|
703
820
|
.map(r => r[1])
|
|
704
|
-
.map(str => (
|
|
821
|
+
.map(str => (hideGapsEffective ? skipBlanks(this.blanks, str) : str));
|
|
705
822
|
},
|
|
706
823
|
/**
|
|
707
824
|
* #getter
|
|
@@ -734,6 +851,182 @@ function stateModelFactory() {
|
|
|
734
851
|
get colStatsSums() {
|
|
735
852
|
return this.colStats.map(val => sum(Object.values(val)));
|
|
736
853
|
},
|
|
854
|
+
/**
|
|
855
|
+
* #getter
|
|
856
|
+
* Pre-computed consensus letter and percent identity color per column.
|
|
857
|
+
* Used by percent_identity_dynamic color scheme.
|
|
858
|
+
*/
|
|
859
|
+
get colConsensus() {
|
|
860
|
+
const { colStats, colStatsSums } = this;
|
|
861
|
+
return colStats.map((stats, i) => {
|
|
862
|
+
const total = colStatsSums[i];
|
|
863
|
+
let maxCount = 0;
|
|
864
|
+
let letter = '';
|
|
865
|
+
for (const key in stats) {
|
|
866
|
+
const val = stats[key];
|
|
867
|
+
if (val > maxCount && key !== '-' && key !== '.') {
|
|
868
|
+
maxCount = val;
|
|
869
|
+
letter = key;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const proportion = maxCount / total;
|
|
873
|
+
return {
|
|
874
|
+
letter,
|
|
875
|
+
color: proportion > 0.4
|
|
876
|
+
? `hsl(240, 30%, ${100 * Math.max(1 - proportion / 3, 0.3)}%)`
|
|
877
|
+
: undefined,
|
|
878
|
+
};
|
|
879
|
+
});
|
|
880
|
+
},
|
|
881
|
+
/**
|
|
882
|
+
* #getter
|
|
883
|
+
* Pre-computed ClustalX colors per column.
|
|
884
|
+
* Returns a map of letter -> color for each column.
|
|
885
|
+
* ref http://www.jalview.org/help/html/colourSchemes/clustal.html
|
|
886
|
+
*/
|
|
887
|
+
get colClustalX() {
|
|
888
|
+
const { colStats, colStatsSums } = this;
|
|
889
|
+
return colStats.map((stats, i) => {
|
|
890
|
+
const total = colStatsSums[i];
|
|
891
|
+
const colors = {};
|
|
892
|
+
const W = stats.W ?? 0;
|
|
893
|
+
const L = stats.L ?? 0;
|
|
894
|
+
const V = stats.V ?? 0;
|
|
895
|
+
const I = stats.I ?? 0;
|
|
896
|
+
const M = stats.M ?? 0;
|
|
897
|
+
const A = stats.A ?? 0;
|
|
898
|
+
const F = stats.F ?? 0;
|
|
899
|
+
const C = stats.C ?? 0;
|
|
900
|
+
const H = stats.H ?? 0;
|
|
901
|
+
const P = stats.P ?? 0;
|
|
902
|
+
const R = stats.R ?? 0;
|
|
903
|
+
const K = stats.K ?? 0;
|
|
904
|
+
const Q = stats.Q ?? 0;
|
|
905
|
+
const E = stats.E ?? 0;
|
|
906
|
+
const D = stats.D ?? 0;
|
|
907
|
+
const T = stats.T ?? 0;
|
|
908
|
+
const S = stats.S ?? 0;
|
|
909
|
+
const G = stats.G ?? 0;
|
|
910
|
+
const Y = stats.Y ?? 0;
|
|
911
|
+
const N = stats.N ?? 0;
|
|
912
|
+
const WLVIMAFCHPY = W + L + V + I + M + A + F + C + H + P + Y;
|
|
913
|
+
const KR = K + R;
|
|
914
|
+
const QE = Q + E;
|
|
915
|
+
const ED = E + D;
|
|
916
|
+
const TS = T + S;
|
|
917
|
+
if (WLVIMAFCHPY / total > 0.6) {
|
|
918
|
+
colors.W = 'rgb(128,179,230)';
|
|
919
|
+
colors.L = 'rgb(128,179,230)';
|
|
920
|
+
colors.V = 'rgb(128,179,230)';
|
|
921
|
+
colors.A = 'rgb(128,179,230)';
|
|
922
|
+
colors.I = 'rgb(128,179,230)';
|
|
923
|
+
colors.M = 'rgb(128,179,230)';
|
|
924
|
+
colors.F = 'rgb(128,179,230)';
|
|
925
|
+
colors.C = 'rgb(128,179,230)';
|
|
926
|
+
}
|
|
927
|
+
if (KR / total > 0.6 ||
|
|
928
|
+
K / total > 0.8 ||
|
|
929
|
+
R / total > 0.8 ||
|
|
930
|
+
Q / total > 0.8) {
|
|
931
|
+
colors.K = '#d88';
|
|
932
|
+
colors.R = '#d88';
|
|
933
|
+
}
|
|
934
|
+
if (KR / total > 0.6 ||
|
|
935
|
+
QE / total > 0.5 ||
|
|
936
|
+
E / total > 0.8 ||
|
|
937
|
+
Q / total > 0.8 ||
|
|
938
|
+
D / total > 0.8) {
|
|
939
|
+
colors.E = 'rgb(192, 72, 192)';
|
|
940
|
+
}
|
|
941
|
+
if (KR / total > 0.6 ||
|
|
942
|
+
ED / total > 0.5 ||
|
|
943
|
+
K / total > 0.8 ||
|
|
944
|
+
R / total > 0.8 ||
|
|
945
|
+
Q / total > 0.8) {
|
|
946
|
+
colors.D = 'rgb(204, 77, 204)';
|
|
947
|
+
}
|
|
948
|
+
if (N / total > 0.5 || Y / total > 0.85) {
|
|
949
|
+
colors.N = '#8f8';
|
|
950
|
+
}
|
|
951
|
+
if (KR / total > 0.6 ||
|
|
952
|
+
QE / total > 0.6 ||
|
|
953
|
+
Q / total > 0.85 ||
|
|
954
|
+
E / total > 0.85 ||
|
|
955
|
+
K / total > 0.85 ||
|
|
956
|
+
R / total > 0.85) {
|
|
957
|
+
colors.Q = '#8f8';
|
|
958
|
+
}
|
|
959
|
+
if (WLVIMAFCHPY / total > 0.6 ||
|
|
960
|
+
TS / total > 0.5 ||
|
|
961
|
+
S / total > 0.85 ||
|
|
962
|
+
T / total > 0.85) {
|
|
963
|
+
colors.S = 'rgb(26,204,26)';
|
|
964
|
+
colors.T = 'rgb(26,204,26)';
|
|
965
|
+
}
|
|
966
|
+
if (C / total > 0.85) {
|
|
967
|
+
colors.C = 'rgb(240, 128, 128)';
|
|
968
|
+
}
|
|
969
|
+
if (G / total > 0) {
|
|
970
|
+
colors.G = 'rgb(240, 144, 72)';
|
|
971
|
+
}
|
|
972
|
+
if (P / total > 0) {
|
|
973
|
+
colors.P = 'rgb(204, 204, 0)';
|
|
974
|
+
}
|
|
975
|
+
if (WLVIMAFCHPY / total > 0.6 ||
|
|
976
|
+
W / total > 0.85 ||
|
|
977
|
+
Y / total > 0.85 ||
|
|
978
|
+
A / total > 0.85 ||
|
|
979
|
+
C / total > 0.85 ||
|
|
980
|
+
P / total > 0.85 ||
|
|
981
|
+
Q / total > 0.85 ||
|
|
982
|
+
F / total > 0.85 ||
|
|
983
|
+
H / total > 0.85 ||
|
|
984
|
+
I / total > 0.85 ||
|
|
985
|
+
L / total > 0.85 ||
|
|
986
|
+
M / total > 0.85 ||
|
|
987
|
+
V / total > 0.85) {
|
|
988
|
+
colors.H = 'rgb(26, 179, 179)';
|
|
989
|
+
colors.Y = 'rgb(26, 179, 179)';
|
|
990
|
+
}
|
|
991
|
+
return colors;
|
|
992
|
+
});
|
|
993
|
+
},
|
|
994
|
+
/**
|
|
995
|
+
* #getter
|
|
996
|
+
* Conservation score per column using Shannon entropy (biojs-msa style).
|
|
997
|
+
* Conservation = (1 - H/Hmax) * (1 - gapFraction)
|
|
998
|
+
* Returns values 0-1 where 1 = fully conserved, 0 = no conservation.
|
|
999
|
+
*/
|
|
1000
|
+
get conservation() {
|
|
1001
|
+
const { colStats, colStatsSums } = this;
|
|
1002
|
+
const alphabetSize = 20;
|
|
1003
|
+
const maxEntropy = Math.log2(alphabetSize);
|
|
1004
|
+
return colStats.map((stats, i) => {
|
|
1005
|
+
const total = colStatsSums[i];
|
|
1006
|
+
if (!total) {
|
|
1007
|
+
return 0;
|
|
1008
|
+
}
|
|
1009
|
+
const gapCount = (stats['-'] || 0) + (stats['.'] || 0);
|
|
1010
|
+
const nonGapTotal = total - gapCount;
|
|
1011
|
+
if (nonGapTotal === 0) {
|
|
1012
|
+
return 0;
|
|
1013
|
+
}
|
|
1014
|
+
let entropy = 0;
|
|
1015
|
+
for (const letter of Object.keys(stats)) {
|
|
1016
|
+
if (letter === '-' || letter === '.') {
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
const count = stats[letter];
|
|
1020
|
+
const freq = count / nonGapTotal;
|
|
1021
|
+
if (freq > 0) {
|
|
1022
|
+
entropy -= freq * Math.log2(freq);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
const gapFraction = gapCount / total;
|
|
1026
|
+
const conservation = Math.max(0, 1 - entropy / maxEntropy);
|
|
1027
|
+
return conservation * (1 - gapFraction);
|
|
1028
|
+
});
|
|
1029
|
+
},
|
|
737
1030
|
/**
|
|
738
1031
|
* #getter
|
|
739
1032
|
* generates a new tree that is clustered with x,y positions
|
|
@@ -765,6 +1058,13 @@ function stateModelFactory() {
|
|
|
765
1058
|
get allBranchesLength0() {
|
|
766
1059
|
return this.hierarchy.links().every(s => !s.source.data.length);
|
|
767
1060
|
},
|
|
1061
|
+
/**
|
|
1062
|
+
* #getter
|
|
1063
|
+
* effective showBranchLen accounting for allBranchesLength0
|
|
1064
|
+
*/
|
|
1065
|
+
get showBranchLenEffective() {
|
|
1066
|
+
return this.allBranchesLength0 ? false : self.showBranchLen;
|
|
1067
|
+
},
|
|
768
1068
|
}))
|
|
769
1069
|
.views(self => ({
|
|
770
1070
|
/**
|
|
@@ -851,6 +1151,17 @@ function stateModelFactory() {
|
|
|
851
1151
|
setDrawMsaLetters(arg) {
|
|
852
1152
|
self.drawMsaLetters = arg;
|
|
853
1153
|
},
|
|
1154
|
+
/**
|
|
1155
|
+
* #action
|
|
1156
|
+
* Calculate a neighbor joining tree from the current MSA using BLOSUM62 distances
|
|
1157
|
+
*/
|
|
1158
|
+
calculateNeighborJoiningTreeFromMSA() {
|
|
1159
|
+
if (self.rows.length < 2) {
|
|
1160
|
+
throw new Error('Need at least 2 sequences to build a tree');
|
|
1161
|
+
}
|
|
1162
|
+
const newickTree = calculateNeighborJoiningTree(self.rows);
|
|
1163
|
+
self.setTree(newickTree);
|
|
1164
|
+
},
|
|
854
1165
|
/**
|
|
855
1166
|
* #action
|
|
856
1167
|
*/
|
|
@@ -972,33 +1283,17 @@ function stateModelFactory() {
|
|
|
972
1283
|
get seqConsensus() {
|
|
973
1284
|
return self.MSA?.seqConsensus;
|
|
974
1285
|
},
|
|
975
|
-
/**
|
|
976
|
-
* #getter
|
|
977
|
-
*/
|
|
978
|
-
get conservation() {
|
|
979
|
-
if (self.columns2d.length) {
|
|
980
|
-
for (let i = 0; i < self.columns2d[0].length; i++) {
|
|
981
|
-
const col = [];
|
|
982
|
-
for (const column of self.columns2d) {
|
|
983
|
-
col.push(column[i]);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
return ['a'];
|
|
988
|
-
},
|
|
989
1286
|
/**
|
|
990
1287
|
* #getter
|
|
991
1288
|
*/
|
|
992
1289
|
get adapterTrackModels() {
|
|
993
|
-
const { rowHeight, MSA,
|
|
994
|
-
return (MSA?.tracks
|
|
1290
|
+
const { rowHeight, MSA, hideGapsEffective, blanks } = self;
|
|
1291
|
+
return (MSA?.tracks
|
|
1292
|
+
.filter(t => t.data)
|
|
1293
|
+
.map(t => ({
|
|
995
1294
|
model: {
|
|
996
1295
|
...t,
|
|
997
|
-
data: t.data
|
|
998
|
-
? hideGaps
|
|
999
|
-
? skipBlanks(blanks, t.data)
|
|
1000
|
-
: t.data
|
|
1001
|
-
: undefined,
|
|
1296
|
+
data: hideGapsEffective ? skipBlanks(blanks, t.data) : t.data,
|
|
1002
1297
|
height: rowHeight,
|
|
1003
1298
|
},
|
|
1004
1299
|
ReactComponent: TextTrack,
|
|
@@ -1008,7 +1303,15 @@ function stateModelFactory() {
|
|
|
1008
1303
|
* #getter
|
|
1009
1304
|
*/
|
|
1010
1305
|
get tracks() {
|
|
1011
|
-
|
|
1306
|
+
const conservationTrack = {
|
|
1307
|
+
model: {
|
|
1308
|
+
id: 'conservation',
|
|
1309
|
+
name: 'Conservation',
|
|
1310
|
+
height: 40,
|
|
1311
|
+
},
|
|
1312
|
+
ReactComponent: ConservationTrack,
|
|
1313
|
+
};
|
|
1314
|
+
return [...this.adapterTrackModels, conservationTrack];
|
|
1012
1315
|
},
|
|
1013
1316
|
/**
|
|
1014
1317
|
* #getter
|
|
@@ -1359,81 +1662,77 @@ function stateModelFactory() {
|
|
|
1359
1662
|
}))
|
|
1360
1663
|
.postProcessSnapshot(result => {
|
|
1361
1664
|
const snap = result;
|
|
1362
|
-
const { data: { tree, msa, treeMetadata },
|
|
1363
|
-
//
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
drawMsaLetters: true,
|
|
1372
|
-
height: 550,
|
|
1373
|
-
rowHeight: defaultRowHeight,
|
|
1374
|
-
scrollY: 0,
|
|
1375
|
-
scrollX: 0,
|
|
1376
|
-
colWidth: defaultColWidth,
|
|
1377
|
-
currentAlignment: 0,
|
|
1378
|
-
// MSA model defaults
|
|
1379
|
-
bgColor: true,
|
|
1380
|
-
colorSchemeName: 'maeditor',
|
|
1381
|
-
// Tree model defaults
|
|
1382
|
-
drawLabels: true,
|
|
1383
|
-
labelsAlignRight: false,
|
|
1384
|
-
treeAreaWidth: 400,
|
|
1385
|
-
treeWidth: 300,
|
|
1386
|
-
treeWidthMatchesArea: true,
|
|
1387
|
-
showBranchLen: true,
|
|
1388
|
-
drawTree: true,
|
|
1389
|
-
drawNodeBubbles: true,
|
|
1390
|
-
};
|
|
1391
|
-
// Properties that should always be included even if they match defaults
|
|
1392
|
-
const alwaysInclude = new Set(['id', 'type', 'relativeTo']);
|
|
1393
|
-
// Filter out properties that match default values
|
|
1394
|
-
function filterDefaults(obj) {
|
|
1395
|
-
const filtered = {};
|
|
1396
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
1397
|
-
// Always include essential properties
|
|
1398
|
-
if (alwaysInclude.has(key)) {
|
|
1399
|
-
filtered[key] = value;
|
|
1400
|
-
continue;
|
|
1401
|
-
}
|
|
1402
|
-
// Skip if value matches default
|
|
1403
|
-
if (defaults[key] === value) {
|
|
1404
|
-
continue;
|
|
1405
|
-
}
|
|
1406
|
-
// Handle nested objects
|
|
1407
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
1408
|
-
const filteredNested = filterDefaults(value);
|
|
1409
|
-
// Only include nested object if it has non-default properties
|
|
1410
|
-
if (Object.keys(filteredNested).length > 0) {
|
|
1411
|
-
filtered[key] = filteredNested;
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
else if (Array.isArray(value)) {
|
|
1415
|
-
// Only include arrays that aren't empty
|
|
1416
|
-
if (value.length > 0) {
|
|
1417
|
-
filtered[key] = value;
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
else {
|
|
1421
|
-
// Include non-default primitives
|
|
1422
|
-
filtered[key] = value;
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
return filtered;
|
|
1426
|
-
}
|
|
1427
|
-
const filteredRest = filterDefaults(rest);
|
|
1665
|
+
const { data: { tree, msa, treeMetadata },
|
|
1666
|
+
// Main model properties
|
|
1667
|
+
showDomains, hideGaps, allowedGappyness, contrastLettering, subFeatureRows, drawMsaLetters, height, rowHeight, scrollY, scrollX, colWidth, currentAlignment, collapsed, collapsedLeaves, showOnly, turnedOffTracks, featureFilters, relativeTo,
|
|
1668
|
+
// MSA model properties
|
|
1669
|
+
bgColor, colorSchemeName,
|
|
1670
|
+
// Tree model properties
|
|
1671
|
+
drawLabels, labelsAlignRight, treeAreaWidth, treeWidth, treeWidthMatchesArea, showBranchLen, drawTree, drawNodeBubbles,
|
|
1672
|
+
// Always include
|
|
1673
|
+
...rest } = snap;
|
|
1428
1674
|
// remove the MSA/tree data from the tree if the filehandle available in
|
|
1429
1675
|
// which case it can be reloaded on refresh
|
|
1430
1676
|
return {
|
|
1677
|
+
...rest,
|
|
1431
1678
|
data: {
|
|
1432
1679
|
...(result.treeFilehandle ? {} : { tree }),
|
|
1433
1680
|
...(result.msaFilehandle ? {} : { msa }),
|
|
1434
1681
|
...(result.treeMetadataFilehandle ? {} : { treeMetadata }),
|
|
1435
1682
|
},
|
|
1436
|
-
|
|
1683
|
+
// Main model - only include non-default values
|
|
1684
|
+
...(showDomains !== defaultShowDomains ? { showDomains } : {}),
|
|
1685
|
+
...(hideGaps !== defaultHideGaps ? { hideGaps } : {}),
|
|
1686
|
+
...(allowedGappyness !== defaultAllowedGappyness
|
|
1687
|
+
? { allowedGappyness }
|
|
1688
|
+
: {}),
|
|
1689
|
+
...(contrastLettering !== defaultContrastLettering
|
|
1690
|
+
? { contrastLettering }
|
|
1691
|
+
: {}),
|
|
1692
|
+
...(subFeatureRows !== defaultSubFeatureRows ? { subFeatureRows } : {}),
|
|
1693
|
+
...(drawMsaLetters !== defaultDrawMsaLetters ? { drawMsaLetters } : {}),
|
|
1694
|
+
...(height !== defaultHeight ? { height } : {}),
|
|
1695
|
+
...(rowHeight !== defaultRowHeight ? { rowHeight } : {}),
|
|
1696
|
+
...(scrollY !== defaultScrollY ? { scrollY } : {}),
|
|
1697
|
+
...(scrollX !== defaultScrollX ? { scrollX } : {}),
|
|
1698
|
+
...(colWidth !== defaultColWidth ? { colWidth } : {}),
|
|
1699
|
+
...(currentAlignment !== defaultCurrentAlignment
|
|
1700
|
+
? { currentAlignment }
|
|
1701
|
+
: {}),
|
|
1702
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1703
|
+
...(collapsed?.length ? { collapsed } : {}),
|
|
1704
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1705
|
+
...(collapsedLeaves?.length ? { collapsedLeaves } : {}),
|
|
1706
|
+
...(showOnly !== undefined ? { showOnly } : {}),
|
|
1707
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1708
|
+
...(turnedOffTracks && Object.keys(turnedOffTracks).length > 0
|
|
1709
|
+
? { turnedOffTracks }
|
|
1710
|
+
: {}),
|
|
1711
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1712
|
+
...(featureFilters && Object.keys(featureFilters).length > 0
|
|
1713
|
+
? { featureFilters }
|
|
1714
|
+
: {}),
|
|
1715
|
+
...(relativeTo !== undefined ? { relativeTo } : {}),
|
|
1716
|
+
// MSA model - only include non-default values
|
|
1717
|
+
...(bgColor !== defaultBgColor ? { bgColor } : {}),
|
|
1718
|
+
...(colorSchemeName !== defaultColorSchemeName
|
|
1719
|
+
? { colorSchemeName }
|
|
1720
|
+
: {}),
|
|
1721
|
+
// Tree model - only include non-default values
|
|
1722
|
+
...(drawLabels !== defaultDrawLabels ? { drawLabels } : {}),
|
|
1723
|
+
...(labelsAlignRight !== defaultLabelsAlignRight
|
|
1724
|
+
? { labelsAlignRight }
|
|
1725
|
+
: {}),
|
|
1726
|
+
...(treeAreaWidth !== defaultTreeAreaWidth ? { treeAreaWidth } : {}),
|
|
1727
|
+
...(treeWidth !== defaultTreeWidth ? { treeWidth } : {}),
|
|
1728
|
+
...(treeWidthMatchesArea !== defaultTreeWidthMatchesArea
|
|
1729
|
+
? { treeWidthMatchesArea }
|
|
1730
|
+
: {}),
|
|
1731
|
+
...(showBranchLen !== defaultShowBranchLen ? { showBranchLen } : {}),
|
|
1732
|
+
...(drawTree !== defaultDrawTree ? { drawTree } : {}),
|
|
1733
|
+
...(drawNodeBubbles !== defaultDrawNodeBubbles
|
|
1734
|
+
? { drawNodeBubbles }
|
|
1735
|
+
: {}),
|
|
1437
1736
|
};
|
|
1438
1737
|
});
|
|
1439
1738
|
}
|