react-msaview 9.1.0 → 9.2.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 +111 -115
- package/bundle/index.js.map +4 -4
- package/dist/blosum62.d.ts +3 -0
- package/dist/blosum62.js +43 -0
- package/dist/blosum62.js.map +1 -0
- package/dist/colorSchemes.js +1 -0
- package/dist/colorSchemes.js.map +1 -1
- package/dist/components/Loading.d.ts +3 -2
- package/dist/components/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.d.ts +3 -2
- package/dist/components/MSAView.js +7 -4
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +64 -12
- package/dist/components/MetadataTable.d.ts +4 -1
- package/dist/components/MetadataTable.js +55 -16
- package/dist/components/MetadataTable.js.map +1 -1
- package/dist/components/Track.js +6 -0
- package/dist/components/Track.js.map +1 -1
- package/dist/components/controls.js +2 -1
- package/dist/components/controls.js.map +1 -1
- package/dist/components/dialogs/AnnotationFileDialog.js +7 -2
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
- package/dist/components/dialogs/FeatureDialog.js +2 -2
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/{header/MSASettingsMenu.d.ts → dialogs/GapThresholdDialog.d.ts} +3 -2
- package/dist/components/dialogs/GapThresholdDialog.js +15 -0
- package/dist/components/dialogs/GapThresholdDialog.js.map +1 -0
- package/dist/components/{header → dialogs}/GappynessSlider.d.ts +1 -1
- package/dist/components/{header → dialogs}/GappynessSlider.js +3 -10
- package/dist/components/dialogs/GappynessSlider.js.map +1 -0
- package/dist/components/{header → dialogs}/GoToBox.d.ts +4 -2
- package/dist/components/dialogs/GoToBox.js +48 -0
- package/dist/components/dialogs/GoToBox.js.map +1 -0
- package/dist/components/dialogs/GoToDialog.d.ts +6 -0
- package/dist/components/dialogs/GoToDialog.js +10 -0
- package/dist/components/dialogs/GoToDialog.js.map +1 -0
- package/dist/components/dialogs/MetadataDialog.js +11 -3
- package/dist/components/dialogs/MetadataDialog.js.map +1 -1
- package/dist/components/dialogs/goTo.d.ts +5 -0
- package/dist/components/{header → dialogs}/goTo.js +1 -1
- package/dist/components/dialogs/goTo.js.map +1 -0
- package/dist/components/dialogs/metadataLabels.d.ts +4 -0
- package/dist/components/dialogs/metadataLabels.js +52 -0
- package/dist/components/dialogs/metadataLabels.js.map +1 -0
- package/dist/components/dialogs/metadataRecord.d.ts +1 -0
- package/dist/components/dialogs/metadataRecord.js +38 -0
- package/dist/components/dialogs/metadataRecord.js.map +1 -0
- package/dist/components/header/Header.d.ts +6 -2
- package/dist/components/header/Header.js +30 -32
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/{TreeSettingsMenu.d.ts → HiddenColumnsChip.d.ts} +2 -2
- package/dist/components/header/HiddenColumnsChip.js +15 -0
- package/dist/components/header/HiddenColumnsChip.js.map +1 -0
- package/dist/components/header/MenuBar.d.ts +6 -0
- package/dist/components/header/MenuBar.js +33 -0
- package/dist/components/header/MenuBar.js.map +1 -0
- package/dist/components/header/ScrollZoomCheckbox.d.ts +7 -0
- package/dist/components/header/ScrollZoomCheckbox.js +19 -0
- package/dist/components/header/ScrollZoomCheckbox.js.map +1 -0
- package/dist/components/header/ZoomControls.js +10 -30
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/colorSchemeMenuItems.d.ts +4 -0
- package/dist/components/header/colorSchemeMenuItems.js +106 -0
- package/dist/components/header/colorSchemeMenuItems.js.map +1 -0
- package/dist/components/header/getAnnotationsMenu.d.ts +3 -30
- package/dist/components/header/getAnnotationsMenu.js +48 -54
- package/dist/components/header/getAnnotationsMenu.js.map +1 -1
- package/dist/components/header/menuBarItems.d.ts +11 -0
- package/dist/components/header/menuBarItems.js +277 -0
- package/dist/components/header/menuBarItems.js.map +1 -0
- package/dist/components/import/ImportForm.js +12 -6
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/minimap/Minimap.js +7 -3
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.js +2 -1
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/minimap/minimapLayout.d.ts +12 -0
- package/dist/components/minimap/minimapLayout.js +20 -0
- package/dist/components/minimap/minimapLayout.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +26 -9
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +15 -11
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/WheelZoomHint.d.ts +7 -0
- package/dist/components/msa/WheelZoomHint.js +38 -0
- package/dist/components/msa/WheelZoomHint.js.map +1 -0
- package/dist/components/msa/msaKeys.js +3 -0
- package/dist/components/msa/msaKeys.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +4 -5
- package/dist/components/msa/msaRaster.js +5 -5
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +22 -2
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +0 -3
- package/dist/components/msa/renderHighlights.js +3 -3
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSABlock.d.ts +6 -1
- package/dist/components/msa/renderMSABlock.js +44 -17
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/tileColor.js +7 -0
- package/dist/components/msa/tileColor.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.js +2 -2
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
- package/dist/components/settings/MenuItemsForm.d.ts +10 -0
- package/dist/components/settings/MenuItemsForm.js +80 -0
- package/dist/components/settings/MenuItemsForm.js.map +1 -0
- package/dist/components/settings/SettingsPanel.d.ts +12 -0
- package/dist/components/settings/SettingsPanel.js +92 -0
- package/dist/components/settings/SettingsPanel.js.map +1 -0
- package/dist/components/{header/FileMenu.d.ts → settings/TrackList.d.ts} +2 -2
- package/dist/components/settings/TrackList.js +58 -0
- package/dist/components/settings/TrackList.js.map +1 -0
- package/dist/components/tracks/TrackBlocks.js +2 -2
- package/dist/components/tracks/TrackBlocks.js.map +1 -1
- package/dist/components/tracks/TrackTooltipContent.js +2 -1
- package/dist/components/tracks/TrackTooltipContent.js.map +1 -1
- package/dist/components/tracks/drawTracks.js +13 -6
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +2 -2
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeMenu.js +4 -6
- package/dist/components/tree/TreeMenu.js.map +1 -1
- package/dist/components/tree/TreeOverview.js +2 -2
- package/dist/components/tree/TreeOverview.js.map +1 -1
- package/dist/constants.d.ts +4 -1
- package/dist/constants.js +7 -1
- package/dist/constants.js.map +1 -1
- package/dist/darkReader.d.ts +7 -0
- package/dist/darkReader.js +42 -0
- package/dist/darkReader.js.map +1 -0
- package/dist/element.js +22 -47
- package/dist/element.js.map +1 -1
- package/dist/featurePanels.d.ts +37 -0
- package/dist/featurePanels.js +187 -0
- package/dist/featurePanels.js.map +1 -0
- package/dist/filehandleLoader.d.ts +41 -0
- package/dist/filehandleLoader.js +99 -0
- package/dist/filehandleLoader.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/treeModel.d.ts +9 -0
- package/dist/model/treeModel.js +12 -1
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +148 -32
- package/dist/model.js +291 -569
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +1 -40
- package/dist/neighborJoining.js.map +1 -1
- package/dist/renderToSvg.js +1 -13
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rerootTree.d.ts +1 -0
- package/dist/rerootTree.js +7 -6
- package/dist/rerootTree.js.map +1 -1
- package/dist/schemeResidueColor.d.ts +12 -0
- package/dist/schemeResidueColor.js +33 -0
- package/dist/schemeResidueColor.js.map +1 -0
- package/dist/similarityColors.d.ts +11 -0
- package/dist/similarityColors.js +90 -0
- package/dist/similarityColors.js.map +1 -0
- package/dist/svgTestUtil.d.ts +64 -12
- package/dist/treeDisplay.d.ts +37 -0
- package/dist/treeDisplay.js +209 -0
- package/dist/treeDisplay.js.map +1 -0
- package/dist/types.d.ts +7 -3
- package/dist/useWheelScroll.d.ts +3 -1
- package/dist/useWheelScroll.js +36 -17
- package/dist/useWheelScroll.js.map +1 -1
- package/dist/util.d.ts +2 -0
- package/dist/util.js +2 -0
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wheelZoom.d.ts +8 -3
- package/dist/wheelZoom.js +44 -32
- package/dist/wheelZoom.js.map +1 -1
- package/dist/zoomSnapshot.d.ts +29 -0
- package/dist/zoomSnapshot.js +47 -0
- package/dist/zoomSnapshot.js.map +1 -0
- package/package.json +11 -11
- package/src/blosum62.ts +45 -0
- package/src/colorSchemes.ts +1 -0
- package/src/components/Loading.tsx +6 -2
- package/src/components/MSAView.tsx +22 -5
- package/src/components/MetadataTable.tsx +83 -26
- package/src/components/Track.tsx +13 -0
- package/src/components/controls.ts +2 -1
- package/src/components/dialogs/AnnotationFileDialog.tsx +12 -1
- package/src/components/dialogs/FeatureDialog.tsx +2 -5
- package/src/components/dialogs/GapThresholdDialog.tsx +36 -0
- package/src/components/{header → dialogs}/GappynessSlider.tsx +3 -10
- package/src/components/{header → dialogs}/GoToBox.tsx +36 -10
- package/src/components/dialogs/GoToDialog.tsx +30 -0
- package/src/components/dialogs/MetadataDialog.tsx +32 -4
- package/src/components/{header → dialogs}/goTo.ts +1 -1
- package/src/components/dialogs/metadataLabels.ts +54 -0
- package/src/components/dialogs/metadataRecord.tsx +61 -0
- package/src/components/header/Header.tsx +75 -42
- package/src/components/header/HiddenColumnsChip.tsx +39 -0
- package/src/components/header/MenuBar.tsx +64 -0
- package/src/components/header/ScrollZoomCheckbox.tsx +46 -0
- package/src/components/header/ZoomControls.tsx +20 -35
- package/src/components/header/colorSchemeMenuItems.ts +121 -0
- package/src/components/header/getAnnotationsMenu.ts +52 -56
- package/src/components/header/menuBarItems.ts +357 -0
- package/src/components/import/ImportForm.tsx +19 -7
- package/src/components/minimap/Minimap.tsx +11 -3
- package/src/components/minimap/MinimapSVG.tsx +17 -1
- package/src/components/minimap/minimapLayout.ts +27 -0
- package/src/components/msa/MSACanvas.tsx +22 -3
- package/src/components/msa/MSACanvasBlock.tsx +18 -11
- package/src/components/msa/MSAMouseoverCanvas.tsx +2 -2
- package/src/components/msa/WheelZoomHint.tsx +56 -0
- package/src/components/msa/msaKeys.ts +3 -0
- package/src/components/msa/msaRaster.ts +6 -8
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +28 -5
- package/src/components/msa/renderHighlights.ts +3 -3
- package/src/components/msa/renderMSABlock.ts +51 -15
- package/src/components/msa/tileColor.ts +7 -0
- package/src/components/rowpanels/RowPanelBlock.tsx +2 -2
- package/src/components/settings/MenuItemsForm.tsx +225 -0
- package/src/components/settings/SettingsPanel.tsx +156 -0
- package/src/components/settings/TrackList.tsx +124 -0
- package/src/components/tracks/TrackBlocks.tsx +2 -2
- package/src/components/tracks/TrackTooltipContent.tsx +2 -1
- package/src/components/tracks/drawTracks.ts +15 -7
- package/src/components/tree/TreeCanvasBlock.tsx +2 -2
- package/src/components/tree/TreeMenu.tsx +4 -6
- package/src/components/tree/TreeOverview.tsx +2 -2
- package/src/constants.ts +9 -1
- package/src/darkReader.ts +57 -0
- package/src/element.ts +34 -57
- package/src/featurePanels.ts +269 -0
- package/src/filehandleLoader.ts +136 -0
- package/src/index.ts +4 -0
- package/src/model/treeModel.ts +13 -0
- package/src/model.ts +370 -748
- package/src/neighborJoining.ts +1 -42
- package/src/renderToSvg.tsx +1 -13
- package/src/rerootTree.ts +7 -6
- package/src/schemeResidueColor.ts +37 -0
- package/src/similarityColors.ts +112 -0
- package/src/treeDisplay.ts +278 -0
- package/src/types.ts +7 -4
- package/src/useWheelScroll.ts +43 -22
- package/src/util.ts +3 -0
- package/src/version.ts +1 -1
- package/src/wheelZoom.ts +55 -33
- package/src/zoomSnapshot.ts +80 -0
- package/dist/components/header/ColorSchemeMenu.d.ts +0 -12
- package/dist/components/header/ColorSchemeMenu.js +0 -37
- package/dist/components/header/ColorSchemeMenu.js.map +0 -1
- package/dist/components/header/FileMenu.js +0 -92
- package/dist/components/header/FileMenu.js.map +0 -1
- package/dist/components/header/GappynessSlider.js.map +0 -1
- package/dist/components/header/GoToBox.js +0 -31
- package/dist/components/header/GoToBox.js.map +0 -1
- package/dist/components/header/MSASettingsMenu.js +0 -11
- package/dist/components/header/MSASettingsMenu.js.map +0 -1
- package/dist/components/header/ScrollZoomIcons.d.ts +0 -6
- package/dist/components/header/ScrollZoomIcons.js +0 -35
- package/dist/components/header/ScrollZoomIcons.js.map +0 -1
- package/dist/components/header/TreeSettingsMenu.js +0 -11
- package/dist/components/header/TreeSettingsMenu.js.map +0 -1
- package/dist/components/header/goTo.d.ts +0 -20
- package/dist/components/header/goTo.js.map +0 -1
- package/dist/components/header/settingsMenuItems.d.ts +0 -5
- package/dist/components/header/settingsMenuItems.js +0 -171
- package/dist/components/header/settingsMenuItems.js.map +0 -1
- package/src/components/header/ColorSchemeMenu.tsx +0 -56
- package/src/components/header/FileMenu.tsx +0 -107
- package/src/components/header/MSASettingsMenu.tsx +0 -26
- package/src/components/header/ScrollZoomIcons.tsx +0 -52
- package/src/components/header/TreeSettingsMenu.tsx +0 -30
- package/src/components/header/settingsMenuItems.ts +0 -216
package/src/model.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { clamp, groupBy, notEmpty, sum } from '@jbrowse/core/util'
|
|
2
|
-
import { openLocation } from '@jbrowse/core/util/io'
|
|
3
2
|
import { ElementId, FileLocation } from '@jbrowse/core/util/types/mst'
|
|
4
3
|
import {
|
|
5
4
|
addDisposer,
|
|
6
5
|
applySnapshot,
|
|
7
6
|
cast,
|
|
8
7
|
getSnapshot,
|
|
9
|
-
isAlive,
|
|
10
8
|
types,
|
|
11
9
|
} from '@jbrowse/mobx-state-tree'
|
|
12
10
|
import {
|
|
@@ -19,10 +17,8 @@ import {
|
|
|
19
17
|
import {
|
|
20
18
|
generateNodeIds,
|
|
21
19
|
gffToAnnotations,
|
|
22
|
-
parseEmfTree,
|
|
23
20
|
parseGFF,
|
|
24
21
|
parseMSA,
|
|
25
|
-
parseNewick,
|
|
26
22
|
} from 'msa-parsers'
|
|
27
23
|
|
|
28
24
|
import { blockFasta } from './blockFasta.ts'
|
|
@@ -36,14 +32,11 @@ import { visibleColRange } from './components/msa/visibleColRange.ts'
|
|
|
36
32
|
import { cladeGutterWidth } from './components/tree/cladeBrackets.ts'
|
|
37
33
|
import { scaleBarLength } from './components/tree/scaleBar.ts'
|
|
38
34
|
import {
|
|
39
|
-
cladeHighlightAlpha,
|
|
40
|
-
cladeHighlightColor,
|
|
41
35
|
defaultAllowedGappyness,
|
|
42
36
|
defaultColWidth,
|
|
43
37
|
defaultColorSchemeName,
|
|
44
38
|
defaultCurrentAlignment,
|
|
45
39
|
defaultDrawMsaLetters,
|
|
46
|
-
defaultFeaturePanelWidth,
|
|
47
40
|
defaultHeight,
|
|
48
41
|
defaultHideGaps,
|
|
49
42
|
defaultRowHeight,
|
|
@@ -72,35 +65,37 @@ import {
|
|
|
72
65
|
scrollZoomAxes,
|
|
73
66
|
segmentFeatureTypes,
|
|
74
67
|
segmentShades,
|
|
68
|
+
settingsPanelWidth,
|
|
75
69
|
treeScaleBarHeight,
|
|
76
70
|
} from './constants.ts'
|
|
77
71
|
import { createPaletteMap } from './createPaletteMap.ts'
|
|
78
72
|
import { exportFileName } from './exportFileName.ts'
|
|
79
|
-
import {
|
|
73
|
+
import { featureName } from './featureFields.ts'
|
|
74
|
+
import {
|
|
75
|
+
colorByRow,
|
|
76
|
+
featureColorMap,
|
|
77
|
+
featureLabelMap,
|
|
78
|
+
featurePanelSpans,
|
|
79
|
+
resolveFeatureScale,
|
|
80
|
+
rowPanelWidth,
|
|
81
|
+
stringFields,
|
|
82
|
+
} from './featurePanels.ts'
|
|
80
83
|
import { featuresToAnnotations } from './featuresLayer.ts'
|
|
81
|
-
import {
|
|
82
|
-
import { flatToTree } from './flatToTree.ts'
|
|
84
|
+
import { installFilehandleLoader } from './filehandleLoader.ts'
|
|
83
85
|
import {
|
|
84
|
-
collapse,
|
|
85
86
|
find,
|
|
86
|
-
forEachDescendant,
|
|
87
87
|
hierarchy,
|
|
88
88
|
layoutTree,
|
|
89
|
-
leafIndex,
|
|
90
89
|
leaves,
|
|
91
|
-
mrca,
|
|
92
90
|
nodeCoveringRows,
|
|
93
|
-
sort,
|
|
94
|
-
sum as hierarchySum,
|
|
95
91
|
} from './hierarchy.ts'
|
|
96
92
|
import { DataModelF } from './model/DataModel.ts'
|
|
97
93
|
import { DialogQueueSessionMixin } from './model/DialogQueue.ts'
|
|
98
94
|
import { MSAModelF } from './model/msaModel.ts'
|
|
99
95
|
import { TreeModelF } from './model/treeModel.ts'
|
|
100
96
|
import { calculateNeighborJoiningTree } from './neighborJoining.ts'
|
|
101
|
-
import { parseAsn1 } from './parseAsn1.ts'
|
|
102
97
|
import { calculatePropertyConservation } from './propertyConservation.ts'
|
|
103
|
-
import {
|
|
98
|
+
import { preorder } from './rerootTree.ts'
|
|
104
99
|
import {
|
|
105
100
|
globalColToVisibleCol,
|
|
106
101
|
seqPosOfGlobalCol,
|
|
@@ -110,33 +105,43 @@ import {
|
|
|
110
105
|
import { resolveScale } from './scales.ts'
|
|
111
106
|
import { buildSeqPosIndex } from './seqPosToGlobalCol.ts'
|
|
112
107
|
import { maxBitsFor } from './sequenceLogo.ts'
|
|
108
|
+
import {
|
|
109
|
+
blosum62ColumnColors,
|
|
110
|
+
identityColumnColors,
|
|
111
|
+
} from './similarityColors.ts'
|
|
113
112
|
import { stripDefault } from './stripDefault.ts'
|
|
114
113
|
import { treeTips, widestLabel } from './tipLabels.ts'
|
|
114
|
+
import {
|
|
115
|
+
buildTreeRoot,
|
|
116
|
+
outgroupNode,
|
|
117
|
+
parseTreeText,
|
|
118
|
+
resolveClades,
|
|
119
|
+
rootTree,
|
|
120
|
+
tipRange,
|
|
121
|
+
} from './treeDisplay.ts'
|
|
115
122
|
import {
|
|
116
123
|
computeRowInsertions,
|
|
117
124
|
dropBlanks,
|
|
118
125
|
len,
|
|
119
|
-
|
|
126
|
+
readComputed,
|
|
120
127
|
skipBlanks,
|
|
121
128
|
withAlpha,
|
|
122
129
|
} from './util.ts'
|
|
123
130
|
import { saveAs } from './vendor/fileSaver.ts'
|
|
124
131
|
import { writeNewick } from './writeNewick.ts'
|
|
125
132
|
import { parseWuss } from './wuss.ts'
|
|
133
|
+
import { takeZoomSnapshot, zoomSnapshotCovers } from './zoomSnapshot.ts'
|
|
126
134
|
|
|
127
135
|
import type { ColumnStats } from './columnStats.ts'
|
|
128
136
|
import type { ScrollZoomAxis } from './constants.ts'
|
|
129
137
|
import type { HierarchyNode } from './hierarchy.ts'
|
|
130
138
|
import type { ExportSvgOptions } from './renderToSvg.tsx'
|
|
131
|
-
import type { TreeNode } from './rerootTree.ts'
|
|
132
|
-
import type { ScaleSpec } from './scales.ts'
|
|
133
139
|
import type {
|
|
134
140
|
Annotation,
|
|
135
141
|
Arc,
|
|
136
142
|
BasicTrack,
|
|
137
143
|
Cell,
|
|
138
144
|
Clade,
|
|
139
|
-
CladeMark,
|
|
140
145
|
ResidueMappingProblem,
|
|
141
146
|
ColumnTrackSpec,
|
|
142
147
|
DomainBand,
|
|
@@ -154,14 +159,11 @@ import type {
|
|
|
154
159
|
ResolvedClade,
|
|
155
160
|
ResolvedEncoding,
|
|
156
161
|
ResolvedFeaturePanel,
|
|
162
|
+
ResolvedStripPanel,
|
|
157
163
|
ResolvedHighlight,
|
|
158
164
|
ResolvedSelection,
|
|
159
|
-
TreeOrder,
|
|
160
165
|
TreeRoot,
|
|
161
166
|
ResolvedRowPanel,
|
|
162
|
-
ResolvedStripPanel,
|
|
163
|
-
RowFeaturesSpec,
|
|
164
|
-
RowPanelSpan,
|
|
165
167
|
RowDataInput,
|
|
166
168
|
RowPanelSpec,
|
|
167
169
|
RowResidue,
|
|
@@ -170,17 +172,11 @@ import type {
|
|
|
170
172
|
UnshareableData,
|
|
171
173
|
Viewport,
|
|
172
174
|
} from './types.ts'
|
|
175
|
+
import type { ZoomSnapshot } from './zoomSnapshot.ts'
|
|
173
176
|
import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
|
|
174
177
|
import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
175
178
|
import type { IComputedValue } from 'mobx'
|
|
176
179
|
|
|
177
|
-
function parseTreeText(text: string) {
|
|
178
|
-
if (text.startsWith('BioTreeContainer')) {
|
|
179
|
-
return flatToTree(parseAsn1(text))
|
|
180
|
-
}
|
|
181
|
-
return parseNewick(text.startsWith('SEQ') ? parseEmfTree(text).tree : text)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
180
|
// The height each kind draws at before the user drags a divider. A text track
|
|
185
181
|
// is absent: it is one alignment row tall and follows rowHeight, so the zoom
|
|
186
182
|
// controls already size it.
|
|
@@ -209,262 +205,6 @@ const ownHeightKey = (id: string) => `own:${id}`
|
|
|
209
205
|
const HELIX_ARC = '#4e79a7'
|
|
210
206
|
const PSEUDOKNOT_ARC = '#e15759'
|
|
211
207
|
|
|
212
|
-
// the fill of a span under a scale that gives its value no color, such as a
|
|
213
|
-
// gene a `map` over gene names leaves out
|
|
214
|
-
const UNSCALED_FEATURE = '#d9d9d9'
|
|
215
|
-
|
|
216
|
-
// the fraction of a span a following one may cover before it takes a lane of
|
|
217
|
-
// its own in a `features` panel
|
|
218
|
-
const laneOverlap = 0.1
|
|
219
|
-
|
|
220
|
-
// a row panel's own width, or a default: the row height, which makes a strip
|
|
221
|
-
// cell square, and a gene neighborhood's width for a features panel. The row
|
|
222
|
-
// height is read only when used, so a panel with a width of its own does not
|
|
223
|
-
// recompute on a vertical zoom
|
|
224
|
-
function rowPanelWidth(panel: RowPanelSpec, view: { rowHeight: number }) {
|
|
225
|
-
return (
|
|
226
|
-
panel.width ??
|
|
227
|
-
(panel.kind === 'features' ? defaultFeaturePanelWidth : view.rowHeight)
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// one row of the row table as the channels read it. A number or a boolean
|
|
232
|
-
// reads as its string, the way a GFF value does, and null or an object drops
|
|
233
|
-
function stringFields(row: unknown) {
|
|
234
|
-
if (typeof row !== 'object' || row === null) {
|
|
235
|
-
return undefined
|
|
236
|
-
}
|
|
237
|
-
const fields: Record<string, string> = {}
|
|
238
|
-
for (const [field, value] of Object.entries(row)) {
|
|
239
|
-
if (typeof value === 'string') {
|
|
240
|
-
fields[field] = value
|
|
241
|
-
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
242
|
-
fields[field] = String(value)
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return fields
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// the color each row takes from a field of the row table under a scale
|
|
249
|
-
function colorByRow(
|
|
250
|
-
rowData: Record<string, Record<string, string> | undefined>,
|
|
251
|
-
field: string,
|
|
252
|
-
colorOf: (value: string) => string | undefined,
|
|
253
|
-
) {
|
|
254
|
-
const colors = new Map<string, string>()
|
|
255
|
-
for (const [name, row] of Object.entries(rowData)) {
|
|
256
|
-
const value = row?.[field]
|
|
257
|
-
const color = value === undefined ? undefined : colorOf(value)
|
|
258
|
-
if (color) {
|
|
259
|
-
colors.set(name, color)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return colors
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
type StripPanelScale = Omit<ResolvedStripPanel, 'id' | 'width' | 'offsetX'>
|
|
266
|
-
type FeaturePanelScale = Omit<
|
|
267
|
-
ResolvedFeaturePanel,
|
|
268
|
-
'id' | 'width' | 'offsetX' | 'spans'
|
|
269
|
-
>
|
|
270
|
-
|
|
271
|
-
// the fill and outline of every feature in a list: its own GFF `color=` first,
|
|
272
|
-
// then the scale an encoding resolves over one of its fields, then the
|
|
273
|
-
// accession palette
|
|
274
|
-
function featureColorMap(
|
|
275
|
-
annotations: Annotation[],
|
|
276
|
-
encoding:
|
|
277
|
-
| { field: string; colorOf: (value: string) => string | undefined }
|
|
278
|
-
| undefined,
|
|
279
|
-
fillPalette: Record<string, string>,
|
|
280
|
-
) {
|
|
281
|
-
const strokes = new Map<string, string>()
|
|
282
|
-
const strokeOf = (fill: string) => {
|
|
283
|
-
const hit = strokes.get(fill)
|
|
284
|
-
if (hit !== undefined) {
|
|
285
|
-
return hit
|
|
286
|
-
}
|
|
287
|
-
const stroke = outlineColor(fill)
|
|
288
|
-
strokes.set(fill, stroke)
|
|
289
|
-
return stroke
|
|
290
|
-
}
|
|
291
|
-
return new Map(
|
|
292
|
-
annotations.map(annotation => {
|
|
293
|
-
const value = encoding
|
|
294
|
-
? featureField(annotation, encoding.field)
|
|
295
|
-
: undefined
|
|
296
|
-
const fill =
|
|
297
|
-
annotation.color ??
|
|
298
|
-
(value === undefined ? undefined : encoding!.colorOf(value)) ??
|
|
299
|
-
(encoding ? UNSCALED_FEATURE : fillPalette[annotation.accession]!)
|
|
300
|
-
return [annotation, { fill, stroke: strokeOf(fill) }]
|
|
301
|
-
}),
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// the text a label channel draws inside each span, for the features carrying
|
|
306
|
-
// the field it names
|
|
307
|
-
function featureLabelMap(annotations: Annotation[], field: string) {
|
|
308
|
-
const labels = new Map<Annotation, string>()
|
|
309
|
-
for (const annotation of annotations) {
|
|
310
|
-
const value = featureField(annotation, field)
|
|
311
|
-
if (value !== undefined) {
|
|
312
|
-
labels.set(annotation, value)
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return labels
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** what `packDomainLanes` needs to lay a row out, before it has its lane */
|
|
319
|
-
type Unlaned = Omit<RowPanelSpan, 'lane' | 'laneCount'> & {
|
|
320
|
-
startCol: number
|
|
321
|
-
endCol: number
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Lanes for `position: "strandpile"`, gggenomes' `position_strandpile`: the
|
|
326
|
-
* forward features stack above the line and the reverse below it, each strand
|
|
327
|
-
* packing on its own. The deepest row on each side sets the grid every row lays
|
|
328
|
-
* out on, so the line between the strands sits at one height down the panel and
|
|
329
|
-
* a reader can scan it. A feature with no strand piles with the forward ones.
|
|
330
|
-
*/
|
|
331
|
-
function strandpileLanes(
|
|
332
|
-
rows: [string, Unlaned[]][],
|
|
333
|
-
): Map<string, RowPanelSpan[]> {
|
|
334
|
-
const piled = rows.map(([name, bands]) => {
|
|
335
|
-
const reverse = packDomainLanes(
|
|
336
|
-
bands.filter(b => b.annotation.strand === -1),
|
|
337
|
-
)
|
|
338
|
-
const forward = packDomainLanes(
|
|
339
|
-
bands.filter(b => b.annotation.strand !== -1),
|
|
340
|
-
)
|
|
341
|
-
return { name, forward, reverse }
|
|
342
|
-
})
|
|
343
|
-
const up = Math.max(0, ...piled.map(p => p.forward[0]?.laneCount ?? 0))
|
|
344
|
-
const down = Math.max(0, ...piled.map(p => p.reverse[0]?.laneCount ?? 0))
|
|
345
|
-
const laneCount = up + down
|
|
346
|
-
return new Map(
|
|
347
|
-
piled.map(({ name, forward, reverse }) => [
|
|
348
|
-
name,
|
|
349
|
-
[
|
|
350
|
-
// forward level 0 is the lane just above the line, stacking upward
|
|
351
|
-
...forward.map(b => ({ ...b, lane: up - 1 - b.lane, laneCount })),
|
|
352
|
-
...reverse.map(b => ({ ...b, lane: up + b.lane, laneCount })),
|
|
353
|
-
],
|
|
354
|
-
]),
|
|
355
|
-
)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/** one row's spans laid out by the panel's `position` */
|
|
359
|
-
function panelLanes(
|
|
360
|
-
panel: RowFeaturesSpec,
|
|
361
|
-
rows: [string, Unlaned[]][],
|
|
362
|
-
): Map<string, RowPanelSpan[]> {
|
|
363
|
-
return panel.position === 'strandpile'
|
|
364
|
-
? strandpileLanes(rows)
|
|
365
|
-
: new Map(rows.map(([name, bands]) => [name, packDomainLanes(bands)]))
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* The spans a `features` panel draws, keyed by row name and measured in the
|
|
370
|
-
* panel's own pixels. `column` takes the bands the overlay draws, at the
|
|
371
|
-
* alignment's column width. `position` packs each row's features in its own
|
|
372
|
-
* residue positions, shifted by an `align` transform, and maps the extent they
|
|
373
|
-
* cover across every row onto the panel width, so a row with no alignment
|
|
374
|
-
* still has an x.
|
|
375
|
-
*/
|
|
376
|
-
function featurePanelSpans({
|
|
377
|
-
panel,
|
|
378
|
-
width,
|
|
379
|
-
colWidth,
|
|
380
|
-
domainBands,
|
|
381
|
-
annotationsByRow,
|
|
382
|
-
shifts,
|
|
383
|
-
}: {
|
|
384
|
-
panel: RowFeaturesSpec
|
|
385
|
-
width: number
|
|
386
|
-
colWidth: number
|
|
387
|
-
domainBands: Map<string, DomainBand[]>
|
|
388
|
-
annotationsByRow: Record<string, Annotation[]>
|
|
389
|
-
shifts: Map<string, number> | undefined
|
|
390
|
-
}): Map<string, RowPanelSpan[]> {
|
|
391
|
-
if (panel.x === 'column') {
|
|
392
|
-
return panelLanes(
|
|
393
|
-
panel,
|
|
394
|
-
[...domainBands].map(([name, bands]) => [
|
|
395
|
-
name,
|
|
396
|
-
bands.map(band => ({
|
|
397
|
-
...band,
|
|
398
|
-
xStart: band.startCol * colWidth,
|
|
399
|
-
xEnd: band.endCol * colWidth,
|
|
400
|
-
})),
|
|
401
|
-
]),
|
|
402
|
-
)
|
|
403
|
-
}
|
|
404
|
-
const shifted = Object.entries(annotationsByRow).map(
|
|
405
|
-
([name, annotations]) => {
|
|
406
|
-
const shift = shifts?.get(name) ?? 0
|
|
407
|
-
return [
|
|
408
|
-
name,
|
|
409
|
-
annotations.map(annotation => ({
|
|
410
|
-
annotation,
|
|
411
|
-
start: annotation.start - 1 + shift,
|
|
412
|
-
end: annotation.end + shift,
|
|
413
|
-
})),
|
|
414
|
-
] as const
|
|
415
|
-
},
|
|
416
|
-
)
|
|
417
|
-
let min = Infinity
|
|
418
|
-
let max = -Infinity
|
|
419
|
-
for (const [, features] of shifted) {
|
|
420
|
-
for (const { start, end } of features) {
|
|
421
|
-
min = Math.min(min, start)
|
|
422
|
-
max = Math.max(max, end)
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
// a gene arrow ends where its feature does, so the extent maps onto the whole
|
|
426
|
-
// panel less the pixel the rightmost stroke needs
|
|
427
|
-
const drawable = Math.max(1, width - 1)
|
|
428
|
-
const scale = max > min ? drawable / (max - min) : 0
|
|
429
|
-
// a lane opens where a span covers more than a tenth of the one before it,
|
|
430
|
-
// which keeps the genes of an operon on one lane: adjacent genes commonly
|
|
431
|
-
// share a few bases, and a stop codon overlapping the next start reads as a
|
|
432
|
-
// second lane over the whole row
|
|
433
|
-
return panelLanes(
|
|
434
|
-
panel,
|
|
435
|
-
shifted.map(([name, features]) => [
|
|
436
|
-
name,
|
|
437
|
-
features.map(({ annotation, start, end }) => ({
|
|
438
|
-
annotation,
|
|
439
|
-
xStart: (start - min) * scale,
|
|
440
|
-
xEnd: (end - min) * scale,
|
|
441
|
-
startCol: start,
|
|
442
|
-
endCol: end - (end - start) * laneOverlap,
|
|
443
|
-
})),
|
|
444
|
-
]),
|
|
445
|
-
)
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// a scale over the values a field takes across every feature loaded, so hiding
|
|
449
|
-
// a type in the filter dialog leaves the rest in their colors, and a legend of
|
|
450
|
-
// the values the features drawn carry
|
|
451
|
-
function resolveFeatureScale(
|
|
452
|
-
field: string,
|
|
453
|
-
scale: ScaleSpec | undefined,
|
|
454
|
-
annotations: Annotation[],
|
|
455
|
-
drawn: Annotation[],
|
|
456
|
-
) {
|
|
457
|
-
const valuesOf = (list: Annotation[]) =>
|
|
458
|
-
list.map(a => featureField(a, field)).filter(notEmpty)
|
|
459
|
-
const resolved = resolveScale(scale, valuesOf(annotations))
|
|
460
|
-
const shown = new Set(valuesOf(drawn))
|
|
461
|
-
return {
|
|
462
|
-
field,
|
|
463
|
-
...resolved,
|
|
464
|
-
legend: resolved.legend.filter(entry => shown.has(entry.id)),
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
208
|
const columnTrackBytes = new WeakMap<ColumnTrackSpec, number>()
|
|
469
209
|
|
|
470
210
|
function columnTrackSize(track: ColumnTrackSpec) {
|
|
@@ -524,6 +264,7 @@ export const preservedOnReset = new Set([
|
|
|
524
264
|
'drawNodeBubbles',
|
|
525
265
|
'drawNodeLabels',
|
|
526
266
|
'showTreeOverview',
|
|
267
|
+
'showTreeScaleBar',
|
|
527
268
|
'overviewHeight',
|
|
528
269
|
'turnedOffTracks',
|
|
529
270
|
'trackHeights',
|
|
@@ -531,266 +272,23 @@ export const preservedOnReset = new Set([
|
|
|
531
272
|
'allowedGappyness',
|
|
532
273
|
'subFeatureRows',
|
|
533
274
|
'showDomainLegend',
|
|
275
|
+
'domainFill',
|
|
276
|
+
'embeddedMode',
|
|
534
277
|
])
|
|
535
278
|
|
|
536
279
|
// `turnedOffTracks` holds only the user's explicit choices, so a hidden-by-default
|
|
537
|
-
// track writes nothing into the snapshot
|
|
538
|
-
// `defaultOff`, since the count varies by file: a Pfam seed has a couple of #=GR
|
|
539
|
-
// lines, an Rfam family one per row.
|
|
280
|
+
// track writes nothing into the snapshot
|
|
540
281
|
function trackIsOff(
|
|
541
282
|
turnedOffTracks: { get: (id: string) => boolean | undefined },
|
|
542
283
|
id: string,
|
|
543
|
-
defaultOff?: boolean,
|
|
544
284
|
) {
|
|
545
|
-
return turnedOffTracks.get(id) ??
|
|
285
|
+
return turnedOffTracks.get(id) ?? defaultOffTracks.has(id)
|
|
546
286
|
}
|
|
547
287
|
|
|
548
|
-
// the marks that set tree state from a node rather than drawing
|
|
549
|
-
const seedingMarks = new Set<CladeMark>(['collapse', 'focus', 'rotate'])
|
|
550
|
-
|
|
551
288
|
// shared empty results, so observers don't see a fresh [] as a change
|
|
552
289
|
const noDomains: Annotation[] = []
|
|
553
290
|
const noClades: ResolvedClade[] = []
|
|
554
291
|
|
|
555
|
-
/**
|
|
556
|
-
* Each tip of the full tree in the order the tree draws it, with the on-screen
|
|
557
|
-
* row that shows it: its own, or the row of the collapsed node standing for it
|
|
558
|
-
*/
|
|
559
|
-
function tipRows(root: HierarchyNode<NodeWithIds>) {
|
|
560
|
-
const rows = new Map<string, { order: number; row: number }>()
|
|
561
|
-
let row = 0
|
|
562
|
-
const pushReversed = (
|
|
563
|
-
stack: HierarchyNode<NodeWithIds>[],
|
|
564
|
-
nodes: HierarchyNode<NodeWithIds>[],
|
|
565
|
-
) => {
|
|
566
|
-
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
567
|
-
stack.push(nodes[i]!)
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
const stack = [root]
|
|
571
|
-
while (stack.length > 0) {
|
|
572
|
-
const node = stack.pop()!
|
|
573
|
-
if (node.children?.length) {
|
|
574
|
-
pushReversed(stack, node.children)
|
|
575
|
-
} else {
|
|
576
|
-
const hidden = [node]
|
|
577
|
-
while (hidden.length > 0) {
|
|
578
|
-
const tip = hidden.pop()!
|
|
579
|
-
const kids = tip.children ?? tip._children
|
|
580
|
-
if (kids?.length) {
|
|
581
|
-
pushReversed(hidden, kids)
|
|
582
|
-
} else {
|
|
583
|
-
rows.set(tip.data.name, { order: rows.size, row })
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
row++
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
return rows
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* The rows a clade covers and the node an `mrca` names, or undefined when the
|
|
594
|
-
* clade does not resolve: a tip name the tree does not have or has twice, or a
|
|
595
|
-
* leaf count `tips` disagrees with. A `range` counts its tips in the order
|
|
596
|
-
* `tips` gives them, so a collapsed clade inside it counts every leaf it
|
|
597
|
-
* stands for. `index` is the memoized leaf pass over the tree.
|
|
598
|
-
*/
|
|
599
|
-
function cladeRows(
|
|
600
|
-
clade: Clade,
|
|
601
|
-
root: HierarchyNode<NodeWithIds>,
|
|
602
|
-
index: Map<string, HierarchyNode<NodeWithIds> | undefined>,
|
|
603
|
-
tips: Map<string, { order: number; row: number }>,
|
|
604
|
-
): { rows: [number, number]; nodeId?: string } | undefined {
|
|
605
|
-
if (clade.range) {
|
|
606
|
-
const [a, b] = clade.range.map(name =>
|
|
607
|
-
index.get(name) ? tips.get(name) : undefined,
|
|
608
|
-
)
|
|
609
|
-
if (a === undefined || b === undefined) {
|
|
610
|
-
return undefined
|
|
611
|
-
}
|
|
612
|
-
const [first, last] = a.order <= b.order ? [a, b] : [b, a]
|
|
613
|
-
return last.order - first.order + 1 === clade.tips
|
|
614
|
-
? { rows: [first.row, last.row] }
|
|
615
|
-
: undefined
|
|
616
|
-
}
|
|
617
|
-
const node = clade.mrca ? mrca(root, clade.mrca, index) : undefined
|
|
618
|
-
if (!node) {
|
|
619
|
-
return undefined
|
|
620
|
-
}
|
|
621
|
-
const nodeTips = leaves(node)
|
|
622
|
-
if (nodeTips.length !== clade.tips) {
|
|
623
|
-
return undefined
|
|
624
|
-
}
|
|
625
|
-
let first = Infinity
|
|
626
|
-
let last = -Infinity
|
|
627
|
-
for (const tip of nodeTips) {
|
|
628
|
-
const row = tips.get(tip.data.name)?.row
|
|
629
|
-
if (row !== undefined) {
|
|
630
|
-
first = Math.min(first, row)
|
|
631
|
-
last = Math.max(last, row)
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
return first <= last
|
|
635
|
-
? { rows: [first, last], nodeId: node.data.id }
|
|
636
|
-
: undefined
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
/**
|
|
640
|
-
* Every clade that resolves, with its fill color settled, against the rows of
|
|
641
|
-
* `displayed`, the tree as drawn. The tree panel resolves against the
|
|
642
|
-
* displayed rows and the overview against its own, so both take the tree they
|
|
643
|
-
* draw. A `range` record names no node, so the seeding marks, which need one,
|
|
644
|
-
* drop it.
|
|
645
|
-
*/
|
|
646
|
-
function resolveClades(
|
|
647
|
-
clades: Clade[],
|
|
648
|
-
root: HierarchyNode<NodeWithIds>,
|
|
649
|
-
displayed: HierarchyNode<NodeWithIds>,
|
|
650
|
-
): ResolvedClade[] {
|
|
651
|
-
const index = leafIndex(root)
|
|
652
|
-
const tips = tipRows(displayed)
|
|
653
|
-
return clades.flatMap(clade => {
|
|
654
|
-
const resolved = cladeRows(clade, root, index, tips)
|
|
655
|
-
const seeding = seedingMarks.has(clade.mark)
|
|
656
|
-
return resolved && !(seeding && resolved.nodeId === undefined)
|
|
657
|
-
? [
|
|
658
|
-
{
|
|
659
|
-
...resolved,
|
|
660
|
-
mark: clade.mark,
|
|
661
|
-
color: withAlpha(
|
|
662
|
-
clade.color ?? cladeHighlightColor,
|
|
663
|
-
cladeHighlightAlpha,
|
|
664
|
-
),
|
|
665
|
-
markColor: clade.color,
|
|
666
|
-
label: clade.label,
|
|
667
|
-
},
|
|
668
|
-
]
|
|
669
|
-
: []
|
|
670
|
-
})
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const childOrders: Record<
|
|
674
|
-
TreeOrder,
|
|
675
|
-
((a: HierarchyNode, b: HierarchyNode) => number) | undefined
|
|
676
|
-
> = {
|
|
677
|
-
input: undefined,
|
|
678
|
-
branchLength: (a, b) => (a.data.length ?? 1) - (b.data.length ?? 1),
|
|
679
|
-
ladderize: (a, b) => a.value! - b.value!,
|
|
680
|
-
ladderizeReverse: (a, b) => b.value! - a.value!,
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
/**
|
|
684
|
-
* The tree with the display transforms applied: leaf counts summed, children
|
|
685
|
-
* put in `order` and each `rotated` node's reversed, `showOnly` taken as the
|
|
686
|
-
* new root, and each `collapsed` clade folded. The overview builds the same
|
|
687
|
-
* tree without the focus, so it shows the focused subtree inside the whole.
|
|
688
|
-
*/
|
|
689
|
-
function buildTreeRoot(
|
|
690
|
-
tree: NodeWithIds,
|
|
691
|
-
{
|
|
692
|
-
collapsed,
|
|
693
|
-
rotated,
|
|
694
|
-
order,
|
|
695
|
-
showOnly,
|
|
696
|
-
}: {
|
|
697
|
-
collapsed: readonly string[]
|
|
698
|
-
rotated: readonly string[]
|
|
699
|
-
order: TreeOrder
|
|
700
|
-
showOnly?: string
|
|
701
|
-
},
|
|
702
|
-
) {
|
|
703
|
-
let hier = hierarchy(tree, d => d.children)
|
|
704
|
-
hierarchySum(hier, d => (d.children.length > 0 ? 0 : 1))
|
|
705
|
-
const compare = childOrders[order]
|
|
706
|
-
if (compare) {
|
|
707
|
-
sort(hier, compare)
|
|
708
|
-
}
|
|
709
|
-
if (rotated.length > 0) {
|
|
710
|
-
const flip = new Set(rotated)
|
|
711
|
-
forEachDescendant(hier, n => {
|
|
712
|
-
if (flip.has(n.data.id)) {
|
|
713
|
-
n.children?.reverse()
|
|
714
|
-
}
|
|
715
|
-
})
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
if (showOnly) {
|
|
719
|
-
const res = find(hier, n => n.data.id === showOnly)
|
|
720
|
-
if (res) {
|
|
721
|
-
hier = res
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
for (const collapsedId of collapsed) {
|
|
726
|
-
const node = find(hier, n => n.data.id === collapsedId)
|
|
727
|
-
if (node) {
|
|
728
|
-
if (node.children) {
|
|
729
|
-
collapse(node)
|
|
730
|
-
} else if (node.parent?.children) {
|
|
731
|
-
node.parent.children = node.parent.children.filter(
|
|
732
|
-
c => c.data.id !== collapsedId,
|
|
733
|
-
)
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
return hier
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* The node whose branch an outgroup roots the tree on: the outgroup's most
|
|
743
|
-
* recent common ancestor, or the rest of the tips' where that ancestor is the
|
|
744
|
-
* root, since the outgroup then straddles the root and the branch above the
|
|
745
|
-
* other side is the same branch of the unrooted tree.
|
|
746
|
-
*/
|
|
747
|
-
function outgroupNode(root: HierarchyNode, outgroup: string[]) {
|
|
748
|
-
const index = leafIndex(root)
|
|
749
|
-
const node = mrca(root, outgroup, index)
|
|
750
|
-
if (node !== root) {
|
|
751
|
-
return node
|
|
752
|
-
}
|
|
753
|
-
const out = new Set(outgroup)
|
|
754
|
-
const rest = leaves(root)
|
|
755
|
-
.map(n => n.data.name)
|
|
756
|
-
.filter(name => !out.has(name))
|
|
757
|
-
const other = mrca(root, rest, index)
|
|
758
|
-
return other === root ? undefined : other
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* `tree` rerooted as `treeRoot` asks, with fresh path ids. The ids of the tree
|
|
763
|
-
* as written become names on unnamed nodes, and a rerooting would carry them
|
|
764
|
-
* along as labels, so they come off first.
|
|
765
|
-
*/
|
|
766
|
-
function rootTree(tree: NodeWithIds, treeRoot: TreeRoot) {
|
|
767
|
-
const plain = new Map<NodeWithIds, TreeNode>()
|
|
768
|
-
for (const node of preorder(tree).reverse()) {
|
|
769
|
-
plain.set(node, {
|
|
770
|
-
name: node.name === node.id ? undefined : node.name,
|
|
771
|
-
length: node.length,
|
|
772
|
-
children: node.children.map(c => plain.get(c)!),
|
|
773
|
-
})
|
|
774
|
-
}
|
|
775
|
-
const input = plain.get(tree)!
|
|
776
|
-
if (treeRoot === 'midpoint') {
|
|
777
|
-
return generateNodeIds(midpointRoot(input))
|
|
778
|
-
}
|
|
779
|
-
const target = outgroupNode(
|
|
780
|
-
hierarchy(tree, d => d.children),
|
|
781
|
-
treeRoot.outgroup,
|
|
782
|
-
)
|
|
783
|
-
return target
|
|
784
|
-
? generateNodeIds(rerootTree(input, plain.get(target.data)!))
|
|
785
|
-
: tree
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// the inclusive tip indices a node covers, from its xMin/xMax in a layout one
|
|
789
|
-
// row per tip
|
|
790
|
-
function tipRange(node: HierarchyNode): [number, number] {
|
|
791
|
-
return [Math.round(node.xMin! - 0.5), Math.round(node.xMax! - 0.5)]
|
|
792
|
-
}
|
|
793
|
-
|
|
794
292
|
// the channels reading the feature table; every other channel reads rowData
|
|
795
293
|
const featureChannels = new Set<EncodingChannel>([
|
|
796
294
|
'featureFill',
|
|
@@ -821,6 +319,31 @@ function sameLength(segment: ResidueSegment) {
|
|
|
821
319
|
)
|
|
822
320
|
}
|
|
823
321
|
|
|
322
|
+
// A zoom that passes the default cell size stops on it, and one that starts on
|
|
323
|
+
// it holds there until the wheel has gathered `zoomDetent` worth of scale, so
|
|
324
|
+
// a zoom in or out finds its way back to the default
|
|
325
|
+
const zoomDetent = Math.log(1.3)
|
|
326
|
+
|
|
327
|
+
function detentZoom(
|
|
328
|
+
value: number,
|
|
329
|
+
scale: number,
|
|
330
|
+
home: number,
|
|
331
|
+
held: number,
|
|
332
|
+
min: number,
|
|
333
|
+
canHold: boolean,
|
|
334
|
+
) {
|
|
335
|
+
if (value === home && canHold) {
|
|
336
|
+
const total = held + Math.log(scale)
|
|
337
|
+
return Math.abs(total) < zoomDetent
|
|
338
|
+
? { value, held: total }
|
|
339
|
+
: { value: clamp(home * Math.exp(total), min, maxCellSize), held: 0 }
|
|
340
|
+
}
|
|
341
|
+
const next = clamp(value * scale, min, maxCellSize)
|
|
342
|
+
return (value - home) * (next - home) < 0
|
|
343
|
+
? { value: home, held: 0 }
|
|
344
|
+
: { value: next, held: 0 }
|
|
345
|
+
}
|
|
346
|
+
|
|
824
347
|
// fit() divides the viewport by the row or column count and multiplies back, so
|
|
825
348
|
// an exact fit can land a fraction of a pixel over. A plain `>` would then show
|
|
826
349
|
// a scrollbar that shrinks the viewport and leaves a gap.
|
|
@@ -855,19 +378,11 @@ function sharedValue(values: (string | undefined)[]) {
|
|
|
855
378
|
: undefined
|
|
856
379
|
}
|
|
857
380
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
const node = stack.pop()!
|
|
864
|
-
order.push(node)
|
|
865
|
-
for (const child of node.children) {
|
|
866
|
-
stack.push(child)
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
return order
|
|
870
|
-
}
|
|
381
|
+
type StripPanelScale = Omit<ResolvedStripPanel, 'id' | 'width' | 'offsetX'>
|
|
382
|
+
type FeaturePanelScale = Omit<
|
|
383
|
+
ResolvedFeaturePanel,
|
|
384
|
+
'id' | 'width' | 'offsetX' | 'spans'
|
|
385
|
+
>
|
|
871
386
|
|
|
872
387
|
/**
|
|
873
388
|
* #stateModel MsaView
|
|
@@ -913,6 +428,13 @@ function stateModelFactory({
|
|
|
913
428
|
* #property
|
|
914
429
|
*/
|
|
915
430
|
showDomains: stripDefault(types.boolean, defaultShowDomains),
|
|
431
|
+
/**
|
|
432
|
+
* #property
|
|
433
|
+
* whether shown domains fill the cells they cover. Off, the residue
|
|
434
|
+
* scheme keeps the cells and each domain draws as a bar under its
|
|
435
|
+
* row. See `cellFill`.
|
|
436
|
+
*/
|
|
437
|
+
domainFill: stripDefault(types.boolean, true),
|
|
916
438
|
/**
|
|
917
439
|
* #property
|
|
918
440
|
* whether the domain legend is expanded. The legend floats over the
|
|
@@ -944,6 +466,13 @@ function stateModelFactory({
|
|
|
944
466
|
*/
|
|
945
467
|
drawMsaLetters: stripDefault(types.boolean, defaultDrawMsaLetters),
|
|
946
468
|
|
|
469
|
+
/**
|
|
470
|
+
* #property
|
|
471
|
+
* draws the toolbar's menus and Settings as icons without text, for a
|
|
472
|
+
* host with little room, such as a JBrowse view
|
|
473
|
+
*/
|
|
474
|
+
embeddedMode: stripDefault(types.boolean, false),
|
|
475
|
+
|
|
947
476
|
/**
|
|
948
477
|
* #property
|
|
949
478
|
* zoom in/out on plain mouse-wheel without holding ctrl
|
|
@@ -1096,10 +625,10 @@ function stateModelFactory({
|
|
|
1096
625
|
|
|
1097
626
|
/**
|
|
1098
627
|
* #property
|
|
1099
|
-
* the user's
|
|
1100
|
-
* accession with the value meaning "off", like
|
|
1101
|
-
* untouched accession is absent and
|
|
1102
|
-
* only with the user's filters
|
|
628
|
+
* the user's departures from each annotation accession's default,
|
|
629
|
+
* keyed by accession with the value meaning "off", like
|
|
630
|
+
* `turnedOffTracks`. An untouched accession is absent and takes its
|
|
631
|
+
* default, so the shared URL grows only with the user's filters
|
|
1103
632
|
*/
|
|
1104
633
|
turnedOffFeatures: stripDefault(types.map(types.boolean), {}),
|
|
1105
634
|
/**
|
|
@@ -1180,6 +709,18 @@ function stateModelFactory({
|
|
|
1180
709
|
* #volatile
|
|
1181
710
|
*/
|
|
1182
711
|
headerHeight: 0,
|
|
712
|
+
/**
|
|
713
|
+
* #volatile
|
|
714
|
+
* whether the settings panel docks beside the alignment, taking
|
|
715
|
+
* `settingsPanelWidth` out of the width the view lays out in
|
|
716
|
+
*/
|
|
717
|
+
settingsOpen: false,
|
|
718
|
+
/**
|
|
719
|
+
* #volatile
|
|
720
|
+
* the log scale a wheel zoom has gathered against the detent at the
|
|
721
|
+
* default column width and row height, per axis
|
|
722
|
+
*/
|
|
723
|
+
zoomHeld: { col: 0, row: 0 },
|
|
1183
724
|
/**
|
|
1184
725
|
* #volatile
|
|
1185
726
|
* leaves the toolbar out, for a host drawing its own controls. Kept out
|
|
@@ -1203,6 +744,13 @@ function stateModelFactory({
|
|
|
1203
744
|
highResScaleFactor:
|
|
1204
745
|
typeof window === 'undefined' ? 1 : window.devicePixelRatio,
|
|
1205
746
|
|
|
747
|
+
/**
|
|
748
|
+
* #volatile
|
|
749
|
+
* the cell size and block grid the alignment last drew at, held from a
|
|
750
|
+
* wheel-zoom frame until the gesture settles. MSACanvas stretches those
|
|
751
|
+
* blocks to the current cell size with one CSS transform
|
|
752
|
+
*/
|
|
753
|
+
zoomSnapshot: undefined as ZoomSnapshot | undefined,
|
|
1206
754
|
/**
|
|
1207
755
|
* #volatile
|
|
1208
756
|
*/
|
|
@@ -1365,6 +913,12 @@ function stateModelFactory({
|
|
|
1365
913
|
setWidth(arg: number) {
|
|
1366
914
|
self.volatileWidth = arg
|
|
1367
915
|
},
|
|
916
|
+
/**
|
|
917
|
+
* #action
|
|
918
|
+
*/
|
|
919
|
+
setSettingsOpen(arg: boolean) {
|
|
920
|
+
self.settingsOpen = arg
|
|
921
|
+
},
|
|
1368
922
|
/**
|
|
1369
923
|
* #action
|
|
1370
924
|
* update the canvas scale factor when the device pixel ratio changes
|
|
@@ -1508,6 +1062,23 @@ function stateModelFactory({
|
|
|
1508
1062
|
setShowDomains(arg: boolean) {
|
|
1509
1063
|
self.showDomains = arg
|
|
1510
1064
|
},
|
|
1065
|
+
/**
|
|
1066
|
+
* #action
|
|
1067
|
+
*/
|
|
1068
|
+
setDomainFill(arg: boolean) {
|
|
1069
|
+
self.domainFill = arg
|
|
1070
|
+
},
|
|
1071
|
+
/**
|
|
1072
|
+
* #action
|
|
1073
|
+
* hand the cell fill to the residue scheme, the domains, or neither,
|
|
1074
|
+
* which colors the letters instead
|
|
1075
|
+
*/
|
|
1076
|
+
setCellFill(mode: 'residue' | 'domain' | 'none') {
|
|
1077
|
+
self.setBgColor(mode !== 'none')
|
|
1078
|
+
if (mode !== 'none') {
|
|
1079
|
+
self.domainFill = mode === 'domain'
|
|
1080
|
+
}
|
|
1081
|
+
},
|
|
1511
1082
|
/**
|
|
1512
1083
|
* #action
|
|
1513
1084
|
* expand or collapse the domain legend that floats over the alignment
|
|
@@ -1536,6 +1107,7 @@ function stateModelFactory({
|
|
|
1536
1107
|
*/
|
|
1537
1108
|
setRowHeight(n: number) {
|
|
1538
1109
|
self.rowHeight = n
|
|
1110
|
+
self.zoomHeld = { ...self.zoomHeld, row: 0 }
|
|
1539
1111
|
},
|
|
1540
1112
|
|
|
1541
1113
|
/**
|
|
@@ -1544,6 +1116,7 @@ function stateModelFactory({
|
|
|
1544
1116
|
*/
|
|
1545
1117
|
setColWidth(n: number) {
|
|
1546
1118
|
self.colWidth = n
|
|
1119
|
+
self.zoomHeld = { ...self.zoomHeld, col: 0 }
|
|
1547
1120
|
},
|
|
1548
1121
|
|
|
1549
1122
|
/**
|
|
@@ -1715,22 +1288,7 @@ function stateModelFactory({
|
|
|
1715
1288
|
self.allowedGappyness < 100)
|
|
1716
1289
|
)
|
|
1717
1290
|
},
|
|
1718
|
-
|
|
1719
|
-
* #getter
|
|
1720
|
-
* the annotations the overlay, the legend and the encodings draw: the
|
|
1721
|
-
* GFF's, then the `features` layer's
|
|
1722
|
-
*/
|
|
1723
|
-
get allAnnotations(): Annotation[] {
|
|
1724
|
-
return self.features.length > 0
|
|
1725
|
-
? [...self.annotations, ...featuresToAnnotations(self.features)]
|
|
1726
|
-
: self.annotations
|
|
1727
|
-
},
|
|
1728
|
-
/**
|
|
1729
|
-
* #getter
|
|
1730
|
-
*/
|
|
1731
|
-
get actuallyShowDomains() {
|
|
1732
|
-
return self.showDomains && this.allAnnotations.length > 0
|
|
1733
|
-
},
|
|
1291
|
+
|
|
1734
1292
|
/**
|
|
1735
1293
|
* #getter
|
|
1736
1294
|
* whether the host restores the loaded documents outside the snapshot.
|
|
@@ -1755,7 +1313,7 @@ function stateModelFactory({
|
|
|
1755
1313
|
if (self.volatileWidth === undefined) {
|
|
1756
1314
|
throw new Error('not initialized')
|
|
1757
1315
|
}
|
|
1758
|
-
return self.volatileWidth
|
|
1316
|
+
return self.volatileWidth - (self.settingsOpen ? settingsPanelWidth : 0)
|
|
1759
1317
|
},
|
|
1760
1318
|
}))
|
|
1761
1319
|
.views(self => ({
|
|
@@ -1805,7 +1363,7 @@ function stateModelFactory({
|
|
|
1805
1363
|
return !!this.tree.noTree
|
|
1806
1364
|
},
|
|
1807
1365
|
get noDomains() {
|
|
1808
|
-
return
|
|
1366
|
+
return this.allAnnotations.length === 0
|
|
1809
1367
|
},
|
|
1810
1368
|
|
|
1811
1369
|
/**
|
|
@@ -1914,6 +1472,27 @@ function stateModelFactory({
|
|
|
1914
1472
|
? parseMSA(text, self.currentAlignment, self.msaFormat)
|
|
1915
1473
|
: null
|
|
1916
1474
|
},
|
|
1475
|
+
/**
|
|
1476
|
+
* #getter
|
|
1477
|
+
* the annotations the overlay, the legend and the encodings draw: the
|
|
1478
|
+
* GFF's, the `features` layer's, then the file's own per-row lines
|
|
1479
|
+
*/
|
|
1480
|
+
get allAnnotations(): Annotation[] {
|
|
1481
|
+
const inline = this.MSA?.annotations ?? []
|
|
1482
|
+
return self.features.length > 0 || inline.length > 0
|
|
1483
|
+
? [
|
|
1484
|
+
...self.annotations,
|
|
1485
|
+
...featuresToAnnotations(self.features),
|
|
1486
|
+
...inline,
|
|
1487
|
+
]
|
|
1488
|
+
: self.annotations
|
|
1489
|
+
},
|
|
1490
|
+
/**
|
|
1491
|
+
* #getter
|
|
1492
|
+
*/
|
|
1493
|
+
get actuallyShowDomains() {
|
|
1494
|
+
return self.showDomains && this.allAnnotations.length > 0
|
|
1495
|
+
},
|
|
1917
1496
|
/**
|
|
1918
1497
|
* #getter
|
|
1919
1498
|
*/
|
|
@@ -2507,6 +2086,22 @@ function stateModelFactory({
|
|
|
2507
2086
|
)
|
|
2508
2087
|
},
|
|
2509
2088
|
|
|
2089
|
+
/**
|
|
2090
|
+
* #getter
|
|
2091
|
+
* Pre-computed similarity shades per column, for blosum62_dynamic.
|
|
2092
|
+
* Returns a map of letter -> color for each column. Amino acids shade by
|
|
2093
|
+
* BLOSUM62 similarity to the rest of the column, and an alignment of
|
|
2094
|
+
* nucleotides by identity, since the matrix names amino acids.
|
|
2095
|
+
*/
|
|
2096
|
+
get colSimilarity() {
|
|
2097
|
+
const { colStats, sequenceType } = this
|
|
2098
|
+
const columnColors =
|
|
2099
|
+
sequenceType === 'amino' ? blosum62ColumnColors : identityColumnColors
|
|
2100
|
+
return Array.from({ length: colStats.numColumns }, (_, col) =>
|
|
2101
|
+
columnColors(colStats, col),
|
|
2102
|
+
)
|
|
2103
|
+
},
|
|
2104
|
+
|
|
2510
2105
|
/**
|
|
2511
2106
|
* #getter
|
|
2512
2107
|
* Conservation score per column using Shannon entropy (biojs-msa style).
|
|
@@ -2903,6 +2498,30 @@ function stateModelFactory({
|
|
|
2903
2498
|
}))
|
|
2904
2499
|
},
|
|
2905
2500
|
|
|
2501
|
+
/**
|
|
2502
|
+
* #getter
|
|
2503
|
+
* the reference row (`relativeTo`) repeated above the alignment, so it
|
|
2504
|
+
* stays in view while the rows compared against it scroll
|
|
2505
|
+
*/
|
|
2506
|
+
get referenceTrackModels(): BasicTrack[] {
|
|
2507
|
+
const { relativeTo } = self
|
|
2508
|
+
const data =
|
|
2509
|
+
relativeTo === undefined ? undefined : self.columns.get(relativeTo)
|
|
2510
|
+
return relativeTo === undefined || data === undefined
|
|
2511
|
+
? []
|
|
2512
|
+
: [
|
|
2513
|
+
{
|
|
2514
|
+
model: {
|
|
2515
|
+
id: 'reference-row',
|
|
2516
|
+
name: relativeTo,
|
|
2517
|
+
kind: 'reference' as const,
|
|
2518
|
+
data,
|
|
2519
|
+
height: self.rowHeight,
|
|
2520
|
+
},
|
|
2521
|
+
},
|
|
2522
|
+
]
|
|
2523
|
+
},
|
|
2524
|
+
|
|
2906
2525
|
get tracks(): BasicTrack[] {
|
|
2907
2526
|
return [
|
|
2908
2527
|
...this.adapterTrackModels,
|
|
@@ -2911,6 +2530,7 @@ function stateModelFactory({
|
|
|
2911
2530
|
// every computed track reads the alignment's columns, and a tree, a
|
|
2912
2531
|
// GFF and a features panel draw a figure with none
|
|
2913
2532
|
...(self.numColumns > 0 ? this.computedTrackModels : []),
|
|
2533
|
+
...this.referenceTrackModels,
|
|
2914
2534
|
]
|
|
2915
2535
|
},
|
|
2916
2536
|
|
|
@@ -2919,8 +2539,7 @@ function stateModelFactory({
|
|
|
2919
2539
|
*/
|
|
2920
2540
|
get turnedOnTracks() {
|
|
2921
2541
|
return this.tracks.filter(
|
|
2922
|
-
f =>
|
|
2923
|
-
!trackIsOff(self.turnedOffTracks, f.model.id, f.model.defaultOff),
|
|
2542
|
+
f => !trackIsOff(self.turnedOffTracks, f.model.id),
|
|
2924
2543
|
)
|
|
2925
2544
|
},
|
|
2926
2545
|
|
|
@@ -3250,7 +2869,11 @@ function stateModelFactory({
|
|
|
3250
2869
|
if (mapping.structure.id !== structureId) {
|
|
3251
2870
|
continue
|
|
3252
2871
|
}
|
|
3253
|
-
if (
|
|
2872
|
+
if (
|
|
2873
|
+
asymId !== undefined &&
|
|
2874
|
+
mapping.structure.asymId !== undefined &&
|
|
2875
|
+
mapping.structure.asymId !== asymId
|
|
2876
|
+
) {
|
|
3254
2877
|
continue
|
|
3255
2878
|
}
|
|
3256
2879
|
const segment = mapping.segments.find(
|
|
@@ -3270,10 +2893,14 @@ function stateModelFactory({
|
|
|
3270
2893
|
.views(self => ({
|
|
3271
2894
|
/**
|
|
3272
2895
|
* #getter
|
|
3273
|
-
* the branch-length scale bar over the tree, undefined
|
|
3274
|
-
* or when the tree area is
|
|
2896
|
+
* the branch-length scale bar over the tree, undefined while
|
|
2897
|
+
* `showTreeScaleBar` is off, in cladogram mode, or when the tree area is
|
|
2898
|
+
* too narrow for one
|
|
3275
2899
|
*/
|
|
3276
2900
|
get treeScaleBar() {
|
|
2901
|
+
if (!self.showTreeScaleBar) {
|
|
2902
|
+
return undefined
|
|
2903
|
+
}
|
|
3277
2904
|
return scaleBarLength(
|
|
3278
2905
|
self.pxPerBranchLength,
|
|
3279
2906
|
self.effectiveTreeAreaWidth - self.marginLeft * 2,
|
|
@@ -3329,11 +2956,43 @@ function stateModelFactory({
|
|
|
3329
2956
|
}
|
|
3330
2957
|
return types
|
|
3331
2958
|
},
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
2959
|
+
/**
|
|
2960
|
+
* accessions hidden until the user turns them on, such as a Stockholm
|
|
2961
|
+
* file's #=GR lines
|
|
2962
|
+
*/
|
|
2963
|
+
get defaultOffAccessions() {
|
|
2964
|
+
return new Set(
|
|
2965
|
+
self.allAnnotations.filter(a => a.defaultOff).map(a => a.accession),
|
|
2966
|
+
)
|
|
2967
|
+
},
|
|
2968
|
+
/**
|
|
2969
|
+
* #method
|
|
2970
|
+
*/
|
|
2971
|
+
featureOff(accession: string) {
|
|
2972
|
+
return (
|
|
2973
|
+
self.turnedOffFeatures.get(accession) ??
|
|
2974
|
+
this.defaultOffAccessions.has(accession)
|
|
3335
2975
|
)
|
|
3336
2976
|
},
|
|
2977
|
+
get filteredAnnotations() {
|
|
2978
|
+
return self.allAnnotations.filter(r => !this.featureOff(r.accession))
|
|
2979
|
+
},
|
|
2980
|
+
/**
|
|
2981
|
+
* #getter
|
|
2982
|
+
* what colors the alignment's cells: the residue scheme, the domains
|
|
2983
|
+
* covering them, or nothing, which leaves the scheme to the letters.
|
|
2984
|
+
* Domains own the fill only while some are drawn, so a file whose
|
|
2985
|
+
* annotations all start hidden keeps its residue colors
|
|
2986
|
+
*/
|
|
2987
|
+
get cellFill(): 'residue' | 'domain' | 'none' {
|
|
2988
|
+
return !self.bgColor
|
|
2989
|
+
? 'none'
|
|
2990
|
+
: self.actuallyShowDomains &&
|
|
2991
|
+
self.domainFill &&
|
|
2992
|
+
this.filteredAnnotations.length > 0
|
|
2993
|
+
? 'domain'
|
|
2994
|
+
: 'residue'
|
|
2995
|
+
},
|
|
3337
2996
|
get annotationsByRow() {
|
|
3338
2997
|
const { rowNamesSet } = self
|
|
3339
2998
|
return groupBy(this.filteredAnnotations, r =>
|
|
@@ -3412,6 +3071,12 @@ function stateModelFactory({
|
|
|
3412
3071
|
self.blocksX.map(bx => [bx, by] as const),
|
|
3413
3072
|
)
|
|
3414
3073
|
},
|
|
3074
|
+
/**
|
|
3075
|
+
* #getter
|
|
3076
|
+
*/
|
|
3077
|
+
get wheelZooming() {
|
|
3078
|
+
return self.zoomSnapshot !== undefined
|
|
3079
|
+
},
|
|
3415
3080
|
|
|
3416
3081
|
/**
|
|
3417
3082
|
* #getter
|
|
@@ -3637,19 +3302,35 @@ function stateModelFactory({
|
|
|
3637
3302
|
transaction(() => {
|
|
3638
3303
|
const colInView = (-self.scrollX + offsetX) / self.colWidth
|
|
3639
3304
|
const rowInView = (-self.scrollY + offsetY) / self.rowHeight
|
|
3305
|
+
// zooming both axes, an axis holds at the default only while the
|
|
3306
|
+
// other sits there too, or one would hold while the other grows
|
|
3307
|
+
const canHold =
|
|
3308
|
+
axis !== 'both' ||
|
|
3309
|
+
(self.colWidth === defaultColWidth &&
|
|
3310
|
+
self.rowHeight === defaultRowHeight)
|
|
3640
3311
|
if (axis !== 'vertical') {
|
|
3641
|
-
|
|
3642
|
-
self.colWidth
|
|
3312
|
+
const { value, held } = detentZoom(
|
|
3313
|
+
self.colWidth,
|
|
3314
|
+
scaleFactor,
|
|
3315
|
+
defaultColWidth,
|
|
3316
|
+
self.zoomHeld.col,
|
|
3643
3317
|
minColWidth,
|
|
3644
|
-
|
|
3318
|
+
canHold,
|
|
3645
3319
|
)
|
|
3320
|
+
self.colWidth = value
|
|
3321
|
+
self.zoomHeld = { ...self.zoomHeld, col: held }
|
|
3646
3322
|
}
|
|
3647
3323
|
if (axis !== 'horizontal') {
|
|
3648
|
-
|
|
3649
|
-
self.rowHeight
|
|
3324
|
+
const { value, held } = detentZoom(
|
|
3325
|
+
self.rowHeight,
|
|
3326
|
+
scaleFactor,
|
|
3327
|
+
defaultRowHeight,
|
|
3328
|
+
self.zoomHeld.row,
|
|
3650
3329
|
minRowHeight,
|
|
3651
|
-
|
|
3330
|
+
canHold,
|
|
3652
3331
|
)
|
|
3332
|
+
self.rowHeight = value
|
|
3333
|
+
self.zoomHeld = { ...self.zoomHeld, row: held }
|
|
3653
3334
|
}
|
|
3654
3335
|
self.scrollX = clamp(
|
|
3655
3336
|
offsetX - colInView * self.colWidth,
|
|
@@ -3670,6 +3351,25 @@ function stateModelFactory({
|
|
|
3670
3351
|
)
|
|
3671
3352
|
})
|
|
3672
3353
|
},
|
|
3354
|
+
/**
|
|
3355
|
+
* #action
|
|
3356
|
+
* zoomToPos for one wheel or pinch frame. The alignment keeps the blocks
|
|
3357
|
+
* it drew at the start of the gesture, scaled, and redraws them only once
|
|
3358
|
+
* they stop covering the viewport; endWheelZoom redraws them sharp
|
|
3359
|
+
*/
|
|
3360
|
+
wheelZoomToPos(scaleFactor: number, offsetX: number, offsetY: number) {
|
|
3361
|
+
self.zoomSnapshot ??= takeZoomSnapshot(self, false)
|
|
3362
|
+
this.zoomToPos(scaleFactor, offsetX, offsetY, self.wheelZoomAxis)
|
|
3363
|
+
if (!zoomSnapshotCovers(self, self.zoomSnapshot)) {
|
|
3364
|
+
self.zoomSnapshot = takeZoomSnapshot(self, true)
|
|
3365
|
+
}
|
|
3366
|
+
},
|
|
3367
|
+
/**
|
|
3368
|
+
* #action
|
|
3369
|
+
*/
|
|
3370
|
+
endWheelZoom() {
|
|
3371
|
+
self.zoomSnapshot = undefined
|
|
3372
|
+
},
|
|
3673
3373
|
/**
|
|
3674
3374
|
* #action
|
|
3675
3375
|
*/
|
|
@@ -3754,11 +3454,22 @@ function stateModelFactory({
|
|
|
3754
3454
|
* #action
|
|
3755
3455
|
*/
|
|
3756
3456
|
toggleTrack(id: string) {
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3457
|
+
self.turnedOffTracks.set(id, !trackIsOff(self.turnedOffTracks, id))
|
|
3458
|
+
},
|
|
3459
|
+
/**
|
|
3460
|
+
* #action
|
|
3461
|
+
* show or hide several tracks at once, as the track selector's Show all
|
|
3462
|
+
* and Hide all do. A choice matching the track's default is dropped, so
|
|
3463
|
+
* it stays out of the shared URL
|
|
3464
|
+
*/
|
|
3465
|
+
setTracksShown(ids: string[], shown: boolean) {
|
|
3466
|
+
for (const id of ids) {
|
|
3467
|
+
if (defaultOffTracks.has(id) === !shown) {
|
|
3468
|
+
self.turnedOffTracks.delete(id)
|
|
3469
|
+
} else {
|
|
3470
|
+
self.turnedOffTracks.set(id, !shown)
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3762
3473
|
},
|
|
3763
3474
|
/**
|
|
3764
3475
|
* #action
|
|
@@ -3923,7 +3634,7 @@ function stateModelFactory({
|
|
|
3923
3634
|
*/
|
|
3924
3635
|
get visibleDomainTypes() {
|
|
3925
3636
|
return this.categoricalDomainTypes
|
|
3926
|
-
.filter(d => !self.
|
|
3637
|
+
.filter(d => !self.featureOff(d.accession))
|
|
3927
3638
|
.toSorted((a, b) => a.start - b.start)
|
|
3928
3639
|
},
|
|
3929
3640
|
|
|
@@ -3992,16 +3703,16 @@ function stateModelFactory({
|
|
|
3992
3703
|
/**
|
|
3993
3704
|
* #getter
|
|
3994
3705
|
* whether the overlay marks each domain with a bar under its row instead
|
|
3995
|
-
* of filling the row behind the letters
|
|
3996
|
-
*
|
|
3997
|
-
*
|
|
3706
|
+
* of filling the row behind the letters: whenever the domains do not own
|
|
3707
|
+
* the cell fill (`cellFill`), a filled box would paint over the residue
|
|
3708
|
+
* colors the user picked. Sub-row layout already stacks
|
|
3998
3709
|
* the boxes clear of the letters, and with the letters too small to draw
|
|
3999
3710
|
* the filled box is the only thing left to read.
|
|
4000
3711
|
*/
|
|
4001
3712
|
get domainUnderline() {
|
|
4002
3713
|
return (
|
|
4003
3714
|
self.actuallyShowDomains &&
|
|
4004
|
-
|
|
3715
|
+
self.cellFill !== 'domain' &&
|
|
4005
3716
|
!self.subFeatureRows &&
|
|
4006
3717
|
self.showMsaLetters
|
|
4007
3718
|
)
|
|
@@ -4523,6 +4234,12 @@ function stateModelFactory({
|
|
|
4523
4234
|
setHideHeader(arg: boolean) {
|
|
4524
4235
|
self.hideHeader = arg
|
|
4525
4236
|
},
|
|
4237
|
+
/**
|
|
4238
|
+
* #action
|
|
4239
|
+
*/
|
|
4240
|
+
setEmbeddedMode(arg: boolean) {
|
|
4241
|
+
self.embeddedMode = arg
|
|
4242
|
+
},
|
|
4526
4243
|
/**
|
|
4527
4244
|
* #action
|
|
4528
4245
|
*/
|
|
@@ -4602,14 +4319,15 @@ function stateModelFactory({
|
|
|
4602
4319
|
},
|
|
4603
4320
|
/**
|
|
4604
4321
|
* #action
|
|
4605
|
-
* show or hide an annotation type. Only
|
|
4606
|
-
* `turnedOffFeatures`
|
|
4322
|
+
* show or hide an annotation type. Only a choice that differs from the
|
|
4323
|
+
* type's default is recorded; see `turnedOffFeatures`
|
|
4607
4324
|
*/
|
|
4608
4325
|
setFilter(accession: string, shown: boolean) {
|
|
4609
|
-
|
|
4610
|
-
|
|
4326
|
+
// only a departure from the accession's default is recorded
|
|
4327
|
+
if (shown === self.defaultOffAccessions.has(accession)) {
|
|
4328
|
+
self.turnedOffFeatures.set(accession, !shown)
|
|
4611
4329
|
} else {
|
|
4612
|
-
self.turnedOffFeatures.
|
|
4330
|
+
self.turnedOffFeatures.delete(accession)
|
|
4613
4331
|
}
|
|
4614
4332
|
},
|
|
4615
4333
|
|
|
@@ -4618,23 +4336,28 @@ function stateModelFactory({
|
|
|
4618
4336
|
*/
|
|
4619
4337
|
fit() {
|
|
4620
4338
|
// two passes: each direction's viewport depends on whether the other
|
|
4621
|
-
// overflows (the minimap takes height, the vertical scrollbar width)
|
|
4339
|
+
// overflows (the minimap takes height, the vertical scrollbar width).
|
|
4340
|
+
// Fit shrinks an alignment into the window but never grows one past
|
|
4341
|
+
// the default cell size, which would stretch a small alignment's rows
|
|
4342
|
+
// and columns out of proportion
|
|
4622
4343
|
transaction(() => {
|
|
4623
4344
|
for (let pass = 0; pass < 2; pass++) {
|
|
4624
|
-
this.fitHorizontally()
|
|
4625
|
-
this.fitVertically()
|
|
4345
|
+
this.fitHorizontally(defaultColWidth)
|
|
4346
|
+
this.fitVertically(defaultRowHeight)
|
|
4626
4347
|
}
|
|
4627
4348
|
})
|
|
4628
4349
|
},
|
|
4629
4350
|
/**
|
|
4630
4351
|
* #action
|
|
4631
4352
|
*/
|
|
4632
|
-
fitVertically() {
|
|
4353
|
+
fitVertically(maxRowHeight = maxCellSize) {
|
|
4633
4354
|
if (self.numRows > 0) {
|
|
4634
|
-
self.
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4355
|
+
self.setRowHeight(
|
|
4356
|
+
clamp(
|
|
4357
|
+
self.msaAreaHeight / self.numRows,
|
|
4358
|
+
minRowHeight,
|
|
4359
|
+
maxRowHeight,
|
|
4360
|
+
),
|
|
4638
4361
|
)
|
|
4639
4362
|
}
|
|
4640
4363
|
self.scrollY = 0
|
|
@@ -4651,10 +4374,12 @@ function stateModelFactory({
|
|
|
4651
4374
|
return
|
|
4652
4375
|
}
|
|
4653
4376
|
transaction(() => {
|
|
4654
|
-
self.
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4377
|
+
self.setColWidth(
|
|
4378
|
+
clamp(
|
|
4379
|
+
self.msaCanvasWidth / (span.endCol - span.startCol + 1),
|
|
4380
|
+
minColWidth,
|
|
4381
|
+
maxCellSize,
|
|
4382
|
+
),
|
|
4658
4383
|
)
|
|
4659
4384
|
self.scrollX = clamp(
|
|
4660
4385
|
-span.startCol * self.colWidth,
|
|
@@ -4700,10 +4425,12 @@ function stateModelFactory({
|
|
|
4700
4425
|
const { rowRuns } = resolvedSelection
|
|
4701
4426
|
const first = rowRuns[0]![0]
|
|
4702
4427
|
const last = rowRuns.at(-1)![1]
|
|
4703
|
-
self.
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4428
|
+
self.setRowHeight(
|
|
4429
|
+
clamp(
|
|
4430
|
+
self.msaAreaHeight / (last - first + 1),
|
|
4431
|
+
minRowHeight,
|
|
4432
|
+
maxCellSize,
|
|
4433
|
+
),
|
|
4707
4434
|
)
|
|
4708
4435
|
self.scrollY = clamp(-first * self.rowHeight, self.maxScrollY, 0)
|
|
4709
4436
|
}
|
|
@@ -4744,13 +4471,15 @@ function stateModelFactory({
|
|
|
4744
4471
|
/**
|
|
4745
4472
|
* #action
|
|
4746
4473
|
*/
|
|
4747
|
-
fitHorizontally() {
|
|
4474
|
+
fitHorizontally(maxColWidth = maxCellSize) {
|
|
4748
4475
|
if (self.numColumns > 0) {
|
|
4749
4476
|
// msaCanvasWidth excludes the vertical scrollbar's 20px
|
|
4750
|
-
self.
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4477
|
+
self.setColWidth(
|
|
4478
|
+
clamp(
|
|
4479
|
+
self.msaCanvasWidth / self.numColumns,
|
|
4480
|
+
minColWidth,
|
|
4481
|
+
maxColWidth,
|
|
4482
|
+
),
|
|
4754
4483
|
)
|
|
4755
4484
|
}
|
|
4756
4485
|
self.scrollX = 0
|
|
@@ -4761,21 +4490,18 @@ function stateModelFactory({
|
|
|
4761
4490
|
self.setHighlightedColumns(self.highlightColumns)
|
|
4762
4491
|
}
|
|
4763
4492
|
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
// `
|
|
4767
|
-
//
|
|
4768
|
-
//
|
|
4769
|
-
//
|
|
4770
|
-
//
|
|
4771
|
-
// `dataInitialized` is true once the MSA alone has loaded, when the
|
|
4772
|
-
// tree is still the flat stub, so a tree filehandle holds the seeding
|
|
4773
|
-
// until its text lands.
|
|
4493
|
+
const awaitingTree = () => !!self.treeFilehandle && !self.data.tree
|
|
4494
|
+
|
|
4495
|
+
// `collapse`, `focus` and `rotate` marks seed `collapsed`, `showOnly`
|
|
4496
|
+
// and `rotated`. Seeding runs once per data load, `clades` change and
|
|
4497
|
+
// reroot, so expanding a seeded clade sticks until one of those. A tree
|
|
4498
|
+
// filehandle holds it back, since `dataInitialized` is true while the
|
|
4499
|
+
// tree is still the flat stub.
|
|
4774
4500
|
addDisposer(
|
|
4775
4501
|
self,
|
|
4776
4502
|
reaction(
|
|
4777
4503
|
() =>
|
|
4778
|
-
self.dataInitialized && !(
|
|
4504
|
+
self.dataInitialized && !awaitingTree()
|
|
4779
4505
|
? { clades: getSnapshot(self.clades), treeRoot: self.treeRoot }
|
|
4780
4506
|
: undefined,
|
|
4781
4507
|
seed => {
|
|
@@ -4813,7 +4539,7 @@ function stateModelFactory({
|
|
|
4813
4539
|
region &&
|
|
4814
4540
|
self.viewInitialized &&
|
|
4815
4541
|
self.numColumns > 0 &&
|
|
4816
|
-
!(
|
|
4542
|
+
!awaitingTree()
|
|
4817
4543
|
) {
|
|
4818
4544
|
this.revealRegion(region)
|
|
4819
4545
|
self.setRegion(undefined)
|
|
@@ -4842,107 +4568,7 @@ function stateModelFactory({
|
|
|
4842
4568
|
})
|
|
4843
4569
|
}
|
|
4844
4570
|
|
|
4845
|
-
|
|
4846
|
-
* Fetch a filehandle whenever it changes, and hand the text to
|
|
4847
|
-
* `onLoad`.
|
|
4848
|
-
*
|
|
4849
|
-
* A generation guard keeps a slower superseded request from overwriting
|
|
4850
|
-
* the newer one's data, status or loading flag. A superseded or cleared
|
|
4851
|
-
* request is aborted and clears its status line.
|
|
4852
|
-
*
|
|
4853
|
-
* `clearFilehandle` runs after a local file loads, since a blob has no
|
|
4854
|
-
* URL to refetch, and when the user cancels, which returns the view to
|
|
4855
|
-
* the import form.
|
|
4856
|
-
*
|
|
4857
|
-
* `what` names the layer in a failure message. An `optional` layer
|
|
4858
|
-
* (annotations, row metadata) that fails adds a warning instead of an
|
|
4859
|
-
* error.
|
|
4860
|
-
*/
|
|
4861
|
-
const loadOnFilehandleChange = ({
|
|
4862
|
-
what,
|
|
4863
|
-
getFilehandle,
|
|
4864
|
-
onLoad,
|
|
4865
|
-
setLoading,
|
|
4866
|
-
clearFilehandle,
|
|
4867
|
-
optional = false,
|
|
4868
|
-
}: {
|
|
4869
|
-
what: string
|
|
4870
|
-
getFilehandle: () => FileLocationType | undefined
|
|
4871
|
-
onLoad: (text: string) => void
|
|
4872
|
-
setLoading?: (arg: boolean) => void
|
|
4873
|
-
clearFilehandle?: () => void
|
|
4874
|
-
optional?: boolean
|
|
4875
|
-
}) => {
|
|
4876
|
-
let generation = 0
|
|
4877
|
-
let cancelInFlight: (() => void) | undefined
|
|
4878
|
-
addDisposer(self, () => {
|
|
4879
|
-
cancelInFlight?.()
|
|
4880
|
-
})
|
|
4881
|
-
addDisposer(
|
|
4882
|
-
self,
|
|
4883
|
-
autorun(async () => {
|
|
4884
|
-
const filehandle = getFilehandle()
|
|
4885
|
-
const current = ++generation
|
|
4886
|
-
cancelInFlight?.()
|
|
4887
|
-
cancelInFlight = undefined
|
|
4888
|
-
if (!filehandle) {
|
|
4889
|
-
setLoading?.(false)
|
|
4890
|
-
return
|
|
4891
|
-
}
|
|
4892
|
-
const isCurrent = () => current === generation && isAlive(self)
|
|
4893
|
-
const controller = new AbortController()
|
|
4894
|
-
cancelInFlight = () => {
|
|
4895
|
-
controller.abort()
|
|
4896
|
-
if (isAlive(self)) {
|
|
4897
|
-
self.setStatus(undefined)
|
|
4898
|
-
}
|
|
4899
|
-
}
|
|
4900
|
-
try {
|
|
4901
|
-
setLoading?.(true)
|
|
4902
|
-
if (!optional) {
|
|
4903
|
-
self.setError(undefined)
|
|
4904
|
-
}
|
|
4905
|
-
const text = await fetchTextWithProgress(
|
|
4906
|
-
openLocation(filehandle),
|
|
4907
|
-
status => {
|
|
4908
|
-
if (isCurrent()) {
|
|
4909
|
-
self.setStatus(status)
|
|
4910
|
-
}
|
|
4911
|
-
},
|
|
4912
|
-
{ controller },
|
|
4913
|
-
)
|
|
4914
|
-
if (isCurrent()) {
|
|
4915
|
-
transaction(() => {
|
|
4916
|
-
onLoad(text)
|
|
4917
|
-
if (filehandle.locationType === 'BlobLocation') {
|
|
4918
|
-
clearFilehandle?.()
|
|
4919
|
-
}
|
|
4920
|
-
})
|
|
4921
|
-
}
|
|
4922
|
-
} catch (e) {
|
|
4923
|
-
if (isCurrent()) {
|
|
4924
|
-
if (isAbortError(e)) {
|
|
4925
|
-
clearFilehandle?.()
|
|
4926
|
-
} else {
|
|
4927
|
-
console.error(e)
|
|
4928
|
-
if (optional) {
|
|
4929
|
-
self.addWarning(`The ${what} did not load: ${e}`)
|
|
4930
|
-
} else {
|
|
4931
|
-
self.setError(e)
|
|
4932
|
-
}
|
|
4933
|
-
}
|
|
4934
|
-
}
|
|
4935
|
-
} finally {
|
|
4936
|
-
if (isCurrent()) {
|
|
4937
|
-
cancelInFlight = undefined
|
|
4938
|
-
setLoading?.(false)
|
|
4939
|
-
}
|
|
4940
|
-
}
|
|
4941
|
-
}),
|
|
4942
|
-
)
|
|
4943
|
-
}
|
|
4944
|
-
|
|
4945
|
-
loadOnFilehandleChange({
|
|
4571
|
+
installFilehandleLoader(self, {
|
|
4946
4572
|
what: 'tree',
|
|
4947
4573
|
getFilehandle: () => self.treeFilehandle,
|
|
4948
4574
|
onLoad: text => {
|
|
@@ -4957,7 +4583,7 @@ function stateModelFactory({
|
|
|
4957
4583
|
})
|
|
4958
4584
|
|
|
4959
4585
|
// treeMetadata has no import-form step, so no loading flag or cancel
|
|
4960
|
-
|
|
4586
|
+
installFilehandleLoader(self, {
|
|
4961
4587
|
what: 'row metadata',
|
|
4962
4588
|
optional: true,
|
|
4963
4589
|
getFilehandle: () => self.treeMetadataFilehandle,
|
|
@@ -4989,7 +4615,7 @@ function stateModelFactory({
|
|
|
4989
4615
|
)
|
|
4990
4616
|
|
|
4991
4617
|
// gffFilehandle loads into data.gff, which the autorun above parses
|
|
4992
|
-
|
|
4618
|
+
installFilehandleLoader(self, {
|
|
4993
4619
|
what: 'annotations',
|
|
4994
4620
|
optional: true,
|
|
4995
4621
|
getFilehandle: () => self.gffFilehandle,
|
|
@@ -5001,7 +4627,7 @@ function stateModelFactory({
|
|
|
5001
4627
|
},
|
|
5002
4628
|
})
|
|
5003
4629
|
|
|
5004
|
-
|
|
4630
|
+
installFilehandleLoader(self, {
|
|
5005
4631
|
what: 'alignment',
|
|
5006
4632
|
getFilehandle: () => self.msaFilehandle,
|
|
5007
4633
|
onLoad: text => {
|
|
@@ -5025,20 +4651,16 @@ function stateModelFactory({
|
|
|
5025
4651
|
self,
|
|
5026
4652
|
autorun(() => {
|
|
5027
4653
|
if (self.dynamicColorSchemeName) {
|
|
5028
|
-
|
|
5029
|
-
self.colStats
|
|
4654
|
+
readComputed(self.colStats)
|
|
5030
4655
|
}
|
|
5031
4656
|
if (self.showColumnStats) {
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
5038
|
-
self.propertyConservation
|
|
4657
|
+
readComputed(
|
|
4658
|
+
self.colStats,
|
|
4659
|
+
self.conservation,
|
|
4660
|
+
self.propertyConservation,
|
|
4661
|
+
)
|
|
5039
4662
|
}
|
|
5040
|
-
|
|
5041
|
-
self.columns
|
|
4663
|
+
readComputed(self.columns)
|
|
5042
4664
|
}),
|
|
5043
4665
|
)
|
|
5044
4666
|
},
|