react-msaview 4.4.5 → 4.5.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 +53 -10
- package/dist/components/msa/renderMSABlock.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/TreeCanvasBlock.js +1 -1
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeNodeMenu.js +2 -2
- package/dist/components/tree/TreeNodeMenu.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +1 -1
- 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 +256 -15
- package/dist/model.js +408 -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 +3 -5
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/rowCoordinateCalculations.test.js +14 -2
- package/dist/rowCoordinateCalculations.test.js.map +1 -1
- package/dist/seqCoordToRowSpecificGlobalCoord.js +9 -5
- package/dist/seqCoordToRowSpecificGlobalCoord.js.map +1 -1
- package/dist/seqCoordToRowSpecificGlobalCoord.test.js +6 -6
- 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 +82 -22
- package/src/components/tracks/renderTracksSvg.ts +157 -0
- package/src/components/tree/TreeCanvasBlock.tsx +1 -1
- package/src/components/tree/TreeNodeMenu.tsx +2 -2
- package/src/components/tree/renderTreeCanvas.ts +1 -1
- 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 +496 -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.test.ts +15 -2
- package/src/rowCoordinateCalculations.ts +3 -5
- package/src/seqCoordToRowSpecificGlobalCoord.test.ts +6 -6
- package/src/seqCoordToRowSpecificGlobalCoord.ts +9 -4
- 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/src/model.ts
CHANGED
|
@@ -20,7 +20,32 @@ import Stockholm from 'stockholm-js'
|
|
|
20
20
|
|
|
21
21
|
import { blocksX, blocksY } from './calculateBlocks'
|
|
22
22
|
import colorSchemes from './colorSchemes'
|
|
23
|
+
import ConservationTrack from './components/ConservationTrack'
|
|
23
24
|
import TextTrack from './components/TextTrack'
|
|
25
|
+
import {
|
|
26
|
+
defaultAllowedGappyness,
|
|
27
|
+
defaultBgColor,
|
|
28
|
+
defaultColWidth,
|
|
29
|
+
defaultColorSchemeName,
|
|
30
|
+
defaultContrastLettering,
|
|
31
|
+
defaultCurrentAlignment,
|
|
32
|
+
defaultDrawLabels,
|
|
33
|
+
defaultDrawMsaLetters,
|
|
34
|
+
defaultDrawNodeBubbles,
|
|
35
|
+
defaultDrawTree,
|
|
36
|
+
defaultHeight,
|
|
37
|
+
defaultHideGaps,
|
|
38
|
+
defaultLabelsAlignRight,
|
|
39
|
+
defaultRowHeight,
|
|
40
|
+
defaultScrollX,
|
|
41
|
+
defaultScrollY,
|
|
42
|
+
defaultShowBranchLen,
|
|
43
|
+
defaultShowDomains,
|
|
44
|
+
defaultSubFeatureRows,
|
|
45
|
+
defaultTreeAreaWidth,
|
|
46
|
+
defaultTreeWidth,
|
|
47
|
+
defaultTreeWidthMatchesArea,
|
|
48
|
+
} from './constants'
|
|
24
49
|
import { flatToTree } from './flatToTree'
|
|
25
50
|
import palettes from './ggplotPalettes'
|
|
26
51
|
import { measureTextCanvas } from './measureTextCanvas'
|
|
@@ -28,8 +53,10 @@ import { DataModelF } from './model/DataModel'
|
|
|
28
53
|
import { DialogQueueSessionMixin } from './model/DialogQueue'
|
|
29
54
|
import { MSAModelF } from './model/msaModel'
|
|
30
55
|
import { TreeModelF } from './model/treeModel'
|
|
56
|
+
import { calculateNeighborJoiningTree } from './neighborJoining'
|
|
31
57
|
import { parseAsn1 } from './parseAsn1'
|
|
32
58
|
import parseNewick from './parseNewick'
|
|
59
|
+
import A3mMSA from './parsers/A3mMSA'
|
|
33
60
|
import ClustalMSA from './parsers/ClustalMSA'
|
|
34
61
|
import EmfMSA from './parsers/EmfMSA'
|
|
35
62
|
import FastaMSA from './parsers/FastaMSA'
|
|
@@ -43,7 +70,6 @@ import { seqCoordToRowSpecificGlobalCoord } from './seqCoordToRowSpecificGlobalC
|
|
|
43
70
|
import {
|
|
44
71
|
collapse,
|
|
45
72
|
generateNodeIds,
|
|
46
|
-
isBlank,
|
|
47
73
|
len,
|
|
48
74
|
maxLength,
|
|
49
75
|
setBrLength,
|
|
@@ -63,8 +89,6 @@ import type { Theme } from '@mui/material'
|
|
|
63
89
|
import type { HierarchyNode } from 'd3-hierarchy'
|
|
64
90
|
import type { Instance } from 'mobx-state-tree'
|
|
65
91
|
|
|
66
|
-
const defaultRowHeight = 16
|
|
67
|
-
const defaultColWidth = 12
|
|
68
92
|
const showZoomStarKey = 'msa-showZoomStar'
|
|
69
93
|
|
|
70
94
|
/**
|
|
@@ -90,24 +114,24 @@ function stateModelFactory() {
|
|
|
90
114
|
/**
|
|
91
115
|
* #property
|
|
92
116
|
*/
|
|
93
|
-
showDomains:
|
|
117
|
+
showDomains: defaultShowDomains,
|
|
94
118
|
/**
|
|
95
119
|
* #property
|
|
96
120
|
*/
|
|
97
|
-
hideGaps:
|
|
121
|
+
hideGaps: defaultHideGaps,
|
|
98
122
|
/**
|
|
99
123
|
* #property
|
|
100
124
|
*/
|
|
101
|
-
allowedGappyness:
|
|
125
|
+
allowedGappyness: defaultAllowedGappyness,
|
|
102
126
|
/**
|
|
103
127
|
* #property
|
|
104
128
|
*/
|
|
105
|
-
contrastLettering:
|
|
129
|
+
contrastLettering: defaultContrastLettering,
|
|
106
130
|
|
|
107
131
|
/**
|
|
108
132
|
* #property
|
|
109
133
|
*/
|
|
110
|
-
subFeatureRows:
|
|
134
|
+
subFeatureRows: defaultSubFeatureRows,
|
|
111
135
|
|
|
112
136
|
/**
|
|
113
137
|
* #property
|
|
@@ -118,13 +142,13 @@ function stateModelFactory() {
|
|
|
118
142
|
/**
|
|
119
143
|
* #property
|
|
120
144
|
*/
|
|
121
|
-
drawMsaLetters:
|
|
145
|
+
drawMsaLetters: defaultDrawMsaLetters,
|
|
122
146
|
|
|
123
147
|
/**
|
|
124
148
|
* #property
|
|
125
149
|
* height of the div containing the view, px
|
|
126
150
|
*/
|
|
127
|
-
height: types.optional(types.number,
|
|
151
|
+
height: types.optional(types.number, defaultHeight),
|
|
128
152
|
|
|
129
153
|
/**
|
|
130
154
|
* #property
|
|
@@ -136,13 +160,13 @@ function stateModelFactory() {
|
|
|
136
160
|
* #property
|
|
137
161
|
* scroll position, Y-offset, px
|
|
138
162
|
*/
|
|
139
|
-
scrollY:
|
|
163
|
+
scrollY: defaultScrollY,
|
|
140
164
|
|
|
141
165
|
/**
|
|
142
166
|
* #property
|
|
143
167
|
* scroll position, X-offset, px
|
|
144
168
|
*/
|
|
145
|
-
scrollX:
|
|
169
|
+
scrollX: defaultScrollX,
|
|
146
170
|
|
|
147
171
|
/**
|
|
148
172
|
* #property
|
|
@@ -173,7 +197,7 @@ function stateModelFactory() {
|
|
|
173
197
|
* #property
|
|
174
198
|
*
|
|
175
199
|
*/
|
|
176
|
-
currentAlignment:
|
|
200
|
+
currentAlignment: defaultCurrentAlignment,
|
|
177
201
|
|
|
178
202
|
/**
|
|
179
203
|
* #property
|
|
@@ -363,7 +387,7 @@ function stateModelFactory() {
|
|
|
363
387
|
self.loadingMSA = arg
|
|
364
388
|
},
|
|
365
389
|
/**
|
|
366
|
-
* #
|
|
390
|
+
* #action
|
|
367
391
|
*/
|
|
368
392
|
setShowZoomStar(arg: boolean) {
|
|
369
393
|
self.showZoomStar = arg
|
|
@@ -416,8 +440,8 @@ function stateModelFactory() {
|
|
|
416
440
|
}
|
|
417
441
|
|
|
418
442
|
// Find the node in the hierarchy
|
|
419
|
-
const node = (self as
|
|
420
|
-
|
|
443
|
+
const node = (self as MsaViewModel).hierarchy.find(
|
|
444
|
+
n => n.data.id === nodeId,
|
|
421
445
|
)
|
|
422
446
|
if (!node) {
|
|
423
447
|
self.hoveredTreeNode = undefined
|
|
@@ -497,7 +521,7 @@ function stateModelFactory() {
|
|
|
497
521
|
/**
|
|
498
522
|
* #action
|
|
499
523
|
*/
|
|
500
|
-
|
|
524
|
+
toggleCollapsedLeaf(node: string) {
|
|
501
525
|
if (self.collapsedLeaves.includes(node)) {
|
|
502
526
|
self.collapsedLeaves.remove(node)
|
|
503
527
|
} else {
|
|
@@ -555,11 +579,23 @@ function stateModelFactory() {
|
|
|
555
579
|
}))
|
|
556
580
|
|
|
557
581
|
.views(self => ({
|
|
582
|
+
/**
|
|
583
|
+
* #getter
|
|
584
|
+
* hideGaps takes effect when there are collapsed rows or allowedGappyness < 100
|
|
585
|
+
*/
|
|
586
|
+
get hideGapsEffective() {
|
|
587
|
+
return (
|
|
588
|
+
self.hideGaps &&
|
|
589
|
+
(self.collapsed.length > 0 ||
|
|
590
|
+
self.collapsedLeaves.length > 0 ||
|
|
591
|
+
self.allowedGappyness < 100)
|
|
592
|
+
)
|
|
593
|
+
},
|
|
558
594
|
/**
|
|
559
595
|
* #getter
|
|
560
596
|
*/
|
|
561
597
|
get realAllowedGappyness() {
|
|
562
|
-
return
|
|
598
|
+
return this.hideGapsEffective ? self.allowedGappyness : 100
|
|
563
599
|
},
|
|
564
600
|
/**
|
|
565
601
|
* #getter
|
|
@@ -643,6 +679,8 @@ function stateModelFactory() {
|
|
|
643
679
|
if (text) {
|
|
644
680
|
if (Stockholm.sniff(text)) {
|
|
645
681
|
return new StockholmMSA(text, self.currentAlignment)
|
|
682
|
+
} else if (A3mMSA.sniff(text)) {
|
|
683
|
+
return new A3mMSA(text)
|
|
646
684
|
} else if (text.startsWith('>')) {
|
|
647
685
|
return new FastaMSA(text)
|
|
648
686
|
} else if (text.startsWith('SEQ')) {
|
|
@@ -697,6 +735,33 @@ function stateModelFactory() {
|
|
|
697
735
|
const { mouseRow } = self
|
|
698
736
|
return mouseRow === undefined ? undefined : this.rowNames[mouseRow]
|
|
699
737
|
},
|
|
738
|
+
/**
|
|
739
|
+
* #getter
|
|
740
|
+
* Returns insertion info if mouse is hovering over an insertion indicator
|
|
741
|
+
*/
|
|
742
|
+
get hoveredInsertion() {
|
|
743
|
+
const { mouseCol, mouseRow } = self
|
|
744
|
+
if (mouseCol === undefined || mouseRow === undefined) {
|
|
745
|
+
return undefined
|
|
746
|
+
}
|
|
747
|
+
const rowName = this.rowNames[mouseRow]
|
|
748
|
+
if (!rowName) {
|
|
749
|
+
return undefined
|
|
750
|
+
}
|
|
751
|
+
const insertions = this.insertionPositions.get(rowName)
|
|
752
|
+
if (!insertions) {
|
|
753
|
+
return undefined
|
|
754
|
+
}
|
|
755
|
+
const insertion = insertions.find(ins => ins.pos === mouseCol)
|
|
756
|
+
if (insertion) {
|
|
757
|
+
return {
|
|
758
|
+
rowName,
|
|
759
|
+
col: mouseCol,
|
|
760
|
+
letters: insertion.letters,
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return undefined
|
|
764
|
+
},
|
|
700
765
|
|
|
701
766
|
/**
|
|
702
767
|
* #getter
|
|
@@ -719,7 +784,7 @@ function stateModelFactory() {
|
|
|
719
784
|
;[...self.collapsed, ...self.collapsedLeaves]
|
|
720
785
|
.map(collapsedId => hier.find(node => node.data.id === collapsedId))
|
|
721
786
|
.filter(notEmpty)
|
|
722
|
-
.
|
|
787
|
+
.forEach(node => {
|
|
723
788
|
collapse(node)
|
|
724
789
|
})
|
|
725
790
|
|
|
@@ -744,28 +809,35 @@ function stateModelFactory() {
|
|
|
744
809
|
* #getter
|
|
745
810
|
*/
|
|
746
811
|
get blanks() {
|
|
747
|
-
const {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
812
|
+
const { hideGapsEffective, realAllowedGappyness } = self
|
|
813
|
+
if (!hideGapsEffective) {
|
|
814
|
+
return []
|
|
815
|
+
}
|
|
816
|
+
const strs = this.leaves
|
|
817
|
+
.map(leaf => this.MSA?.getRow(leaf.data.name))
|
|
818
|
+
.filter(notEmpty)
|
|
819
|
+
if (strs.length === 0) {
|
|
820
|
+
return []
|
|
821
|
+
}
|
|
822
|
+
const numCols = strs[0]!.length
|
|
823
|
+
const numRows = strs.length
|
|
824
|
+
const threshold = Math.ceil((realAllowedGappyness / 100) * numRows)
|
|
825
|
+
const blankCounts = new Uint16Array(numCols)
|
|
826
|
+
for (let j = 0; j < numRows; j++) {
|
|
827
|
+
const str = strs[j]!
|
|
828
|
+
for (let i = 0; i < numCols; i++) {
|
|
829
|
+
// bit trick: (code - 45) >>> 0 <= 1 checks for '-' (45) or '.' (46)
|
|
830
|
+
if ((str.charCodeAt(i) - 45) >>> 0 <= 1) {
|
|
831
|
+
blankCounts[i]!++
|
|
766
832
|
}
|
|
767
833
|
}
|
|
768
834
|
}
|
|
835
|
+
const blanks = []
|
|
836
|
+
for (let i = 0; i < numCols; i++) {
|
|
837
|
+
if (blankCounts[i]! >= threshold) {
|
|
838
|
+
blanks.push(i)
|
|
839
|
+
}
|
|
840
|
+
}
|
|
769
841
|
return blanks
|
|
770
842
|
},
|
|
771
843
|
/**
|
|
@@ -774,6 +846,60 @@ function stateModelFactory() {
|
|
|
774
846
|
get blanksSet() {
|
|
775
847
|
return new Set(this.blanks)
|
|
776
848
|
},
|
|
849
|
+
/**
|
|
850
|
+
* #getter
|
|
851
|
+
* Returns a map of row name to array of insertions with display position and letters
|
|
852
|
+
*/
|
|
853
|
+
get insertionPositions() {
|
|
854
|
+
const { hideGapsEffective } = self
|
|
855
|
+
const { blanks, rows } = this
|
|
856
|
+
const blanksLen = blanks.length
|
|
857
|
+
if (blanksLen === 0 || !hideGapsEffective) {
|
|
858
|
+
return new Map<string, { pos: number; letters: string }[]>()
|
|
859
|
+
}
|
|
860
|
+
const result = new Map<string, { pos: number; letters: string }[]>()
|
|
861
|
+
for (const [name, seq] of rows) {
|
|
862
|
+
const insertions: { pos: number; letters: string }[] = []
|
|
863
|
+
let displayPos = 0
|
|
864
|
+
let blankIdx = 0
|
|
865
|
+
let currentInsertPos = -1
|
|
866
|
+
let letterChars: string[] = []
|
|
867
|
+
const seqLen = seq.length
|
|
868
|
+
for (let i = 0; i < seqLen; i++) {
|
|
869
|
+
if (blankIdx < blanksLen && blanks[blankIdx] === i) {
|
|
870
|
+
// bit trick: (code - 45) >>> 0 <= 1 checks for '-' (45) or '.' (46)
|
|
871
|
+
const code = seq.charCodeAt(i)
|
|
872
|
+
if (!((code - 45) >>> 0 <= 1)) {
|
|
873
|
+
if (currentInsertPos === displayPos) {
|
|
874
|
+
letterChars.push(seq[i]!)
|
|
875
|
+
} else {
|
|
876
|
+
if (letterChars.length > 0) {
|
|
877
|
+
insertions.push({
|
|
878
|
+
pos: currentInsertPos,
|
|
879
|
+
letters: letterChars.join(''),
|
|
880
|
+
})
|
|
881
|
+
}
|
|
882
|
+
currentInsertPos = displayPos
|
|
883
|
+
letterChars = [seq[i]!]
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
blankIdx++
|
|
887
|
+
} else {
|
|
888
|
+
displayPos++
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (letterChars.length > 0) {
|
|
892
|
+
insertions.push({
|
|
893
|
+
pos: currentInsertPos,
|
|
894
|
+
letters: letterChars.join(''),
|
|
895
|
+
})
|
|
896
|
+
}
|
|
897
|
+
if (insertions.length > 0) {
|
|
898
|
+
result.set(name, insertions)
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return result
|
|
902
|
+
},
|
|
777
903
|
/**
|
|
778
904
|
* #getter
|
|
779
905
|
*/
|
|
@@ -810,10 +936,10 @@ function stateModelFactory() {
|
|
|
810
936
|
* #getter
|
|
811
937
|
*/
|
|
812
938
|
get columns2d() {
|
|
813
|
-
const {
|
|
939
|
+
const { hideGapsEffective } = self
|
|
814
940
|
return this.rows
|
|
815
941
|
.map(r => r[1])
|
|
816
|
-
.map(str => (
|
|
942
|
+
.map(str => (hideGapsEffective ? skipBlanks(this.blanks, str) : str))
|
|
817
943
|
},
|
|
818
944
|
/**
|
|
819
945
|
* #getter
|
|
@@ -847,6 +973,216 @@ function stateModelFactory() {
|
|
|
847
973
|
get colStatsSums() {
|
|
848
974
|
return this.colStats.map(val => sum(Object.values(val)))
|
|
849
975
|
},
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* #getter
|
|
979
|
+
* Pre-computed consensus letter and percent identity color per column.
|
|
980
|
+
* Used by percent_identity_dynamic color scheme.
|
|
981
|
+
*/
|
|
982
|
+
get colConsensus() {
|
|
983
|
+
const { colStats, colStatsSums } = this
|
|
984
|
+
return colStats.map((stats, i) => {
|
|
985
|
+
const total = colStatsSums[i]!
|
|
986
|
+
let maxCount = 0
|
|
987
|
+
let letter = ''
|
|
988
|
+
for (const key in stats) {
|
|
989
|
+
const val = stats[key]!
|
|
990
|
+
if (val > maxCount && key !== '-' && key !== '.') {
|
|
991
|
+
maxCount = val
|
|
992
|
+
letter = key
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
const proportion = maxCount / total
|
|
996
|
+
return {
|
|
997
|
+
letter,
|
|
998
|
+
color:
|
|
999
|
+
proportion > 0.4
|
|
1000
|
+
? `hsl(240, 30%, ${100 * Math.max(1 - proportion / 3, 0.3)}%)`
|
|
1001
|
+
: undefined,
|
|
1002
|
+
}
|
|
1003
|
+
})
|
|
1004
|
+
},
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* #getter
|
|
1008
|
+
* Pre-computed ClustalX colors per column.
|
|
1009
|
+
* Returns a map of letter -> color for each column.
|
|
1010
|
+
* ref http://www.jalview.org/help/html/colourSchemes/clustal.html
|
|
1011
|
+
*/
|
|
1012
|
+
get colClustalX() {
|
|
1013
|
+
const { colStats, colStatsSums } = this
|
|
1014
|
+
return colStats.map((stats, i) => {
|
|
1015
|
+
const total = colStatsSums[i]!
|
|
1016
|
+
const colors: Record<string, string> = {}
|
|
1017
|
+
|
|
1018
|
+
const W = stats.W ?? 0
|
|
1019
|
+
const L = stats.L ?? 0
|
|
1020
|
+
const V = stats.V ?? 0
|
|
1021
|
+
const I = stats.I ?? 0
|
|
1022
|
+
const M = stats.M ?? 0
|
|
1023
|
+
const A = stats.A ?? 0
|
|
1024
|
+
const F = stats.F ?? 0
|
|
1025
|
+
const C = stats.C ?? 0
|
|
1026
|
+
const H = stats.H ?? 0
|
|
1027
|
+
const P = stats.P ?? 0
|
|
1028
|
+
const R = stats.R ?? 0
|
|
1029
|
+
const K = stats.K ?? 0
|
|
1030
|
+
const Q = stats.Q ?? 0
|
|
1031
|
+
const E = stats.E ?? 0
|
|
1032
|
+
const D = stats.D ?? 0
|
|
1033
|
+
const T = stats.T ?? 0
|
|
1034
|
+
const S = stats.S ?? 0
|
|
1035
|
+
const G = stats.G ?? 0
|
|
1036
|
+
const Y = stats.Y ?? 0
|
|
1037
|
+
const N = stats.N ?? 0
|
|
1038
|
+
|
|
1039
|
+
const WLVIMAFCHPY = W + L + V + I + M + A + F + C + H + P + Y
|
|
1040
|
+
const KR = K + R
|
|
1041
|
+
const QE = Q + E
|
|
1042
|
+
const ED = E + D
|
|
1043
|
+
const TS = T + S
|
|
1044
|
+
|
|
1045
|
+
if (WLVIMAFCHPY / total > 0.6) {
|
|
1046
|
+
colors.W = 'rgb(128,179,230)'
|
|
1047
|
+
colors.L = 'rgb(128,179,230)'
|
|
1048
|
+
colors.V = 'rgb(128,179,230)'
|
|
1049
|
+
colors.A = 'rgb(128,179,230)'
|
|
1050
|
+
colors.I = 'rgb(128,179,230)'
|
|
1051
|
+
colors.M = 'rgb(128,179,230)'
|
|
1052
|
+
colors.F = 'rgb(128,179,230)'
|
|
1053
|
+
colors.C = 'rgb(128,179,230)'
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
if (
|
|
1057
|
+
KR / total > 0.6 ||
|
|
1058
|
+
K / total > 0.8 ||
|
|
1059
|
+
R / total > 0.8 ||
|
|
1060
|
+
Q / total > 0.8
|
|
1061
|
+
) {
|
|
1062
|
+
colors.K = '#d88'
|
|
1063
|
+
colors.R = '#d88'
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (
|
|
1067
|
+
KR / total > 0.6 ||
|
|
1068
|
+
QE / total > 0.5 ||
|
|
1069
|
+
E / total > 0.8 ||
|
|
1070
|
+
Q / total > 0.8 ||
|
|
1071
|
+
D / total > 0.8
|
|
1072
|
+
) {
|
|
1073
|
+
colors.E = 'rgb(192, 72, 192)'
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (
|
|
1077
|
+
KR / total > 0.6 ||
|
|
1078
|
+
ED / total > 0.5 ||
|
|
1079
|
+
K / total > 0.8 ||
|
|
1080
|
+
R / total > 0.8 ||
|
|
1081
|
+
Q / total > 0.8
|
|
1082
|
+
) {
|
|
1083
|
+
colors.D = 'rgb(204, 77, 204)'
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (N / total > 0.5 || Y / total > 0.85) {
|
|
1087
|
+
colors.N = '#8f8'
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (
|
|
1091
|
+
KR / total > 0.6 ||
|
|
1092
|
+
QE / total > 0.6 ||
|
|
1093
|
+
Q / total > 0.85 ||
|
|
1094
|
+
E / total > 0.85 ||
|
|
1095
|
+
K / total > 0.85 ||
|
|
1096
|
+
R / total > 0.85
|
|
1097
|
+
) {
|
|
1098
|
+
colors.Q = '#8f8'
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
if (
|
|
1102
|
+
WLVIMAFCHPY / total > 0.6 ||
|
|
1103
|
+
TS / total > 0.5 ||
|
|
1104
|
+
S / total > 0.85 ||
|
|
1105
|
+
T / total > 0.85
|
|
1106
|
+
) {
|
|
1107
|
+
colors.S = 'rgb(26,204,26)'
|
|
1108
|
+
colors.T = 'rgb(26,204,26)'
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
if (C / total > 0.85) {
|
|
1112
|
+
colors.C = 'rgb(240, 128, 128)'
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (G / total > 0) {
|
|
1116
|
+
colors.G = 'rgb(240, 144, 72)'
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
if (P / total > 0) {
|
|
1120
|
+
colors.P = 'rgb(204, 204, 0)'
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if (
|
|
1124
|
+
WLVIMAFCHPY / total > 0.6 ||
|
|
1125
|
+
W / total > 0.85 ||
|
|
1126
|
+
Y / total > 0.85 ||
|
|
1127
|
+
A / total > 0.85 ||
|
|
1128
|
+
C / total > 0.85 ||
|
|
1129
|
+
P / total > 0.85 ||
|
|
1130
|
+
Q / total > 0.85 ||
|
|
1131
|
+
F / total > 0.85 ||
|
|
1132
|
+
H / total > 0.85 ||
|
|
1133
|
+
I / total > 0.85 ||
|
|
1134
|
+
L / total > 0.85 ||
|
|
1135
|
+
M / total > 0.85 ||
|
|
1136
|
+
V / total > 0.85
|
|
1137
|
+
) {
|
|
1138
|
+
colors.H = 'rgb(26, 179, 179)'
|
|
1139
|
+
colors.Y = 'rgb(26, 179, 179)'
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
return colors
|
|
1143
|
+
})
|
|
1144
|
+
},
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* #getter
|
|
1148
|
+
* Conservation score per column using Shannon entropy (biojs-msa style).
|
|
1149
|
+
* Conservation = (1 - H/Hmax) * (1 - gapFraction)
|
|
1150
|
+
* Returns values 0-1 where 1 = fully conserved, 0 = no conservation.
|
|
1151
|
+
*/
|
|
1152
|
+
get conservation() {
|
|
1153
|
+
const { colStats, colStatsSums } = this
|
|
1154
|
+
const alphabetSize = 20
|
|
1155
|
+
const maxEntropy = Math.log2(alphabetSize)
|
|
1156
|
+
|
|
1157
|
+
return colStats.map((stats, i) => {
|
|
1158
|
+
const total = colStatsSums[i]
|
|
1159
|
+
if (!total) {
|
|
1160
|
+
return 0
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
const gapCount = (stats['-'] || 0) + (stats['.'] || 0)
|
|
1164
|
+
const nonGapTotal = total - gapCount
|
|
1165
|
+
if (nonGapTotal === 0) {
|
|
1166
|
+
return 0
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
let entropy = 0
|
|
1170
|
+
for (const letter of Object.keys(stats)) {
|
|
1171
|
+
if (letter === '-' || letter === '.') {
|
|
1172
|
+
continue
|
|
1173
|
+
}
|
|
1174
|
+
const count = stats[letter]!
|
|
1175
|
+
const freq = count / nonGapTotal
|
|
1176
|
+
if (freq > 0) {
|
|
1177
|
+
entropy -= freq * Math.log2(freq)
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const gapFraction = gapCount / total
|
|
1182
|
+
const conservation = Math.max(0, 1 - entropy / maxEntropy)
|
|
1183
|
+
return conservation * (1 - gapFraction)
|
|
1184
|
+
})
|
|
1185
|
+
},
|
|
850
1186
|
/**
|
|
851
1187
|
* #getter
|
|
852
1188
|
* generates a new tree that is clustered with x,y positions
|
|
@@ -972,6 +1308,18 @@ function stateModelFactory() {
|
|
|
972
1308
|
self.drawMsaLetters = arg
|
|
973
1309
|
},
|
|
974
1310
|
|
|
1311
|
+
/**
|
|
1312
|
+
* #action
|
|
1313
|
+
* Calculate a neighbor joining tree from the current MSA using BLOSUM62 distances
|
|
1314
|
+
*/
|
|
1315
|
+
calculateNeighborJoiningTreeFromMSA() {
|
|
1316
|
+
if (self.rows.length < 2) {
|
|
1317
|
+
throw new Error('Need at least 2 sequences to build a tree')
|
|
1318
|
+
}
|
|
1319
|
+
const newickTree = calculateNeighborJoiningTree(self.rows)
|
|
1320
|
+
self.setTree(newickTree)
|
|
1321
|
+
},
|
|
1322
|
+
|
|
975
1323
|
/**
|
|
976
1324
|
* #action
|
|
977
1325
|
*/
|
|
@@ -1105,39 +1453,22 @@ function stateModelFactory() {
|
|
|
1105
1453
|
return self.MSA?.seqConsensus
|
|
1106
1454
|
},
|
|
1107
1455
|
|
|
1108
|
-
/**
|
|
1109
|
-
* #getter
|
|
1110
|
-
*/
|
|
1111
|
-
get conservation() {
|
|
1112
|
-
if (self.columns2d.length) {
|
|
1113
|
-
for (let i = 0; i < self.columns2d[0]!.length; i++) {
|
|
1114
|
-
const col = []
|
|
1115
|
-
for (const column of self.columns2d) {
|
|
1116
|
-
col.push(column[i])
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
return ['a']
|
|
1121
|
-
},
|
|
1122
|
-
|
|
1123
1456
|
/**
|
|
1124
1457
|
* #getter
|
|
1125
1458
|
*/
|
|
1126
1459
|
get adapterTrackModels(): BasicTrack[] {
|
|
1127
|
-
const { rowHeight, MSA,
|
|
1460
|
+
const { rowHeight, MSA, hideGapsEffective, blanks } = self
|
|
1128
1461
|
return (
|
|
1129
|
-
MSA?.tracks
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
}
|
|
1139
|
-
ReactComponent: TextTrack,
|
|
1140
|
-
})) || []
|
|
1462
|
+
MSA?.tracks
|
|
1463
|
+
.filter(t => t.data)
|
|
1464
|
+
.map(t => ({
|
|
1465
|
+
model: {
|
|
1466
|
+
...t,
|
|
1467
|
+
data: hideGapsEffective ? skipBlanks(blanks, t.data!) : t.data,
|
|
1468
|
+
height: rowHeight,
|
|
1469
|
+
} as TextTrackModel,
|
|
1470
|
+
ReactComponent: TextTrack,
|
|
1471
|
+
})) || []
|
|
1141
1472
|
)
|
|
1142
1473
|
},
|
|
1143
1474
|
|
|
@@ -1145,7 +1476,15 @@ function stateModelFactory() {
|
|
|
1145
1476
|
* #getter
|
|
1146
1477
|
*/
|
|
1147
1478
|
get tracks(): BasicTrack[] {
|
|
1148
|
-
|
|
1479
|
+
const conservationTrack: BasicTrack = {
|
|
1480
|
+
model: {
|
|
1481
|
+
id: 'conservation',
|
|
1482
|
+
name: 'Conservation',
|
|
1483
|
+
height: 40,
|
|
1484
|
+
},
|
|
1485
|
+
ReactComponent: ConservationTrack,
|
|
1486
|
+
}
|
|
1487
|
+
return [...this.adapterTrackModels, conservationTrack]
|
|
1149
1488
|
},
|
|
1150
1489
|
|
|
1151
1490
|
/**
|
|
@@ -1374,6 +1713,7 @@ function stateModelFactory() {
|
|
|
1374
1713
|
async exportSVG(opts: {
|
|
1375
1714
|
theme: Theme
|
|
1376
1715
|
includeMinimap?: boolean
|
|
1716
|
+
includeTracks?: boolean
|
|
1377
1717
|
exportType: string
|
|
1378
1718
|
}) {
|
|
1379
1719
|
const { renderToSvg } = await import('./renderToSvg')
|
|
@@ -1556,88 +1896,104 @@ function stateModelFactory() {
|
|
|
1556
1896
|
const snap = result as Omit<typeof result, symbol>
|
|
1557
1897
|
const {
|
|
1558
1898
|
data: { tree, msa, treeMetadata },
|
|
1899
|
+
// Main model properties
|
|
1900
|
+
showDomains,
|
|
1901
|
+
hideGaps,
|
|
1902
|
+
allowedGappyness,
|
|
1903
|
+
contrastLettering,
|
|
1904
|
+
subFeatureRows,
|
|
1905
|
+
drawMsaLetters,
|
|
1906
|
+
height,
|
|
1907
|
+
rowHeight,
|
|
1908
|
+
scrollY,
|
|
1909
|
+
scrollX,
|
|
1910
|
+
colWidth,
|
|
1911
|
+
currentAlignment,
|
|
1912
|
+
collapsed,
|
|
1913
|
+
collapsedLeaves,
|
|
1914
|
+
showOnly,
|
|
1915
|
+
turnedOffTracks,
|
|
1916
|
+
featureFilters,
|
|
1917
|
+
relativeTo,
|
|
1918
|
+
// MSA model properties
|
|
1919
|
+
bgColor,
|
|
1920
|
+
colorSchemeName,
|
|
1921
|
+
// Tree model properties
|
|
1922
|
+
drawLabels,
|
|
1923
|
+
labelsAlignRight,
|
|
1924
|
+
treeAreaWidth,
|
|
1925
|
+
treeWidth,
|
|
1926
|
+
treeWidthMatchesArea,
|
|
1927
|
+
showBranchLen,
|
|
1928
|
+
drawTree,
|
|
1929
|
+
drawNodeBubbles,
|
|
1930
|
+
// Always include
|
|
1559
1931
|
...rest
|
|
1560
1932
|
} = snap
|
|
1561
1933
|
|
|
1562
|
-
// Default values to filter out
|
|
1563
|
-
const defaults = {
|
|
1564
|
-
// Main model defaults
|
|
1565
|
-
showDomains: false,
|
|
1566
|
-
hideGaps: true,
|
|
1567
|
-
allowedGappyness: 100,
|
|
1568
|
-
contrastLettering: true,
|
|
1569
|
-
subFeatureRows: false,
|
|
1570
|
-
drawMsaLetters: true,
|
|
1571
|
-
height: 550,
|
|
1572
|
-
rowHeight: defaultRowHeight,
|
|
1573
|
-
scrollY: 0,
|
|
1574
|
-
scrollX: 0,
|
|
1575
|
-
colWidth: defaultColWidth,
|
|
1576
|
-
currentAlignment: 0,
|
|
1577
|
-
// MSA model defaults
|
|
1578
|
-
bgColor: true,
|
|
1579
|
-
colorSchemeName: 'maeditor',
|
|
1580
|
-
// Tree model defaults
|
|
1581
|
-
drawLabels: true,
|
|
1582
|
-
labelsAlignRight: false,
|
|
1583
|
-
treeAreaWidth: 400,
|
|
1584
|
-
treeWidth: 300,
|
|
1585
|
-
treeWidthMatchesArea: true,
|
|
1586
|
-
showBranchLen: true,
|
|
1587
|
-
drawTree: true,
|
|
1588
|
-
drawNodeBubbles: true,
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
// Properties that should always be included even if they match defaults
|
|
1592
|
-
const alwaysInclude = new Set(['id', 'type', 'relativeTo'])
|
|
1593
|
-
|
|
1594
|
-
// Filter out properties that match default values
|
|
1595
|
-
function filterDefaults(obj: Record<string, any>): Record<string, any> {
|
|
1596
|
-
const filtered: Record<string, any> = {}
|
|
1597
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
1598
|
-
// Always include essential properties
|
|
1599
|
-
if (alwaysInclude.has(key)) {
|
|
1600
|
-
filtered[key] = value
|
|
1601
|
-
continue
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
// Skip if value matches default
|
|
1605
|
-
if (defaults[key as keyof typeof defaults] === value) {
|
|
1606
|
-
continue
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
// Handle nested objects
|
|
1610
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
1611
|
-
const filteredNested = filterDefaults(value)
|
|
1612
|
-
// Only include nested object if it has non-default properties
|
|
1613
|
-
if (Object.keys(filteredNested).length > 0) {
|
|
1614
|
-
filtered[key] = filteredNested
|
|
1615
|
-
}
|
|
1616
|
-
} else if (Array.isArray(value)) {
|
|
1617
|
-
// Only include arrays that aren't empty
|
|
1618
|
-
if (value.length > 0) {
|
|
1619
|
-
filtered[key] = value
|
|
1620
|
-
}
|
|
1621
|
-
} else {
|
|
1622
|
-
// Include non-default primitives
|
|
1623
|
-
filtered[key] = value
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
return filtered
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
const filteredRest = filterDefaults(rest)
|
|
1630
|
-
|
|
1631
1934
|
// remove the MSA/tree data from the tree if the filehandle available in
|
|
1632
1935
|
// which case it can be reloaded on refresh
|
|
1633
1936
|
return {
|
|
1937
|
+
...rest,
|
|
1634
1938
|
data: {
|
|
1635
1939
|
...(result.treeFilehandle ? {} : { tree }),
|
|
1636
1940
|
...(result.msaFilehandle ? {} : { msa }),
|
|
1637
1941
|
...(result.treeMetadataFilehandle ? {} : { treeMetadata }),
|
|
1638
1942
|
},
|
|
1639
|
-
|
|
1640
|
-
|
|
1943
|
+
// Main model - only include non-default values
|
|
1944
|
+
...(showDomains !== defaultShowDomains ? { showDomains } : {}),
|
|
1945
|
+
...(hideGaps !== defaultHideGaps ? { hideGaps } : {}),
|
|
1946
|
+
...(allowedGappyness !== defaultAllowedGappyness
|
|
1947
|
+
? { allowedGappyness }
|
|
1948
|
+
: {}),
|
|
1949
|
+
...(contrastLettering !== defaultContrastLettering
|
|
1950
|
+
? { contrastLettering }
|
|
1951
|
+
: {}),
|
|
1952
|
+
...(subFeatureRows !== defaultSubFeatureRows ? { subFeatureRows } : {}),
|
|
1953
|
+
...(drawMsaLetters !== defaultDrawMsaLetters ? { drawMsaLetters } : {}),
|
|
1954
|
+
...(height !== defaultHeight ? { height } : {}),
|
|
1955
|
+
...(rowHeight !== defaultRowHeight ? { rowHeight } : {}),
|
|
1956
|
+
...(scrollY !== defaultScrollY ? { scrollY } : {}),
|
|
1957
|
+
...(scrollX !== defaultScrollX ? { scrollX } : {}),
|
|
1958
|
+
...(colWidth !== defaultColWidth ? { colWidth } : {}),
|
|
1959
|
+
...(currentAlignment !== defaultCurrentAlignment
|
|
1960
|
+
? { currentAlignment }
|
|
1961
|
+
: {}),
|
|
1962
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1963
|
+
...(collapsed?.length ? { collapsed } : {}),
|
|
1964
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1965
|
+
...(collapsedLeaves?.length ? { collapsedLeaves } : {}),
|
|
1966
|
+
...(showOnly !== undefined ? { showOnly } : {}),
|
|
1967
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1968
|
+
...(turnedOffTracks && Object.keys(turnedOffTracks).length > 0
|
|
1969
|
+
? { turnedOffTracks }
|
|
1970
|
+
: {}),
|
|
1971
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1972
|
+
...(featureFilters && Object.keys(featureFilters).length > 0
|
|
1973
|
+
? { featureFilters }
|
|
1974
|
+
: {}),
|
|
1975
|
+
...(relativeTo !== undefined ? { relativeTo } : {}),
|
|
1976
|
+
// MSA model - only include non-default values
|
|
1977
|
+
...(bgColor !== defaultBgColor ? { bgColor } : {}),
|
|
1978
|
+
...(colorSchemeName !== defaultColorSchemeName
|
|
1979
|
+
? { colorSchemeName }
|
|
1980
|
+
: {}),
|
|
1981
|
+
// Tree model - only include non-default values
|
|
1982
|
+
...(drawLabels !== defaultDrawLabels ? { drawLabels } : {}),
|
|
1983
|
+
...(labelsAlignRight !== defaultLabelsAlignRight
|
|
1984
|
+
? { labelsAlignRight }
|
|
1985
|
+
: {}),
|
|
1986
|
+
...(treeAreaWidth !== defaultTreeAreaWidth ? { treeAreaWidth } : {}),
|
|
1987
|
+
...(treeWidth !== defaultTreeWidth ? { treeWidth } : {}),
|
|
1988
|
+
...(treeWidthMatchesArea !== defaultTreeWidthMatchesArea
|
|
1989
|
+
? { treeWidthMatchesArea }
|
|
1990
|
+
: {}),
|
|
1991
|
+
...(showBranchLen !== defaultShowBranchLen ? { showBranchLen } : {}),
|
|
1992
|
+
...(drawTree !== defaultDrawTree ? { drawTree } : {}),
|
|
1993
|
+
...(drawNodeBubbles !== defaultDrawNodeBubbles
|
|
1994
|
+
? { drawNodeBubbles }
|
|
1995
|
+
: {}),
|
|
1996
|
+
} as typeof snap
|
|
1641
1997
|
})
|
|
1642
1998
|
}
|
|
1643
1999
|
|