react-msaview 9.1.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/bundle/index.js +111 -115
- package/bundle/index.js.map +4 -4
- package/dist/blosum62.d.ts +3 -0
- package/dist/blosum62.js +43 -0
- package/dist/blosum62.js.map +1 -0
- package/dist/colorSchemes.js +1 -0
- package/dist/colorSchemes.js.map +1 -1
- package/dist/components/Loading.d.ts +3 -2
- package/dist/components/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.d.ts +3 -2
- package/dist/components/MSAView.js +7 -4
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +62 -12
- package/dist/components/MetadataTable.d.ts +4 -1
- package/dist/components/MetadataTable.js +55 -16
- package/dist/components/MetadataTable.js.map +1 -1
- package/dist/components/Track.js +6 -0
- package/dist/components/Track.js.map +1 -1
- package/dist/components/controls.js +2 -1
- package/dist/components/controls.js.map +1 -1
- package/dist/components/dialogs/AnnotationFileDialog.js +7 -2
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
- package/dist/components/dialogs/FeatureDialog.js +2 -2
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/{header/MSASettingsMenu.d.ts → dialogs/GapThresholdDialog.d.ts} +3 -2
- package/dist/components/dialogs/GapThresholdDialog.js +15 -0
- package/dist/components/dialogs/GapThresholdDialog.js.map +1 -0
- package/dist/components/{header → dialogs}/GappynessSlider.d.ts +1 -1
- package/dist/components/{header → dialogs}/GappynessSlider.js +3 -10
- package/dist/components/dialogs/GappynessSlider.js.map +1 -0
- package/dist/components/{header → dialogs}/GoToBox.d.ts +4 -2
- package/dist/components/dialogs/GoToBox.js +48 -0
- package/dist/components/dialogs/GoToBox.js.map +1 -0
- package/dist/components/dialogs/GoToDialog.d.ts +6 -0
- package/dist/components/dialogs/GoToDialog.js +10 -0
- package/dist/components/dialogs/GoToDialog.js.map +1 -0
- package/dist/components/dialogs/MetadataDialog.js +11 -3
- package/dist/components/dialogs/MetadataDialog.js.map +1 -1
- package/dist/components/dialogs/goTo.d.ts +5 -0
- package/dist/components/{header → dialogs}/goTo.js +1 -1
- package/dist/components/dialogs/goTo.js.map +1 -0
- package/dist/components/dialogs/metadataLabels.d.ts +4 -0
- package/dist/components/dialogs/metadataLabels.js +52 -0
- package/dist/components/dialogs/metadataLabels.js.map +1 -0
- package/dist/components/dialogs/metadataRecord.d.ts +1 -0
- package/dist/components/dialogs/metadataRecord.js +38 -0
- package/dist/components/dialogs/metadataRecord.js.map +1 -0
- package/dist/components/header/Header.d.ts +6 -2
- package/dist/components/header/Header.js +30 -32
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/{TreeSettingsMenu.d.ts → HiddenColumnsChip.d.ts} +2 -2
- package/dist/components/header/HiddenColumnsChip.js +15 -0
- package/dist/components/header/HiddenColumnsChip.js.map +1 -0
- package/dist/components/header/MenuBar.d.ts +6 -0
- package/dist/components/header/MenuBar.js +33 -0
- package/dist/components/header/MenuBar.js.map +1 -0
- package/dist/components/header/ScrollZoomCheckbox.d.ts +7 -0
- package/dist/components/header/ScrollZoomCheckbox.js +19 -0
- package/dist/components/header/ScrollZoomCheckbox.js.map +1 -0
- package/dist/components/header/ZoomControls.js +10 -30
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/colorSchemeMenuItems.d.ts +4 -0
- package/dist/components/header/colorSchemeMenuItems.js +106 -0
- package/dist/components/header/colorSchemeMenuItems.js.map +1 -0
- package/dist/components/header/getAnnotationsMenu.d.ts +3 -30
- package/dist/components/header/getAnnotationsMenu.js +48 -54
- package/dist/components/header/getAnnotationsMenu.js.map +1 -1
- package/dist/components/header/menuBarItems.d.ts +11 -0
- package/dist/components/header/menuBarItems.js +277 -0
- package/dist/components/header/menuBarItems.js.map +1 -0
- package/dist/components/import/ImportForm.js +12 -6
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/minimap/Minimap.js +7 -3
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.js +2 -1
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/minimap/minimapLayout.d.ts +12 -0
- package/dist/components/minimap/minimapLayout.js +20 -0
- package/dist/components/minimap/minimapLayout.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +26 -9
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +15 -11
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/WheelZoomHint.d.ts +7 -0
- package/dist/components/msa/WheelZoomHint.js +38 -0
- package/dist/components/msa/WheelZoomHint.js.map +1 -0
- package/dist/components/msa/msaKeys.js +3 -0
- package/dist/components/msa/msaKeys.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +4 -5
- package/dist/components/msa/msaRaster.js +5 -5
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +22 -2
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +0 -3
- package/dist/components/msa/renderHighlights.js +3 -3
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSABlock.d.ts +6 -1
- package/dist/components/msa/renderMSABlock.js +44 -17
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/tileColor.js +7 -0
- package/dist/components/msa/tileColor.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.js +2 -2
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
- package/dist/components/settings/MenuItemsForm.d.ts +10 -0
- package/dist/components/settings/MenuItemsForm.js +80 -0
- package/dist/components/settings/MenuItemsForm.js.map +1 -0
- package/dist/components/settings/SettingsPanel.d.ts +12 -0
- package/dist/components/settings/SettingsPanel.js +92 -0
- package/dist/components/settings/SettingsPanel.js.map +1 -0
- package/dist/components/{header/FileMenu.d.ts → settings/TrackList.d.ts} +2 -2
- package/dist/components/settings/TrackList.js +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/tree/TreeCanvasBlock.js +2 -2
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeMenu.js +4 -6
- package/dist/components/tree/TreeMenu.js.map +1 -1
- package/dist/components/tree/TreeOverview.js +2 -2
- package/dist/components/tree/TreeOverview.js.map +1 -1
- package/dist/constants.d.ts +4 -1
- package/dist/constants.js +7 -1
- package/dist/constants.js.map +1 -1
- package/dist/darkReader.d.ts +7 -0
- package/dist/darkReader.js +42 -0
- package/dist/darkReader.js.map +1 -0
- package/dist/element.js +22 -47
- package/dist/element.js.map +1 -1
- package/dist/featurePanels.d.ts +37 -0
- package/dist/featurePanels.js +187 -0
- package/dist/featurePanels.js.map +1 -0
- package/dist/filehandleLoader.d.ts +41 -0
- package/dist/filehandleLoader.js +99 -0
- package/dist/filehandleLoader.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/treeModel.d.ts +9 -0
- package/dist/model/treeModel.js +12 -1
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +142 -32
- package/dist/model.js +268 -569
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +1 -40
- package/dist/neighborJoining.js.map +1 -1
- package/dist/renderToSvg.js +1 -13
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rerootTree.d.ts +1 -0
- package/dist/rerootTree.js +7 -6
- package/dist/rerootTree.js.map +1 -1
- package/dist/schemeResidueColor.d.ts +12 -0
- package/dist/schemeResidueColor.js +33 -0
- package/dist/schemeResidueColor.js.map +1 -0
- package/dist/similarityColors.d.ts +11 -0
- package/dist/similarityColors.js +90 -0
- package/dist/similarityColors.js.map +1 -0
- package/dist/svgTestUtil.d.ts +62 -12
- package/dist/treeDisplay.d.ts +37 -0
- package/dist/treeDisplay.js +209 -0
- package/dist/treeDisplay.js.map +1 -0
- package/dist/types.d.ts +6 -2
- package/dist/useWheelScroll.d.ts +3 -1
- package/dist/useWheelScroll.js +36 -17
- package/dist/useWheelScroll.js.map +1 -1
- package/dist/util.d.ts +2 -0
- package/dist/util.js +2 -0
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wheelZoom.d.ts +8 -3
- package/dist/wheelZoom.js +44 -32
- package/dist/wheelZoom.js.map +1 -1
- package/dist/zoomSnapshot.d.ts +29 -0
- package/dist/zoomSnapshot.js +47 -0
- package/dist/zoomSnapshot.js.map +1 -0
- package/package.json +11 -11
- package/src/blosum62.ts +45 -0
- package/src/colorSchemes.ts +1 -0
- package/src/components/Loading.tsx +6 -2
- package/src/components/MSAView.tsx +22 -5
- package/src/components/MetadataTable.tsx +83 -26
- package/src/components/Track.tsx +13 -0
- package/src/components/controls.ts +2 -1
- package/src/components/dialogs/AnnotationFileDialog.tsx +12 -1
- package/src/components/dialogs/FeatureDialog.tsx +2 -5
- package/src/components/dialogs/GapThresholdDialog.tsx +36 -0
- package/src/components/{header → dialogs}/GappynessSlider.tsx +3 -10
- package/src/components/{header → dialogs}/GoToBox.tsx +36 -10
- package/src/components/dialogs/GoToDialog.tsx +30 -0
- package/src/components/dialogs/MetadataDialog.tsx +32 -4
- package/src/components/{header → dialogs}/goTo.ts +1 -1
- package/src/components/dialogs/metadataLabels.ts +54 -0
- package/src/components/dialogs/metadataRecord.tsx +61 -0
- package/src/components/header/Header.tsx +75 -42
- package/src/components/header/HiddenColumnsChip.tsx +39 -0
- package/src/components/header/MenuBar.tsx +64 -0
- package/src/components/header/ScrollZoomCheckbox.tsx +46 -0
- package/src/components/header/ZoomControls.tsx +20 -35
- package/src/components/header/colorSchemeMenuItems.ts +121 -0
- package/src/components/header/getAnnotationsMenu.ts +52 -56
- package/src/components/header/menuBarItems.ts +357 -0
- package/src/components/import/ImportForm.tsx +19 -7
- package/src/components/minimap/Minimap.tsx +11 -3
- package/src/components/minimap/MinimapSVG.tsx +17 -1
- package/src/components/minimap/minimapLayout.ts +27 -0
- package/src/components/msa/MSACanvas.tsx +22 -3
- package/src/components/msa/MSACanvasBlock.tsx +18 -11
- package/src/components/msa/MSAMouseoverCanvas.tsx +2 -2
- package/src/components/msa/WheelZoomHint.tsx +56 -0
- package/src/components/msa/msaKeys.ts +3 -0
- package/src/components/msa/msaRaster.ts +6 -8
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +28 -5
- package/src/components/msa/renderHighlights.ts +3 -3
- package/src/components/msa/renderMSABlock.ts +51 -15
- package/src/components/msa/tileColor.ts +7 -0
- package/src/components/rowpanels/RowPanelBlock.tsx +2 -2
- package/src/components/settings/MenuItemsForm.tsx +225 -0
- package/src/components/settings/SettingsPanel.tsx +156 -0
- package/src/components/settings/TrackList.tsx +119 -0
- package/src/components/tracks/TrackBlocks.tsx +2 -2
- package/src/components/tree/TreeCanvasBlock.tsx +2 -2
- package/src/components/tree/TreeMenu.tsx +4 -6
- package/src/components/tree/TreeOverview.tsx +2 -2
- package/src/constants.ts +9 -1
- package/src/darkReader.ts +57 -0
- package/src/element.ts +34 -57
- package/src/featurePanels.ts +269 -0
- package/src/filehandleLoader.ts +136 -0
- package/src/index.ts +4 -0
- package/src/model/treeModel.ts +13 -0
- package/src/model.ts +345 -748
- package/src/neighborJoining.ts +1 -42
- package/src/renderToSvg.tsx +1 -13
- package/src/rerootTree.ts +7 -6
- package/src/schemeResidueColor.ts +37 -0
- package/src/similarityColors.ts +112 -0
- package/src/treeDisplay.ts +278 -0
- package/src/types.ts +6 -3
- package/src/useWheelScroll.ts +43 -22
- package/src/util.ts +3 -0
- package/src/version.ts +1 -1
- package/src/wheelZoom.ts +55 -33
- package/src/zoomSnapshot.ts +80 -0
- package/dist/components/header/ColorSchemeMenu.d.ts +0 -12
- package/dist/components/header/ColorSchemeMenu.js +0 -37
- package/dist/components/header/ColorSchemeMenu.js.map +0 -1
- package/dist/components/header/FileMenu.js +0 -92
- package/dist/components/header/FileMenu.js.map +0 -1
- package/dist/components/header/GappynessSlider.js.map +0 -1
- package/dist/components/header/GoToBox.js +0 -31
- package/dist/components/header/GoToBox.js.map +0 -1
- package/dist/components/header/MSASettingsMenu.js +0 -11
- package/dist/components/header/MSASettingsMenu.js.map +0 -1
- package/dist/components/header/ScrollZoomIcons.d.ts +0 -6
- package/dist/components/header/ScrollZoomIcons.js +0 -35
- package/dist/components/header/ScrollZoomIcons.js.map +0 -1
- package/dist/components/header/TreeSettingsMenu.js +0 -11
- package/dist/components/header/TreeSettingsMenu.js.map +0 -1
- package/dist/components/header/goTo.d.ts +0 -20
- package/dist/components/header/goTo.js.map +0 -1
- package/dist/components/header/settingsMenuItems.d.ts +0 -5
- package/dist/components/header/settingsMenuItems.js +0 -171
- package/dist/components/header/settingsMenuItems.js.map +0 -1
- package/src/components/header/ColorSchemeMenu.tsx +0 -56
- package/src/components/header/FileMenu.tsx +0 -107
- package/src/components/header/MSASettingsMenu.tsx +0 -26
- package/src/components/header/ScrollZoomIcons.tsx +0 -52
- package/src/components/header/TreeSettingsMenu.tsx +0 -30
- package/src/components/header/settingsMenuItems.ts +0 -216
package/src/neighborJoining.ts
CHANGED
|
@@ -1,48 +1,7 @@
|
|
|
1
1
|
// Neighbor Joining tree construction using BLOSUM62 distances
|
|
2
2
|
// Based on Saitou & Nei (1987) "The neighbor-joining method"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const BLOSUM62_KEYS = 'ARNDCQEGHILKMFPSTWYVBZX*'
|
|
6
|
-
// prettier-ignore
|
|
7
|
-
const BLOSUM62_DATA = [
|
|
8
|
-
[ 4,-1,-2,-2, 0,-1,-1, 0,-2,-1,-1,-1,-1,-2,-1, 1, 0,-3,-2, 0,-2,-1, 0,-4],
|
|
9
|
-
[-1, 5, 0,-2,-3, 1, 0,-2, 0,-3,-2, 2,-1,-3,-2,-1,-1,-3,-2,-3,-1, 0,-1,-4],
|
|
10
|
-
[-2, 0, 6, 1,-3, 0, 0, 0, 1,-3,-3, 0,-2,-3,-2, 1, 0,-4,-2,-3, 3, 0,-1,-4],
|
|
11
|
-
[-2,-2, 1, 6,-3, 0, 2,-1,-1,-3,-4,-1,-3,-3,-1, 0,-1,-4,-3,-3, 4, 1,-1,-4],
|
|
12
|
-
[ 0,-3,-3,-3, 9,-3,-4,-3,-3,-1,-1,-3,-1,-2,-3,-1,-1,-2,-2,-1,-3,-3,-2,-4],
|
|
13
|
-
[-1, 1, 0, 0,-3, 5, 2,-2, 0,-3,-2, 1, 0,-3,-1, 0,-1,-2,-1,-2, 0, 3,-1,-4],
|
|
14
|
-
[-1, 0, 0, 2,-4, 2, 5,-2, 0,-3,-3, 1,-2,-3,-1, 0,-1,-3,-2,-2, 1, 4,-1,-4],
|
|
15
|
-
[ 0,-2, 0,-1,-3,-2,-2, 6,-2,-4,-4,-2,-3,-3,-2, 0,-2,-2,-3,-3,-1,-2,-1,-4],
|
|
16
|
-
[-2, 0, 1,-1,-3, 0, 0,-2, 8,-3,-3,-1,-2,-1,-2,-1,-2,-2, 2,-3, 0, 0,-1,-4],
|
|
17
|
-
[-1,-3,-3,-3,-1,-3,-3,-4,-3, 4, 2,-3, 1, 0,-3,-2,-1,-3,-1, 3,-3,-3,-1,-4],
|
|
18
|
-
[-1,-2,-3,-4,-1,-2,-3,-4,-3, 2, 4,-2, 2, 0,-3,-2,-1,-2,-1, 1,-4,-3,-1,-4],
|
|
19
|
-
[-1, 2, 0,-1,-3, 1, 1,-2,-1,-3,-2, 5,-1,-3,-1, 0,-1,-3,-2,-2, 0, 1,-1,-4],
|
|
20
|
-
[-1,-1,-2,-3,-1, 0,-2,-3,-2, 1, 2,-1, 5, 0,-2,-1,-1,-1,-1, 1,-3,-1,-1,-4],
|
|
21
|
-
[-2,-3,-3,-3,-2,-3,-3,-3,-1, 0, 0,-3, 0, 6,-4,-2,-2, 1, 3,-1,-3,-3,-1,-4],
|
|
22
|
-
[-1,-2,-2,-1,-3,-1,-1,-2,-2,-3,-3,-1,-2,-4, 7,-1,-1,-4,-3,-2,-2,-1,-2,-4],
|
|
23
|
-
[ 1,-1, 1, 0,-1, 0, 0, 0,-1,-2,-2, 0,-1,-2,-1, 4, 1,-3,-2,-2, 0, 0, 0,-4],
|
|
24
|
-
[ 0,-1, 0,-1,-1,-1,-1,-2,-2,-1,-1,-1,-1,-2,-1, 1, 5,-2,-2, 0,-1,-1, 0,-4],
|
|
25
|
-
[-3,-3,-4,-4,-2,-2,-3,-2,-2,-3,-2,-3,-1, 1,-4,-3,-2,11, 2,-3,-4,-3,-2,-4],
|
|
26
|
-
[-2,-2,-2,-3,-2,-1,-2,-3, 2,-1,-1,-2,-1, 3,-3,-2,-2, 2, 7,-1,-3,-2,-1,-4],
|
|
27
|
-
[ 0,-3,-3,-3,-1,-2,-2,-3,-3, 3, 1,-2, 1,-1,-2,-2, 0,-3,-1, 4,-3,-2,-1,-4],
|
|
28
|
-
[-2,-1, 3, 4,-3, 0, 1,-1, 0,-3,-4, 0,-3,-3,-2, 0,-1,-4,-3,-3, 4, 1,-1,-4],
|
|
29
|
-
[-1, 0, 0, 1,-3, 3, 4,-2, 0,-3,-3, 1,-1,-3,-1, 0,-1,-3,-2,-2, 1, 4,-1,-4],
|
|
30
|
-
[ 0,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2, 0, 0,-2,-1,-1,-1,-1,-1,-4],
|
|
31
|
-
[-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4, 1],
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
const NUM_SYMBOLS = BLOSUM62_KEYS.length
|
|
35
|
-
const BLOSUM62 = Int8Array.from(BLOSUM62_DATA.flat())
|
|
36
|
-
|
|
37
|
-
// charCode -> row/column in BLOSUM62, -1 for anything the matrix does not name.
|
|
38
|
-
// On a 120x800 alignment a Map keyed by upper-cased character took 2.6s to
|
|
39
|
-
// build a tree, against 0.34s for this table.
|
|
40
|
-
const symbolOfCode = new Int8Array(128).fill(-1)
|
|
41
|
-
for (let i = 0; i < NUM_SYMBOLS; i++) {
|
|
42
|
-
const key = BLOSUM62_KEYS[i]!
|
|
43
|
-
symbolOfCode[key.charCodeAt(0)] = i
|
|
44
|
-
symbolOfCode[key.toLowerCase().charCodeAt(0)] = i
|
|
45
|
-
}
|
|
4
|
+
import { BLOSUM62, NUM_SYMBOLS, symbolOfCode } from './blosum62.ts'
|
|
46
5
|
|
|
47
6
|
// gap, or past the end of a row shorter than the alignment
|
|
48
7
|
const GAP = -2
|
package/src/renderToSvg.tsx
CHANGED
|
@@ -7,7 +7,6 @@ import { visibleRowRange } from './components/getVisibleLeaves.ts'
|
|
|
7
7
|
import MinimapSVG from './components/minimap/MinimapSVG.tsx'
|
|
8
8
|
import { legendRows } from './components/msa/legendRows.ts'
|
|
9
9
|
import { rasterApplies, rasterImageHref } from './components/msa/msaRaster.ts'
|
|
10
|
-
import { renderBoxFeatureCanvasBlock } from './components/msa/renderBoxFeatureCanvasBlock.ts'
|
|
11
10
|
import { renderPersistentHighlights } from './components/msa/renderHighlights.ts'
|
|
12
11
|
import { renderMSABlock } from './components/msa/renderMSABlock.ts'
|
|
13
12
|
import { visibleColRange } from './components/msa/visibleColRange.ts'
|
|
@@ -412,18 +411,6 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
412
411
|
const raster = rasterBackground({ model, theme, layout })
|
|
413
412
|
|
|
414
413
|
const msaCtx = new Context(msaAreaWidth, contentHeight)
|
|
415
|
-
renderBoxFeatureCanvasBlock({
|
|
416
|
-
model,
|
|
417
|
-
theme,
|
|
418
|
-
ctx: msaCtx,
|
|
419
|
-
offsetX,
|
|
420
|
-
offsetY,
|
|
421
|
-
// the overlay culls bands to this width; without it the export falls back
|
|
422
|
-
// to the on-screen 500px block size and drops every band past it
|
|
423
|
-
blockSizeXOverride: msaAreaWidth,
|
|
424
|
-
blockSizeYOverride: contentHeight,
|
|
425
|
-
highResScaleFactorOverride: 1,
|
|
426
|
-
})
|
|
427
414
|
renderMSABlock({
|
|
428
415
|
model,
|
|
429
416
|
theme,
|
|
@@ -434,6 +421,7 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
434
421
|
blockSizeYOverride: contentHeight,
|
|
435
422
|
highResScaleFactorOverride: 1,
|
|
436
423
|
rasterTiles: !!raster,
|
|
424
|
+
domains: true,
|
|
437
425
|
})
|
|
438
426
|
msaCtx.resetTransform()
|
|
439
427
|
renderPersistentHighlights({
|
package/src/rerootTree.ts
CHANGED
|
@@ -13,19 +13,18 @@ export interface TreeNode {
|
|
|
13
13
|
children?: TreeNode[]
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
function preorder<N extends TreeNode>(root: N) {
|
|
16
|
+
export function preorder<N extends TreeNode>(root: N, parents?: Map<N, N>) {
|
|
17
17
|
const order: N[] = []
|
|
18
|
-
const parents = new Map<N, N>()
|
|
19
18
|
const stack = [root]
|
|
20
19
|
while (stack.length > 0) {
|
|
21
20
|
const node = stack.pop()!
|
|
22
21
|
order.push(node)
|
|
23
22
|
for (const child of (node.children ?? []) as N[]) {
|
|
24
|
-
parents
|
|
23
|
+
parents?.set(child, node)
|
|
25
24
|
stack.push(child)
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
|
-
return
|
|
27
|
+
return order
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
function hasLengths(order: TreeNode[]) {
|
|
@@ -56,7 +55,8 @@ export function rerootTree<N extends TreeNode>(
|
|
|
56
55
|
target: N,
|
|
57
56
|
offset?: number,
|
|
58
57
|
): TreeNode {
|
|
59
|
-
const
|
|
58
|
+
const parents = new Map<N, N>()
|
|
59
|
+
const order = preorder(root, parents)
|
|
60
60
|
if (!parents.has(target)) {
|
|
61
61
|
return root
|
|
62
62
|
}
|
|
@@ -112,7 +112,8 @@ export function rerootTree<N extends TreeNode>(
|
|
|
112
112
|
* counts branches.
|
|
113
113
|
*/
|
|
114
114
|
export function midpointRoot<N extends TreeNode>(root: N): TreeNode {
|
|
115
|
-
const
|
|
115
|
+
const parents = new Map<N, N>()
|
|
116
|
+
const order = preorder(root, parents)
|
|
116
117
|
const tips = order.filter(n => !n.children?.length)
|
|
117
118
|
if (tips.length < 2) {
|
|
118
119
|
return root
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { colord } from 'colord'
|
|
2
|
+
|
|
3
|
+
import { tileColorFn } from './components/msa/tileColor.ts'
|
|
4
|
+
|
|
5
|
+
import type { MsaViewModel } from './model.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A reader for the color the active scheme gives a row's residue, as hex, for a
|
|
9
|
+
* host that paints another view, such as a structure, with the alignment's own
|
|
10
|
+
* colors. `seqPos` is 1-based, like `structureResidue`. The reader returns
|
|
11
|
+
* undefined where the scheme leaves the residue uncolored, the row lacks it, or
|
|
12
|
+
* its column is hidden.
|
|
13
|
+
*
|
|
14
|
+
* Build it inside a reaction: a new scheme, a hidden gap column or a collapsed
|
|
15
|
+
* clade then re-runs the caller.
|
|
16
|
+
*/
|
|
17
|
+
export function schemeResidueColor(model: MsaViewModel) {
|
|
18
|
+
const colorAt = tileColorFn(model)
|
|
19
|
+
const { columns } = model
|
|
20
|
+
const hexes = new Map<string, string | undefined>()
|
|
21
|
+
const toHex = (color: string) => {
|
|
22
|
+
if (!hexes.has(color)) {
|
|
23
|
+
const parsed = colord(color)
|
|
24
|
+
hexes.set(color, parsed.isValid() ? parsed.toHex() : undefined)
|
|
25
|
+
}
|
|
26
|
+
return hexes.get(color)
|
|
27
|
+
}
|
|
28
|
+
return (rowName: string, seqPos: number) => {
|
|
29
|
+
const col = model.seqPosToVisibleCol(rowName, seqPos - 1)
|
|
30
|
+
const letter = col === undefined ? undefined : columns.get(rowName)?.[col]
|
|
31
|
+
const color =
|
|
32
|
+
col === undefined || letter === undefined
|
|
33
|
+
? undefined
|
|
34
|
+
: colorAt(col, letter)
|
|
35
|
+
return color === undefined ? undefined : toHex(color)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Similarity shading, after biotite's plot_alignment_similarity_based and
|
|
2
|
+
// Jalview's BLOSUM62 score scheme. Each residue takes a shade of one hue by how
|
|
3
|
+
// similar it is, on average, to the other residues of its column.
|
|
4
|
+
|
|
5
|
+
import { BLOSUM62, NUM_SYMBOLS, symbolOfCode } from './blosum62.ts'
|
|
6
|
+
import { letterOfResidueSlot, numResidueSlots } from './columnCounts.ts'
|
|
7
|
+
|
|
8
|
+
import type { ColumnCounts } from './columnCounts.ts'
|
|
9
|
+
|
|
10
|
+
const AMINO_ACIDS = 20
|
|
11
|
+
const HUE = 240
|
|
12
|
+
const SATURATION = 50
|
|
13
|
+
const LIGHTEST = 96
|
|
14
|
+
const DARKEST = 40
|
|
15
|
+
|
|
16
|
+
// BLOSUM62 scaled per row to 0..1 over the twenty amino acids, so a residue's
|
|
17
|
+
// own score is 1 and its worst substitution 0. Not symmetric, like biotite's.
|
|
18
|
+
const normalized = new Float32Array(NUM_SYMBOLS * NUM_SYMBOLS)
|
|
19
|
+
for (let a = 0; a < NUM_SYMBOLS; a++) {
|
|
20
|
+
let min = Infinity
|
|
21
|
+
let max = -Infinity
|
|
22
|
+
for (let b = 0; b < AMINO_ACIDS; b++) {
|
|
23
|
+
const score = BLOSUM62[a * NUM_SYMBOLS + b]!
|
|
24
|
+
min = Math.min(min, score)
|
|
25
|
+
max = Math.max(max, score)
|
|
26
|
+
}
|
|
27
|
+
for (let b = 0; b < NUM_SYMBOLS; b++) {
|
|
28
|
+
const scaled = (BLOSUM62[a * NUM_SYMBOLS + b]! - min) / (max - min)
|
|
29
|
+
normalized[a * NUM_SYMBOLS + b] = Math.min(Math.max(scaled, 0), 1)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// residue slot -> row of the matrix, -1 for a letter the matrix does not name
|
|
34
|
+
const symbolOfSlot = Int8Array.from({ length: numResidueSlots }, (_, slot) => {
|
|
35
|
+
const letter = letterOfResidueSlot(slot)
|
|
36
|
+
return letter.length === 1 ? symbolOfCode[letter.charCodeAt(0)]! : -1
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
function shade(similarity: number) {
|
|
40
|
+
const lightness = Math.round(LIGHTEST - (LIGHTEST - DARKEST) * similarity)
|
|
41
|
+
return `hsl(${HUE}, ${SATURATION}%, ${lightness}%)`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface Measure {
|
|
45
|
+
/** whether a residue counts toward the means at all */
|
|
46
|
+
counted: (slot: number) => boolean
|
|
47
|
+
/** whether a residue takes a shade, given it is counted */
|
|
48
|
+
shaded: (slot: number) => boolean
|
|
49
|
+
/** similarity of residue `a` to residue `b`, 0..1 */
|
|
50
|
+
score: (a: number, b: number) => number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Shades each residue of one column by its mean similarity to the column's
|
|
55
|
+
* other residues, gaps left out of the mean. A column of fewer than two counted
|
|
56
|
+
* residues has nothing to compare and colors nothing.
|
|
57
|
+
*/
|
|
58
|
+
function shadeColumn(counts: ColumnCounts, col: number, measure: Measure) {
|
|
59
|
+
const { counted, shaded, score } = measure
|
|
60
|
+
const present: [slot: number, count: number][] = []
|
|
61
|
+
let total = 0
|
|
62
|
+
counts.forEachResidue(col, (slot, count) => {
|
|
63
|
+
if (counted(slot)) {
|
|
64
|
+
present.push([slot, count])
|
|
65
|
+
total += count
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const colors: Record<string, string> = {}
|
|
70
|
+
if (total < 2) {
|
|
71
|
+
return colors
|
|
72
|
+
}
|
|
73
|
+
for (const [a] of present) {
|
|
74
|
+
if (shaded(a)) {
|
|
75
|
+
let sum = -score(a, a)
|
|
76
|
+
for (const [b, count] of present) {
|
|
77
|
+
sum += count * score(a, b)
|
|
78
|
+
}
|
|
79
|
+
colors[letterOfResidueSlot(a)] = shade(sum / (total - 1))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return colors
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const blosum62: Measure = {
|
|
86
|
+
counted: slot => symbolOfSlot[slot]! >= 0,
|
|
87
|
+
shaded: slot => symbolOfSlot[slot]! < AMINO_ACIDS,
|
|
88
|
+
score: (a, b) =>
|
|
89
|
+
normalized[symbolOfSlot[a]! * NUM_SYMBOLS + symbolOfSlot[b]!]!,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const identity: Measure = {
|
|
93
|
+
counted: () => true,
|
|
94
|
+
shaded: () => true,
|
|
95
|
+
score: (a, b) => (a === b ? 1 : 0),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The amino acids of a column shaded by BLOSUM62 similarity. Letters the matrix
|
|
100
|
+
* does not name, such as U, O and J, count for nothing and take no shade.
|
|
101
|
+
*/
|
|
102
|
+
export function blosum62ColumnColors(counts: ColumnCounts, col: number) {
|
|
103
|
+
return shadeColumn(counts, col, blosum62)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Nucleotides and anything else without a substitution matrix: a residue's
|
|
108
|
+
* similarity is the fraction of the rest of its column that equals it.
|
|
109
|
+
*/
|
|
110
|
+
export function identityColumnColors(counts: ColumnCounts, col: number) {
|
|
111
|
+
return shadeColumn(counts, col, identity)
|
|
112
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { generateNodeIds, parseEmfTree, parseNewick } from 'msa-parsers'
|
|
2
|
+
|
|
3
|
+
import { cladeHighlightAlpha, cladeHighlightColor } from './constants.ts'
|
|
4
|
+
import { flatToTree } from './flatToTree.ts'
|
|
5
|
+
import {
|
|
6
|
+
collapse,
|
|
7
|
+
find,
|
|
8
|
+
forEachDescendant,
|
|
9
|
+
hierarchy,
|
|
10
|
+
leafIndex,
|
|
11
|
+
leaves,
|
|
12
|
+
mrca,
|
|
13
|
+
sort,
|
|
14
|
+
sum as hierarchySum,
|
|
15
|
+
} from './hierarchy.ts'
|
|
16
|
+
import { parseAsn1 } from './parseAsn1.ts'
|
|
17
|
+
import { midpointRoot, preorder, rerootTree } from './rerootTree.ts'
|
|
18
|
+
import { withAlpha } from './util.ts'
|
|
19
|
+
|
|
20
|
+
import type { HierarchyNode } from './hierarchy.ts'
|
|
21
|
+
import type { TreeNode } from './rerootTree.ts'
|
|
22
|
+
import type {
|
|
23
|
+
Clade,
|
|
24
|
+
CladeMark,
|
|
25
|
+
NodeWithIds,
|
|
26
|
+
ResolvedClade,
|
|
27
|
+
TreeOrder,
|
|
28
|
+
TreeRoot,
|
|
29
|
+
} from './types.ts'
|
|
30
|
+
|
|
31
|
+
export function parseTreeText(text: string) {
|
|
32
|
+
if (text.startsWith('BioTreeContainer')) {
|
|
33
|
+
return flatToTree(parseAsn1(text))
|
|
34
|
+
}
|
|
35
|
+
return parseNewick(text.startsWith('SEQ') ? parseEmfTree(text).tree : text)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// the marks that set tree state from a node rather than drawing
|
|
39
|
+
const seedingMarks = new Set<CladeMark>(['collapse', 'focus', 'rotate'])
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Each tip of the full tree in the order the tree draws it, with the on-screen
|
|
43
|
+
* row that shows it: its own, or the row of the collapsed node standing for it
|
|
44
|
+
*/
|
|
45
|
+
function tipRows(root: HierarchyNode<NodeWithIds>) {
|
|
46
|
+
const rows = new Map<string, { order: number; row: number }>()
|
|
47
|
+
let row = 0
|
|
48
|
+
const pushReversed = (
|
|
49
|
+
stack: HierarchyNode<NodeWithIds>[],
|
|
50
|
+
nodes: HierarchyNode<NodeWithIds>[],
|
|
51
|
+
) => {
|
|
52
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
53
|
+
stack.push(nodes[i]!)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const stack = [root]
|
|
57
|
+
while (stack.length > 0) {
|
|
58
|
+
const node = stack.pop()!
|
|
59
|
+
if (node.children?.length) {
|
|
60
|
+
pushReversed(stack, node.children)
|
|
61
|
+
} else {
|
|
62
|
+
const hidden = [node]
|
|
63
|
+
while (hidden.length > 0) {
|
|
64
|
+
const tip = hidden.pop()!
|
|
65
|
+
const kids = tip.children ?? tip._children
|
|
66
|
+
if (kids?.length) {
|
|
67
|
+
pushReversed(hidden, kids)
|
|
68
|
+
} else {
|
|
69
|
+
rows.set(tip.data.name, { order: rows.size, row })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
row++
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return rows
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The rows a clade covers and the node an `mrca` names, or undefined when the
|
|
80
|
+
* clade does not resolve: a tip name the tree does not have or has twice, or a
|
|
81
|
+
* leaf count `tips` disagrees with. A `range` counts its tips in the order
|
|
82
|
+
* `tips` gives them, so a collapsed clade inside it counts every leaf it
|
|
83
|
+
* stands for. `index` is the memoized leaf pass over the tree.
|
|
84
|
+
*/
|
|
85
|
+
function cladeRows(
|
|
86
|
+
clade: Clade,
|
|
87
|
+
root: HierarchyNode<NodeWithIds>,
|
|
88
|
+
index: Map<string, HierarchyNode<NodeWithIds> | undefined>,
|
|
89
|
+
tips: Map<string, { order: number; row: number }>,
|
|
90
|
+
): { rows: [number, number]; nodeId?: string } | undefined {
|
|
91
|
+
if (clade.range) {
|
|
92
|
+
const [a, b] = clade.range.map(name =>
|
|
93
|
+
index.get(name) ? tips.get(name) : undefined,
|
|
94
|
+
)
|
|
95
|
+
if (a === undefined || b === undefined) {
|
|
96
|
+
return undefined
|
|
97
|
+
}
|
|
98
|
+
const [first, last] = a.order <= b.order ? [a, b] : [b, a]
|
|
99
|
+
return last.order - first.order + 1 === clade.tips
|
|
100
|
+
? { rows: [first.row, last.row] }
|
|
101
|
+
: undefined
|
|
102
|
+
}
|
|
103
|
+
const node = clade.mrca ? mrca(root, clade.mrca, index) : undefined
|
|
104
|
+
if (!node) {
|
|
105
|
+
return undefined
|
|
106
|
+
}
|
|
107
|
+
const nodeTips = leaves(node)
|
|
108
|
+
if (nodeTips.length !== clade.tips) {
|
|
109
|
+
return undefined
|
|
110
|
+
}
|
|
111
|
+
let first = Infinity
|
|
112
|
+
let last = -Infinity
|
|
113
|
+
for (const tip of nodeTips) {
|
|
114
|
+
const row = tips.get(tip.data.name)?.row
|
|
115
|
+
if (row !== undefined) {
|
|
116
|
+
first = Math.min(first, row)
|
|
117
|
+
last = Math.max(last, row)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return first <= last
|
|
121
|
+
? { rows: [first, last], nodeId: node.data.id }
|
|
122
|
+
: undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Every clade that resolves, with its fill color settled, against the rows of
|
|
127
|
+
* `displayed`, the tree as drawn. The tree panel resolves against the
|
|
128
|
+
* displayed rows and the overview against its own, so both take the tree they
|
|
129
|
+
* draw. A `range` record names no node, so the seeding marks, which need one,
|
|
130
|
+
* drop it.
|
|
131
|
+
*/
|
|
132
|
+
export function resolveClades(
|
|
133
|
+
clades: Clade[],
|
|
134
|
+
root: HierarchyNode<NodeWithIds>,
|
|
135
|
+
displayed: HierarchyNode<NodeWithIds>,
|
|
136
|
+
): ResolvedClade[] {
|
|
137
|
+
const index = leafIndex(root)
|
|
138
|
+
const tips = tipRows(displayed)
|
|
139
|
+
return clades.flatMap(clade => {
|
|
140
|
+
const resolved = cladeRows(clade, root, index, tips)
|
|
141
|
+
const seeding = seedingMarks.has(clade.mark)
|
|
142
|
+
return resolved && !(seeding && resolved.nodeId === undefined)
|
|
143
|
+
? [
|
|
144
|
+
{
|
|
145
|
+
...resolved,
|
|
146
|
+
mark: clade.mark,
|
|
147
|
+
color: withAlpha(
|
|
148
|
+
clade.color ?? cladeHighlightColor,
|
|
149
|
+
cladeHighlightAlpha,
|
|
150
|
+
),
|
|
151
|
+
markColor: clade.color,
|
|
152
|
+
label: clade.label,
|
|
153
|
+
},
|
|
154
|
+
]
|
|
155
|
+
: []
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const childOrders: Record<
|
|
160
|
+
TreeOrder,
|
|
161
|
+
((a: HierarchyNode, b: HierarchyNode) => number) | undefined
|
|
162
|
+
> = {
|
|
163
|
+
input: undefined,
|
|
164
|
+
branchLength: (a, b) => (a.data.length ?? 1) - (b.data.length ?? 1),
|
|
165
|
+
ladderize: (a, b) => a.value! - b.value!,
|
|
166
|
+
ladderizeReverse: (a, b) => b.value! - a.value!,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The tree with the display transforms applied: leaf counts summed, children
|
|
171
|
+
* put in `order` and each `rotated` node's reversed, `showOnly` taken as the
|
|
172
|
+
* new root, and each `collapsed` clade folded. The overview builds the same
|
|
173
|
+
* tree without the focus, so it shows the focused subtree inside the whole.
|
|
174
|
+
*/
|
|
175
|
+
export function buildTreeRoot(
|
|
176
|
+
tree: NodeWithIds,
|
|
177
|
+
{
|
|
178
|
+
collapsed,
|
|
179
|
+
rotated,
|
|
180
|
+
order,
|
|
181
|
+
showOnly,
|
|
182
|
+
}: {
|
|
183
|
+
collapsed: readonly string[]
|
|
184
|
+
rotated: readonly string[]
|
|
185
|
+
order: TreeOrder
|
|
186
|
+
showOnly?: string
|
|
187
|
+
},
|
|
188
|
+
) {
|
|
189
|
+
let hier = hierarchy(tree, d => d.children)
|
|
190
|
+
hierarchySum(hier, d => (d.children.length > 0 ? 0 : 1))
|
|
191
|
+
const compare = childOrders[order]
|
|
192
|
+
if (compare) {
|
|
193
|
+
sort(hier, compare)
|
|
194
|
+
}
|
|
195
|
+
if (rotated.length > 0) {
|
|
196
|
+
const flip = new Set(rotated)
|
|
197
|
+
forEachDescendant(hier, n => {
|
|
198
|
+
if (flip.has(n.data.id)) {
|
|
199
|
+
n.children?.reverse()
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (showOnly) {
|
|
205
|
+
const res = find(hier, n => n.data.id === showOnly)
|
|
206
|
+
if (res) {
|
|
207
|
+
hier = res
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const collapsedId of collapsed) {
|
|
212
|
+
const node = find(hier, n => n.data.id === collapsedId)
|
|
213
|
+
if (node) {
|
|
214
|
+
if (node.children) {
|
|
215
|
+
collapse(node)
|
|
216
|
+
} else if (node.parent?.children) {
|
|
217
|
+
node.parent.children = node.parent.children.filter(
|
|
218
|
+
c => c.data.id !== collapsedId,
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return hier
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The node whose branch an outgroup roots the tree on: the outgroup's most
|
|
229
|
+
* recent common ancestor, or the rest of the tips' where that ancestor is the
|
|
230
|
+
* root, since the outgroup then straddles the root and the branch above the
|
|
231
|
+
* other side is the same branch of the unrooted tree.
|
|
232
|
+
*/
|
|
233
|
+
export function outgroupNode(root: HierarchyNode, outgroup: string[]) {
|
|
234
|
+
const index = leafIndex(root)
|
|
235
|
+
const node = mrca(root, outgroup, index)
|
|
236
|
+
if (node !== root) {
|
|
237
|
+
return node
|
|
238
|
+
}
|
|
239
|
+
const out = new Set(outgroup)
|
|
240
|
+
const rest = leaves(root)
|
|
241
|
+
.map(n => n.data.name)
|
|
242
|
+
.filter(name => !out.has(name))
|
|
243
|
+
const other = mrca(root, rest, index)
|
|
244
|
+
return other === root ? undefined : other
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* `tree` rerooted as `treeRoot` asks, with fresh path ids. The ids of the tree
|
|
249
|
+
* as written become names on unnamed nodes, and a rerooting would carry them
|
|
250
|
+
* along as labels, so they come off first.
|
|
251
|
+
*/
|
|
252
|
+
export function rootTree(tree: NodeWithIds, treeRoot: TreeRoot) {
|
|
253
|
+
const plain = new Map<NodeWithIds, TreeNode>()
|
|
254
|
+
for (const node of preorder(tree).reverse()) {
|
|
255
|
+
plain.set(node, {
|
|
256
|
+
name: node.name === node.id ? undefined : node.name,
|
|
257
|
+
length: node.length,
|
|
258
|
+
children: node.children.map(c => plain.get(c)!),
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
const input = plain.get(tree)!
|
|
262
|
+
if (treeRoot === 'midpoint') {
|
|
263
|
+
return generateNodeIds(midpointRoot(input))
|
|
264
|
+
}
|
|
265
|
+
const target = outgroupNode(
|
|
266
|
+
hierarchy(tree, d => d.children),
|
|
267
|
+
treeRoot.outgroup,
|
|
268
|
+
)
|
|
269
|
+
return target
|
|
270
|
+
? generateNodeIds(rerootTree(input, plain.get(target.data)!))
|
|
271
|
+
: tree
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// the inclusive tip indices a node covers, from its xMin/xMax in a layout one
|
|
275
|
+
// row per tip
|
|
276
|
+
export function tipRange(node: HierarchyNode): [number, number] {
|
|
277
|
+
return [Math.round(node.xMin! - 0.5), Math.round(node.xMax! - 0.5)]
|
|
278
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -27,14 +27,11 @@ export type ColorScheme = string | Required<Pick<ScaleSpec, 'map'>>
|
|
|
27
27
|
export interface BasicTrackModel {
|
|
28
28
|
id: string
|
|
29
29
|
name: string
|
|
30
|
-
associatedRowName?: string
|
|
31
30
|
height: number
|
|
32
31
|
kind: TrackKind
|
|
33
32
|
// the model height this track's divider writes, shared with every track
|
|
34
33
|
// carrying the same key. Absent on a track no divider resizes.
|
|
35
34
|
heightKey?: string
|
|
36
|
-
// hidden until the user asks for it, unless they have already chosen
|
|
37
|
-
defaultOff?: boolean
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
export interface TextTrackModel extends BasicTrackModel {
|
|
@@ -209,6 +206,12 @@ export interface MappedStructure {
|
|
|
209
206
|
id: string
|
|
210
207
|
kind?: 'experimental' | 'predicted'
|
|
211
208
|
asymId?: string
|
|
209
|
+
/**
|
|
210
|
+
* which id `asymId` is, the file's `label_asym_id` or the author's
|
|
211
|
+
* `auth_asym_id`. Unset reads as either, label first, which is right for the
|
|
212
|
+
* common entry whose two ids agree and wrong where they collide.
|
|
213
|
+
*/
|
|
214
|
+
asymIdKind?: 'label' | 'auth'
|
|
212
215
|
url?: string
|
|
213
216
|
}
|
|
214
217
|
|