react-msaview 9.0.0 → 9.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -10
- 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 +2 -3
- package/dist/colorSchemes.js.map +1 -1
- package/dist/columnCounts.d.ts +0 -1
- package/dist/columnCounts.js +1 -1
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/Loading.d.ts +3 -2
- package/dist/components/Loading.js +7 -7
- 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 +1941 -4
- package/dist/components/MSAViewer.js +144 -201
- package/dist/components/MSAViewer.js.map +1 -1
- 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/ResizeHandles.js +3 -2
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/Track.js +8 -1
- package/dist/components/Track.js.map +1 -1
- package/dist/components/controls.d.ts +11 -0
- package/dist/components/controls.js +49 -0
- package/dist/components/controls.js.map +1 -0
- package/dist/components/dialogs/AnnotationFileDialog.js +12 -18
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +5 -7
- package/dist/components/dialogs/ExportSVGDialog.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/TreeSettingsMenu.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/HelpDialog.d.ts +6 -0
- package/dist/components/dialogs/HelpDialog.js +30 -0
- package/dist/components/dialogs/HelpDialog.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 -34
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HiddenColumnsChip.d.ts +6 -0
- package/dist/components/header/HiddenColumnsChip.js +15 -0
- package/dist/components/header/HiddenColumnsChip.js.map +1 -0
- package/dist/components/header/{FileMenu.d.ts → MenuBar.d.ts} +3 -3
- 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 +12 -48
- 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/import/data/seq2.d.ts +0 -1
- package/dist/components/import/data/seq2.js +0 -17
- package/dist/components/import/data/seq2.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 +28 -10
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +17 -16
- 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/drawFeatureSpans.d.ts +50 -11
- package/dist/components/msa/drawFeatureSpans.js +99 -56
- package/dist/components/msa/drawFeatureSpans.js.map +1 -1
- package/dist/components/msa/msaKeys.js +3 -0
- package/dist/components/msa/msaKeys.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +14 -2
- package/dist/components/msa/msaRaster.js +20 -10
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +9 -0
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +39 -8
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +2 -5
- package/dist/components/msa/renderHighlights.js +5 -6
- 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/renderMSAMouseover.js +2 -1
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/msa/tileColor.js +7 -0
- package/dist/components/msa/tileColor.js.map +1 -1
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/rowpanels/RowPanelBlock.js +2 -2
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
- package/dist/components/rowpanels/renderFeaturePanel.d.ts +5 -1
- package/dist/components/rowpanels/renderFeaturePanel.js +12 -5
- package/dist/components/rowpanels/renderFeaturePanel.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/ZoomMenu.d.ts → settings/TrackList.d.ts} +2 -2
- package/dist/components/settings/TrackList.js +55 -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/drawTracks.d.ts +1 -53
- package/dist/components/tracks/drawTracks.js +5 -5
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/CladeLabels.d.ts +1 -1
- package/dist/components/tree/CladeLabels.js +22 -34
- package/dist/components/tree/CladeLabels.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/components/tree/cladeBrackets.d.ts +17 -10
- package/dist/components/tree/cladeBrackets.js +33 -3
- package/dist/components/tree/cladeBrackets.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +2 -8
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeOverview.js +4 -11
- package/dist/components/tree/renderTreeOverview.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 +25 -53
- package/dist/element.js.map +1 -1
- package/dist/exportLayout.d.ts +39 -0
- package/dist/exportLayout.js +78 -0
- package/dist/exportLayout.js.map +1 -0
- 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/headlessRenderEnv.d.ts +12 -39
- package/dist/headlessRenderEnv.js +18 -12
- package/dist/headlessRenderEnv.js.map +1 -1
- package/dist/hierarchy.d.ts +5 -8
- package/dist/hierarchy.js +5 -79
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/measureTextCanvas.d.ts +7 -0
- package/dist/measureTextCanvas.js +8 -6
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/treeModel.d.ts +6 -2
- package/dist/model/treeModel.js +8 -4
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +180 -76
- package/dist/model.js +358 -618
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +1 -40
- package/dist/neighborJoining.js.map +1 -1
- package/dist/renderToSvg.d.ts +2 -7
- package/dist/renderToSvg.js +33 -133
- 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 +74 -32
- 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 +6 -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/viewerSnapshot.d.ts +169 -0
- package/dist/viewerSnapshot.js +100 -0
- package/dist/viewerSnapshot.js.map +1 -0
- 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 +2 -4
- package/src/columnCounts.ts +1 -1
- package/src/components/Loading.tsx +25 -14
- package/src/components/MSAView.tsx +22 -5
- package/src/components/MSAViewer.tsx +275 -251
- package/src/components/MetadataTable.tsx +83 -26
- package/src/components/ResizeHandles.tsx +9 -2
- package/src/components/Track.tsx +15 -1
- package/src/components/controls.ts +62 -0
- package/src/components/dialogs/AnnotationFileDialog.tsx +16 -21
- package/src/components/dialogs/ExportSVGDialog.tsx +8 -19
- 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/HelpDialog.tsx +79 -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 -44
- 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 +23 -74
- 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/import/data/seq2.ts +0 -18
- 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 +24 -4
- package/src/components/msa/MSACanvasBlock.tsx +20 -17
- package/src/components/msa/MSAMouseoverCanvas.tsx +2 -2
- package/src/components/msa/WheelZoomHint.tsx +56 -0
- package/src/components/msa/drawFeatureSpans.ts +160 -79
- package/src/components/msa/msaKeys.ts +3 -0
- package/src/components/msa/msaRaster.ts +26 -10
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +62 -24
- package/src/components/msa/renderHighlights.ts +5 -6
- package/src/components/msa/renderMSABlock.ts +51 -15
- package/src/components/msa/renderMSAMouseover.ts +5 -1
- package/src/components/msa/tileColor.ts +7 -0
- package/src/components/renderCtx.ts +0 -1
- package/src/components/rowpanels/RowPanelBlock.tsx +2 -2
- package/src/components/rowpanels/renderFeaturePanel.ts +26 -7
- package/src/components/settings/MenuItemsForm.tsx +225 -0
- package/src/components/settings/SettingsPanel.tsx +156 -0
- package/src/components/settings/TrackList.tsx +119 -0
- package/src/components/tracks/TrackBlocks.tsx +2 -2
- package/src/components/tracks/drawTracks.ts +5 -5
- package/src/components/tree/CladeLabels.tsx +30 -54
- 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/components/tree/cladeBrackets.ts +50 -3
- package/src/components/tree/renderTreeCanvas.ts +2 -13
- package/src/components/tree/renderTreeOverview.ts +7 -14
- package/src/constants.ts +9 -1
- package/src/darkReader.ts +57 -0
- package/src/element.ts +37 -64
- package/src/exportLayout.ts +134 -0
- package/src/featurePanels.ts +269 -0
- package/src/filehandleLoader.ts +136 -0
- package/src/headlessRenderEnv.ts +24 -11
- package/src/hierarchy.ts +5 -88
- package/src/index.ts +9 -5
- package/src/measureTextCanvas.ts +8 -6
- package/src/model/treeModel.ts +9 -3
- package/src/model.ts +452 -807
- package/src/neighborJoining.ts +1 -42
- package/src/renderToSvg.tsx +56 -207
- 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 +6 -4
- package/src/useWheelScroll.ts +43 -22
- package/src/util.ts +3 -0
- package/src/version.ts +1 -1
- package/src/viewerSnapshot.ts +147 -0
- 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 -66
- 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.d.ts +0 -6
- 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 -42
- package/dist/components/header/TreeSettingsMenu.js.map +0 -1
- package/dist/components/header/ZoomMenu.js +0 -37
- package/dist/components/header/ZoomMenu.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 -4
- package/dist/components/header/settingsMenuItems.js +0 -127
- package/dist/components/header/settingsMenuItems.js.map +0 -1
- package/src/components/header/ColorSchemeMenu.tsx +0 -56
- package/src/components/header/FileMenu.tsx +0 -81
- package/src/components/header/MSASettingsMenu.tsx +0 -26
- package/src/components/header/ScrollZoomIcons.tsx +0 -52
- package/src/components/header/TreeSettingsMenu.tsx +0 -60
- package/src/components/header/ZoomMenu.tsx +0 -48
- package/src/components/header/settingsMenuItems.ts +0 -157
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,11 +17,8 @@ import {
|
|
|
19
17
|
import {
|
|
20
18
|
generateNodeIds,
|
|
21
19
|
gffToAnnotations,
|
|
22
|
-
interProScanToAnnotations,
|
|
23
|
-
parseEmfTree,
|
|
24
20
|
parseGFF,
|
|
25
21
|
parseMSA,
|
|
26
|
-
parseNewick,
|
|
27
22
|
} from 'msa-parsers'
|
|
28
23
|
|
|
29
24
|
import { blockFasta } from './blockFasta.ts'
|
|
@@ -34,18 +29,14 @@ import { columnCountsFromRows, letterOfResidueSlot } from './columnCounts.ts'
|
|
|
34
29
|
import { columnStats } from './columnStats.ts'
|
|
35
30
|
import { packDomainLanes } from './components/msa/packDomainLanes.ts'
|
|
36
31
|
import { visibleColRange } from './components/msa/visibleColRange.ts'
|
|
37
|
-
import TrackBlocks from './components/tracks/TrackBlocks.tsx'
|
|
38
32
|
import { cladeGutterWidth } from './components/tree/cladeBrackets.ts'
|
|
39
33
|
import { scaleBarLength } from './components/tree/scaleBar.ts'
|
|
40
34
|
import {
|
|
41
|
-
cladeHighlightAlpha,
|
|
42
|
-
cladeHighlightColor,
|
|
43
35
|
defaultAllowedGappyness,
|
|
44
36
|
defaultColWidth,
|
|
45
37
|
defaultColorSchemeName,
|
|
46
38
|
defaultCurrentAlignment,
|
|
47
39
|
defaultDrawMsaLetters,
|
|
48
|
-
defaultFeaturePanelWidth,
|
|
49
40
|
defaultHeight,
|
|
50
41
|
defaultHideGaps,
|
|
51
42
|
defaultRowHeight,
|
|
@@ -74,39 +65,37 @@ import {
|
|
|
74
65
|
scrollZoomAxes,
|
|
75
66
|
segmentFeatureTypes,
|
|
76
67
|
segmentShades,
|
|
68
|
+
settingsPanelWidth,
|
|
77
69
|
treeScaleBarHeight,
|
|
78
70
|
} from './constants.ts'
|
|
79
71
|
import { createPaletteMap } from './createPaletteMap.ts'
|
|
80
72
|
import { exportFileName } from './exportFileName.ts'
|
|
81
|
-
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'
|
|
82
83
|
import { featuresToAnnotations } from './featuresLayer.ts'
|
|
83
|
-
import {
|
|
84
|
-
import { flatToTree } from './flatToTree.ts'
|
|
84
|
+
import { installFilehandleLoader } from './filehandleLoader.ts'
|
|
85
85
|
import {
|
|
86
|
-
calcDepthToLeaf,
|
|
87
|
-
clusterLayout,
|
|
88
|
-
collapse,
|
|
89
86
|
find,
|
|
90
|
-
forEachDescendant,
|
|
91
87
|
hierarchy,
|
|
92
88
|
layoutTree,
|
|
93
|
-
leafIndex,
|
|
94
89
|
leaves,
|
|
95
|
-
maxLength,
|
|
96
|
-
mrca,
|
|
97
90
|
nodeCoveringRows,
|
|
98
|
-
setBrLength,
|
|
99
|
-
sort,
|
|
100
|
-
sum as hierarchySum,
|
|
101
91
|
} from './hierarchy.ts'
|
|
102
92
|
import { DataModelF } from './model/DataModel.ts'
|
|
103
93
|
import { DialogQueueSessionMixin } from './model/DialogQueue.ts'
|
|
104
94
|
import { MSAModelF } from './model/msaModel.ts'
|
|
105
95
|
import { TreeModelF } from './model/treeModel.ts'
|
|
106
96
|
import { calculateNeighborJoiningTree } from './neighborJoining.ts'
|
|
107
|
-
import { parseAsn1 } from './parseAsn1.ts'
|
|
108
97
|
import { calculatePropertyConservation } from './propertyConservation.ts'
|
|
109
|
-
import {
|
|
98
|
+
import { preorder } from './rerootTree.ts'
|
|
110
99
|
import {
|
|
111
100
|
globalColToVisibleCol,
|
|
112
101
|
seqPosOfGlobalCol,
|
|
@@ -116,33 +105,43 @@ import {
|
|
|
116
105
|
import { resolveScale } from './scales.ts'
|
|
117
106
|
import { buildSeqPosIndex } from './seqPosToGlobalCol.ts'
|
|
118
107
|
import { maxBitsFor } from './sequenceLogo.ts'
|
|
108
|
+
import {
|
|
109
|
+
blosum62ColumnColors,
|
|
110
|
+
identityColumnColors,
|
|
111
|
+
} from './similarityColors.ts'
|
|
119
112
|
import { stripDefault } from './stripDefault.ts'
|
|
120
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'
|
|
121
122
|
import {
|
|
122
123
|
computeRowInsertions,
|
|
123
124
|
dropBlanks,
|
|
124
125
|
len,
|
|
125
|
-
|
|
126
|
+
readComputed,
|
|
126
127
|
skipBlanks,
|
|
127
128
|
withAlpha,
|
|
128
129
|
} from './util.ts'
|
|
129
130
|
import { saveAs } from './vendor/fileSaver.ts'
|
|
130
131
|
import { writeNewick } from './writeNewick.ts'
|
|
131
132
|
import { parseWuss } from './wuss.ts'
|
|
133
|
+
import { takeZoomSnapshot, zoomSnapshotCovers } from './zoomSnapshot.ts'
|
|
132
134
|
|
|
133
135
|
import type { ColumnStats } from './columnStats.ts'
|
|
134
136
|
import type { ScrollZoomAxis } from './constants.ts'
|
|
135
137
|
import type { HierarchyNode } from './hierarchy.ts'
|
|
136
138
|
import type { ExportSvgOptions } from './renderToSvg.tsx'
|
|
137
|
-
import type { TreeNode } from './rerootTree.ts'
|
|
138
|
-
import type { ScaleSpec } from './scales.ts'
|
|
139
139
|
import type {
|
|
140
140
|
Annotation,
|
|
141
141
|
Arc,
|
|
142
142
|
BasicTrack,
|
|
143
143
|
Cell,
|
|
144
144
|
Clade,
|
|
145
|
-
CladeMark,
|
|
146
145
|
ResidueMappingProblem,
|
|
147
146
|
ColumnTrackSpec,
|
|
148
147
|
DomainBand,
|
|
@@ -160,14 +159,11 @@ import type {
|
|
|
160
159
|
ResolvedClade,
|
|
161
160
|
ResolvedEncoding,
|
|
162
161
|
ResolvedFeaturePanel,
|
|
162
|
+
ResolvedStripPanel,
|
|
163
163
|
ResolvedHighlight,
|
|
164
164
|
ResolvedSelection,
|
|
165
|
-
TreeOrder,
|
|
166
165
|
TreeRoot,
|
|
167
166
|
ResolvedRowPanel,
|
|
168
|
-
ResolvedStripPanel,
|
|
169
|
-
RowFeaturesSpec,
|
|
170
|
-
RowPanelSpan,
|
|
171
167
|
RowDataInput,
|
|
172
168
|
RowPanelSpec,
|
|
173
169
|
RowResidue,
|
|
@@ -176,17 +172,10 @@ import type {
|
|
|
176
172
|
UnshareableData,
|
|
177
173
|
Viewport,
|
|
178
174
|
} from './types.ts'
|
|
175
|
+
import type { ZoomSnapshot } from './zoomSnapshot.ts'
|
|
179
176
|
import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
|
|
180
177
|
import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
181
178
|
import type { IComputedValue } from 'mobx'
|
|
182
|
-
import type { InterProScanResults } from 'msa-parsers'
|
|
183
|
-
|
|
184
|
-
function parseTreeText(text: string) {
|
|
185
|
-
if (text.startsWith('BioTreeContainer')) {
|
|
186
|
-
return flatToTree(parseAsn1(text))
|
|
187
|
-
}
|
|
188
|
-
return parseNewick(text.startsWith('SEQ') ? parseEmfTree(text).tree : text)
|
|
189
|
-
}
|
|
190
179
|
|
|
191
180
|
// The height each kind draws at before the user drags a divider. A text track
|
|
192
181
|
// is absent: it is one alignment row tall and follows rowHeight, so the zoom
|
|
@@ -216,262 +205,6 @@ const ownHeightKey = (id: string) => `own:${id}`
|
|
|
216
205
|
const HELIX_ARC = '#4e79a7'
|
|
217
206
|
const PSEUDOKNOT_ARC = '#e15759'
|
|
218
207
|
|
|
219
|
-
// the fill of a span under a scale that gives its value no color, such as a
|
|
220
|
-
// gene a `map` over gene names leaves out
|
|
221
|
-
const UNSCALED_FEATURE = '#d9d9d9'
|
|
222
|
-
|
|
223
|
-
// the fraction of a span a following one may cover before it takes a lane of
|
|
224
|
-
// its own in a `features` panel
|
|
225
|
-
const laneOverlap = 0.1
|
|
226
|
-
|
|
227
|
-
// a row panel's own width, or a default: the row height, which makes a strip
|
|
228
|
-
// cell square, and a gene neighborhood's width for a features panel. The row
|
|
229
|
-
// height is read only when used, so a panel with a width of its own does not
|
|
230
|
-
// recompute on a vertical zoom
|
|
231
|
-
function rowPanelWidth(panel: RowPanelSpec, view: { rowHeight: number }) {
|
|
232
|
-
return (
|
|
233
|
-
panel.width ??
|
|
234
|
-
(panel.kind === 'features' ? defaultFeaturePanelWidth : view.rowHeight)
|
|
235
|
-
)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// one row of the row table as the channels read it. A number or a boolean
|
|
239
|
-
// reads as its string, the way a GFF value does, and null or an object drops
|
|
240
|
-
function stringFields(row: unknown) {
|
|
241
|
-
if (typeof row !== 'object' || row === null) {
|
|
242
|
-
return undefined
|
|
243
|
-
}
|
|
244
|
-
const fields: Record<string, string> = {}
|
|
245
|
-
for (const [field, value] of Object.entries(row)) {
|
|
246
|
-
if (typeof value === 'string') {
|
|
247
|
-
fields[field] = value
|
|
248
|
-
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
249
|
-
fields[field] = String(value)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return fields
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// the color each row takes from a field of the row table under a scale
|
|
256
|
-
function colorByRow(
|
|
257
|
-
rowData: Record<string, Record<string, string> | undefined>,
|
|
258
|
-
field: string,
|
|
259
|
-
colorOf: (value: string) => string | undefined,
|
|
260
|
-
) {
|
|
261
|
-
const colors = new Map<string, string>()
|
|
262
|
-
for (const [name, row] of Object.entries(rowData)) {
|
|
263
|
-
const value = row?.[field]
|
|
264
|
-
const color = value === undefined ? undefined : colorOf(value)
|
|
265
|
-
if (color) {
|
|
266
|
-
colors.set(name, color)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return colors
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
type StripPanelScale = Omit<ResolvedStripPanel, 'id' | 'width' | 'offsetX'>
|
|
273
|
-
type FeaturePanelScale = Omit<
|
|
274
|
-
ResolvedFeaturePanel,
|
|
275
|
-
'id' | 'width' | 'offsetX' | 'spans'
|
|
276
|
-
>
|
|
277
|
-
|
|
278
|
-
// the fill and outline of every feature in a list: its own GFF `color=` first,
|
|
279
|
-
// then the scale an encoding resolves over one of its fields, then the
|
|
280
|
-
// accession palette
|
|
281
|
-
function featureColorMap(
|
|
282
|
-
annotations: Annotation[],
|
|
283
|
-
encoding:
|
|
284
|
-
| { field: string; colorOf: (value: string) => string | undefined }
|
|
285
|
-
| undefined,
|
|
286
|
-
fillPalette: Record<string, string>,
|
|
287
|
-
) {
|
|
288
|
-
const strokes = new Map<string, string>()
|
|
289
|
-
const strokeOf = (fill: string) => {
|
|
290
|
-
const hit = strokes.get(fill)
|
|
291
|
-
if (hit !== undefined) {
|
|
292
|
-
return hit
|
|
293
|
-
}
|
|
294
|
-
const stroke = outlineColor(fill)
|
|
295
|
-
strokes.set(fill, stroke)
|
|
296
|
-
return stroke
|
|
297
|
-
}
|
|
298
|
-
return new Map(
|
|
299
|
-
annotations.map(annotation => {
|
|
300
|
-
const value = encoding
|
|
301
|
-
? featureField(annotation, encoding.field)
|
|
302
|
-
: undefined
|
|
303
|
-
const fill =
|
|
304
|
-
annotation.color ??
|
|
305
|
-
(value === undefined ? undefined : encoding!.colorOf(value)) ??
|
|
306
|
-
(encoding ? UNSCALED_FEATURE : fillPalette[annotation.accession]!)
|
|
307
|
-
return [annotation, { fill, stroke: strokeOf(fill) }]
|
|
308
|
-
}),
|
|
309
|
-
)
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// the text a label channel draws inside each span, for the features carrying
|
|
313
|
-
// the field it names
|
|
314
|
-
function featureLabelMap(annotations: Annotation[], field: string) {
|
|
315
|
-
const labels = new Map<Annotation, string>()
|
|
316
|
-
for (const annotation of annotations) {
|
|
317
|
-
const value = featureField(annotation, field)
|
|
318
|
-
if (value !== undefined) {
|
|
319
|
-
labels.set(annotation, value)
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return labels
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/** what `packDomainLanes` needs to lay a row out, before it has its lane */
|
|
326
|
-
type Unlaned = Omit<RowPanelSpan, 'lane' | 'laneCount'> & {
|
|
327
|
-
startCol: number
|
|
328
|
-
endCol: number
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Lanes for `position: "strandpile"`, gggenomes' `position_strandpile`: the
|
|
333
|
-
* forward features stack above the line and the reverse below it, each strand
|
|
334
|
-
* packing on its own. The deepest row on each side sets the grid every row lays
|
|
335
|
-
* out on, so the line between the strands sits at one height down the panel and
|
|
336
|
-
* a reader can scan it. A feature with no strand piles with the forward ones.
|
|
337
|
-
*/
|
|
338
|
-
function strandpileLanes(
|
|
339
|
-
rows: [string, Unlaned[]][],
|
|
340
|
-
): Map<string, RowPanelSpan[]> {
|
|
341
|
-
const piled = rows.map(([name, bands]) => {
|
|
342
|
-
const reverse = packDomainLanes(
|
|
343
|
-
bands.filter(b => b.annotation.strand === -1),
|
|
344
|
-
)
|
|
345
|
-
const forward = packDomainLanes(
|
|
346
|
-
bands.filter(b => b.annotation.strand !== -1),
|
|
347
|
-
)
|
|
348
|
-
return { name, forward, reverse }
|
|
349
|
-
})
|
|
350
|
-
const up = Math.max(0, ...piled.map(p => p.forward[0]?.laneCount ?? 0))
|
|
351
|
-
const down = Math.max(0, ...piled.map(p => p.reverse[0]?.laneCount ?? 0))
|
|
352
|
-
const laneCount = up + down
|
|
353
|
-
return new Map(
|
|
354
|
-
piled.map(({ name, forward, reverse }) => [
|
|
355
|
-
name,
|
|
356
|
-
[
|
|
357
|
-
// forward level 0 is the lane just above the line, stacking upward
|
|
358
|
-
...forward.map(b => ({ ...b, lane: up - 1 - b.lane, laneCount })),
|
|
359
|
-
...reverse.map(b => ({ ...b, lane: up + b.lane, laneCount })),
|
|
360
|
-
],
|
|
361
|
-
]),
|
|
362
|
-
)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/** one row's spans laid out by the panel's `position` */
|
|
366
|
-
function panelLanes(
|
|
367
|
-
panel: RowFeaturesSpec,
|
|
368
|
-
rows: [string, Unlaned[]][],
|
|
369
|
-
): Map<string, RowPanelSpan[]> {
|
|
370
|
-
return panel.position === 'strandpile'
|
|
371
|
-
? strandpileLanes(rows)
|
|
372
|
-
: new Map(rows.map(([name, bands]) => [name, packDomainLanes(bands)]))
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* The spans a `features` panel draws, keyed by row name and measured in the
|
|
377
|
-
* panel's own pixels. `column` takes the bands the overlay draws, at the
|
|
378
|
-
* alignment's column width. `position` packs each row's features in its own
|
|
379
|
-
* residue positions, shifted by an `align` transform, and maps the extent they
|
|
380
|
-
* cover across every row onto the panel width, so a row with no alignment
|
|
381
|
-
* still has an x.
|
|
382
|
-
*/
|
|
383
|
-
function featurePanelSpans({
|
|
384
|
-
panel,
|
|
385
|
-
width,
|
|
386
|
-
colWidth,
|
|
387
|
-
domainBands,
|
|
388
|
-
annotationsByRow,
|
|
389
|
-
shifts,
|
|
390
|
-
}: {
|
|
391
|
-
panel: RowFeaturesSpec
|
|
392
|
-
width: number
|
|
393
|
-
colWidth: number
|
|
394
|
-
domainBands: Map<string, DomainBand[]>
|
|
395
|
-
annotationsByRow: Record<string, Annotation[]>
|
|
396
|
-
shifts: Map<string, number> | undefined
|
|
397
|
-
}): Map<string, RowPanelSpan[]> {
|
|
398
|
-
if (panel.x === 'column') {
|
|
399
|
-
return panelLanes(
|
|
400
|
-
panel,
|
|
401
|
-
[...domainBands].map(([name, bands]) => [
|
|
402
|
-
name,
|
|
403
|
-
bands.map(band => ({
|
|
404
|
-
...band,
|
|
405
|
-
xStart: band.startCol * colWidth,
|
|
406
|
-
xEnd: band.endCol * colWidth,
|
|
407
|
-
})),
|
|
408
|
-
]),
|
|
409
|
-
)
|
|
410
|
-
}
|
|
411
|
-
const shifted = Object.entries(annotationsByRow).map(
|
|
412
|
-
([name, annotations]) => {
|
|
413
|
-
const shift = shifts?.get(name) ?? 0
|
|
414
|
-
return [
|
|
415
|
-
name,
|
|
416
|
-
annotations.map(annotation => ({
|
|
417
|
-
annotation,
|
|
418
|
-
start: annotation.start - 1 + shift,
|
|
419
|
-
end: annotation.end + shift,
|
|
420
|
-
})),
|
|
421
|
-
] as const
|
|
422
|
-
},
|
|
423
|
-
)
|
|
424
|
-
let min = Infinity
|
|
425
|
-
let max = -Infinity
|
|
426
|
-
for (const [, features] of shifted) {
|
|
427
|
-
for (const { start, end } of features) {
|
|
428
|
-
min = Math.min(min, start)
|
|
429
|
-
max = Math.max(max, end)
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
// a gene arrow ends where its feature does, so the extent maps onto the whole
|
|
433
|
-
// panel less the pixel the rightmost stroke needs
|
|
434
|
-
const drawable = Math.max(1, width - 1)
|
|
435
|
-
const scale = max > min ? drawable / (max - min) : 0
|
|
436
|
-
// a lane opens where a span covers more than a tenth of the one before it,
|
|
437
|
-
// which keeps the genes of an operon on one lane: adjacent genes commonly
|
|
438
|
-
// share a few bases, and a stop codon overlapping the next start reads as a
|
|
439
|
-
// second lane over the whole row
|
|
440
|
-
return panelLanes(
|
|
441
|
-
panel,
|
|
442
|
-
shifted.map(([name, features]) => [
|
|
443
|
-
name,
|
|
444
|
-
features.map(({ annotation, start, end }) => ({
|
|
445
|
-
annotation,
|
|
446
|
-
xStart: (start - min) * scale,
|
|
447
|
-
xEnd: (end - min) * scale,
|
|
448
|
-
startCol: start,
|
|
449
|
-
endCol: end - (end - start) * laneOverlap,
|
|
450
|
-
})),
|
|
451
|
-
]),
|
|
452
|
-
)
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// a scale over the values a field takes across every feature loaded, so hiding
|
|
456
|
-
// a type in the filter dialog leaves the rest in their colors, and a legend of
|
|
457
|
-
// the values the features drawn carry
|
|
458
|
-
function resolveFeatureScale(
|
|
459
|
-
field: string,
|
|
460
|
-
scale: ScaleSpec | undefined,
|
|
461
|
-
annotations: Annotation[],
|
|
462
|
-
drawn: Annotation[],
|
|
463
|
-
) {
|
|
464
|
-
const valuesOf = (list: Annotation[]) =>
|
|
465
|
-
list.map(a => featureField(a, field)).filter(notEmpty)
|
|
466
|
-
const resolved = resolveScale(scale, valuesOf(annotations))
|
|
467
|
-
const shown = new Set(valuesOf(drawn))
|
|
468
|
-
return {
|
|
469
|
-
field,
|
|
470
|
-
...resolved,
|
|
471
|
-
legend: resolved.legend.filter(entry => shown.has(entry.id)),
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
208
|
const columnTrackBytes = new WeakMap<ColumnTrackSpec, number>()
|
|
476
209
|
|
|
477
210
|
function columnTrackSize(track: ColumnTrackSpec) {
|
|
@@ -531,6 +264,7 @@ export const preservedOnReset = new Set([
|
|
|
531
264
|
'drawNodeBubbles',
|
|
532
265
|
'drawNodeLabels',
|
|
533
266
|
'showTreeOverview',
|
|
267
|
+
'showTreeScaleBar',
|
|
534
268
|
'overviewHeight',
|
|
535
269
|
'turnedOffTracks',
|
|
536
270
|
'trackHeights',
|
|
@@ -538,224 +272,23 @@ export const preservedOnReset = new Set([
|
|
|
538
272
|
'allowedGappyness',
|
|
539
273
|
'subFeatureRows',
|
|
540
274
|
'showDomainLegend',
|
|
275
|
+
'domainFill',
|
|
276
|
+
'embeddedMode',
|
|
541
277
|
])
|
|
542
278
|
|
|
543
279
|
// `turnedOffTracks` holds only the user's explicit choices, so a hidden-by-default
|
|
544
|
-
// track writes nothing into the snapshot
|
|
545
|
-
// `defaultOff`, since the count varies by file: a Pfam seed has a couple of #=GR
|
|
546
|
-
// lines, an Rfam family one per row.
|
|
280
|
+
// track writes nothing into the snapshot
|
|
547
281
|
function trackIsOff(
|
|
548
282
|
turnedOffTracks: { get: (id: string) => boolean | undefined },
|
|
549
283
|
id: string,
|
|
550
|
-
defaultOff?: boolean,
|
|
551
284
|
) {
|
|
552
|
-
return turnedOffTracks.get(id) ??
|
|
285
|
+
return turnedOffTracks.get(id) ?? defaultOffTracks.has(id)
|
|
553
286
|
}
|
|
554
287
|
|
|
555
|
-
// the marks that set tree state from a node rather than drawing
|
|
556
|
-
const seedingMarks = new Set<CladeMark>(['collapse', 'focus', 'rotate'])
|
|
557
|
-
|
|
558
288
|
// shared empty results, so observers don't see a fresh [] as a change
|
|
559
289
|
const noDomains: Annotation[] = []
|
|
560
290
|
const noClades: ResolvedClade[] = []
|
|
561
291
|
|
|
562
|
-
/**
|
|
563
|
-
* The rows a clade covers and the node an `mrca` names, or undefined when the
|
|
564
|
-
* clade does not resolve: a tip name the tree does not have or has twice, or a
|
|
565
|
-
* leaf count `tips` disagrees with. `index` and `rowNamesSet` are the memoized
|
|
566
|
-
* passes over the tree.
|
|
567
|
-
*/
|
|
568
|
-
function cladeRows(
|
|
569
|
-
clade: Clade,
|
|
570
|
-
root: HierarchyNode<NodeWithIds>,
|
|
571
|
-
index: Map<string, HierarchyNode<NodeWithIds> | undefined>,
|
|
572
|
-
rowNamesSet: Map<string, number>,
|
|
573
|
-
): { rows: [number, number]; nodeId?: string } | undefined {
|
|
574
|
-
if (clade.range) {
|
|
575
|
-
const [a, b] = clade.range.map(name =>
|
|
576
|
-
index.get(name) ? rowNamesSet.get(name) : undefined,
|
|
577
|
-
)
|
|
578
|
-
if (a === undefined || b === undefined) {
|
|
579
|
-
return undefined
|
|
580
|
-
}
|
|
581
|
-
const rows: [number, number] = a <= b ? [a, b] : [b, a]
|
|
582
|
-
return rows[1] - rows[0] + 1 === clade.tips ? { rows } : undefined
|
|
583
|
-
}
|
|
584
|
-
const node = clade.mrca ? mrca(root, clade.mrca, index) : undefined
|
|
585
|
-
if (!node) {
|
|
586
|
-
return undefined
|
|
587
|
-
}
|
|
588
|
-
const tips = leaves(node)
|
|
589
|
-
if (tips.length !== clade.tips) {
|
|
590
|
-
return undefined
|
|
591
|
-
}
|
|
592
|
-
let first = Infinity
|
|
593
|
-
let last = -Infinity
|
|
594
|
-
for (const tip of tips) {
|
|
595
|
-
const row = rowNamesSet.get(tip.data.name)
|
|
596
|
-
if (row !== undefined) {
|
|
597
|
-
first = Math.min(first, row)
|
|
598
|
-
last = Math.max(last, row)
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
return first <= last
|
|
602
|
-
? { rows: [first, last], nodeId: node.data.id }
|
|
603
|
-
: undefined
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* Every clade that resolves, with its fill color settled, against the leaf
|
|
608
|
-
* order `rowNamesSet` gives. The tree panel resolves against the displayed
|
|
609
|
-
* rows and the overview against its own, so both take the tree they draw. A
|
|
610
|
-
* `range` record names no node, so the seeding marks, which need one, drop it.
|
|
611
|
-
*/
|
|
612
|
-
function resolveClades(
|
|
613
|
-
clades: Clade[],
|
|
614
|
-
root: HierarchyNode<NodeWithIds>,
|
|
615
|
-
rowNamesSet: Map<string, number>,
|
|
616
|
-
): ResolvedClade[] {
|
|
617
|
-
const index = leafIndex(root)
|
|
618
|
-
return clades.flatMap(clade => {
|
|
619
|
-
const resolved = cladeRows(clade, root, index, rowNamesSet)
|
|
620
|
-
const seeding = seedingMarks.has(clade.mark)
|
|
621
|
-
return resolved && !(seeding && resolved.nodeId === undefined)
|
|
622
|
-
? [
|
|
623
|
-
{
|
|
624
|
-
...resolved,
|
|
625
|
-
mark: clade.mark,
|
|
626
|
-
color: withAlpha(
|
|
627
|
-
clade.color ?? cladeHighlightColor,
|
|
628
|
-
cladeHighlightAlpha,
|
|
629
|
-
),
|
|
630
|
-
markColor: clade.color,
|
|
631
|
-
label: clade.label,
|
|
632
|
-
},
|
|
633
|
-
]
|
|
634
|
-
: []
|
|
635
|
-
})
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const childOrders: Record<
|
|
639
|
-
TreeOrder,
|
|
640
|
-
((a: HierarchyNode, b: HierarchyNode) => number) | undefined
|
|
641
|
-
> = {
|
|
642
|
-
input: undefined,
|
|
643
|
-
branchLength: (a, b) => (a.data.length ?? 1) - (b.data.length ?? 1),
|
|
644
|
-
ladderize: (a, b) => a.value! - b.value!,
|
|
645
|
-
ladderizeReverse: (a, b) => b.value! - a.value!,
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* The tree with the display transforms applied: leaf counts summed, children
|
|
650
|
-
* put in `order` and each `rotated` node's reversed, `showOnly` taken as the
|
|
651
|
-
* new root, and each `collapsed` clade folded. The overview builds the same
|
|
652
|
-
* tree without the focus, so it shows the focused subtree inside the whole.
|
|
653
|
-
*/
|
|
654
|
-
function buildTreeRoot(
|
|
655
|
-
tree: NodeWithIds,
|
|
656
|
-
{
|
|
657
|
-
collapsed,
|
|
658
|
-
rotated,
|
|
659
|
-
order,
|
|
660
|
-
showOnly,
|
|
661
|
-
}: {
|
|
662
|
-
collapsed: readonly string[]
|
|
663
|
-
rotated: readonly string[]
|
|
664
|
-
order: TreeOrder
|
|
665
|
-
showOnly?: string
|
|
666
|
-
},
|
|
667
|
-
) {
|
|
668
|
-
let hier = hierarchy(tree, d => d.children)
|
|
669
|
-
hierarchySum(hier, d => (d.children.length > 0 ? 0 : 1))
|
|
670
|
-
const compare = childOrders[order]
|
|
671
|
-
if (compare) {
|
|
672
|
-
sort(hier, compare)
|
|
673
|
-
}
|
|
674
|
-
if (rotated.length > 0) {
|
|
675
|
-
const flip = new Set(rotated)
|
|
676
|
-
forEachDescendant(hier, n => {
|
|
677
|
-
if (flip.has(n.data.id)) {
|
|
678
|
-
n.children?.reverse()
|
|
679
|
-
}
|
|
680
|
-
})
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
if (showOnly) {
|
|
684
|
-
const res = find(hier, n => n.data.id === showOnly)
|
|
685
|
-
if (res) {
|
|
686
|
-
hier = res
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
for (const collapsedId of collapsed) {
|
|
691
|
-
const node = find(hier, n => n.data.id === collapsedId)
|
|
692
|
-
if (node) {
|
|
693
|
-
if (node.children) {
|
|
694
|
-
collapse(node)
|
|
695
|
-
} else if (node.parent?.children) {
|
|
696
|
-
node.parent.children = node.parent.children.filter(
|
|
697
|
-
c => c.data.id !== collapsedId,
|
|
698
|
-
)
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
return hier
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
/**
|
|
707
|
-
* The node whose branch an outgroup roots the tree on: the outgroup's most
|
|
708
|
-
* recent common ancestor, or the rest of the tips' where that ancestor is the
|
|
709
|
-
* root, since the outgroup then straddles the root and the branch above the
|
|
710
|
-
* other side is the same branch of the unrooted tree.
|
|
711
|
-
*/
|
|
712
|
-
function outgroupNode(root: HierarchyNode, outgroup: string[]) {
|
|
713
|
-
const index = leafIndex(root)
|
|
714
|
-
const node = mrca(root, outgroup, index)
|
|
715
|
-
if (node !== root) {
|
|
716
|
-
return node
|
|
717
|
-
}
|
|
718
|
-
const out = new Set(outgroup)
|
|
719
|
-
const rest = leaves(root)
|
|
720
|
-
.map(n => n.data.name)
|
|
721
|
-
.filter(name => !out.has(name))
|
|
722
|
-
const other = mrca(root, rest, index)
|
|
723
|
-
return other === root ? undefined : other
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
/**
|
|
727
|
-
* `tree` rerooted as `treeRoot` asks, with fresh path ids. The ids of the tree
|
|
728
|
-
* as written become names on unnamed nodes, and a rerooting would carry them
|
|
729
|
-
* along as labels, so they come off first.
|
|
730
|
-
*/
|
|
731
|
-
function rootTree(tree: NodeWithIds, treeRoot: TreeRoot) {
|
|
732
|
-
const plain = new Map<NodeWithIds, TreeNode>()
|
|
733
|
-
for (const node of preorder(tree).reverse()) {
|
|
734
|
-
plain.set(node, {
|
|
735
|
-
name: node.name === node.id ? undefined : node.name,
|
|
736
|
-
length: node.length,
|
|
737
|
-
children: node.children.map(c => plain.get(c)!),
|
|
738
|
-
})
|
|
739
|
-
}
|
|
740
|
-
const input = plain.get(tree)!
|
|
741
|
-
if (treeRoot === 'midpoint') {
|
|
742
|
-
return generateNodeIds(midpointRoot(input))
|
|
743
|
-
}
|
|
744
|
-
const target = outgroupNode(
|
|
745
|
-
hierarchy(tree, d => d.children),
|
|
746
|
-
treeRoot.outgroup,
|
|
747
|
-
)
|
|
748
|
-
return target
|
|
749
|
-
? generateNodeIds(rerootTree(input, plain.get(target.data)!))
|
|
750
|
-
: tree
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// the inclusive tip indices a node covers, from the row-space extent
|
|
754
|
-
// clusterLayout writes to xMin/xMax as tip centers
|
|
755
|
-
function tipRange(node: HierarchyNode): [number, number] {
|
|
756
|
-
return [Math.round(node.xMin! - 0.5), Math.round(node.xMax! - 0.5)]
|
|
757
|
-
}
|
|
758
|
-
|
|
759
292
|
// the channels reading the feature table; every other channel reads rowData
|
|
760
293
|
const featureChannels = new Set<EncodingChannel>([
|
|
761
294
|
'featureFill',
|
|
@@ -765,6 +298,16 @@ const featureChannels = new Set<EncodingChannel>([
|
|
|
765
298
|
// seqPos -> column indexes per row, keyed on the parse so they are garbage
|
|
766
299
|
// collected with it. A computed would rebuild every row's index when read
|
|
767
300
|
// outside a reactive context.
|
|
301
|
+
// Newick tools write a colon in a name as an underscore, so a tree's row
|
|
302
|
+
// `chr1_100-200` is the GFF's `chr1:100-200`, as FastaMSA.getRow resolves it
|
|
303
|
+
function annotationRowName(id: string, rowNames: Map<string, number>) {
|
|
304
|
+
if (rowNames.has(id)) {
|
|
305
|
+
return id
|
|
306
|
+
}
|
|
307
|
+
const underscored = id.replaceAll(':', '_')
|
|
308
|
+
return rowNames.has(underscored) ? underscored : id
|
|
309
|
+
}
|
|
310
|
+
|
|
768
311
|
const seqPosIndexCache = new WeakMap<object, Map<string, Int32Array>>()
|
|
769
312
|
|
|
770
313
|
// A segment is a 1:1 run. A segment whose sides differ in length is malformed,
|
|
@@ -776,6 +319,31 @@ function sameLength(segment: ResidueSegment) {
|
|
|
776
319
|
)
|
|
777
320
|
}
|
|
778
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
|
+
|
|
779
347
|
// fit() divides the viewport by the row or column count and multiplies back, so
|
|
780
348
|
// an exact fit can land a fraction of a pixel over. A plain `>` would then show
|
|
781
349
|
// a scrollbar that shrinks the viewport and leaves a gap.
|
|
@@ -810,19 +378,11 @@ function sharedValue(values: (string | undefined)[]) {
|
|
|
810
378
|
: undefined
|
|
811
379
|
}
|
|
812
380
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const node = stack.pop()!
|
|
819
|
-
order.push(node)
|
|
820
|
-
for (const child of node.children) {
|
|
821
|
-
stack.push(child)
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
return order
|
|
825
|
-
}
|
|
381
|
+
type StripPanelScale = Omit<ResolvedStripPanel, 'id' | 'width' | 'offsetX'>
|
|
382
|
+
type FeaturePanelScale = Omit<
|
|
383
|
+
ResolvedFeaturePanel,
|
|
384
|
+
'id' | 'width' | 'offsetX' | 'spans'
|
|
385
|
+
>
|
|
826
386
|
|
|
827
387
|
/**
|
|
828
388
|
* #stateModel MsaView
|
|
@@ -868,6 +428,13 @@ function stateModelFactory({
|
|
|
868
428
|
* #property
|
|
869
429
|
*/
|
|
870
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),
|
|
871
438
|
/**
|
|
872
439
|
* #property
|
|
873
440
|
* whether the domain legend is expanded. The legend floats over the
|
|
@@ -899,6 +466,13 @@ function stateModelFactory({
|
|
|
899
466
|
*/
|
|
900
467
|
drawMsaLetters: stripDefault(types.boolean, defaultDrawMsaLetters),
|
|
901
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
|
+
|
|
902
476
|
/**
|
|
903
477
|
* #property
|
|
904
478
|
* zoom in/out on plain mouse-wheel without holding ctrl
|
|
@@ -1051,10 +625,10 @@ function stateModelFactory({
|
|
|
1051
625
|
|
|
1052
626
|
/**
|
|
1053
627
|
* #property
|
|
1054
|
-
* the user's
|
|
1055
|
-
* accession with the value meaning "off", like
|
|
1056
|
-
* untouched accession is absent and
|
|
1057
|
-
* 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
|
|
1058
632
|
*/
|
|
1059
633
|
turnedOffFeatures: stripDefault(types.map(types.boolean), {}),
|
|
1060
634
|
/**
|
|
@@ -1096,9 +670,10 @@ function stateModelFactory({
|
|
|
1096
670
|
/**
|
|
1097
671
|
* #property
|
|
1098
672
|
* where the view opens, in `highlights` coordinates: `{row, start,
|
|
1099
|
-
* end}` zooms onto residues of that row
|
|
1100
|
-
*
|
|
1101
|
-
* then cleared, so a reloaded
|
|
673
|
+
* end}` zooms onto residues of that row and scrolls the row to the
|
|
674
|
+
* middle, `{start, end}` zooms onto alignment columns. Applied once the
|
|
675
|
+
* alignment and any tree file have loaded, then cleared, so a reloaded
|
|
676
|
+
* session keeps the reader's own scroll.
|
|
1102
677
|
*/
|
|
1103
678
|
region: types.frozen<Region | undefined>(),
|
|
1104
679
|
/**
|
|
@@ -1134,12 +709,30 @@ function stateModelFactory({
|
|
|
1134
709
|
* #volatile
|
|
1135
710
|
*/
|
|
1136
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 },
|
|
1137
724
|
/**
|
|
1138
725
|
* #volatile
|
|
1139
726
|
* leaves the toolbar out, for a host drawing its own controls. Kept out
|
|
1140
727
|
* of the snapshot so a link opened in the full app shows the toolbar.
|
|
1141
728
|
*/
|
|
1142
729
|
hideHeader: false,
|
|
730
|
+
/**
|
|
731
|
+
* #volatile
|
|
732
|
+
* shows the import form while the view has no data. MSAViewer turns it
|
|
733
|
+
* off, since its host supplies the data and the form is the demo app's
|
|
734
|
+
*/
|
|
735
|
+
importForm: true,
|
|
1143
736
|
/**
|
|
1144
737
|
* #volatile
|
|
1145
738
|
*/
|
|
@@ -1151,6 +744,13 @@ function stateModelFactory({
|
|
|
1151
744
|
highResScaleFactor:
|
|
1152
745
|
typeof window === 'undefined' ? 1 : window.devicePixelRatio,
|
|
1153
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,
|
|
1154
754
|
/**
|
|
1155
755
|
* #volatile
|
|
1156
756
|
*/
|
|
@@ -1313,6 +913,12 @@ function stateModelFactory({
|
|
|
1313
913
|
setWidth(arg: number) {
|
|
1314
914
|
self.volatileWidth = arg
|
|
1315
915
|
},
|
|
916
|
+
/**
|
|
917
|
+
* #action
|
|
918
|
+
*/
|
|
919
|
+
setSettingsOpen(arg: boolean) {
|
|
920
|
+
self.settingsOpen = arg
|
|
921
|
+
},
|
|
1316
922
|
/**
|
|
1317
923
|
* #action
|
|
1318
924
|
* update the canvas scale factor when the device pixel ratio changes
|
|
@@ -1456,6 +1062,23 @@ function stateModelFactory({
|
|
|
1456
1062
|
setShowDomains(arg: boolean) {
|
|
1457
1063
|
self.showDomains = arg
|
|
1458
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
|
+
},
|
|
1459
1082
|
/**
|
|
1460
1083
|
* #action
|
|
1461
1084
|
* expand or collapse the domain legend that floats over the alignment
|
|
@@ -1484,6 +1107,7 @@ function stateModelFactory({
|
|
|
1484
1107
|
*/
|
|
1485
1108
|
setRowHeight(n: number) {
|
|
1486
1109
|
self.rowHeight = n
|
|
1110
|
+
self.zoomHeld = { ...self.zoomHeld, row: 0 }
|
|
1487
1111
|
},
|
|
1488
1112
|
|
|
1489
1113
|
/**
|
|
@@ -1492,6 +1116,7 @@ function stateModelFactory({
|
|
|
1492
1116
|
*/
|
|
1493
1117
|
setColWidth(n: number) {
|
|
1494
1118
|
self.colWidth = n
|
|
1119
|
+
self.zoomHeld = { ...self.zoomHeld, col: 0 }
|
|
1495
1120
|
},
|
|
1496
1121
|
|
|
1497
1122
|
/**
|
|
@@ -1651,36 +1276,19 @@ function stateModelFactory({
|
|
|
1651
1276
|
.views(self => ({
|
|
1652
1277
|
/**
|
|
1653
1278
|
* #getter
|
|
1654
|
-
* hideGaps takes effect when
|
|
1279
|
+
* hideGaps takes effect when a collapse or a focus hides rows, whose
|
|
1280
|
+
* residues leave columns all gaps among the rest, or when
|
|
1281
|
+
* allowedGappyness is under 100
|
|
1655
1282
|
*/
|
|
1656
1283
|
get hideGapsEffective() {
|
|
1657
1284
|
return (
|
|
1658
1285
|
self.hideGaps &&
|
|
1659
|
-
(self.collapsed.length > 0 ||
|
|
1286
|
+
(self.collapsed.length > 0 ||
|
|
1287
|
+
self.showOnly !== undefined ||
|
|
1288
|
+
self.allowedGappyness < 100)
|
|
1660
1289
|
)
|
|
1661
1290
|
},
|
|
1662
|
-
|
|
1663
|
-
* #getter
|
|
1664
|
-
*/
|
|
1665
|
-
get realAllowedGappyness() {
|
|
1666
|
-
return this.hideGapsEffective ? self.allowedGappyness : 100
|
|
1667
|
-
},
|
|
1668
|
-
/**
|
|
1669
|
-
* #getter
|
|
1670
|
-
* the annotations the overlay, the legend and the encodings draw: the
|
|
1671
|
-
* GFF's, then the `features` layer's
|
|
1672
|
-
*/
|
|
1673
|
-
get allAnnotations(): Annotation[] {
|
|
1674
|
-
return self.features.length > 0
|
|
1675
|
-
? [...self.annotations, ...featuresToAnnotations(self.features)]
|
|
1676
|
-
: self.annotations
|
|
1677
|
-
},
|
|
1678
|
-
/**
|
|
1679
|
-
* #getter
|
|
1680
|
-
*/
|
|
1681
|
-
get actuallyShowDomains() {
|
|
1682
|
-
return self.showDomains && this.allAnnotations.length > 0
|
|
1683
|
-
},
|
|
1291
|
+
|
|
1684
1292
|
/**
|
|
1685
1293
|
* #getter
|
|
1686
1294
|
* whether the host restores the loaded documents outside the snapshot.
|
|
@@ -1705,7 +1313,7 @@ function stateModelFactory({
|
|
|
1705
1313
|
if (self.volatileWidth === undefined) {
|
|
1706
1314
|
throw new Error('not initialized')
|
|
1707
1315
|
}
|
|
1708
|
-
return self.volatileWidth
|
|
1316
|
+
return self.volatileWidth - (self.settingsOpen ? settingsPanelWidth : 0)
|
|
1709
1317
|
},
|
|
1710
1318
|
}))
|
|
1711
1319
|
.views(self => ({
|
|
@@ -1755,7 +1363,7 @@ function stateModelFactory({
|
|
|
1755
1363
|
return !!this.tree.noTree
|
|
1756
1364
|
},
|
|
1757
1365
|
get noDomains() {
|
|
1758
|
-
return
|
|
1366
|
+
return this.allAnnotations.length === 0
|
|
1759
1367
|
},
|
|
1760
1368
|
|
|
1761
1369
|
/**
|
|
@@ -1864,6 +1472,27 @@ function stateModelFactory({
|
|
|
1864
1472
|
? parseMSA(text, self.currentAlignment, self.msaFormat)
|
|
1865
1473
|
: null
|
|
1866
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
|
+
},
|
|
1867
1496
|
/**
|
|
1868
1497
|
* #getter
|
|
1869
1498
|
*/
|
|
@@ -1931,7 +1560,7 @@ function stateModelFactory({
|
|
|
1931
1560
|
return resolveClades(
|
|
1932
1561
|
self.clades,
|
|
1933
1562
|
hierarchy(this.tree, d => d.children),
|
|
1934
|
-
this.
|
|
1563
|
+
this.root,
|
|
1935
1564
|
)
|
|
1936
1565
|
},
|
|
1937
1566
|
/**
|
|
@@ -2120,21 +1749,19 @@ function stateModelFactory({
|
|
|
2120
1749
|
if (!self.showTreeOverview) {
|
|
2121
1750
|
return undefined
|
|
2122
1751
|
}
|
|
2123
|
-
const
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
const extent = maxLength(root) - rootLen
|
|
2132
|
-
setBrLength(root, -rootLen, extent ? 1 / extent : 0)
|
|
1752
|
+
const layout = layoutTree(
|
|
1753
|
+
buildTreeRoot(this.tree, {
|
|
1754
|
+
collapsed: self.collapsed,
|
|
1755
|
+
rotated: self.rotated,
|
|
1756
|
+
order: self.treeOrder,
|
|
1757
|
+
}),
|
|
1758
|
+
{ rowHeight: 1, treeWidth: 1 },
|
|
1759
|
+
)
|
|
2133
1760
|
return {
|
|
2134
|
-
root,
|
|
2135
|
-
numTips,
|
|
2136
|
-
maxDepthToLeaf:
|
|
2137
|
-
showBranchLen: self.showBranchLen &&
|
|
1761
|
+
root: layout.root,
|
|
1762
|
+
numTips: layout.leaves.length,
|
|
1763
|
+
maxDepthToLeaf: layout.root.depthToLeaf,
|
|
1764
|
+
showBranchLen: self.showBranchLen && layout.rootToTipLength > 0,
|
|
2138
1765
|
}
|
|
2139
1766
|
},
|
|
2140
1767
|
|
|
@@ -2148,10 +1775,11 @@ function stateModelFactory({
|
|
|
2148
1775
|
if (!layout || self.clades.length === 0) {
|
|
2149
1776
|
return noClades
|
|
2150
1777
|
}
|
|
2151
|
-
|
|
2152
|
-
|
|
1778
|
+
return resolveClades(
|
|
1779
|
+
self.clades,
|
|
1780
|
+
hierarchy(this.tree, d => d.children),
|
|
1781
|
+
layout.root,
|
|
2153
1782
|
)
|
|
2154
|
-
return resolveClades(self.clades, layout.root, rowNames)
|
|
2155
1783
|
},
|
|
2156
1784
|
|
|
2157
1785
|
/**
|
|
@@ -2244,7 +1872,7 @@ function stateModelFactory({
|
|
|
2244
1872
|
* #getter
|
|
2245
1873
|
*/
|
|
2246
1874
|
get blanks() {
|
|
2247
|
-
const { hideGapsEffective,
|
|
1875
|
+
const { hideGapsEffective, allowedGappyness } = self
|
|
2248
1876
|
if (!hideGapsEffective) {
|
|
2249
1877
|
return []
|
|
2250
1878
|
}
|
|
@@ -2260,7 +1888,7 @@ function stateModelFactory({
|
|
|
2260
1888
|
// row that stops early counts as gapped for the remainder
|
|
2261
1889
|
const numCols = this.MSA?.getWidth() ?? 0
|
|
2262
1890
|
const numRows = strs.length
|
|
2263
|
-
const threshold = Math.ceil((
|
|
1891
|
+
const threshold = Math.ceil((allowedGappyness / 100) * numRows)
|
|
2264
1892
|
const blankCounts = new Uint32Array(numCols)
|
|
2265
1893
|
for (let j = 0; j < numRows; j++) {
|
|
2266
1894
|
const str = strs[j]!
|
|
@@ -2344,11 +1972,19 @@ function stateModelFactory({
|
|
|
2344
1972
|
return index
|
|
2345
1973
|
},
|
|
2346
1974
|
|
|
1975
|
+
/**
|
|
1976
|
+
* #method
|
|
1977
|
+
* the row's aligned sequence by the name the tree gives it, including
|
|
1978
|
+
* rows a collapsed clade hides. `MSA.getRow` resolves a FASTA name whose
|
|
1979
|
+
* colon a Newick tool wrote as an underscore, which `rowMap` does not
|
|
1980
|
+
*/
|
|
1981
|
+
rowSequence(name: string) {
|
|
1982
|
+
return this.MSA?.getRow(name) || undefined
|
|
1983
|
+
},
|
|
2347
1984
|
/**
|
|
2348
1985
|
* #getter
|
|
2349
|
-
* every sequence in the alignment, keyed by
|
|
2350
|
-
*
|
|
2351
|
-
* by row name use this
|
|
1986
|
+
* every sequence in the alignment, keyed by the alignment's own row
|
|
1987
|
+
* names, including rows a collapsed clade hides
|
|
2352
1988
|
*/
|
|
2353
1989
|
get rowMap() {
|
|
2354
1990
|
const MSA = this.MSA
|
|
@@ -2399,10 +2035,11 @@ function stateModelFactory({
|
|
|
2399
2035
|
*/
|
|
2400
2036
|
get sequenceType(): 'dna' | 'rna' | 'amino' {
|
|
2401
2037
|
const letters = this.colStats.lettersPresent
|
|
2402
|
-
//
|
|
2403
|
-
//
|
|
2404
|
-
const
|
|
2405
|
-
const
|
|
2038
|
+
// the two-base IUPAC codes keep an ambiguous read nucleotide; the
|
|
2039
|
+
// three-base ones (B, D, H, V) are common amino acids, so they don't
|
|
2040
|
+
const ambiguous = ['N', 'R', 'Y', 'K', 'M', 'S', 'W']
|
|
2041
|
+
const dna = new Set(['A', 'C', 'G', 'T', ...ambiguous])
|
|
2042
|
+
const rna = new Set(['A', 'C', 'G', 'U', ...ambiguous])
|
|
2406
2043
|
const isDna = letters.size > 0 && [...letters].every(l => dna.has(l))
|
|
2407
2044
|
const isRna = letters.size > 0 && [...letters].every(l => rna.has(l))
|
|
2408
2045
|
return isDna ? 'dna' : isRna ? 'rna' : 'amino'
|
|
@@ -2449,6 +2086,22 @@ function stateModelFactory({
|
|
|
2449
2086
|
)
|
|
2450
2087
|
},
|
|
2451
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
|
+
|
|
2452
2105
|
/**
|
|
2453
2106
|
* #getter
|
|
2454
2107
|
* Conservation score per column using Shannon entropy (biojs-msa style).
|
|
@@ -2536,15 +2189,6 @@ function stateModelFactory({
|
|
|
2536
2189
|
return this.treeLayout.rootToTipLength
|
|
2537
2190
|
},
|
|
2538
2191
|
|
|
2539
|
-
/**
|
|
2540
|
-
* #getter
|
|
2541
|
-
* x-position of the farthest tip in a phylogram, px: effectiveTreeWidth,
|
|
2542
|
-
* or 0 for a tree with no branch lengths (drawn as a cladogram)
|
|
2543
|
-
*/
|
|
2544
|
-
get maxBranchLength() {
|
|
2545
|
-
return this.rootToTipLength ? this.effectiveTreeWidth : 0
|
|
2546
|
-
},
|
|
2547
|
-
|
|
2548
2192
|
/**
|
|
2549
2193
|
* #getter
|
|
2550
2194
|
* pixels per unit of branch length in the phylogram layout, 0 in
|
|
@@ -2721,7 +2365,6 @@ function stateModelFactory({
|
|
|
2721
2365
|
data: content?.data,
|
|
2722
2366
|
arcs: content?.arcs,
|
|
2723
2367
|
},
|
|
2724
|
-
ReactComponent: TrackBlocks,
|
|
2725
2368
|
}
|
|
2726
2369
|
})
|
|
2727
2370
|
columnTrackModels.set(track, computedModel)
|
|
@@ -2783,7 +2426,6 @@ function stateModelFactory({
|
|
|
2783
2426
|
data: hideGapsEffective ? skipBlanks(blanks, t.data!) : t.data,
|
|
2784
2427
|
height: rowHeight,
|
|
2785
2428
|
},
|
|
2786
|
-
ReactComponent: TrackBlocks,
|
|
2787
2429
|
}))
|
|
2788
2430
|
},
|
|
2789
2431
|
|
|
@@ -2806,7 +2448,6 @@ function stateModelFactory({
|
|
|
2806
2448
|
height: self.trackHeight('arc'),
|
|
2807
2449
|
arcs,
|
|
2808
2450
|
},
|
|
2809
|
-
ReactComponent: TrackBlocks,
|
|
2810
2451
|
},
|
|
2811
2452
|
]
|
|
2812
2453
|
: []
|
|
@@ -2854,7 +2495,6 @@ function stateModelFactory({
|
|
|
2854
2495
|
heightKey: resizableKinds.has(model.kind) ? model.kind : undefined,
|
|
2855
2496
|
height: self.trackHeight(model.kind),
|
|
2856
2497
|
},
|
|
2857
|
-
ReactComponent: TrackBlocks,
|
|
2858
2498
|
}))
|
|
2859
2499
|
},
|
|
2860
2500
|
|
|
@@ -2874,8 +2514,7 @@ function stateModelFactory({
|
|
|
2874
2514
|
*/
|
|
2875
2515
|
get turnedOnTracks() {
|
|
2876
2516
|
return this.tracks.filter(
|
|
2877
|
-
f =>
|
|
2878
|
-
!trackIsOff(self.turnedOffTracks, f.model.id, f.model.defaultOff),
|
|
2517
|
+
f => !trackIsOff(self.turnedOffTracks, f.model.id),
|
|
2879
2518
|
)
|
|
2880
2519
|
},
|
|
2881
2520
|
|
|
@@ -2897,7 +2536,7 @@ function stateModelFactory({
|
|
|
2897
2536
|
* @returns The character at that position
|
|
2898
2537
|
*/
|
|
2899
2538
|
visibleColToRowLetter(rowName: string, visibleCol: number) {
|
|
2900
|
-
return self.
|
|
2539
|
+
return self.rowSequence(rowName)?.[
|
|
2901
2540
|
this.visibleColToGlobalCol(visibleCol)
|
|
2902
2541
|
]
|
|
2903
2542
|
},
|
|
@@ -2982,7 +2621,7 @@ function stateModelFactory({
|
|
|
2982
2621
|
* @returns The global column index in the full MSA, or undefined
|
|
2983
2622
|
*/
|
|
2984
2623
|
seqPosToGlobalCol(rowName: string, seqPos: number) {
|
|
2985
|
-
const seq = self.
|
|
2624
|
+
const seq = self.rowSequence(rowName)
|
|
2986
2625
|
if (seq === undefined) {
|
|
2987
2626
|
return undefined
|
|
2988
2627
|
}
|
|
@@ -3205,7 +2844,11 @@ function stateModelFactory({
|
|
|
3205
2844
|
if (mapping.structure.id !== structureId) {
|
|
3206
2845
|
continue
|
|
3207
2846
|
}
|
|
3208
|
-
if (
|
|
2847
|
+
if (
|
|
2848
|
+
asymId !== undefined &&
|
|
2849
|
+
mapping.structure.asymId !== undefined &&
|
|
2850
|
+
mapping.structure.asymId !== asymId
|
|
2851
|
+
) {
|
|
3209
2852
|
continue
|
|
3210
2853
|
}
|
|
3211
2854
|
const segment = mapping.segments.find(
|
|
@@ -3225,10 +2868,14 @@ function stateModelFactory({
|
|
|
3225
2868
|
.views(self => ({
|
|
3226
2869
|
/**
|
|
3227
2870
|
* #getter
|
|
3228
|
-
* the branch-length scale bar over the tree, undefined
|
|
3229
|
-
* or when the tree area is
|
|
2871
|
+
* the branch-length scale bar over the tree, undefined while
|
|
2872
|
+
* `showTreeScaleBar` is off, in cladogram mode, or when the tree area is
|
|
2873
|
+
* too narrow for one
|
|
3230
2874
|
*/
|
|
3231
2875
|
get treeScaleBar() {
|
|
2876
|
+
if (!self.showTreeScaleBar) {
|
|
2877
|
+
return undefined
|
|
2878
|
+
}
|
|
3232
2879
|
return scaleBarLength(
|
|
3233
2880
|
self.pxPerBranchLength,
|
|
3234
2881
|
self.effectiveTreeAreaWidth - self.marginLeft * 2,
|
|
@@ -3284,13 +2931,48 @@ function stateModelFactory({
|
|
|
3284
2931
|
}
|
|
3285
2932
|
return types
|
|
3286
2933
|
},
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
2934
|
+
/**
|
|
2935
|
+
* accessions hidden until the user turns them on, such as a Stockholm
|
|
2936
|
+
* file's #=GR lines
|
|
2937
|
+
*/
|
|
2938
|
+
get defaultOffAccessions() {
|
|
2939
|
+
return new Set(
|
|
2940
|
+
self.allAnnotations.filter(a => a.defaultOff).map(a => a.accession),
|
|
3290
2941
|
)
|
|
3291
2942
|
},
|
|
2943
|
+
/**
|
|
2944
|
+
* #method
|
|
2945
|
+
*/
|
|
2946
|
+
featureOff(accession: string) {
|
|
2947
|
+
return (
|
|
2948
|
+
self.turnedOffFeatures.get(accession) ??
|
|
2949
|
+
this.defaultOffAccessions.has(accession)
|
|
2950
|
+
)
|
|
2951
|
+
},
|
|
2952
|
+
get filteredAnnotations() {
|
|
2953
|
+
return self.allAnnotations.filter(r => !this.featureOff(r.accession))
|
|
2954
|
+
},
|
|
2955
|
+
/**
|
|
2956
|
+
* #getter
|
|
2957
|
+
* what colors the alignment's cells: the residue scheme, the domains
|
|
2958
|
+
* covering them, or nothing, which leaves the scheme to the letters.
|
|
2959
|
+
* Domains own the fill only while some are drawn, so a file whose
|
|
2960
|
+
* annotations all start hidden keeps its residue colors
|
|
2961
|
+
*/
|
|
2962
|
+
get cellFill(): 'residue' | 'domain' | 'none' {
|
|
2963
|
+
return !self.bgColor
|
|
2964
|
+
? 'none'
|
|
2965
|
+
: self.actuallyShowDomains &&
|
|
2966
|
+
self.domainFill &&
|
|
2967
|
+
this.filteredAnnotations.length > 0
|
|
2968
|
+
? 'domain'
|
|
2969
|
+
: 'residue'
|
|
2970
|
+
},
|
|
3292
2971
|
get annotationsByRow() {
|
|
3293
|
-
|
|
2972
|
+
const { rowNamesSet } = self
|
|
2973
|
+
return groupBy(this.filteredAnnotations, r =>
|
|
2974
|
+
annotationRowName(r.id, rowNamesSet),
|
|
2975
|
+
)
|
|
3294
2976
|
},
|
|
3295
2977
|
}))
|
|
3296
2978
|
.views(self => ({
|
|
@@ -3364,6 +3046,12 @@ function stateModelFactory({
|
|
|
3364
3046
|
self.blocksX.map(bx => [bx, by] as const),
|
|
3365
3047
|
)
|
|
3366
3048
|
},
|
|
3049
|
+
/**
|
|
3050
|
+
* #getter
|
|
3051
|
+
*/
|
|
3052
|
+
get wheelZooming() {
|
|
3053
|
+
return self.zoomSnapshot !== undefined
|
|
3054
|
+
},
|
|
3367
3055
|
|
|
3368
3056
|
/**
|
|
3369
3057
|
* #getter
|
|
@@ -3381,7 +3069,9 @@ function stateModelFactory({
|
|
|
3381
3069
|
const { annotations, rowNamesSet } = self
|
|
3382
3070
|
return self.dataInitialized &&
|
|
3383
3071
|
annotations.length > 0 &&
|
|
3384
|
-
!annotations.some(a =>
|
|
3072
|
+
!annotations.some(a =>
|
|
3073
|
+
rowNamesSet.has(annotationRowName(a.id, rowNamesSet)),
|
|
3074
|
+
)
|
|
3385
3075
|
? [
|
|
3386
3076
|
`0 of ${annotations.length} annotations name a row in this alignment`,
|
|
3387
3077
|
]
|
|
@@ -3521,10 +3211,13 @@ function stateModelFactory({
|
|
|
3521
3211
|
* #action
|
|
3522
3212
|
* swap in a different tree over the same alignment. Clears `collapsed`,
|
|
3523
3213
|
* `rotated` and `showOnly`, since path-derived node ids (node-0-0-1) from the old
|
|
3524
|
-
* tree would match unrelated nodes in the new one
|
|
3214
|
+
* tree would match unrelated nodes in the new one, and the tree's
|
|
3215
|
+
* filehandle, which the snapshot would otherwise reopen in place of
|
|
3216
|
+
* the new tree
|
|
3525
3217
|
*/
|
|
3526
3218
|
replaceTree(newick: string) {
|
|
3527
3219
|
transaction(() => {
|
|
3220
|
+
self.setTreeFilehandle(undefined)
|
|
3528
3221
|
self.collapsed.clear()
|
|
3529
3222
|
self.rotated.clear()
|
|
3530
3223
|
self.setShowOnly(undefined)
|
|
@@ -3552,30 +3245,6 @@ function stateModelFactory({
|
|
|
3552
3245
|
axis,
|
|
3553
3246
|
)
|
|
3554
3247
|
},
|
|
3555
|
-
/**
|
|
3556
|
-
* #action
|
|
3557
|
-
*/
|
|
3558
|
-
zoomOutHorizontal() {
|
|
3559
|
-
this.zoomAtCenter(0.75, 'horizontal')
|
|
3560
|
-
},
|
|
3561
|
-
/**
|
|
3562
|
-
* #action
|
|
3563
|
-
*/
|
|
3564
|
-
zoomInHorizontal() {
|
|
3565
|
-
this.zoomAtCenter(1.5, 'horizontal')
|
|
3566
|
-
},
|
|
3567
|
-
/**
|
|
3568
|
-
* #action
|
|
3569
|
-
*/
|
|
3570
|
-
zoomInVertical() {
|
|
3571
|
-
this.zoomAtCenter(1.5, 'vertical')
|
|
3572
|
-
},
|
|
3573
|
-
/**
|
|
3574
|
-
* #action
|
|
3575
|
-
*/
|
|
3576
|
-
zoomOutVertical() {
|
|
3577
|
-
this.zoomAtCenter(0.75, 'vertical')
|
|
3578
|
-
},
|
|
3579
3248
|
/**
|
|
3580
3249
|
* #action
|
|
3581
3250
|
*/
|
|
@@ -3608,19 +3277,35 @@ function stateModelFactory({
|
|
|
3608
3277
|
transaction(() => {
|
|
3609
3278
|
const colInView = (-self.scrollX + offsetX) / self.colWidth
|
|
3610
3279
|
const rowInView = (-self.scrollY + offsetY) / self.rowHeight
|
|
3280
|
+
// zooming both axes, an axis holds at the default only while the
|
|
3281
|
+
// other sits there too, or one would hold while the other grows
|
|
3282
|
+
const canHold =
|
|
3283
|
+
axis !== 'both' ||
|
|
3284
|
+
(self.colWidth === defaultColWidth &&
|
|
3285
|
+
self.rowHeight === defaultRowHeight)
|
|
3611
3286
|
if (axis !== 'vertical') {
|
|
3612
|
-
|
|
3613
|
-
self.colWidth
|
|
3287
|
+
const { value, held } = detentZoom(
|
|
3288
|
+
self.colWidth,
|
|
3289
|
+
scaleFactor,
|
|
3290
|
+
defaultColWidth,
|
|
3291
|
+
self.zoomHeld.col,
|
|
3614
3292
|
minColWidth,
|
|
3615
|
-
|
|
3293
|
+
canHold,
|
|
3616
3294
|
)
|
|
3295
|
+
self.colWidth = value
|
|
3296
|
+
self.zoomHeld = { ...self.zoomHeld, col: held }
|
|
3617
3297
|
}
|
|
3618
3298
|
if (axis !== 'horizontal') {
|
|
3619
|
-
|
|
3620
|
-
self.rowHeight
|
|
3299
|
+
const { value, held } = detentZoom(
|
|
3300
|
+
self.rowHeight,
|
|
3301
|
+
scaleFactor,
|
|
3302
|
+
defaultRowHeight,
|
|
3303
|
+
self.zoomHeld.row,
|
|
3621
3304
|
minRowHeight,
|
|
3622
|
-
|
|
3305
|
+
canHold,
|
|
3623
3306
|
)
|
|
3307
|
+
self.rowHeight = value
|
|
3308
|
+
self.zoomHeld = { ...self.zoomHeld, row: held }
|
|
3624
3309
|
}
|
|
3625
3310
|
self.scrollX = clamp(
|
|
3626
3311
|
offsetX - colInView * self.colWidth,
|
|
@@ -3641,6 +3326,25 @@ function stateModelFactory({
|
|
|
3641
3326
|
)
|
|
3642
3327
|
})
|
|
3643
3328
|
},
|
|
3329
|
+
/**
|
|
3330
|
+
* #action
|
|
3331
|
+
* zoomToPos for one wheel or pinch frame. The alignment keeps the blocks
|
|
3332
|
+
* it drew at the start of the gesture, scaled, and redraws them only once
|
|
3333
|
+
* they stop covering the viewport; endWheelZoom redraws them sharp
|
|
3334
|
+
*/
|
|
3335
|
+
wheelZoomToPos(scaleFactor: number, offsetX: number, offsetY: number) {
|
|
3336
|
+
self.zoomSnapshot ??= takeZoomSnapshot(self, false)
|
|
3337
|
+
this.zoomToPos(scaleFactor, offsetX, offsetY, self.wheelZoomAxis)
|
|
3338
|
+
if (!zoomSnapshotCovers(self, self.zoomSnapshot)) {
|
|
3339
|
+
self.zoomSnapshot = takeZoomSnapshot(self, true)
|
|
3340
|
+
}
|
|
3341
|
+
},
|
|
3342
|
+
/**
|
|
3343
|
+
* #action
|
|
3344
|
+
*/
|
|
3345
|
+
endWheelZoom() {
|
|
3346
|
+
self.zoomSnapshot = undefined
|
|
3347
|
+
},
|
|
3644
3348
|
/**
|
|
3645
3349
|
* #action
|
|
3646
3350
|
*/
|
|
@@ -3668,16 +3372,6 @@ function stateModelFactory({
|
|
|
3668
3372
|
self.annotations = annotations
|
|
3669
3373
|
},
|
|
3670
3374
|
|
|
3671
|
-
/**
|
|
3672
|
-
* #action
|
|
3673
|
-
* set the overlay from raw InterProScan results keyed by row name. Kept
|
|
3674
|
-
* for downstream plugins that hold the EBI wire format; new code should
|
|
3675
|
-
* adapt to Annotation[] and call setAnnotations.
|
|
3676
|
-
*/
|
|
3677
|
-
setDomains(data?: Record<string, InterProScanResults>) {
|
|
3678
|
-
this.setAnnotations(data ? interProScanToAnnotations(data) : [])
|
|
3679
|
-
},
|
|
3680
|
-
|
|
3681
3375
|
applyGFFText(gffText: string) {
|
|
3682
3376
|
this.setAnnotations(gffToAnnotations(parseGFF(gffText)))
|
|
3683
3377
|
},
|
|
@@ -3735,11 +3429,22 @@ function stateModelFactory({
|
|
|
3735
3429
|
* #action
|
|
3736
3430
|
*/
|
|
3737
3431
|
toggleTrack(id: string) {
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3432
|
+
self.turnedOffTracks.set(id, !trackIsOff(self.turnedOffTracks, id))
|
|
3433
|
+
},
|
|
3434
|
+
/**
|
|
3435
|
+
* #action
|
|
3436
|
+
* show or hide several tracks at once, as the track selector's Show all
|
|
3437
|
+
* and Hide all do. A choice matching the track's default is dropped, so
|
|
3438
|
+
* it stays out of the shared URL
|
|
3439
|
+
*/
|
|
3440
|
+
setTracksShown(ids: string[], shown: boolean) {
|
|
3441
|
+
for (const id of ids) {
|
|
3442
|
+
if (defaultOffTracks.has(id) === !shown) {
|
|
3443
|
+
self.turnedOffTracks.delete(id)
|
|
3444
|
+
} else {
|
|
3445
|
+
self.turnedOffTracks.set(id, !shown)
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3743
3448
|
},
|
|
3744
3449
|
/**
|
|
3745
3450
|
* #action
|
|
@@ -3765,7 +3470,7 @@ function stateModelFactory({
|
|
|
3765
3470
|
column: column + 1,
|
|
3766
3471
|
row,
|
|
3767
3472
|
residue: self.visibleColToSeqPosOneBased(row, visibleCol),
|
|
3768
|
-
letter: self.
|
|
3473
|
+
letter: self.rowSequence(row)?.[column],
|
|
3769
3474
|
}
|
|
3770
3475
|
},
|
|
3771
3476
|
/**
|
|
@@ -3904,7 +3609,7 @@ function stateModelFactory({
|
|
|
3904
3609
|
*/
|
|
3905
3610
|
get visibleDomainTypes() {
|
|
3906
3611
|
return this.categoricalDomainTypes
|
|
3907
|
-
.filter(d => !self.
|
|
3612
|
+
.filter(d => !self.featureOff(d.accession))
|
|
3908
3613
|
.toSorted((a, b) => a.start - b.start)
|
|
3909
3614
|
},
|
|
3910
3615
|
|
|
@@ -3954,7 +3659,7 @@ function stateModelFactory({
|
|
|
3954
3659
|
: visibleDomainTypes.map(d => ({
|
|
3955
3660
|
id: d.accession,
|
|
3956
3661
|
label: d.name,
|
|
3957
|
-
color: fillPalette[d.accession]!,
|
|
3662
|
+
color: d.color ?? fillPalette[d.accession]!,
|
|
3958
3663
|
})),
|
|
3959
3664
|
'domains',
|
|
3960
3665
|
)
|
|
@@ -3973,16 +3678,16 @@ function stateModelFactory({
|
|
|
3973
3678
|
/**
|
|
3974
3679
|
* #getter
|
|
3975
3680
|
* whether the overlay marks each domain with a bar under its row instead
|
|
3976
|
-
* of filling the row behind the letters
|
|
3977
|
-
*
|
|
3978
|
-
*
|
|
3681
|
+
* of filling the row behind the letters: whenever the domains do not own
|
|
3682
|
+
* the cell fill (`cellFill`), a filled box would paint over the residue
|
|
3683
|
+
* colors the user picked. Sub-row layout already stacks
|
|
3979
3684
|
* the boxes clear of the letters, and with the letters too small to draw
|
|
3980
3685
|
* the filled box is the only thing left to read.
|
|
3981
3686
|
*/
|
|
3982
3687
|
get domainUnderline() {
|
|
3983
3688
|
return (
|
|
3984
3689
|
self.actuallyShowDomains &&
|
|
3985
|
-
|
|
3690
|
+
self.cellFill !== 'domain' &&
|
|
3986
3691
|
!self.subFeatureRows &&
|
|
3987
3692
|
self.showMsaLetters
|
|
3988
3693
|
)
|
|
@@ -4221,7 +3926,7 @@ function stateModelFactory({
|
|
|
4221
3926
|
* to `end`, gaps included
|
|
4222
3927
|
*/
|
|
4223
3928
|
get selectionFasta() {
|
|
4224
|
-
const { rowNames
|
|
3929
|
+
const { rowNames } = self
|
|
4225
3930
|
const resolved = this.resolvedSelection
|
|
4226
3931
|
const span = this.selectionFileSpan
|
|
4227
3932
|
if (!resolved || !span) {
|
|
@@ -4230,7 +3935,7 @@ function stateModelFactory({
|
|
|
4230
3935
|
const rows = resolved.rowRuns
|
|
4231
3936
|
.flatMap(([first, last]) => rowNames.slice(first, last + 1))
|
|
4232
3937
|
.flatMap(name => {
|
|
4233
|
-
const seq =
|
|
3938
|
+
const seq = self.rowSequence(name)
|
|
4234
3939
|
return seq === undefined ? [] : [[name, seq] as const]
|
|
4235
3940
|
})
|
|
4236
3941
|
return blockFasta(rows, span.start, span.end)
|
|
@@ -4327,7 +4032,7 @@ function stateModelFactory({
|
|
|
4327
4032
|
this.visibleDomainTypes.map(d => ({
|
|
4328
4033
|
id: d.accession,
|
|
4329
4034
|
label: d.name,
|
|
4330
|
-
color: fillPalette[d.accession]!,
|
|
4035
|
+
color: d.color ?? fillPalette[d.accession]!,
|
|
4331
4036
|
})),
|
|
4332
4037
|
}
|
|
4333
4038
|
}
|
|
@@ -4504,6 +4209,18 @@ function stateModelFactory({
|
|
|
4504
4209
|
setHideHeader(arg: boolean) {
|
|
4505
4210
|
self.hideHeader = arg
|
|
4506
4211
|
},
|
|
4212
|
+
/**
|
|
4213
|
+
* #action
|
|
4214
|
+
*/
|
|
4215
|
+
setEmbeddedMode(arg: boolean) {
|
|
4216
|
+
self.embeddedMode = arg
|
|
4217
|
+
},
|
|
4218
|
+
/**
|
|
4219
|
+
* #action
|
|
4220
|
+
*/
|
|
4221
|
+
setImportForm(arg: boolean) {
|
|
4222
|
+
self.importForm = arg
|
|
4223
|
+
},
|
|
4507
4224
|
/**
|
|
4508
4225
|
* #action
|
|
4509
4226
|
* focus the subtree a click `y` pixels down the tree overview lands on.
|
|
@@ -4577,14 +4294,15 @@ function stateModelFactory({
|
|
|
4577
4294
|
},
|
|
4578
4295
|
/**
|
|
4579
4296
|
* #action
|
|
4580
|
-
* show or hide an annotation type. Only
|
|
4581
|
-
* `turnedOffFeatures`
|
|
4297
|
+
* show or hide an annotation type. Only a choice that differs from the
|
|
4298
|
+
* type's default is recorded; see `turnedOffFeatures`
|
|
4582
4299
|
*/
|
|
4583
4300
|
setFilter(accession: string, shown: boolean) {
|
|
4584
|
-
|
|
4585
|
-
|
|
4301
|
+
// only a departure from the accession's default is recorded
|
|
4302
|
+
if (shown === self.defaultOffAccessions.has(accession)) {
|
|
4303
|
+
self.turnedOffFeatures.set(accession, !shown)
|
|
4586
4304
|
} else {
|
|
4587
|
-
self.turnedOffFeatures.
|
|
4305
|
+
self.turnedOffFeatures.delete(accession)
|
|
4588
4306
|
}
|
|
4589
4307
|
},
|
|
4590
4308
|
|
|
@@ -4593,23 +4311,28 @@ function stateModelFactory({
|
|
|
4593
4311
|
*/
|
|
4594
4312
|
fit() {
|
|
4595
4313
|
// two passes: each direction's viewport depends on whether the other
|
|
4596
|
-
// overflows (the minimap takes height, the vertical scrollbar width)
|
|
4314
|
+
// overflows (the minimap takes height, the vertical scrollbar width).
|
|
4315
|
+
// Fit shrinks an alignment into the window but never grows one past
|
|
4316
|
+
// the default cell size, which would stretch a small alignment's rows
|
|
4317
|
+
// and columns out of proportion
|
|
4597
4318
|
transaction(() => {
|
|
4598
4319
|
for (let pass = 0; pass < 2; pass++) {
|
|
4599
|
-
this.fitHorizontally()
|
|
4600
|
-
this.fitVertically()
|
|
4320
|
+
this.fitHorizontally(defaultColWidth)
|
|
4321
|
+
this.fitVertically(defaultRowHeight)
|
|
4601
4322
|
}
|
|
4602
4323
|
})
|
|
4603
4324
|
},
|
|
4604
4325
|
/**
|
|
4605
4326
|
* #action
|
|
4606
4327
|
*/
|
|
4607
|
-
fitVertically() {
|
|
4328
|
+
fitVertically(maxRowHeight = maxCellSize) {
|
|
4608
4329
|
if (self.numRows > 0) {
|
|
4609
|
-
self.
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4330
|
+
self.setRowHeight(
|
|
4331
|
+
clamp(
|
|
4332
|
+
self.msaAreaHeight / self.numRows,
|
|
4333
|
+
minRowHeight,
|
|
4334
|
+
maxRowHeight,
|
|
4335
|
+
),
|
|
4613
4336
|
)
|
|
4614
4337
|
}
|
|
4615
4338
|
self.scrollY = 0
|
|
@@ -4626,10 +4349,12 @@ function stateModelFactory({
|
|
|
4626
4349
|
return
|
|
4627
4350
|
}
|
|
4628
4351
|
transaction(() => {
|
|
4629
|
-
self.
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4352
|
+
self.setColWidth(
|
|
4353
|
+
clamp(
|
|
4354
|
+
self.msaCanvasWidth / (span.endCol - span.startCol + 1),
|
|
4355
|
+
minColWidth,
|
|
4356
|
+
maxCellSize,
|
|
4357
|
+
),
|
|
4633
4358
|
)
|
|
4634
4359
|
self.scrollX = clamp(
|
|
4635
4360
|
-span.startCol * self.colWidth,
|
|
@@ -4638,6 +4363,27 @@ function stateModelFactory({
|
|
|
4638
4363
|
)
|
|
4639
4364
|
})
|
|
4640
4365
|
},
|
|
4366
|
+
/**
|
|
4367
|
+
* #action
|
|
4368
|
+
* zoomToRegion, and for a region naming a row, also scroll that row to
|
|
4369
|
+
* the middle of the alignment, as Go to does. What `region` opens on; a
|
|
4370
|
+
* caller following a range as it moves wants zoomToRegion alone, which
|
|
4371
|
+
* leaves the reader's vertical scroll where they put it.
|
|
4372
|
+
*/
|
|
4373
|
+
revealRegion(region: Region) {
|
|
4374
|
+
transaction(() => {
|
|
4375
|
+
this.zoomToRegion(region)
|
|
4376
|
+
const row =
|
|
4377
|
+
region.row === undefined
|
|
4378
|
+
? undefined
|
|
4379
|
+
: self.rowNamesSet.get(region.row)
|
|
4380
|
+
if (row !== undefined && self.viewInitialized) {
|
|
4381
|
+
self.setScrollY(
|
|
4382
|
+
-(row + 0.5) * self.rowHeight + self.msaAreaHeight / 2,
|
|
4383
|
+
)
|
|
4384
|
+
}
|
|
4385
|
+
})
|
|
4386
|
+
},
|
|
4641
4387
|
/**
|
|
4642
4388
|
* #action
|
|
4643
4389
|
* zoom and scroll so the selected columns fill the alignment's width,
|
|
@@ -4654,10 +4400,12 @@ function stateModelFactory({
|
|
|
4654
4400
|
const { rowRuns } = resolvedSelection
|
|
4655
4401
|
const first = rowRuns[0]![0]
|
|
4656
4402
|
const last = rowRuns.at(-1)![1]
|
|
4657
|
-
self.
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4403
|
+
self.setRowHeight(
|
|
4404
|
+
clamp(
|
|
4405
|
+
self.msaAreaHeight / (last - first + 1),
|
|
4406
|
+
minRowHeight,
|
|
4407
|
+
maxCellSize,
|
|
4408
|
+
),
|
|
4661
4409
|
)
|
|
4662
4410
|
self.scrollY = clamp(-first * self.rowHeight, self.maxScrollY, 0)
|
|
4663
4411
|
}
|
|
@@ -4698,13 +4446,15 @@ function stateModelFactory({
|
|
|
4698
4446
|
/**
|
|
4699
4447
|
* #action
|
|
4700
4448
|
*/
|
|
4701
|
-
fitHorizontally() {
|
|
4449
|
+
fitHorizontally(maxColWidth = maxCellSize) {
|
|
4702
4450
|
if (self.numColumns > 0) {
|
|
4703
4451
|
// msaCanvasWidth excludes the vertical scrollbar's 20px
|
|
4704
|
-
self.
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4452
|
+
self.setColWidth(
|
|
4453
|
+
clamp(
|
|
4454
|
+
self.msaCanvasWidth / self.numColumns,
|
|
4455
|
+
minColWidth,
|
|
4456
|
+
maxColWidth,
|
|
4457
|
+
),
|
|
4708
4458
|
)
|
|
4709
4459
|
}
|
|
4710
4460
|
self.scrollX = 0
|
|
@@ -4715,21 +4465,18 @@ function stateModelFactory({
|
|
|
4715
4465
|
self.setHighlightedColumns(self.highlightColumns)
|
|
4716
4466
|
}
|
|
4717
4467
|
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
// `
|
|
4721
|
-
//
|
|
4722
|
-
//
|
|
4723
|
-
//
|
|
4724
|
-
//
|
|
4725
|
-
// `dataInitialized` is true once the MSA alone has loaded, when the
|
|
4726
|
-
// tree is still the flat stub, so a tree filehandle holds the seeding
|
|
4727
|
-
// until its text lands.
|
|
4468
|
+
const awaitingTree = () => !!self.treeFilehandle && !self.data.tree
|
|
4469
|
+
|
|
4470
|
+
// `collapse`, `focus` and `rotate` marks seed `collapsed`, `showOnly`
|
|
4471
|
+
// and `rotated`. Seeding runs once per data load, `clades` change and
|
|
4472
|
+
// reroot, so expanding a seeded clade sticks until one of those. A tree
|
|
4473
|
+
// filehandle holds it back, since `dataInitialized` is true while the
|
|
4474
|
+
// tree is still the flat stub.
|
|
4728
4475
|
addDisposer(
|
|
4729
4476
|
self,
|
|
4730
4477
|
reaction(
|
|
4731
4478
|
() =>
|
|
4732
|
-
self.dataInitialized && !(
|
|
4479
|
+
self.dataInitialized && !awaitingTree()
|
|
4733
4480
|
? { clades: getSnapshot(self.clades), treeRoot: self.treeRoot }
|
|
4734
4481
|
: undefined,
|
|
4735
4482
|
seed => {
|
|
@@ -4767,9 +4514,9 @@ function stateModelFactory({
|
|
|
4767
4514
|
region &&
|
|
4768
4515
|
self.viewInitialized &&
|
|
4769
4516
|
self.numColumns > 0 &&
|
|
4770
|
-
!(
|
|
4517
|
+
!awaitingTree()
|
|
4771
4518
|
) {
|
|
4772
|
-
this.
|
|
4519
|
+
this.revealRegion(region)
|
|
4773
4520
|
self.setRegion(undefined)
|
|
4774
4521
|
}
|
|
4775
4522
|
}),
|
|
@@ -4796,105 +4543,7 @@ function stateModelFactory({
|
|
|
4796
4543
|
})
|
|
4797
4544
|
}
|
|
4798
4545
|
|
|
4799
|
-
|
|
4800
|
-
* Fetch a filehandle whenever it changes, and hand the text to
|
|
4801
|
-
* `onLoad`.
|
|
4802
|
-
*
|
|
4803
|
-
* A generation guard keeps a slower superseded request from overwriting
|
|
4804
|
-
* the newer one's data, status or loading flag. A superseded or cleared
|
|
4805
|
-
* request is aborted and clears its status line.
|
|
4806
|
-
*
|
|
4807
|
-
* `clearFilehandle` runs after a local file loads, since a blob has no
|
|
4808
|
-
* URL to refetch, and when the user cancels, which returns the view to
|
|
4809
|
-
* the import form.
|
|
4810
|
-
*
|
|
4811
|
-
* `what` names the layer in a failure message. An `optional` layer
|
|
4812
|
-
* (annotations, row metadata) that fails adds a warning instead of an
|
|
4813
|
-
* error.
|
|
4814
|
-
*/
|
|
4815
|
-
const loadOnFilehandleChange = ({
|
|
4816
|
-
what,
|
|
4817
|
-
getFilehandle,
|
|
4818
|
-
onLoad,
|
|
4819
|
-
setLoading,
|
|
4820
|
-
clearFilehandle,
|
|
4821
|
-
optional = false,
|
|
4822
|
-
}: {
|
|
4823
|
-
what: string
|
|
4824
|
-
getFilehandle: () => FileLocationType | undefined
|
|
4825
|
-
onLoad: (text: string) => void
|
|
4826
|
-
setLoading?: (arg: boolean) => void
|
|
4827
|
-
clearFilehandle?: () => void
|
|
4828
|
-
optional?: boolean
|
|
4829
|
-
}) => {
|
|
4830
|
-
let generation = 0
|
|
4831
|
-
let cancelInFlight: (() => void) | undefined
|
|
4832
|
-
addDisposer(self, () => {
|
|
4833
|
-
cancelInFlight?.()
|
|
4834
|
-
})
|
|
4835
|
-
addDisposer(
|
|
4836
|
-
self,
|
|
4837
|
-
autorun(async () => {
|
|
4838
|
-
const filehandle = getFilehandle()
|
|
4839
|
-
const current = ++generation
|
|
4840
|
-
cancelInFlight?.()
|
|
4841
|
-
cancelInFlight = undefined
|
|
4842
|
-
if (!filehandle) {
|
|
4843
|
-
setLoading?.(false)
|
|
4844
|
-
return
|
|
4845
|
-
}
|
|
4846
|
-
const isCurrent = () => current === generation && isAlive(self)
|
|
4847
|
-
const controller = new AbortController()
|
|
4848
|
-
cancelInFlight = () => {
|
|
4849
|
-
controller.abort()
|
|
4850
|
-
if (isAlive(self)) {
|
|
4851
|
-
self.setStatus(undefined)
|
|
4852
|
-
}
|
|
4853
|
-
}
|
|
4854
|
-
try {
|
|
4855
|
-
setLoading?.(true)
|
|
4856
|
-
self.setError(undefined)
|
|
4857
|
-
const text = await fetchTextWithProgress(
|
|
4858
|
-
openLocation(filehandle),
|
|
4859
|
-
status => {
|
|
4860
|
-
if (isCurrent()) {
|
|
4861
|
-
self.setStatus(status)
|
|
4862
|
-
}
|
|
4863
|
-
},
|
|
4864
|
-
{ controller },
|
|
4865
|
-
)
|
|
4866
|
-
if (isCurrent()) {
|
|
4867
|
-
transaction(() => {
|
|
4868
|
-
onLoad(text)
|
|
4869
|
-
if (filehandle.locationType === 'BlobLocation') {
|
|
4870
|
-
clearFilehandle?.()
|
|
4871
|
-
}
|
|
4872
|
-
})
|
|
4873
|
-
}
|
|
4874
|
-
} catch (e) {
|
|
4875
|
-
if (isCurrent()) {
|
|
4876
|
-
if (isAbortError(e)) {
|
|
4877
|
-
clearFilehandle?.()
|
|
4878
|
-
} else {
|
|
4879
|
-
console.error(e)
|
|
4880
|
-
if (optional) {
|
|
4881
|
-
self.addWarning(`The ${what} did not load: ${e}`)
|
|
4882
|
-
} else {
|
|
4883
|
-
self.setError(e)
|
|
4884
|
-
}
|
|
4885
|
-
}
|
|
4886
|
-
}
|
|
4887
|
-
} finally {
|
|
4888
|
-
if (isCurrent()) {
|
|
4889
|
-
cancelInFlight = undefined
|
|
4890
|
-
setLoading?.(false)
|
|
4891
|
-
}
|
|
4892
|
-
}
|
|
4893
|
-
}),
|
|
4894
|
-
)
|
|
4895
|
-
}
|
|
4896
|
-
|
|
4897
|
-
loadOnFilehandleChange({
|
|
4546
|
+
installFilehandleLoader(self, {
|
|
4898
4547
|
what: 'tree',
|
|
4899
4548
|
getFilehandle: () => self.treeFilehandle,
|
|
4900
4549
|
onLoad: text => {
|
|
@@ -4909,7 +4558,7 @@ function stateModelFactory({
|
|
|
4909
4558
|
})
|
|
4910
4559
|
|
|
4911
4560
|
// treeMetadata has no import-form step, so no loading flag or cancel
|
|
4912
|
-
|
|
4561
|
+
installFilehandleLoader(self, {
|
|
4913
4562
|
what: 'row metadata',
|
|
4914
4563
|
optional: true,
|
|
4915
4564
|
getFilehandle: () => self.treeMetadataFilehandle,
|
|
@@ -4941,7 +4590,7 @@ function stateModelFactory({
|
|
|
4941
4590
|
)
|
|
4942
4591
|
|
|
4943
4592
|
// gffFilehandle loads into data.gff, which the autorun above parses
|
|
4944
|
-
|
|
4593
|
+
installFilehandleLoader(self, {
|
|
4945
4594
|
what: 'annotations',
|
|
4946
4595
|
optional: true,
|
|
4947
4596
|
getFilehandle: () => self.gffFilehandle,
|
|
@@ -4953,7 +4602,7 @@ function stateModelFactory({
|
|
|
4953
4602
|
},
|
|
4954
4603
|
})
|
|
4955
4604
|
|
|
4956
|
-
|
|
4605
|
+
installFilehandleLoader(self, {
|
|
4957
4606
|
what: 'alignment',
|
|
4958
4607
|
getFilehandle: () => self.msaFilehandle,
|
|
4959
4608
|
onLoad: text => {
|
|
@@ -4977,20 +4626,16 @@ function stateModelFactory({
|
|
|
4977
4626
|
self,
|
|
4978
4627
|
autorun(() => {
|
|
4979
4628
|
if (self.dynamicColorSchemeName) {
|
|
4980
|
-
|
|
4981
|
-
self.colStats
|
|
4629
|
+
readComputed(self.colStats)
|
|
4982
4630
|
}
|
|
4983
4631
|
if (self.showColumnStats) {
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
4990
|
-
self.propertyConservation
|
|
4632
|
+
readComputed(
|
|
4633
|
+
self.colStats,
|
|
4634
|
+
self.conservation,
|
|
4635
|
+
self.propertyConservation,
|
|
4636
|
+
)
|
|
4991
4637
|
}
|
|
4992
|
-
|
|
4993
|
-
self.columns
|
|
4638
|
+
readComputed(self.columns)
|
|
4994
4639
|
}),
|
|
4995
4640
|
)
|
|
4996
4641
|
},
|