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
|
@@ -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 {
|
|
@@ -64,7 +61,6 @@ export interface ArcTrackModel extends BasicTrackModel {
|
|
|
64
61
|
}
|
|
65
62
|
|
|
66
63
|
export interface BasicTrack {
|
|
67
|
-
ReactComponent: React.FC<any>
|
|
68
64
|
model: TextTrackModel & BarTrackModel & ArcTrackModel
|
|
69
65
|
}
|
|
70
66
|
|
|
@@ -210,6 +206,12 @@ export interface MappedStructure {
|
|
|
210
206
|
id: string
|
|
211
207
|
kind?: 'experimental' | 'predicted'
|
|
212
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'
|
|
213
215
|
url?: string
|
|
214
216
|
}
|
|
215
217
|
|
package/src/useWheelScroll.ts
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
normalizeWheelDelta,
|
|
7
|
-
zoomFactorFromAccum,
|
|
8
|
-
} from './wheelZoom.ts'
|
|
3
|
+
import { WheelZoomSmoother } from './wheelZoom.ts'
|
|
4
|
+
|
|
5
|
+
const zoomSettleMs = 150
|
|
9
6
|
|
|
10
7
|
export function useWheelScroll({
|
|
11
8
|
ref,
|
|
12
9
|
onScrollX,
|
|
13
10
|
onScrollY,
|
|
14
11
|
onZoom,
|
|
12
|
+
onZoomSettle,
|
|
13
|
+
onWheelScroll,
|
|
15
14
|
scrollZoom,
|
|
16
15
|
}: {
|
|
17
16
|
ref: React.RefObject<HTMLDivElement | null>
|
|
18
17
|
onScrollX?: (delta: number) => void
|
|
19
18
|
onScrollY?: (delta: number) => void
|
|
20
19
|
onZoom?: (scaleFactor: number, offsetX: number, offsetY: number) => void
|
|
20
|
+
onZoomSettle?: () => void
|
|
21
|
+
onWheelScroll?: (event: WheelEvent) => void
|
|
21
22
|
scrollZoom?: boolean
|
|
22
23
|
}) {
|
|
23
24
|
const scheduled = useRef(false)
|
|
24
|
-
const
|
|
25
|
+
const zoomFrame = useRef<number | undefined>(undefined)
|
|
26
|
+
const zoomSettle = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
27
|
+
undefined,
|
|
28
|
+
)
|
|
25
29
|
const deltaX = useRef(0)
|
|
26
30
|
const deltaY = useRef(0)
|
|
27
|
-
const
|
|
31
|
+
const zoomSmoother = useRef(new WheelZoomSmoother())
|
|
28
32
|
const zoomX = useRef(0)
|
|
29
33
|
const zoomY = useRef(0)
|
|
30
34
|
const lastZoomTime = useRef<number | null>(null)
|
|
@@ -47,28 +51,31 @@ export function useWheelScroll({
|
|
|
47
51
|
!event.shiftKey &&
|
|
48
52
|
Math.abs(event.deltaY) >= Math.abs(event.deltaX)
|
|
49
53
|
if (onZoom && (isCtrlZoom || isScrollZoom)) {
|
|
54
|
+
const zoom = onZoom
|
|
50
55
|
const rect = curr!.getBoundingClientRect()
|
|
51
|
-
|
|
52
|
-
// factor so fine pinches and coarse notches feel consistent
|
|
53
|
-
const dY = normalizeWheelDelta(event.deltaY, event.deltaMode)
|
|
54
|
-
const divisor = isCtrlZoom ? getZoomNormalizer(dY) : SCROLL_ZOOM_DIVISOR
|
|
55
|
-
zoomAccum.current += dY / divisor
|
|
56
|
+
zoomSmoother.current.add(event.deltaY, event.deltaMode, isCtrlZoom)
|
|
56
57
|
zoomX.current = event.clientX - rect.left
|
|
57
58
|
zoomY.current = event.clientY - rect.top
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
if (zoomFrame.current === undefined) {
|
|
60
|
+
lastZoomTime.current = null
|
|
61
|
+
const frame = (now: number) => {
|
|
61
62
|
const elapsed =
|
|
62
63
|
lastZoomTime.current === null ? 16.67 : now - lastZoomTime.current
|
|
63
64
|
lastZoomTime.current = now
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
zoom(
|
|
66
|
+
zoomSmoother.current.step(Math.min(100, elapsed)),
|
|
66
67
|
zoomX.current,
|
|
67
68
|
zoomY.current,
|
|
68
69
|
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
clearTimeout(zoomSettle.current)
|
|
71
|
+
zoomSettle.current = setTimeout(() => {
|
|
72
|
+
onZoomSettle?.()
|
|
73
|
+
}, zoomSettleMs)
|
|
74
|
+
zoomFrame.current = zoomSmoother.current.idle
|
|
75
|
+
? undefined
|
|
76
|
+
: requestAnimationFrame(frame)
|
|
77
|
+
}
|
|
78
|
+
zoomFrame.current = requestAnimationFrame(frame)
|
|
72
79
|
}
|
|
73
80
|
event.preventDefault()
|
|
74
81
|
event.stopPropagation()
|
|
@@ -79,6 +86,7 @@ export function useWheelScroll({
|
|
|
79
86
|
const consumeX = !!onScrollX && event.deltaX !== 0
|
|
80
87
|
const consumeY = !!onScrollY && event.deltaY !== 0
|
|
81
88
|
if (consumeX || consumeY) {
|
|
89
|
+
onWheelScroll?.(event)
|
|
82
90
|
if (onScrollX) {
|
|
83
91
|
deltaX.current += event.deltaX
|
|
84
92
|
}
|
|
@@ -107,8 +115,21 @@ export function useWheelScroll({
|
|
|
107
115
|
curr.addEventListener('wheel', onWheel, { passive: false })
|
|
108
116
|
return () => {
|
|
109
117
|
curr.removeEventListener('wheel', onWheel)
|
|
118
|
+
if (zoomFrame.current !== undefined) {
|
|
119
|
+
cancelAnimationFrame(zoomFrame.current)
|
|
120
|
+
zoomFrame.current = undefined
|
|
121
|
+
}
|
|
122
|
+
clearTimeout(zoomSettle.current)
|
|
110
123
|
}
|
|
111
|
-
}, [
|
|
124
|
+
}, [
|
|
125
|
+
ref,
|
|
126
|
+
onScrollX,
|
|
127
|
+
onScrollY,
|
|
128
|
+
onZoom,
|
|
129
|
+
onZoomSettle,
|
|
130
|
+
onWheelScroll,
|
|
131
|
+
scrollZoom,
|
|
132
|
+
])
|
|
112
133
|
|
|
113
134
|
useEffect(() => {
|
|
114
135
|
if (!mouseDragging) {
|
package/src/util.ts
CHANGED
|
@@ -142,3 +142,6 @@ export function computeRowInsertions(blanks: number[], seq: string) {
|
|
|
142
142
|
export function len(a: { end: number; start: number }) {
|
|
143
143
|
return a.end - a.start
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
/** reads computeds so a reaction observes them, which keeps them cached */
|
|
147
|
+
export function readComputed(..._values: unknown[]) {}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '9.
|
|
1
|
+
export const version = '9.1.1'
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
|
|
2
|
+
|
|
3
|
+
import type { MSAViewerProps } from './components/MSAViewer.tsx'
|
|
4
|
+
import type { ColorScheme, Highlight } from './types.ts'
|
|
5
|
+
|
|
6
|
+
export function colorSchemeSnapshot(scheme: ColorScheme | undefined) {
|
|
7
|
+
return typeof scheme === 'string'
|
|
8
|
+
? { colorSchemeName: scheme }
|
|
9
|
+
: scheme
|
|
10
|
+
? { customColorScheme: scheme.map }
|
|
11
|
+
: {}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The `highlightColumns` prop as `highlights`: one `{start, end}` per run of
|
|
16
|
+
* adjacent 1-based alignment columns
|
|
17
|
+
*/
|
|
18
|
+
export function columnHighlights(columns: number[] = []) {
|
|
19
|
+
const sorted = [...new Set(columns)].sort((a, b) => a - b)
|
|
20
|
+
const runs: { start: number; end: number }[] = []
|
|
21
|
+
for (const column of sorted) {
|
|
22
|
+
const last = runs.at(-1)
|
|
23
|
+
if (last?.end === column - 1) {
|
|
24
|
+
last.end = column
|
|
25
|
+
} else {
|
|
26
|
+
runs.push({ start: column, end: column })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return runs
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function viewerHighlights({
|
|
33
|
+
highlights = [],
|
|
34
|
+
highlightColumns,
|
|
35
|
+
}: Pick<MSAViewerProps, 'highlights' | 'highlightColumns'>): Highlight[] {
|
|
36
|
+
return [...highlights, ...columnHighlights(highlightColumns)]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function urlLocation(uri: string | undefined) {
|
|
40
|
+
return uri ? { uri, locationType: 'UriLocation' as const } : undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** each filehandle prop, or the URL prop standing in for it */
|
|
44
|
+
export function fileLocations({
|
|
45
|
+
msaFilehandle,
|
|
46
|
+
treeFilehandle,
|
|
47
|
+
gffFilehandle,
|
|
48
|
+
msaUrl,
|
|
49
|
+
treeUrl,
|
|
50
|
+
gffUrl,
|
|
51
|
+
}: MSAViewerProps) {
|
|
52
|
+
return {
|
|
53
|
+
msaFilehandle: msaFilehandle ?? urlLocation(msaUrl),
|
|
54
|
+
treeFilehandle: treeFilehandle ?? urlLocation(treeUrl),
|
|
55
|
+
gffFilehandle: gffFilehandle ?? urlLocation(gffUrl),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveTheme(theme: MSAViewerProps['theme']) {
|
|
60
|
+
return createJBrowseTheme(
|
|
61
|
+
theme === 'dark'
|
|
62
|
+
? { palette: { mode: 'dark' } }
|
|
63
|
+
: typeof theme === 'object'
|
|
64
|
+
? theme
|
|
65
|
+
: undefined,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The model snapshot `MSAViewer` creates from its props. The CLI's `--props`
|
|
71
|
+
* reads the same JSON the R and Python widgets send, so a figure exported
|
|
72
|
+
* headlessly draws what the widget drew.
|
|
73
|
+
*/
|
|
74
|
+
export function viewerSnapshot(props: MSAViewerProps) {
|
|
75
|
+
const {
|
|
76
|
+
msa,
|
|
77
|
+
tree,
|
|
78
|
+
gff,
|
|
79
|
+
rowData,
|
|
80
|
+
colorScheme,
|
|
81
|
+
height,
|
|
82
|
+
colWidth,
|
|
83
|
+
rowHeight,
|
|
84
|
+
allowedGappyness,
|
|
85
|
+
highlightColumns,
|
|
86
|
+
highlights,
|
|
87
|
+
features,
|
|
88
|
+
selection,
|
|
89
|
+
clades,
|
|
90
|
+
relativeTo,
|
|
91
|
+
columnTracks,
|
|
92
|
+
residueMappings,
|
|
93
|
+
encodings,
|
|
94
|
+
rowPanels,
|
|
95
|
+
drawTree,
|
|
96
|
+
treeAreaWidth,
|
|
97
|
+
showBranchLen,
|
|
98
|
+
treeOrder,
|
|
99
|
+
treeRoot,
|
|
100
|
+
region,
|
|
101
|
+
residueEncoding,
|
|
102
|
+
} = props
|
|
103
|
+
const { msaFilehandle, treeFilehandle, gffFilehandle } = fileLocations(props)
|
|
104
|
+
return {
|
|
105
|
+
type: 'MsaView' as const,
|
|
106
|
+
...(msa || tree || gff || rowData
|
|
107
|
+
? {
|
|
108
|
+
data: {
|
|
109
|
+
msa: msa ?? '',
|
|
110
|
+
tree: tree ?? '',
|
|
111
|
+
gff,
|
|
112
|
+
// the row table is stored as this JSON string, which is what a
|
|
113
|
+
// shared URL and the size limit already cover
|
|
114
|
+
...(rowData ? { treeMetadata: JSON.stringify(rowData) } : {}),
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
: {}),
|
|
118
|
+
...(msaFilehandle ? { msaFilehandle } : {}),
|
|
119
|
+
...(treeFilehandle ? { treeFilehandle } : {}),
|
|
120
|
+
...(gffFilehandle ? { gffFilehandle } : {}),
|
|
121
|
+
...colorSchemeSnapshot(colorScheme),
|
|
122
|
+
...(height ? { height } : {}),
|
|
123
|
+
...(colWidth ? { colWidth } : {}),
|
|
124
|
+
...(rowHeight ? { rowHeight } : {}),
|
|
125
|
+
...(allowedGappyness !== undefined ? { allowedGappyness } : {}),
|
|
126
|
+
...(highlights || highlightColumns
|
|
127
|
+
? { highlights: viewerHighlights({ highlights, highlightColumns }) }
|
|
128
|
+
: {}),
|
|
129
|
+
...(features ? { features } : {}),
|
|
130
|
+
...(selection ? { selection } : {}),
|
|
131
|
+
...(clades ? { clades } : {}),
|
|
132
|
+
...(relativeTo ? { relativeTo } : {}),
|
|
133
|
+
...(columnTracks ? { columnTracks } : {}),
|
|
134
|
+
...(residueMappings ? { residueMappings } : {}),
|
|
135
|
+
...(encodings ? { encodings } : {}),
|
|
136
|
+
...(rowPanels ? { rowPanels } : {}),
|
|
137
|
+
...(drawTree !== undefined ? { drawTree } : {}),
|
|
138
|
+
...(treeAreaWidth ? { treeAreaWidth } : {}),
|
|
139
|
+
...(showBranchLen !== undefined ? { showBranchLen } : {}),
|
|
140
|
+
...(treeOrder ? { treeOrder } : {}),
|
|
141
|
+
...(treeRoot ? { treeRoot } : {}),
|
|
142
|
+
...(region ? { region } : {}),
|
|
143
|
+
...(residueEncoding !== undefined
|
|
144
|
+
? { bgColor: residueEncoding === 'fill' }
|
|
145
|
+
: {}),
|
|
146
|
+
}
|
|
147
|
+
}
|
package/src/wheelZoom.ts
CHANGED
|
@@ -1,47 +1,69 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
// pinches and coarse mouse notches.
|
|
1
|
+
// Chrome and Firefox encode a trackpad pinch as ctrl+wheel with
|
|
2
|
+
// deltaY = -100 * ln(scale), so dividing by 100 tracks the fingers 1:1
|
|
3
|
+
const PINCH_PX_PER_LOG = 100
|
|
5
4
|
|
|
6
|
-
//
|
|
7
|
-
|
|
5
|
+
// a plain scroll covers more pixels than a pinch for the same intent
|
|
6
|
+
const SCROLL_PX_PER_LOG = 400
|
|
8
7
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
// a pixel delta at least this large counts as a wheel notch
|
|
9
|
+
const NOTCH_MIN_PX = 40
|
|
10
|
+
|
|
11
|
+
export const NOTCH_ZOOM = 1.25
|
|
12
|
+
|
|
13
|
+
// the time constant a notch eases in over
|
|
14
|
+
const NOTCH_TAU_MS = 60
|
|
15
|
+
|
|
16
|
+
// a burst of continuous events coalesced into one frame applies at most 2x
|
|
17
|
+
const MAX_FRAME_LOG = Math.log(2)
|
|
18
|
+
|
|
19
|
+
// the queue holds at most four notches, which caps a fast spin
|
|
20
|
+
const MAX_PENDING_LOG = 4 * Math.log(NOTCH_ZOOM)
|
|
12
21
|
|
|
13
|
-
// Convert a wheel delta to pixels regardless of the browser's deltaMode; some
|
|
14
|
-
// browsers/mice report lines or pages instead of pixels.
|
|
15
22
|
export function normalizeWheelDelta(delta: number, mode: number) {
|
|
16
23
|
if (mode === 1) {
|
|
17
|
-
return delta * 16
|
|
24
|
+
return delta * 16
|
|
18
25
|
}
|
|
19
26
|
if (mode === 2) {
|
|
20
|
-
return delta * 100
|
|
27
|
+
return delta * 100
|
|
21
28
|
}
|
|
22
|
-
return delta
|
|
29
|
+
return delta
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const clampAbs = (x: number, max: number) => Math.max(-max, Math.min(max, x))
|
|
33
|
+
|
|
34
|
+
// Positive log zooms in. Feed every wheel event to add() and call step() once
|
|
35
|
+
// per animation frame with the time since the last frame; step() returns the
|
|
36
|
+
// multiplier for colWidth/rowHeight.
|
|
37
|
+
export class WheelZoomSmoother {
|
|
38
|
+
private continuous = 0
|
|
39
|
+
private pending = 0
|
|
40
|
+
|
|
41
|
+
add(deltaY: number, deltaMode: number, isPinch: boolean) {
|
|
42
|
+
const px = normalizeWheelDelta(deltaY, deltaMode)
|
|
43
|
+
if (deltaMode !== 0 || Math.abs(px) >= NOTCH_MIN_PX) {
|
|
44
|
+
const notches =
|
|
45
|
+
deltaMode !== 0 ? 1 : Math.min(Math.max(Math.abs(px) / 100, 1), 3)
|
|
46
|
+
this.pending = clampAbs(
|
|
47
|
+
this.pending - Math.sign(px) * notches * Math.log(NOTCH_ZOOM),
|
|
48
|
+
MAX_PENDING_LOG,
|
|
49
|
+
)
|
|
50
|
+
} else {
|
|
51
|
+
this.continuous -= px / (isPinch ? PINCH_PX_PER_LOG : SCROLL_PX_PER_LOG)
|
|
52
|
+
}
|
|
34
53
|
}
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
|
|
55
|
+
get idle() {
|
|
56
|
+
return this.continuous === 0 && this.pending === 0
|
|
37
57
|
}
|
|
38
|
-
return 75
|
|
39
|
-
}
|
|
40
58
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
step(elapsedMs: number) {
|
|
60
|
+
let eased = this.pending * (1 - Math.exp(-elapsedMs / NOTCH_TAU_MS))
|
|
61
|
+
if (Math.abs(this.pending - eased) < 0.005) {
|
|
62
|
+
eased = this.pending
|
|
63
|
+
}
|
|
64
|
+
this.pending -= eased
|
|
65
|
+
const log = clampAbs(this.continuous + eased, MAX_FRAME_LOG)
|
|
66
|
+
this.continuous = 0
|
|
67
|
+
return Math.exp(log)
|
|
68
|
+
}
|
|
47
69
|
}
|