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,269 @@
|
|
|
1
|
+
import { notEmpty } from '@jbrowse/core/util'
|
|
2
|
+
|
|
3
|
+
import { packDomainLanes } from './components/msa/packDomainLanes.ts'
|
|
4
|
+
import { defaultFeaturePanelWidth } from './constants.ts'
|
|
5
|
+
import { featureField } from './featureFields.ts'
|
|
6
|
+
import { resolveScale } from './scales.ts'
|
|
7
|
+
import { outlineColor } from './util.ts'
|
|
8
|
+
|
|
9
|
+
import type { ScaleSpec } from './scales.ts'
|
|
10
|
+
import type {
|
|
11
|
+
Annotation,
|
|
12
|
+
DomainBand,
|
|
13
|
+
RowFeaturesSpec,
|
|
14
|
+
RowPanelSpan,
|
|
15
|
+
RowPanelSpec,
|
|
16
|
+
} from './types.ts'
|
|
17
|
+
|
|
18
|
+
// the fill of a span under a scale that gives its value no color, such as a
|
|
19
|
+
// gene a `map` over gene names leaves out
|
|
20
|
+
const UNSCALED_FEATURE = '#d9d9d9'
|
|
21
|
+
|
|
22
|
+
// the fraction of a span a following one may cover before it takes a lane of
|
|
23
|
+
// its own in a `features` panel
|
|
24
|
+
const laneOverlap = 0.1
|
|
25
|
+
|
|
26
|
+
// a row panel's own width, or a default: the row height, which makes a strip
|
|
27
|
+
// cell square, and a gene neighborhood's width for a features panel. The row
|
|
28
|
+
// height is read only when used, so a panel with a width of its own does not
|
|
29
|
+
// recompute on a vertical zoom
|
|
30
|
+
export function rowPanelWidth(
|
|
31
|
+
panel: RowPanelSpec,
|
|
32
|
+
view: { rowHeight: number },
|
|
33
|
+
) {
|
|
34
|
+
return (
|
|
35
|
+
panel.width ??
|
|
36
|
+
(panel.kind === 'features' ? defaultFeaturePanelWidth : view.rowHeight)
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// one row of the row table as the channels read it. A number or a boolean
|
|
41
|
+
// reads as its string, the way a GFF value does, and null or an object drops
|
|
42
|
+
export function stringFields(row: unknown) {
|
|
43
|
+
if (typeof row !== 'object' || row === null) {
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
const fields: Record<string, string> = {}
|
|
47
|
+
for (const [field, value] of Object.entries(row)) {
|
|
48
|
+
if (typeof value === 'string') {
|
|
49
|
+
fields[field] = value
|
|
50
|
+
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
51
|
+
fields[field] = String(value)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return fields
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// the color each row takes from a field of the row table under a scale
|
|
58
|
+
export function colorByRow(
|
|
59
|
+
rowData: Record<string, Record<string, string> | undefined>,
|
|
60
|
+
field: string,
|
|
61
|
+
colorOf: (value: string) => string | undefined,
|
|
62
|
+
) {
|
|
63
|
+
const colors = new Map<string, string>()
|
|
64
|
+
for (const [name, row] of Object.entries(rowData)) {
|
|
65
|
+
const value = row?.[field]
|
|
66
|
+
const color = value === undefined ? undefined : colorOf(value)
|
|
67
|
+
if (color) {
|
|
68
|
+
colors.set(name, color)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return colors
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// the fill and outline of every feature in a list: its own GFF `color=` first,
|
|
75
|
+
// then the scale an encoding resolves over one of its fields, then the
|
|
76
|
+
// accession palette
|
|
77
|
+
export function featureColorMap(
|
|
78
|
+
annotations: Annotation[],
|
|
79
|
+
encoding:
|
|
80
|
+
| { field: string; colorOf: (value: string) => string | undefined }
|
|
81
|
+
| undefined,
|
|
82
|
+
fillPalette: Record<string, string>,
|
|
83
|
+
) {
|
|
84
|
+
const strokes = new Map<string, string>()
|
|
85
|
+
const strokeOf = (fill: string) => {
|
|
86
|
+
const hit = strokes.get(fill)
|
|
87
|
+
if (hit !== undefined) {
|
|
88
|
+
return hit
|
|
89
|
+
}
|
|
90
|
+
const stroke = outlineColor(fill)
|
|
91
|
+
strokes.set(fill, stroke)
|
|
92
|
+
return stroke
|
|
93
|
+
}
|
|
94
|
+
return new Map(
|
|
95
|
+
annotations.map(annotation => {
|
|
96
|
+
const value = encoding
|
|
97
|
+
? featureField(annotation, encoding.field)
|
|
98
|
+
: undefined
|
|
99
|
+
const fill =
|
|
100
|
+
annotation.color ??
|
|
101
|
+
(value === undefined ? undefined : encoding!.colorOf(value)) ??
|
|
102
|
+
(encoding ? UNSCALED_FEATURE : fillPalette[annotation.accession]!)
|
|
103
|
+
return [annotation, { fill, stroke: strokeOf(fill) }]
|
|
104
|
+
}),
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// the text a label channel draws inside each span, for the features carrying
|
|
109
|
+
// the field it names
|
|
110
|
+
export function featureLabelMap(annotations: Annotation[], field: string) {
|
|
111
|
+
const labels = new Map<Annotation, string>()
|
|
112
|
+
for (const annotation of annotations) {
|
|
113
|
+
const value = featureField(annotation, field)
|
|
114
|
+
if (value !== undefined) {
|
|
115
|
+
labels.set(annotation, value)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return labels
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** what `packDomainLanes` needs to lay a row out, before it has its lane */
|
|
122
|
+
type Unlaned = Omit<RowPanelSpan, 'lane' | 'laneCount'> & {
|
|
123
|
+
startCol: number
|
|
124
|
+
endCol: number
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Lanes for `position: "strandpile"`, gggenomes' `position_strandpile`: the
|
|
129
|
+
* forward features stack above the line and the reverse below it, each strand
|
|
130
|
+
* packing on its own. The deepest row on each side sets the grid every row lays
|
|
131
|
+
* out on, so the line between the strands sits at one height down the panel and
|
|
132
|
+
* a reader can scan it. A feature with no strand piles with the forward ones.
|
|
133
|
+
*/
|
|
134
|
+
function strandpileLanes(
|
|
135
|
+
rows: [string, Unlaned[]][],
|
|
136
|
+
): Map<string, RowPanelSpan[]> {
|
|
137
|
+
const piled = rows.map(([name, bands]) => {
|
|
138
|
+
const reverse = packDomainLanes(
|
|
139
|
+
bands.filter(b => b.annotation.strand === -1),
|
|
140
|
+
)
|
|
141
|
+
const forward = packDomainLanes(
|
|
142
|
+
bands.filter(b => b.annotation.strand !== -1),
|
|
143
|
+
)
|
|
144
|
+
return { name, forward, reverse }
|
|
145
|
+
})
|
|
146
|
+
const up = Math.max(0, ...piled.map(p => p.forward[0]?.laneCount ?? 0))
|
|
147
|
+
const down = Math.max(0, ...piled.map(p => p.reverse[0]?.laneCount ?? 0))
|
|
148
|
+
const laneCount = up + down
|
|
149
|
+
return new Map(
|
|
150
|
+
piled.map(({ name, forward, reverse }) => [
|
|
151
|
+
name,
|
|
152
|
+
[
|
|
153
|
+
// forward level 0 is the lane just above the line, stacking upward
|
|
154
|
+
...forward.map(b => ({ ...b, lane: up - 1 - b.lane, laneCount })),
|
|
155
|
+
...reverse.map(b => ({ ...b, lane: up + b.lane, laneCount })),
|
|
156
|
+
],
|
|
157
|
+
]),
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** one row's spans laid out by the panel's `position` */
|
|
162
|
+
function panelLanes(
|
|
163
|
+
panel: RowFeaturesSpec,
|
|
164
|
+
rows: [string, Unlaned[]][],
|
|
165
|
+
): Map<string, RowPanelSpan[]> {
|
|
166
|
+
return panel.position === 'strandpile'
|
|
167
|
+
? strandpileLanes(rows)
|
|
168
|
+
: new Map(rows.map(([name, bands]) => [name, packDomainLanes(bands)]))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The spans a `features` panel draws, keyed by row name and measured in the
|
|
173
|
+
* panel's own pixels. `column` takes the bands the overlay draws, at the
|
|
174
|
+
* alignment's column width. `position` packs each row's features in its own
|
|
175
|
+
* residue positions, shifted by an `align` transform, and maps the extent they
|
|
176
|
+
* cover across every row onto the panel width, so a row with no alignment
|
|
177
|
+
* still has an x.
|
|
178
|
+
*/
|
|
179
|
+
export function featurePanelSpans({
|
|
180
|
+
panel,
|
|
181
|
+
width,
|
|
182
|
+
colWidth,
|
|
183
|
+
domainBands,
|
|
184
|
+
annotationsByRow,
|
|
185
|
+
shifts,
|
|
186
|
+
}: {
|
|
187
|
+
panel: RowFeaturesSpec
|
|
188
|
+
width: number
|
|
189
|
+
colWidth: number
|
|
190
|
+
domainBands: Map<string, DomainBand[]>
|
|
191
|
+
annotationsByRow: Record<string, Annotation[]>
|
|
192
|
+
shifts: Map<string, number> | undefined
|
|
193
|
+
}): Map<string, RowPanelSpan[]> {
|
|
194
|
+
if (panel.x === 'column') {
|
|
195
|
+
return panelLanes(
|
|
196
|
+
panel,
|
|
197
|
+
[...domainBands].map(([name, bands]) => [
|
|
198
|
+
name,
|
|
199
|
+
bands.map(band => ({
|
|
200
|
+
...band,
|
|
201
|
+
xStart: band.startCol * colWidth,
|
|
202
|
+
xEnd: band.endCol * colWidth,
|
|
203
|
+
})),
|
|
204
|
+
]),
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
const shifted = Object.entries(annotationsByRow).map(
|
|
208
|
+
([name, annotations]) => {
|
|
209
|
+
const shift = shifts?.get(name) ?? 0
|
|
210
|
+
return [
|
|
211
|
+
name,
|
|
212
|
+
annotations.map(annotation => ({
|
|
213
|
+
annotation,
|
|
214
|
+
start: annotation.start - 1 + shift,
|
|
215
|
+
end: annotation.end + shift,
|
|
216
|
+
})),
|
|
217
|
+
] as const
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
let min = Infinity
|
|
221
|
+
let max = -Infinity
|
|
222
|
+
for (const [, features] of shifted) {
|
|
223
|
+
for (const { start, end } of features) {
|
|
224
|
+
min = Math.min(min, start)
|
|
225
|
+
max = Math.max(max, end)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// a gene arrow ends where its feature does, so the extent maps onto the whole
|
|
229
|
+
// panel less the pixel the rightmost stroke needs
|
|
230
|
+
const drawable = Math.max(1, width - 1)
|
|
231
|
+
const scale = max > min ? drawable / (max - min) : 0
|
|
232
|
+
// a lane opens where a span covers more than a tenth of the one before it,
|
|
233
|
+
// which keeps the genes of an operon on one lane: adjacent genes commonly
|
|
234
|
+
// share a few bases, and a stop codon overlapping the next start reads as a
|
|
235
|
+
// second lane over the whole row
|
|
236
|
+
return panelLanes(
|
|
237
|
+
panel,
|
|
238
|
+
shifted.map(([name, features]) => [
|
|
239
|
+
name,
|
|
240
|
+
features.map(({ annotation, start, end }) => ({
|
|
241
|
+
annotation,
|
|
242
|
+
xStart: (start - min) * scale,
|
|
243
|
+
xEnd: (end - min) * scale,
|
|
244
|
+
startCol: start,
|
|
245
|
+
endCol: end - (end - start) * laneOverlap,
|
|
246
|
+
})),
|
|
247
|
+
]),
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// a scale over the values a field takes across every feature loaded, so hiding
|
|
252
|
+
// a type in the filter dialog leaves the rest in their colors, and a legend of
|
|
253
|
+
// the values the features drawn carry
|
|
254
|
+
export function resolveFeatureScale(
|
|
255
|
+
field: string,
|
|
256
|
+
scale: ScaleSpec | undefined,
|
|
257
|
+
annotations: Annotation[],
|
|
258
|
+
drawn: Annotation[],
|
|
259
|
+
) {
|
|
260
|
+
const valuesOf = (list: Annotation[]) =>
|
|
261
|
+
list.map(a => featureField(a, field)).filter(notEmpty)
|
|
262
|
+
const resolved = resolveScale(scale, valuesOf(annotations))
|
|
263
|
+
const shown = new Set(valuesOf(drawn))
|
|
264
|
+
return {
|
|
265
|
+
field,
|
|
266
|
+
...resolved,
|
|
267
|
+
legend: resolved.legend.filter(entry => shown.has(entry.id)),
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { openLocation } from '@jbrowse/core/util/io'
|
|
2
|
+
import { addDisposer, isAlive } from '@jbrowse/mobx-state-tree'
|
|
3
|
+
import { autorun, transaction } from 'mobx'
|
|
4
|
+
|
|
5
|
+
import { fetchTextWithProgress, isAbortError } from './fetchUtils.ts'
|
|
6
|
+
|
|
7
|
+
import type { FetchStatus } from './fetchUtils.ts'
|
|
8
|
+
import type { FileLocation } from '@jbrowse/core/util/types'
|
|
9
|
+
import type { IStateTreeNode } from '@jbrowse/mobx-state-tree'
|
|
10
|
+
|
|
11
|
+
interface LoaderHost extends IStateTreeNode {
|
|
12
|
+
setStatus: (status?: FetchStatus) => void
|
|
13
|
+
setError: (error?: unknown) => void
|
|
14
|
+
addWarning: (warning: string) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface FilehandleLoader {
|
|
18
|
+
/** the layer's name in a failure message */
|
|
19
|
+
what: string
|
|
20
|
+
getFilehandle: () => FileLocation | undefined
|
|
21
|
+
onLoad: (text: string) => void
|
|
22
|
+
setLoading?: (loading: boolean) => void
|
|
23
|
+
/** runs after a local file loads, since a blob has no URL to refetch, and on cancel */
|
|
24
|
+
clearFilehandle?: () => void
|
|
25
|
+
/** a failure adds a warning instead of an error */
|
|
26
|
+
optional?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Latest-wins abort rotation, after `createAbortRotation` in
|
|
31
|
+
* jbrowse-components. Each `begin()` aborts the previous fetch and returns a
|
|
32
|
+
* signal with an `isCurrent()` guard; gate every write after an await on it so a
|
|
33
|
+
* superseded or torn-down fetch cannot overwrite fresher data. `end()` in the
|
|
34
|
+
* `finally` closes the guard of a fetch that completed.
|
|
35
|
+
*/
|
|
36
|
+
export function createAbortRotation(self: IStateTreeNode) {
|
|
37
|
+
let current: { controller: AbortController; ended: boolean } | undefined
|
|
38
|
+
return {
|
|
39
|
+
/** aborts the running fetch, and returns whether one was running */
|
|
40
|
+
cancel() {
|
|
41
|
+
const running = current !== undefined && !current.ended
|
|
42
|
+
current?.controller.abort()
|
|
43
|
+
current = undefined
|
|
44
|
+
return running
|
|
45
|
+
},
|
|
46
|
+
begin() {
|
|
47
|
+
current?.controller.abort()
|
|
48
|
+
const mine = { controller: new AbortController(), ended: false }
|
|
49
|
+
current = mine
|
|
50
|
+
return {
|
|
51
|
+
controller: mine.controller,
|
|
52
|
+
isCurrent: () => !mine.ended && mine === current && isAlive(self),
|
|
53
|
+
end() {
|
|
54
|
+
mine.ended = true
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Fetches the filehandle whenever it changes and hands the text to `onLoad`. A
|
|
63
|
+
* superseded or cleared request is aborted and clears its status line.
|
|
64
|
+
*/
|
|
65
|
+
export function installFilehandleLoader(
|
|
66
|
+
self: LoaderHost,
|
|
67
|
+
{
|
|
68
|
+
what,
|
|
69
|
+
getFilehandle,
|
|
70
|
+
onLoad,
|
|
71
|
+
setLoading,
|
|
72
|
+
clearFilehandle,
|
|
73
|
+
optional = false,
|
|
74
|
+
}: FilehandleLoader,
|
|
75
|
+
) {
|
|
76
|
+
const rotation = createAbortRotation(self)
|
|
77
|
+
const cancel = () => {
|
|
78
|
+
if (rotation.cancel() && isAlive(self)) {
|
|
79
|
+
self.setStatus(undefined)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
addDisposer(self, cancel)
|
|
83
|
+
addDisposer(
|
|
84
|
+
self,
|
|
85
|
+
autorun(async () => {
|
|
86
|
+
const filehandle = getFilehandle()
|
|
87
|
+
cancel()
|
|
88
|
+
if (!filehandle) {
|
|
89
|
+
setLoading?.(false)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
const { controller, isCurrent, end } = rotation.begin()
|
|
93
|
+
try {
|
|
94
|
+
setLoading?.(true)
|
|
95
|
+
if (!optional) {
|
|
96
|
+
self.setError(undefined)
|
|
97
|
+
}
|
|
98
|
+
const text = await fetchTextWithProgress(
|
|
99
|
+
openLocation(filehandle),
|
|
100
|
+
status => {
|
|
101
|
+
if (isCurrent()) {
|
|
102
|
+
self.setStatus(status)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{ controller },
|
|
106
|
+
)
|
|
107
|
+
if (isCurrent()) {
|
|
108
|
+
transaction(() => {
|
|
109
|
+
onLoad(text)
|
|
110
|
+
if (filehandle.locationType === 'BlobLocation') {
|
|
111
|
+
clearFilehandle?.()
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
} catch (e) {
|
|
116
|
+
if (isCurrent()) {
|
|
117
|
+
if (isAbortError(e)) {
|
|
118
|
+
clearFilehandle?.()
|
|
119
|
+
} else {
|
|
120
|
+
console.error(e)
|
|
121
|
+
if (optional) {
|
|
122
|
+
self.addWarning(`The ${what} did not load: ${e}`)
|
|
123
|
+
} else {
|
|
124
|
+
self.setError(e)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
if (isCurrent()) {
|
|
130
|
+
setLoading?.(false)
|
|
131
|
+
}
|
|
132
|
+
end()
|
|
133
|
+
}
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
}
|
package/src/headlessRenderEnv.ts
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* leaves the rest undefined -- every emitted transform then carries `NaN` in its
|
|
13
13
|
* x components, which no assertion on a drawn x-position can survive.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
import { CHAR_WIDTH_RATIO } from './measureTextCanvas.ts'
|
|
16
|
+
|
|
17
|
+
class HeadlessDOMMatrix {
|
|
16
18
|
a: number
|
|
17
19
|
b: number
|
|
18
20
|
c: number
|
|
@@ -50,7 +52,7 @@ export class HeadlessDOMMatrix {
|
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
class HeadlessDOMPoint {
|
|
54
56
|
constructor(
|
|
55
57
|
public x = 0,
|
|
56
58
|
public y = 0,
|
|
@@ -69,10 +71,11 @@ export class HeadlessDOMPoint {
|
|
|
69
71
|
* its own jsdom passes that window instead, because Node has no global
|
|
70
72
|
* HTMLCanvasElement to patch.
|
|
71
73
|
*
|
|
72
|
-
* `measureText` reports 0.6em per character, close
|
|
73
|
-
* average for layout and
|
|
74
|
-
* compute the width a renderer will see
|
|
75
|
-
*
|
|
74
|
+
* Without `measureContext`, `measureText` reports 0.6em per character, close
|
|
75
|
+
* enough to a real sans-serif average for layout and exactly predictable: a
|
|
76
|
+
* test can compute the width a renderer will see. A caller with a real canvas,
|
|
77
|
+
* such as react-msaview-cli with @napi-rs/canvas, passes one of its 2D
|
|
78
|
+
* contexts as `measureContext` to measure with the installed fonts.
|
|
76
79
|
*
|
|
77
80
|
* `contextExtras` adds to the context a test hands out -- the raster test
|
|
78
81
|
* supplies the ImageData methods that make the export take its image path.
|
|
@@ -80,6 +83,7 @@ export class HeadlessDOMPoint {
|
|
|
80
83
|
export function installHeadlessRenderEnv(
|
|
81
84
|
win: unknown = globalThis,
|
|
82
85
|
contextExtras: Partial<CanvasRenderingContext2D> = {},
|
|
86
|
+
measureContext?: MeasureContext,
|
|
83
87
|
) {
|
|
84
88
|
const g = globalThis as Record<string, unknown>
|
|
85
89
|
g.DOMMatrix = HeadlessDOMMatrix
|
|
@@ -96,14 +100,23 @@ export function installHeadlessRenderEnv(
|
|
|
96
100
|
set font(v: string) {
|
|
97
101
|
font = v
|
|
98
102
|
},
|
|
99
|
-
measureText: (t: string) =>
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
measureText: (t: string) => {
|
|
104
|
+
if (measureContext) {
|
|
105
|
+
measureContext.font = font
|
|
106
|
+
return { width: measureContext.measureText(t).width }
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
width:
|
|
110
|
+
t.length * (Number.parseFloat(font) || 10) * CHAR_WIDTH_RATIO,
|
|
111
|
+
}
|
|
112
|
+
},
|
|
102
113
|
},
|
|
103
114
|
contextExtras,
|
|
104
115
|
) as unknown as CanvasRenderingContext2D
|
|
105
116
|
} as unknown as typeof HTMLCanvasElement.prototype.getContext
|
|
106
117
|
}
|
|
107
118
|
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
export interface MeasureContext {
|
|
120
|
+
font: string
|
|
121
|
+
measureText(text: string): { width: number }
|
|
122
|
+
}
|
package/src/hierarchy.ts
CHANGED
|
@@ -61,56 +61,6 @@ export function hierarchy<T>(
|
|
|
61
61
|
return coreHierarchy(data, childrenAccessor)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export function clusterLayout<T>(
|
|
65
|
-
root: HierarchyNode<T>,
|
|
66
|
-
sizeX: number,
|
|
67
|
-
sizeY: number,
|
|
68
|
-
) {
|
|
69
|
-
const leafNodes = leaves(root)
|
|
70
|
-
const n = leafNodes.length
|
|
71
|
-
const step = sizeX / n
|
|
72
|
-
|
|
73
|
-
for (let i = 0; i < n; i++) {
|
|
74
|
-
leafNodes[i]!.x = (i + 0.5) * step
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// x of an internal node is the mean of its children's x, so process in
|
|
78
|
-
// post-order (children before parents). The subtree span comes from the same
|
|
79
|
-
// pass, for the block culls in the tree renderer.
|
|
80
|
-
const nodes = descendants(root)
|
|
81
|
-
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
82
|
-
const node = nodes[i]!
|
|
83
|
-
if (node.children) {
|
|
84
|
-
let sum = 0
|
|
85
|
-
let min = Infinity
|
|
86
|
-
let max = -Infinity
|
|
87
|
-
for (const child of node.children) {
|
|
88
|
-
sum += child.x!
|
|
89
|
-
min = Math.min(min, child.xMin!)
|
|
90
|
-
max = Math.max(max, child.xMax!)
|
|
91
|
-
}
|
|
92
|
-
node.x = sum / node.children.length
|
|
93
|
-
node.xMin = min
|
|
94
|
-
node.xMax = max
|
|
95
|
-
} else {
|
|
96
|
-
node.xMin = node.x
|
|
97
|
-
node.xMax = node.x
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const rootHeight = root.height
|
|
102
|
-
const stack = [{ node: root, depth: 0 }]
|
|
103
|
-
while (stack.length > 0) {
|
|
104
|
-
const { node, depth } = stack.pop()!
|
|
105
|
-
node.y = rootHeight === 0 ? sizeY : (depth / rootHeight) * sizeY
|
|
106
|
-
if (node.children) {
|
|
107
|
-
for (const child of node.children) {
|
|
108
|
-
stack.push({ node: child, depth: depth + 1 })
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
64
|
export interface TreeScale {
|
|
115
65
|
readonly rowHeight: number
|
|
116
66
|
readonly treeWidth: number
|
|
@@ -186,10 +136,10 @@ export class LaidOutNode<T> implements HierarchyNode<T> {
|
|
|
186
136
|
}
|
|
187
137
|
|
|
188
138
|
/**
|
|
189
|
-
* The
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
139
|
+
* The tree laid out on new nodes: tips one row apart, a parent at the mean of
|
|
140
|
+
* its children, and branch length measured from the root, whose own branch is
|
|
141
|
+
* not drawn. A collapsed node records the farthest tip of its hidden subtree,
|
|
142
|
+
* where the base of its triangle goes.
|
|
193
143
|
*/
|
|
194
144
|
export function layoutTree<T extends { length?: number }>(
|
|
195
145
|
root: HierarchyNode<T>,
|
|
@@ -329,39 +279,6 @@ export function calcDepthToLeaf<T>(node: HierarchyNode<T>): number {
|
|
|
329
279
|
return node.depthToLeaf!
|
|
330
280
|
}
|
|
331
281
|
|
|
332
|
-
// Max root-to-leaf sum of branch lengths within the subtree at d. Negative
|
|
333
|
-
// lengths are floored at zero, the same as setBrLength does when it lays the
|
|
334
|
-
// tree out, so the extent this reports is the one that gets drawn
|
|
335
|
-
export function maxLength(d: HierarchyNode): number {
|
|
336
|
-
const nodes = descendants(d)
|
|
337
|
-
const pathLen = new Map<HierarchyNode, number>()
|
|
338
|
-
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
339
|
-
const n = nodes[i]!
|
|
340
|
-
let childMax = 0
|
|
341
|
-
if (n.children) {
|
|
342
|
-
for (const child of n.children) {
|
|
343
|
-
childMax = Math.max(childMax, pathLen.get(child)!)
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
pathLen.set(n, Math.max(n.data.length || 0, 0) + childMax)
|
|
347
|
-
}
|
|
348
|
-
return pathLen.get(d)!
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
export function setBrLength(d: HierarchyNode, y0: number, k: number) {
|
|
352
|
-
const stack = [{ node: d, y0 }]
|
|
353
|
-
while (stack.length > 0) {
|
|
354
|
-
const { node, y0: acc } = stack.pop()!
|
|
355
|
-
const next = acc + Math.max(node.data.length || 0, 0)
|
|
356
|
-
node.len = next * k
|
|
357
|
-
if (node.children) {
|
|
358
|
-
for (const child of node.children) {
|
|
359
|
-
stack.push({ node: child, y0: next })
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
282
|
/**
|
|
366
283
|
* Every leaf by name, with a name two leaves share mapping to undefined: a tip
|
|
367
284
|
* set naming it cannot say which tip it means.
|
|
@@ -416,7 +333,7 @@ export function mrca<T extends { name: string }>(
|
|
|
416
333
|
|
|
417
334
|
/**
|
|
418
335
|
* The deepest node whose subtree covers a run of rows, given in the row-space
|
|
419
|
-
* coordinates
|
|
336
|
+
* coordinates layoutTree writes to xMin/xMax. A subtree's leaves are one
|
|
420
337
|
* contiguous run, so at most one child per level covers the run and the descent
|
|
421
338
|
* visits the tree's depth rather than its nodes.
|
|
422
339
|
*/
|
package/src/index.ts
CHANGED
|
@@ -11,10 +11,8 @@ export { renderToSvg } from './renderToSvg.tsx'
|
|
|
11
11
|
export type { ExportSvgOptions } from './renderToSvg.tsx'
|
|
12
12
|
// renderToSvg outside a browser needs DOM bits jsdom omits; react-msaview-cli
|
|
13
13
|
// and the README figure generator both drive it that way
|
|
14
|
-
export {
|
|
15
|
-
|
|
16
|
-
installHeadlessRenderEnv,
|
|
17
|
-
} from './headlessRenderEnv.ts'
|
|
14
|
+
export { installHeadlessRenderEnv } from './headlessRenderEnv.ts'
|
|
15
|
+
export { CHAR_WIDTH_RATIO } from './measureTextCanvas.ts'
|
|
18
16
|
// the row count above which calculateNeighborJoiningTreeFromMSA throws, for
|
|
19
17
|
// hosts gating their own menu item
|
|
20
18
|
export { maxNeighborJoiningRows } from './constants.ts'
|
|
@@ -27,8 +25,13 @@ export type {
|
|
|
27
25
|
RegionShorthand,
|
|
28
26
|
} from './expandSpec.ts'
|
|
29
27
|
export { default as MSAView } from './components/Loading.tsx'
|
|
28
|
+
export type { ToolbarSlots } from './components/header/Header.tsx'
|
|
30
29
|
export { default as MSAViewer } from './components/MSAViewer.tsx'
|
|
31
30
|
export type { MSAViewerProps } from './components/MSAViewer.tsx'
|
|
31
|
+
// the snapshot MSAViewer builds from its props, for the CLI's --props
|
|
32
|
+
export { resolveTheme, viewerSnapshot } from './viewerSnapshot.ts'
|
|
33
|
+
// the active scheme's hex color for a row residue, to paint a structure with
|
|
34
|
+
export { schemeResidueColor } from './schemeResidueColor.ts'
|
|
32
35
|
export { mount } from './mount.tsx'
|
|
33
36
|
export { defineMsaElement } from './element.ts'
|
|
34
37
|
export { useMsaSvgFigure } from './useMsaSvgFigure.ts'
|
|
@@ -37,7 +40,7 @@ export type { MountedViewer } from './mount.tsx'
|
|
|
37
40
|
export { type MsaViewModel, default as MSAModelF } from './model.ts'
|
|
38
41
|
export type { MSAFormat, MSAParserType } from 'msa-parsers'
|
|
39
42
|
export type { HierarchyNode, LaidOutNode, TreeScale } from './hierarchy.ts'
|
|
40
|
-
// InterProScanResults is the EBI wire format
|
|
43
|
+
// InterProScanResults is the EBI wire format interProScanToAnnotations reads;
|
|
41
44
|
// Annotation is the source-agnostic shape everything else is written against
|
|
42
45
|
export type { InterProScanResults } from 'msa-parsers'
|
|
43
46
|
// ColumnCounts, ColumnStats, DomainBand and TidyDomainAnnotation surface in the
|
|
@@ -47,6 +50,7 @@ export type { InterProScanResults } from 'msa-parsers'
|
|
|
47
50
|
// reason.
|
|
48
51
|
export type { ColumnCounts } from './columnCounts.ts'
|
|
49
52
|
export type { ColumnStats } from './columnStats.ts'
|
|
53
|
+
export type { ZoomSnapshot } from './zoomSnapshot.ts'
|
|
50
54
|
// the scale an encoding reads its field through, and what resolving one gives
|
|
51
55
|
export type { ResolvedScale, ScaleSpec } from './scales.ts'
|
|
52
56
|
export type {
|
package/src/measureTextCanvas.ts
CHANGED
|
@@ -16,16 +16,18 @@ function getCtx() {
|
|
|
16
16
|
return ctxHandle
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Rough average glyph width as a fraction of the font size for the sans-serif
|
|
21
|
+
* stack the labels use. measureTextCanvas falls back on it where there is no
|
|
22
|
+
* canvas to measure with (jsdom, SSR), and the headless render environment's
|
|
23
|
+
* `measureText` reports it.
|
|
24
|
+
*/
|
|
25
|
+
export const CHAR_WIDTH_RATIO = 0.6
|
|
24
26
|
|
|
25
27
|
export function measureTextCanvas(text: string, fontSize: number) {
|
|
26
28
|
const ctx = getCtx()
|
|
27
29
|
if (!ctx) {
|
|
28
|
-
return text.length * fontSize *
|
|
30
|
+
return text.length * fontSize * CHAR_WIDTH_RATIO
|
|
29
31
|
}
|
|
30
32
|
setFontSize(ctx, fontSize)
|
|
31
33
|
return ctx.measureText(text).width
|