react-msaview 5.5.0 → 5.6.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/bundle/index.js +173 -162
- package/bundle/index.js.map +4 -4
- package/dist/components/ConservationTrack.js +20 -26
- package/dist/components/ConservationTrack.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +7 -1
- package/dist/components/MSAViewer.js +4 -1
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/SequenceTextArea.js +1 -1
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/TextTrack.js +20 -27
- 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/dialogs/TrackInfoDialog.js +1 -1
- package/dist/components/dialogs/TrackInfoDialog.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 -16
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/Header.js +6 -5
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderInfoArea.js +1 -1
- package/dist/components/header/HeaderInfoArea.js.map +1 -1
- package/dist/components/header/HeaderStatusArea.js +1 -1
- package/dist/components/header/HeaderStatusArea.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 +14 -12
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/ZoomMenu.js +1 -10
- package/dist/components/header/ZoomMenu.js.map +1 -1
- package/dist/components/header/settingsMenuItems.js +9 -1
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/{header/HeaderMenu.d.ts → msa/ColumnStats.d.ts} +3 -3
- package/dist/components/msa/ColumnStats.js +42 -0
- package/dist/components/msa/ColumnStats.js.map +1 -0
- package/dist/components/msa/DomainLegend.js +1 -1
- package/dist/components/msa/DomainLegend.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +39 -39
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +6 -12
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +3 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +1 -2
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/overlayColors.d.ts +3 -0
- package/dist/components/overlayColors.js +11 -0
- package/dist/components/overlayColors.js.map +1 -0
- package/dist/components/tracks/renderTracksSvg.d.ts +6 -3
- package/dist/components/tracks/renderTracksSvg.js +17 -8
- package/dist/components/tracks/renderTracksSvg.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +31 -38
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +12 -47
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/clickMap.d.ts +24 -0
- package/dist/components/tree/clickMap.js +33 -0
- package/dist/components/tree/clickMap.js.map +1 -0
- package/dist/components/tree/renderTreeCanvas.d.ts +1 -21
- package/dist/components/tree/renderTreeCanvas.js +8 -7
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/fetchUtils.d.ts +2 -1
- package/dist/fetchUtils.js +3 -2
- package/dist/fetchUtils.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/model/msaModel.d.ts +25 -2
- package/dist/model/msaModel.js +15 -3
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.d.ts +46 -5
- package/dist/model/treeModel.js +20 -7
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +545 -132
- package/dist/model.js +101 -75
- package/dist/model.js.map +1 -1
- package/dist/propertyConservation.d.ts +7 -0
- package/dist/propertyConservation.js +66 -0
- package/dist/propertyConservation.js.map +1 -0
- package/dist/types.d.ts +4 -1
- package/dist/useCanvasAutorun.d.ts +2 -0
- package/dist/useCanvasAutorun.js +23 -0
- package/dist/useCanvasAutorun.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -6
- package/src/colorSchemes.docs.test.ts +57 -0
- package/src/columnStats.test.ts +94 -0
- package/src/components/ConservationTrack.tsx +17 -15
- package/src/components/MSAViewer.tsx +12 -0
- package/src/components/SequenceTextArea.tsx +1 -1
- package/src/components/TextTrack.tsx +7 -11
- package/src/components/Track.tsx +1 -1
- package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
- package/src/components/header/ColorSchemeMenu.tsx +1 -0
- package/src/components/header/FileMenu.tsx +1 -15
- package/src/components/header/Header.tsx +11 -8
- package/src/components/header/HeaderInfoArea.tsx +1 -1
- package/src/components/header/HeaderStatusArea.tsx +1 -1
- package/src/components/header/MSASettingsMenu.tsx +1 -0
- package/src/components/header/TreeSettingsMenu.tsx +1 -0
- package/src/components/header/ZoomControls.tsx +24 -17
- package/src/components/header/ZoomMenu.tsx +1 -10
- package/src/components/header/settingsMenuItems.ts +9 -1
- package/src/components/msa/ColumnStats.tsx +54 -0
- package/src/components/msa/DomainLegend.tsx +5 -1
- package/src/components/msa/MSACanvasBlock.tsx +29 -26
- package/src/components/msa/MSAMouseoverCanvas.tsx +10 -13
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +3 -1
- package/src/components/msa/renderMSAMouseover.ts +2 -2
- package/src/components/overlayColors.ts +12 -0
- package/src/components/tracks/renderTracksSvg.ts +22 -8
- package/src/components/tree/TreeCanvas.tsx +47 -54
- package/src/components/tree/TreeCanvasBlock.tsx +10 -68
- package/src/components/tree/clickMap.ts +55 -0
- package/src/components/tree/renderTreeCanvas.ts +10 -34
- package/src/constants.ts +1 -0
- package/src/fetchUtils.ts +6 -3
- package/src/index.ts +1 -1
- package/src/model/msaModel.ts +24 -3
- package/src/model/treeModel.ts +27 -7
- package/src/model.ts +122 -117
- package/src/propertyConservation.test.ts +47 -0
- package/src/propertyConservation.ts +73 -0
- package/src/stripDefaultSnapshot.test.ts +57 -0
- package/src/types.ts +7 -1
- package/src/useCanvasAutorun.ts +28 -0
- package/src/version.ts +1 -1
- package/dist/components/dialogs/SettingsDialog.d.ts +0 -7
- package/dist/components/dialogs/SettingsDialog.js +0 -100
- package/dist/components/dialogs/SettingsDialog.js.map +0 -1
- package/dist/components/header/HeaderMenu.js +0 -27
- package/dist/components/header/HeaderMenu.js.map +0 -1
- package/src/components/dialogs/SettingsDialog.tsx +0 -234
- package/src/components/header/HeaderMenu.tsx +0 -38
package/src/fetchUtils.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { fetchAndMaybeUnzipText } from '@jbrowse/core/util'
|
|
1
|
+
import { fetchAndMaybeUnzipText, statusMessageText } from '@jbrowse/core/util'
|
|
2
|
+
|
|
3
|
+
import type { RpcStatus } from '@jbrowse/core/util'
|
|
2
4
|
|
|
3
5
|
export interface FetchStatus {
|
|
4
6
|
msg: string
|
|
@@ -10,7 +12,7 @@ type Filehandle = Parameters<typeof fetchAndMaybeUnzipText>[0]
|
|
|
10
12
|
|
|
11
13
|
type ProgressFetcher = (
|
|
12
14
|
loc: Filehandle,
|
|
13
|
-
opts: { signal: AbortSignal; statusCallback: (
|
|
15
|
+
opts: { signal: AbortSignal; statusCallback: (status: RpcStatus) => void },
|
|
14
16
|
) => Promise<string>
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -28,7 +30,8 @@ export async function fetchTextWithProgress(
|
|
|
28
30
|
try {
|
|
29
31
|
return await fetcher(loc, {
|
|
30
32
|
signal: controller.signal,
|
|
31
|
-
statusCallback:
|
|
33
|
+
statusCallback: status => {
|
|
34
|
+
const msg = statusMessageText(status)
|
|
32
35
|
setStatus(
|
|
33
36
|
msg
|
|
34
37
|
? {
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ export { renderToSvg } from './renderToSvg.tsx'
|
|
|
9
9
|
export { default as MSAView } from './components/Loading.tsx'
|
|
10
10
|
export { default as MSAViewer } from './components/MSAViewer.tsx'
|
|
11
11
|
export { type MsaViewModel, default as MSAModelF } from './model.ts'
|
|
12
|
-
export type { MSAParserType } from 'msa-parsers'
|
|
12
|
+
export type { MSAFormat, MSAParserType } from 'msa-parsers'
|
|
13
13
|
export type { HierarchyNode } from './hierarchy.ts'
|
|
14
14
|
export type { InterProScanResults } from './launchInterProScan.ts'
|
|
15
15
|
export type {
|
package/src/model/msaModel.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { types } from '@jbrowse/mobx-state-tree'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
defaultBgColor,
|
|
5
|
+
defaultColorSchemeName,
|
|
6
|
+
defaultShowColumnStats,
|
|
7
|
+
} from '../constants.ts'
|
|
4
8
|
|
|
5
9
|
import type { MSAFormat } from 'msa-parsers'
|
|
6
10
|
|
|
@@ -16,13 +20,23 @@ export function MSAModelF() {
|
|
|
16
20
|
* #property
|
|
17
21
|
* draw MSA tiles with a background color
|
|
18
22
|
*/
|
|
19
|
-
bgColor: defaultBgColor,
|
|
23
|
+
bgColor: types.stripDefault(types.boolean, defaultBgColor),
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* #property
|
|
23
27
|
* default color scheme name
|
|
24
28
|
*/
|
|
25
|
-
colorSchemeName: defaultColorSchemeName,
|
|
29
|
+
colorSchemeName: types.stripDefault(types.string, defaultColorSchemeName),
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* #property
|
|
33
|
+
* show a per-column statistics tooltip (consensus, conservation, gaps,
|
|
34
|
+
* residue distribution) while hovering the alignment
|
|
35
|
+
*/
|
|
36
|
+
showColumnStats: types.stripDefault(
|
|
37
|
+
types.boolean,
|
|
38
|
+
defaultShowColumnStats,
|
|
39
|
+
),
|
|
26
40
|
|
|
27
41
|
/**
|
|
28
42
|
* #property
|
|
@@ -49,6 +63,13 @@ export function MSAModelF() {
|
|
|
49
63
|
self.bgColor = arg
|
|
50
64
|
},
|
|
51
65
|
|
|
66
|
+
/**
|
|
67
|
+
* #action
|
|
68
|
+
*/
|
|
69
|
+
setShowColumnStats(arg: boolean) {
|
|
70
|
+
self.showColumnStats = arg
|
|
71
|
+
},
|
|
72
|
+
|
|
52
73
|
/**
|
|
53
74
|
* #action
|
|
54
75
|
* force a specific MSA parser, or pass undefined to auto-detect
|
package/src/model/treeModel.ts
CHANGED
|
@@ -19,25 +19,28 @@ export function TreeModelF() {
|
|
|
19
19
|
/**
|
|
20
20
|
* #property
|
|
21
21
|
*/
|
|
22
|
-
drawLabels: defaultDrawLabels,
|
|
22
|
+
drawLabels: types.stripDefault(types.boolean, defaultDrawLabels),
|
|
23
23
|
/**
|
|
24
24
|
* #property
|
|
25
25
|
* right-align the labels
|
|
26
26
|
*/
|
|
27
|
-
labelsAlignRight:
|
|
27
|
+
labelsAlignRight: types.stripDefault(
|
|
28
|
+
types.boolean,
|
|
29
|
+
defaultLabelsAlignRight,
|
|
30
|
+
),
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* #property
|
|
31
34
|
* width of the area the tree is drawn in, px
|
|
32
35
|
*/
|
|
33
|
-
treeAreaWidth: types.
|
|
36
|
+
treeAreaWidth: types.stripDefault(types.number, defaultTreeAreaWidth),
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
39
|
* #property
|
|
37
40
|
* width of the tree within the treeArea, px. automatically synced to
|
|
38
41
|
* fit within treeAreaWidth
|
|
39
42
|
*/
|
|
40
|
-
treeWidth: types.
|
|
43
|
+
treeWidth: types.stripDefault(types.number, defaultTreeWidth),
|
|
41
44
|
|
|
42
45
|
/**
|
|
43
46
|
* #property
|
|
@@ -45,19 +48,30 @@ export function TreeModelF() {
|
|
|
45
48
|
* lengths. if false, the layout is a "cladogram" that does not take into
|
|
46
49
|
* account evolutionary distances
|
|
47
50
|
*/
|
|
48
|
-
showBranchLen: defaultShowBranchLen,
|
|
51
|
+
showBranchLen: types.stripDefault(types.boolean, defaultShowBranchLen),
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
54
|
* #property
|
|
52
55
|
* draw tree, boolean
|
|
53
56
|
*/
|
|
54
|
-
drawTree: defaultDrawTree,
|
|
57
|
+
drawTree: types.stripDefault(types.boolean, defaultDrawTree),
|
|
55
58
|
|
|
56
59
|
/**
|
|
57
60
|
* #property
|
|
58
61
|
* draw clickable node bubbles on the tree
|
|
59
62
|
*/
|
|
60
|
-
drawNodeBubbles:
|
|
63
|
+
drawNodeBubbles: types.stripDefault(
|
|
64
|
+
types.boolean,
|
|
65
|
+
defaultDrawNodeBubbles,
|
|
66
|
+
),
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* #property
|
|
70
|
+
* auto-size treeAreaWidth to fit the row labels (plus the tree, if drawn)
|
|
71
|
+
* instead of using a fixed width. useful when there is no tree, so the
|
|
72
|
+
* label gutter isn't padded out to the default 400px
|
|
73
|
+
*/
|
|
74
|
+
autoTreeAreaWidth: types.stripDefault(types.boolean, false),
|
|
61
75
|
})
|
|
62
76
|
.actions(self => ({
|
|
63
77
|
/**
|
|
@@ -87,6 +101,12 @@ export function TreeModelF() {
|
|
|
87
101
|
setDrawTree(arg: boolean) {
|
|
88
102
|
self.drawTree = arg
|
|
89
103
|
},
|
|
104
|
+
/**
|
|
105
|
+
* #action
|
|
106
|
+
*/
|
|
107
|
+
setAutoTreeAreaWidth(arg: boolean) {
|
|
108
|
+
self.autoTreeAreaWidth = arg
|
|
109
|
+
},
|
|
90
110
|
|
|
91
111
|
/**
|
|
92
112
|
* #action
|
package/src/model.ts
CHANGED
|
@@ -20,26 +20,18 @@ import ConservationTrack from './components/ConservationTrack.tsx'
|
|
|
20
20
|
import TextTrack from './components/TextTrack.tsx'
|
|
21
21
|
import {
|
|
22
22
|
defaultAllowedGappyness,
|
|
23
|
-
defaultBgColor,
|
|
24
23
|
defaultColWidth,
|
|
25
24
|
defaultColorSchemeName,
|
|
26
25
|
defaultCurrentAlignment,
|
|
27
|
-
defaultDrawLabels,
|
|
28
26
|
defaultDrawMsaLetters,
|
|
29
|
-
defaultDrawNodeBubbles,
|
|
30
|
-
defaultDrawTree,
|
|
31
27
|
defaultHeight,
|
|
32
28
|
defaultHideGaps,
|
|
33
|
-
defaultLabelsAlignRight,
|
|
34
29
|
defaultRowHeight,
|
|
35
30
|
defaultScrollX,
|
|
36
31
|
defaultScrollY,
|
|
37
32
|
defaultScrollZoom,
|
|
38
|
-
defaultShowBranchLen,
|
|
39
33
|
defaultShowDomains,
|
|
40
34
|
defaultSubFeatureRows,
|
|
41
|
-
defaultTreeAreaWidth,
|
|
42
|
-
defaultTreeWidth,
|
|
43
35
|
maxCellSize,
|
|
44
36
|
minColWidth,
|
|
45
37
|
minRowHeight,
|
|
@@ -72,6 +64,7 @@ import { MSAModelF } from './model/msaModel.ts'
|
|
|
72
64
|
import { TreeModelF } from './model/treeModel.ts'
|
|
73
65
|
import { calculateNeighborJoiningTree } from './neighborJoining.ts'
|
|
74
66
|
import { parseAsn1 } from './parseAsn1.ts'
|
|
67
|
+
import { calculatePropertyConservation } from './propertyConservation.ts'
|
|
75
68
|
import {
|
|
76
69
|
globalColToVisibleCol,
|
|
77
70
|
visibleColToGlobalCol,
|
|
@@ -131,19 +124,25 @@ function stateModelFactory() {
|
|
|
131
124
|
/**
|
|
132
125
|
* #property
|
|
133
126
|
*/
|
|
134
|
-
showDomains: defaultShowDomains,
|
|
127
|
+
showDomains: types.stripDefault(types.boolean, defaultShowDomains),
|
|
135
128
|
/**
|
|
136
129
|
* #property
|
|
137
130
|
*/
|
|
138
|
-
hideGaps: defaultHideGaps,
|
|
131
|
+
hideGaps: types.stripDefault(types.boolean, defaultHideGaps),
|
|
139
132
|
/**
|
|
140
133
|
* #property
|
|
141
134
|
*/
|
|
142
|
-
allowedGappyness:
|
|
135
|
+
allowedGappyness: types.stripDefault(
|
|
136
|
+
types.number,
|
|
137
|
+
defaultAllowedGappyness,
|
|
138
|
+
),
|
|
143
139
|
/**
|
|
144
140
|
* #property
|
|
145
141
|
*/
|
|
146
|
-
subFeatureRows:
|
|
142
|
+
subFeatureRows: types.stripDefault(
|
|
143
|
+
types.boolean,
|
|
144
|
+
defaultSubFeatureRows,
|
|
145
|
+
),
|
|
147
146
|
|
|
148
147
|
/**
|
|
149
148
|
* #property
|
|
@@ -154,43 +153,46 @@ function stateModelFactory() {
|
|
|
154
153
|
/**
|
|
155
154
|
* #property
|
|
156
155
|
*/
|
|
157
|
-
drawMsaLetters:
|
|
156
|
+
drawMsaLetters: types.stripDefault(
|
|
157
|
+
types.boolean,
|
|
158
|
+
defaultDrawMsaLetters,
|
|
159
|
+
),
|
|
158
160
|
|
|
159
161
|
/**
|
|
160
162
|
* #property
|
|
161
163
|
* zoom in/out on plain mouse-wheel without holding ctrl
|
|
162
164
|
*/
|
|
163
|
-
scrollZoom: defaultScrollZoom,
|
|
165
|
+
scrollZoom: types.stripDefault(types.boolean, defaultScrollZoom),
|
|
164
166
|
|
|
165
167
|
/**
|
|
166
168
|
* #property
|
|
167
169
|
* height of the div containing the view, px
|
|
168
170
|
*/
|
|
169
|
-
height: types.
|
|
171
|
+
height: types.stripDefault(types.number, defaultHeight),
|
|
170
172
|
|
|
171
173
|
/**
|
|
172
174
|
* #property
|
|
173
175
|
* height of each row, px
|
|
174
176
|
*/
|
|
175
|
-
rowHeight: defaultRowHeight,
|
|
177
|
+
rowHeight: types.stripDefault(types.number, defaultRowHeight),
|
|
176
178
|
|
|
177
179
|
/**
|
|
178
180
|
* #property
|
|
179
181
|
* scroll position, Y-offset, px
|
|
180
182
|
*/
|
|
181
|
-
scrollY: defaultScrollY,
|
|
183
|
+
scrollY: types.stripDefault(types.number, defaultScrollY),
|
|
182
184
|
|
|
183
185
|
/**
|
|
184
186
|
* #property
|
|
185
187
|
* scroll position, X-offset, px
|
|
186
188
|
*/
|
|
187
|
-
scrollX: defaultScrollX,
|
|
189
|
+
scrollX: types.stripDefault(types.number, defaultScrollX),
|
|
188
190
|
|
|
189
191
|
/**
|
|
190
192
|
* #property
|
|
191
193
|
* width of columns, px
|
|
192
194
|
*/
|
|
193
|
-
colWidth: defaultColWidth,
|
|
195
|
+
colWidth: types.stripDefault(types.number, defaultColWidth),
|
|
194
196
|
|
|
195
197
|
/**
|
|
196
198
|
* #property
|
|
@@ -220,14 +222,17 @@ function stateModelFactory() {
|
|
|
220
222
|
/**
|
|
221
223
|
* #property
|
|
222
224
|
*/
|
|
223
|
-
currentAlignment:
|
|
225
|
+
currentAlignment: types.stripDefault(
|
|
226
|
+
types.number,
|
|
227
|
+
defaultCurrentAlignment,
|
|
228
|
+
),
|
|
224
229
|
|
|
225
230
|
/**
|
|
226
231
|
* #property
|
|
227
232
|
* array of tree parent nodes that are 'collapsed' (all children are
|
|
228
233
|
* hidden)
|
|
229
234
|
*/
|
|
230
|
-
collapsed: types.array(types.string),
|
|
235
|
+
collapsed: types.stripDefault(types.array(types.string), []),
|
|
231
236
|
|
|
232
237
|
/**
|
|
233
238
|
* #property
|
|
@@ -238,7 +243,7 @@ function stateModelFactory() {
|
|
|
238
243
|
* #property
|
|
239
244
|
* turned off tracks
|
|
240
245
|
*/
|
|
241
|
-
turnedOffTracks: types.map(types.boolean),
|
|
246
|
+
turnedOffTracks: types.stripDefault(types.map(types.boolean), {}),
|
|
242
247
|
|
|
243
248
|
/**
|
|
244
249
|
* #property
|
|
@@ -254,7 +259,7 @@ function stateModelFactory() {
|
|
|
254
259
|
/**
|
|
255
260
|
* #property
|
|
256
261
|
*/
|
|
257
|
-
featureFilters: types.map(types.boolean),
|
|
262
|
+
featureFilters: types.stripDefault(types.map(types.boolean), {}),
|
|
258
263
|
/**
|
|
259
264
|
* #property
|
|
260
265
|
*/
|
|
@@ -279,8 +284,7 @@ function stateModelFactory() {
|
|
|
279
284
|
* #volatile
|
|
280
285
|
*/
|
|
281
286
|
status: undefined as
|
|
282
|
-
|
|
283
|
-
| undefined,
|
|
287
|
+
{ msg: string; url?: string; onCancel?: () => void } | undefined,
|
|
284
288
|
/**
|
|
285
289
|
* #volatile
|
|
286
290
|
* high resolution scale factor, helps make canvas look better on hi-dpi
|
|
@@ -343,8 +347,7 @@ function stateModelFactory() {
|
|
|
343
347
|
* the currently hovered tree node ID and its descendant leaf names
|
|
344
348
|
*/
|
|
345
349
|
hoveredTreeNode: undefined as
|
|
346
|
-
|
|
347
|
-
| undefined,
|
|
350
|
+
{ nodeId: string; descendantNames: string[] } | undefined,
|
|
348
351
|
|
|
349
352
|
/**
|
|
350
353
|
* #volatile
|
|
@@ -377,8 +380,7 @@ function stateModelFactory() {
|
|
|
377
380
|
* #volatile
|
|
378
381
|
*/
|
|
379
382
|
interProAnnotations: undefined as
|
|
380
|
-
|
|
|
381
|
-
| Record<string, InterProScanResults>,
|
|
383
|
+
undefined | Record<string, InterProScanResults>,
|
|
382
384
|
}))
|
|
383
385
|
.actions(self => ({
|
|
384
386
|
/**
|
|
@@ -1044,6 +1046,17 @@ function stateModelFactory() {
|
|
|
1044
1046
|
return conservation * (1 - gapFraction)
|
|
1045
1047
|
})
|
|
1046
1048
|
},
|
|
1049
|
+
/**
|
|
1050
|
+
* #getter
|
|
1051
|
+
* Per-column conservation of physicochemical property class (amino acids
|
|
1052
|
+
* only). Surfaces conservative-substitution sites that identity-based
|
|
1053
|
+
* conservation misses. Empty for nucleotide alignments.
|
|
1054
|
+
*/
|
|
1055
|
+
get propertyConservation() {
|
|
1056
|
+
return this.sequenceType === 'amino'
|
|
1057
|
+
? calculatePropertyConservation(this.colStats, this.colStatsSums)
|
|
1058
|
+
: []
|
|
1059
|
+
},
|
|
1047
1060
|
/**
|
|
1048
1061
|
* #getter
|
|
1049
1062
|
* generates a new tree that is clustered with x,y positions
|
|
@@ -1466,10 +1479,24 @@ function stateModelFactory() {
|
|
|
1466
1479
|
id: 'conservation',
|
|
1467
1480
|
name: 'Conservation',
|
|
1468
1481
|
height: self.conservationTrackHeight,
|
|
1482
|
+
barColor: 'gray',
|
|
1469
1483
|
},
|
|
1470
1484
|
ReactComponent: ConservationTrack,
|
|
1471
1485
|
}
|
|
1472
|
-
|
|
1486
|
+
const propertyConservationTrack: BasicTrack = {
|
|
1487
|
+
model: {
|
|
1488
|
+
id: 'property-conservation',
|
|
1489
|
+
name: 'Property conservation',
|
|
1490
|
+
height: self.conservationTrackHeight,
|
|
1491
|
+
barColor: '#6a51a3',
|
|
1492
|
+
},
|
|
1493
|
+
ReactComponent: ConservationTrack,
|
|
1494
|
+
}
|
|
1495
|
+
return [
|
|
1496
|
+
...this.adapterTrackModels,
|
|
1497
|
+
conservationTrack,
|
|
1498
|
+
...(self.sequenceType === 'amino' ? [propertyConservationTrack] : []),
|
|
1499
|
+
]
|
|
1473
1500
|
},
|
|
1474
1501
|
|
|
1475
1502
|
/**
|
|
@@ -1743,6 +1770,41 @@ function stateModelFactory() {
|
|
|
1743
1770
|
return []
|
|
1744
1771
|
},
|
|
1745
1772
|
|
|
1773
|
+
/**
|
|
1774
|
+
* #getter
|
|
1775
|
+
* per-column summary statistics for the hovered column: consensus residue
|
|
1776
|
+
* and its identity fraction, conservation score, gap fraction, and the
|
|
1777
|
+
* sorted non-gap residue distribution. undefined when nothing is hovered.
|
|
1778
|
+
*/
|
|
1779
|
+
get mouseOverColumnStats() {
|
|
1780
|
+
const { mouseCol } = self
|
|
1781
|
+
if (mouseCol === undefined) {
|
|
1782
|
+
return undefined
|
|
1783
|
+
}
|
|
1784
|
+
const stats = self.colStats[mouseCol]
|
|
1785
|
+
const total = self.colStatsSums[mouseCol]
|
|
1786
|
+
if (!stats || !total) {
|
|
1787
|
+
return undefined
|
|
1788
|
+
}
|
|
1789
|
+
const gaps = (stats['-'] ?? 0) + (stats['.'] ?? 0)
|
|
1790
|
+
const distribution = Object.entries(stats)
|
|
1791
|
+
.filter(([letter]) => letter !== '-' && letter !== '.')
|
|
1792
|
+
.sort((a, b) => b[1] - a[1])
|
|
1793
|
+
const consensus = distribution[0]
|
|
1794
|
+
return {
|
|
1795
|
+
col: mouseCol,
|
|
1796
|
+
total,
|
|
1797
|
+
gaps,
|
|
1798
|
+
gapFraction: gaps / total,
|
|
1799
|
+
conservation: self.conservation[mouseCol] ?? 0,
|
|
1800
|
+
propertyConservation: self.propertyConservation[mouseCol],
|
|
1801
|
+
consensusLetter: consensus?.[0] ?? '',
|
|
1802
|
+
consensusCount: consensus?.[1] ?? 0,
|
|
1803
|
+
consensusFraction: consensus ? consensus[1] / total : 0,
|
|
1804
|
+
distribution,
|
|
1805
|
+
}
|
|
1806
|
+
},
|
|
1807
|
+
|
|
1746
1808
|
/**
|
|
1747
1809
|
* #method
|
|
1748
1810
|
*/
|
|
@@ -2046,6 +2108,23 @@ function stateModelFactory() {
|
|
|
2046
2108
|
}),
|
|
2047
2109
|
)
|
|
2048
2110
|
|
|
2111
|
+
// autorun: when autoTreeAreaWidth is set and no tree is drawn, shrink the
|
|
2112
|
+
// tree area to fit the row labels rather than padding it to the fixed
|
|
2113
|
+
// default. Gated on noTree/!drawTree so it never fights the treeWidth sync
|
|
2114
|
+
// below (treeAreaWidth here depends only on labelsWidth, not treeWidth).
|
|
2115
|
+
addDisposer(
|
|
2116
|
+
self,
|
|
2117
|
+
autorun(() => {
|
|
2118
|
+
if (
|
|
2119
|
+
self.autoTreeAreaWidth &&
|
|
2120
|
+
(self.noTree || !self.drawTree) &&
|
|
2121
|
+
self.labelsWidth
|
|
2122
|
+
) {
|
|
2123
|
+
self.setTreeAreaWidth(self.labelsWidth + self.marginLeft + 12)
|
|
2124
|
+
}
|
|
2125
|
+
}),
|
|
2126
|
+
)
|
|
2127
|
+
|
|
2049
2128
|
// autorun synchronizes treeWidth with treeAreaWidth
|
|
2050
2129
|
addDisposer(
|
|
2051
2130
|
self,
|
|
@@ -2060,93 +2139,19 @@ function stateModelFactory() {
|
|
|
2060
2139
|
)
|
|
2061
2140
|
},
|
|
2062
2141
|
}))
|
|
2063
|
-
.postProcessSnapshot(
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
scrollX,
|
|
2077
|
-
colWidth,
|
|
2078
|
-
currentAlignment,
|
|
2079
|
-
collapsed,
|
|
2080
|
-
showOnly,
|
|
2081
|
-
turnedOffTracks,
|
|
2082
|
-
featureFilters,
|
|
2083
|
-
relativeTo,
|
|
2084
|
-
// MSA model properties
|
|
2085
|
-
bgColor,
|
|
2086
|
-
colorSchemeName,
|
|
2087
|
-
// Tree model properties
|
|
2088
|
-
drawLabels,
|
|
2089
|
-
labelsAlignRight,
|
|
2090
|
-
treeAreaWidth,
|
|
2091
|
-
treeWidth,
|
|
2092
|
-
showBranchLen,
|
|
2093
|
-
drawTree,
|
|
2094
|
-
drawNodeBubbles,
|
|
2095
|
-
// Always include
|
|
2096
|
-
...rest
|
|
2097
|
-
} = snap
|
|
2098
|
-
|
|
2099
|
-
const defaults: Record<string, unknown> = {
|
|
2100
|
-
showDomains: defaultShowDomains,
|
|
2101
|
-
hideGaps: defaultHideGaps,
|
|
2102
|
-
allowedGappyness: defaultAllowedGappyness,
|
|
2103
|
-
subFeatureRows: defaultSubFeatureRows,
|
|
2104
|
-
drawMsaLetters: defaultDrawMsaLetters,
|
|
2105
|
-
height: defaultHeight,
|
|
2106
|
-
rowHeight: defaultRowHeight,
|
|
2107
|
-
scrollY: defaultScrollY,
|
|
2108
|
-
scrollX: defaultScrollX,
|
|
2109
|
-
colWidth: defaultColWidth,
|
|
2110
|
-
currentAlignment: defaultCurrentAlignment,
|
|
2111
|
-
bgColor: defaultBgColor,
|
|
2112
|
-
colorSchemeName: defaultColorSchemeName,
|
|
2113
|
-
drawLabels: defaultDrawLabels,
|
|
2114
|
-
labelsAlignRight: defaultLabelsAlignRight,
|
|
2115
|
-
treeAreaWidth: defaultTreeAreaWidth,
|
|
2116
|
-
treeWidth: defaultTreeWidth,
|
|
2117
|
-
showBranchLen: defaultShowBranchLen,
|
|
2118
|
-
drawTree: defaultDrawTree,
|
|
2119
|
-
drawNodeBubbles: defaultDrawNodeBubbles,
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
const nonDefaults = Object.fromEntries(
|
|
2123
|
-
Object.entries(defaults)
|
|
2124
|
-
.filter(([key, def]) => snap[key as keyof typeof snap] !== def)
|
|
2125
|
-
.map(([key]) => [key, snap[key as keyof typeof snap]]),
|
|
2126
|
-
)
|
|
2127
|
-
|
|
2128
|
-
return {
|
|
2129
|
-
...rest,
|
|
2130
|
-
data: {
|
|
2131
|
-
...(result.treeFilehandle ? {} : { tree }),
|
|
2132
|
-
...(result.msaFilehandle ? {} : { msa }),
|
|
2133
|
-
...(result.treeMetadataFilehandle ? {} : { treeMetadata }),
|
|
2134
|
-
},
|
|
2135
|
-
...nonDefaults,
|
|
2136
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
2137
|
-
...(collapsed?.length ? { collapsed } : {}),
|
|
2138
|
-
...(showOnly !== undefined ? { showOnly } : {}),
|
|
2139
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
2140
|
-
...(turnedOffTracks && Object.keys(turnedOffTracks).length > 0
|
|
2141
|
-
? { turnedOffTracks }
|
|
2142
|
-
: {}),
|
|
2143
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
2144
|
-
...(featureFilters && Object.keys(featureFilters).length > 0
|
|
2145
|
-
? { featureFilters }
|
|
2146
|
-
: {}),
|
|
2147
|
-
...(relativeTo !== undefined ? { relativeTo } : {}),
|
|
2148
|
-
} as typeof snap
|
|
2149
|
-
})
|
|
2142
|
+
.postProcessSnapshot(({ data, ...rest }) => ({
|
|
2143
|
+
// per-property defaults are stripped by types.stripDefault; the only thing
|
|
2144
|
+
// it can't express is this cross-field rule: drop inline tree/msa/metadata
|
|
2145
|
+
// when a sibling filehandle can refetch them, keeping sessions/URLs small
|
|
2146
|
+
...rest,
|
|
2147
|
+
data: {
|
|
2148
|
+
...(rest.treeFilehandle ? {} : { tree: data.tree }),
|
|
2149
|
+
...(rest.msaFilehandle ? {} : { msa: data.msa }),
|
|
2150
|
+
...(rest.treeMetadataFilehandle
|
|
2151
|
+
? {}
|
|
2152
|
+
: { treeMetadata: data.treeMetadata }),
|
|
2153
|
+
},
|
|
2154
|
+
}))
|
|
2150
2155
|
}
|
|
2151
2156
|
|
|
2152
2157
|
export default stateModelFactory
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { calculatePropertyConservation } from './propertyConservation.ts'
|
|
4
|
+
|
|
5
|
+
function counts(letters: string) {
|
|
6
|
+
const stats: Record<string, number> = {}
|
|
7
|
+
for (const c of letters) {
|
|
8
|
+
stats[c] = (stats[c] ?? 0) + 1
|
|
9
|
+
}
|
|
10
|
+
return stats
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function propertyConservation(columns: string[]) {
|
|
14
|
+
const colStats = columns.map(counts)
|
|
15
|
+
const colStatsSums = colStats.map(s =>
|
|
16
|
+
Object.values(s).reduce((a, b) => a + b, 0),
|
|
17
|
+
)
|
|
18
|
+
return calculatePropertyConservation(colStats, colStatsSums)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('property conservation', () => {
|
|
22
|
+
test('an invariant column scores 1', () => {
|
|
23
|
+
expect(propertyConservation(['LLLL'])[0]).toBe(1)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('a column varying only within one property class stays fully conserved', () => {
|
|
27
|
+
// L/I/V/M are all hydrophobic: identity varies but the property does not
|
|
28
|
+
expect(propertyConservation(['LIVM'])[0]).toBe(1)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('mixing opposite-charge classes lowers the score', () => {
|
|
32
|
+
// K/R (positive) vs D/E (negative): two equally-sized classes
|
|
33
|
+
const score = propertyConservation(['KRDE'])[0]!
|
|
34
|
+
expect(score).toBeGreaterThan(0)
|
|
35
|
+
expect(score).toBeLessThan(1)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('gaps reduce the score proportionally', () => {
|
|
39
|
+
const full = propertyConservation(['LLLL'])[0]!
|
|
40
|
+
const halfGapped = propertyConservation(['LL--'])[0]!
|
|
41
|
+
expect(halfGapped).toBeCloseTo(full * 0.5)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('an all-gap column scores 0', () => {
|
|
45
|
+
expect(propertyConservation(['----'])[0]).toBe(0)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Disjoint physicochemical classes for the 20 standard amino acids. Property
|
|
2
|
+
// conservation asks a different question than identity conservation: a column
|
|
3
|
+
// can vary in exact residue yet stay within one property class (a conservative
|
|
4
|
+
// substitution), which often marks a functionally constrained site that plain
|
|
5
|
+
// identity conservation reports as variable.
|
|
6
|
+
//
|
|
7
|
+
// Histidine is grouped with the aromatics (it is only weakly/conditionally
|
|
8
|
+
// positive at physiological pH); this mirrors common substitution-class schemes.
|
|
9
|
+
const aminoPropertyClass: Record<string, string> = {
|
|
10
|
+
A: 'hydrophobic',
|
|
11
|
+
V: 'hydrophobic',
|
|
12
|
+
L: 'hydrophobic',
|
|
13
|
+
I: 'hydrophobic',
|
|
14
|
+
M: 'hydrophobic',
|
|
15
|
+
C: 'hydrophobic',
|
|
16
|
+
F: 'aromatic',
|
|
17
|
+
W: 'aromatic',
|
|
18
|
+
Y: 'aromatic',
|
|
19
|
+
H: 'aromatic',
|
|
20
|
+
K: 'positive',
|
|
21
|
+
R: 'positive',
|
|
22
|
+
D: 'negative',
|
|
23
|
+
E: 'negative',
|
|
24
|
+
S: 'polar',
|
|
25
|
+
T: 'polar',
|
|
26
|
+
N: 'polar',
|
|
27
|
+
Q: 'polar',
|
|
28
|
+
G: 'special',
|
|
29
|
+
P: 'special',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const numPropertyClasses = new Set(Object.values(aminoPropertyClass)).size
|
|
33
|
+
const maxPropertyEntropy = Math.log2(numPropertyClasses)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Per-column property conservation using Shannon entropy over physicochemical
|
|
37
|
+
* classes rather than exact residues. Returns 0-1 where 1 = every non-gap
|
|
38
|
+
* residue shares one property class. Mirrors the identity-conservation formula
|
|
39
|
+
* so the two tracks are directly comparable: (1 - H/Hmax) * (1 - gapFraction).
|
|
40
|
+
*/
|
|
41
|
+
export function calculatePropertyConservation(
|
|
42
|
+
colStats: Record<string, number>[],
|
|
43
|
+
colStatsSums: number[],
|
|
44
|
+
) {
|
|
45
|
+
return colStats.map((stats, i) => {
|
|
46
|
+
const total = colStatsSums[i]
|
|
47
|
+
if (!total) {
|
|
48
|
+
return 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let nonGapTotal = 0
|
|
52
|
+
const classCounts: Record<string, number> = {}
|
|
53
|
+
for (const [letter, count] of Object.entries(stats)) {
|
|
54
|
+
const cls = aminoPropertyClass[letter]
|
|
55
|
+
if (cls) {
|
|
56
|
+
classCounts[cls] = (classCounts[cls] ?? 0) + count
|
|
57
|
+
nonGapTotal += count
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (nonGapTotal === 0) {
|
|
61
|
+
return 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let entropy = 0
|
|
65
|
+
for (const count of Object.values(classCounts)) {
|
|
66
|
+
const freq = count / nonGapTotal
|
|
67
|
+
entropy -= freq * Math.log2(freq)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const gapFraction = (total - nonGapTotal) / total
|
|
71
|
+
return Math.max(0, 1 - entropy / maxPropertyEntropy) * (1 - gapFraction)
|
|
72
|
+
})
|
|
73
|
+
}
|