react-msaview 7.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -10
- package/bundle/index.js +122 -111
- package/bundle/index.js.map +4 -4
- package/dist/colorSchemes.js +1 -2
- package/dist/colorSchemes.js.map +1 -1
- package/dist/columnStats.d.ts +28 -0
- package/dist/columnStats.js +34 -0
- package/dist/columnStats.js.map +1 -0
- package/dist/components/DragHandle.d.ts +3 -2
- package/dist/components/DragHandle.js +11 -5
- package/dist/components/DragHandle.js.map +1 -1
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +17 -6
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +64 -7
- package/dist/components/MSAViewer.js +128 -23
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/PortalTooltip.d.ts +11 -0
- package/dist/components/PortalTooltip.js +29 -0
- package/dist/components/PortalTooltip.js.map +1 -0
- package/dist/components/ResizeHandles.js +11 -29
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/SequenceTextArea.js +3 -3
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/Track.js +41 -22
- package/dist/components/Track.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +5 -3
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/TrackInfoDialog.js +1 -1
- package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
- package/dist/components/header/GappynessSlider.js +6 -11
- package/dist/components/header/GappynessSlider.js.map +1 -1
- package/dist/components/header/Header.js +3 -0
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/LoadWarnings.js +3 -4
- package/dist/components/header/LoadWarnings.js.map +1 -1
- package/dist/components/header/ScrollZoomIcons.d.ts +6 -0
- package/dist/components/header/ScrollZoomIcons.js +35 -0
- package/dist/components/header/ScrollZoomIcons.js.map +1 -0
- package/dist/components/header/TreeSettingsMenu.js +1 -3
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/UnshareableDataWarning.js +2 -4
- package/dist/components/header/UnshareableDataWarning.js.map +1 -1
- package/dist/components/header/ZoomControls.js +49 -6
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/getAnnotationsMenu.js +3 -6
- package/dist/components/header/getAnnotationsMenu.js.map +1 -1
- package/dist/components/header/settingsMenuItems.js +10 -7
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/util.js +1 -2
- package/dist/components/import/util.js.map +1 -1
- package/dist/components/minimap/Minimap.js +1 -2
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/msa/AnnotationLegend.js +27 -19
- package/dist/components/msa/AnnotationLegend.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +1 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +13 -15
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +2 -1
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/domainBandCursor.d.ts +7 -11
- package/dist/components/msa/domainBandCursor.js +7 -11
- package/dist/components/msa/domainBandCursor.js.map +1 -1
- package/dist/components/msa/drawFeatureSpans.d.ts +53 -0
- package/dist/components/msa/drawFeatureSpans.js +78 -0
- package/dist/components/msa/drawFeatureSpans.js.map +1 -0
- package/dist/components/msa/legendRows.d.ts +8 -0
- package/dist/components/msa/legendRows.js +16 -0
- package/dist/components/msa/legendRows.js.map +1 -0
- package/dist/components/msa/msaRaster.d.ts +24 -31
- package/dist/components/msa/msaRaster.js +30 -39
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/packDomainLanes.d.ts +20 -0
- package/dist/components/msa/packDomainLanes.js +33 -0
- package/dist/components/msa/packDomainLanes.js.map +1 -0
- package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +3 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +51 -87
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +15 -13
- package/dist/components/msa/renderHighlights.js +43 -20
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +5 -4
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/tileColor.js +4 -7
- package/dist/components/msa/tileColor.js.map +1 -1
- package/dist/components/msa/useMsaBlockMouse.d.ts +5 -3
- package/dist/components/msa/useMsaBlockMouse.js +6 -6
- package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.d.ts +9 -0
- package/dist/components/rowpanels/RowPanelBlock.js +86 -0
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -0
- package/dist/components/rowpanels/RowPanelHeaders.d.ts +12 -0
- package/dist/components/rowpanels/RowPanelHeaders.js +38 -0
- package/dist/components/rowpanels/RowPanelHeaders.js.map +1 -0
- package/dist/components/{msa/ColumnStats.d.ts → rowpanels/RowPanels.d.ts} +2 -2
- package/dist/components/rowpanels/RowPanels.js +32 -0
- package/dist/components/rowpanels/RowPanels.js.map +1 -0
- package/dist/components/rowpanels/headerLayout.d.ts +1 -0
- package/dist/components/rowpanels/headerLayout.js +5 -0
- package/dist/components/rowpanels/headerLayout.js.map +1 -0
- package/dist/components/rowpanels/renderFeaturePanel.d.ts +21 -0
- package/dist/components/rowpanels/renderFeaturePanel.js +37 -0
- package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -0
- package/dist/components/rowpanels/renderRowPanel.d.ts +23 -0
- package/dist/components/rowpanels/renderRowPanel.js +22 -0
- package/dist/components/rowpanels/renderRowPanel.js.map +1 -0
- package/dist/components/rowpanels/renderStrip.d.ts +20 -0
- package/dist/components/rowpanels/renderStrip.js +26 -0
- package/dist/components/rowpanels/renderStrip.js.map +1 -0
- package/dist/components/tracks/TrackBlocks.js +3 -5
- package/dist/components/tracks/TrackBlocks.js.map +1 -1
- package/dist/components/tracks/TrackTooltipContent.d.ts +15 -0
- package/dist/components/tracks/TrackTooltipContent.js +109 -0
- package/dist/components/tracks/TrackTooltipContent.js.map +1 -0
- package/dist/components/tracks/drawTracks.d.ts +4 -4
- package/dist/components/tracks/drawTracks.js +17 -34
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tracks/useTrackHover.d.ts +22 -0
- package/dist/components/tracks/useTrackHover.js +30 -0
- package/dist/components/tracks/useTrackHover.js.map +1 -0
- package/dist/components/tree/CladeLabels.d.ts +12 -0
- package/dist/components/tree/CladeLabels.js +48 -0
- package/dist/components/tree/CladeLabels.js.map +1 -0
- package/dist/components/tree/TreeCanvas.js +4 -1
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +4 -21
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeOverview.d.ts +14 -0
- package/dist/components/tree/TreeOverview.js +75 -0
- package/dist/components/tree/TreeOverview.js.map +1 -0
- package/dist/components/tree/TreeRuler.js +9 -13
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/cladeBrackets.d.ts +25 -0
- package/dist/components/tree/cladeBrackets.js +51 -0
- package/dist/components/tree/cladeBrackets.js.map +1 -0
- package/dist/components/tree/dialogs/TreeNodeInfoDialog.js +2 -2
- package/dist/components/tree/dialogs/TreeNodeInfoDialog.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +158 -24
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeOverview.d.ts +52 -0
- package/dist/components/tree/renderTreeOverview.js +138 -0
- package/dist/components/tree/renderTreeOverview.js.map +1 -0
- package/dist/components/tree/useTreeHover.js +2 -4
- package/dist/components/tree/useTreeHover.js.map +1 -1
- package/dist/constants.d.ts +15 -0
- package/dist/constants.js +48 -22
- package/dist/constants.js.map +1 -1
- package/dist/createPaletteMap.d.ts +7 -6
- package/dist/createPaletteMap.js +10 -9
- package/dist/createPaletteMap.js.map +1 -1
- package/dist/element.d.ts +13 -0
- package/dist/element.js +269 -0
- package/dist/element.js.map +1 -0
- package/dist/featureFields.d.ts +11 -0
- package/dist/featureFields.js +17 -0
- package/dist/featureFields.js.map +1 -0
- package/dist/flatToTree.js +1 -2
- package/dist/flatToTree.js.map +1 -1
- package/dist/ggplotPalettes.d.ts +10 -0
- package/dist/ggplotPalettes.js +58 -0
- package/dist/ggplotPalettes.js.map +1 -1
- package/dist/hierarchy.d.ts +27 -0
- package/dist/hierarchy.js +66 -9
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/model/DataModel.js +2 -5
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model/msaModel.d.ts +3 -2
- package/dist/model/msaModel.js +3 -2
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.d.ts +30 -0
- package/dist/model/treeModel.js +37 -1
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +531 -275
- package/dist/model.js +1295 -547
- package/dist/model.js.map +1 -1
- package/dist/mount.d.ts +11 -0
- package/dist/mount.js +22 -0
- package/dist/mount.js.map +1 -0
- package/dist/neighborJoining.js +8 -32
- package/dist/neighborJoining.js.map +1 -1
- package/dist/renderToStaticMarkup.d.ts +10 -19
- package/dist/renderToStaticMarkup.js +13 -24
- package/dist/renderToStaticMarkup.js.map +1 -1
- package/dist/renderToSvg.js +180 -56
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +4 -6
- package/dist/rowCoordinateCalculations.js +4 -6
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/scales.d.ts +22 -0
- package/dist/scales.js +30 -0
- package/dist/scales.js.map +1 -0
- package/dist/seqPosToGlobalCol.d.ts +2 -3
- package/dist/seqPosToGlobalCol.js +2 -3
- package/dist/seqPosToGlobalCol.js.map +1 -1
- package/dist/sequenceLogo.d.ts +6 -11
- package/dist/sequenceLogo.js +6 -11
- package/dist/sequenceLogo.js.map +1 -1
- package/dist/stripDefault.d.ts +7 -14
- package/dist/stripDefault.js +7 -14
- package/dist/stripDefault.js.map +1 -1
- package/dist/svgTestUtil.d.ts +172 -52
- package/dist/types.d.ts +219 -21
- package/dist/umd.d.ts +2 -0
- package/dist/umd.js +4 -2
- package/dist/umd.js.map +1 -1
- package/dist/useCanvasAutorun.d.ts +5 -10
- package/dist/useCanvasAutorun.js +5 -10
- package/dist/useCanvasAutorun.js.map +1 -1
- package/dist/useMsaSvgFigure.d.ts +20 -0
- package/dist/useMsaSvgFigure.js +46 -0
- package/dist/useMsaSvgFigure.js.map +1 -0
- package/dist/util.d.ts +5 -6
- package/dist/util.js +13 -6
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +19 -12
- package/src/colorSchemes.ts +1 -2
- package/src/columnStats.ts +58 -0
- package/src/components/DragHandle.tsx +18 -7
- package/src/components/Loading.tsx +2 -6
- package/src/components/MSAView.tsx +19 -5
- package/src/components/MSAViewer.tsx +240 -28
- package/src/components/PortalTooltip.tsx +47 -0
- package/src/components/ResizeHandles.tsx +17 -32
- package/src/components/SequenceTextArea.tsx +3 -3
- package/src/components/Track.tsx +55 -18
- package/src/components/dialogs/ExportSVGDialog.tsx +7 -2
- package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
- package/src/components/header/GappynessSlider.tsx +6 -11
- package/src/components/header/Header.tsx +6 -0
- package/src/components/header/LoadWarnings.tsx +3 -4
- package/src/components/header/ScrollZoomIcons.tsx +52 -0
- package/src/components/header/TreeSettingsMenu.tsx +1 -3
- package/src/components/header/UnshareableDataWarning.tsx +2 -4
- package/src/components/header/ZoomControls.tsx +74 -14
- package/src/components/header/getAnnotationsMenu.ts +3 -6
- package/src/components/header/settingsMenuItems.ts +11 -6
- package/src/components/import/ImportFormExamples.tsx +2 -2
- package/src/components/import/util.ts +1 -2
- package/src/components/minimap/Minimap.tsx +1 -2
- package/src/components/msa/AnnotationLegend.tsx +48 -38
- package/src/components/msa/MSACanvas.tsx +3 -5
- package/src/components/msa/MSACanvasBlock.tsx +13 -16
- package/src/components/msa/MSAMouseoverCanvas.tsx +2 -1
- package/src/components/msa/domainBandCursor.ts +7 -11
- package/src/components/msa/drawFeatureSpans.ts +150 -0
- package/src/components/msa/legendRows.ts +24 -0
- package/src/components/msa/msaRaster.ts +30 -39
- package/src/components/msa/packDomainLanes.ts +36 -0
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +69 -123
- package/src/components/msa/renderHighlights.ts +53 -20
- package/src/components/msa/renderMSABlock.ts +5 -3
- package/src/components/msa/tileColor.ts +4 -7
- package/src/components/msa/useMsaBlockMouse.ts +6 -6
- package/src/components/rowpanels/RowPanelBlock.tsx +139 -0
- package/src/components/rowpanels/RowPanelHeaders.tsx +57 -0
- package/src/components/rowpanels/RowPanels.tsx +77 -0
- package/src/components/rowpanels/headerLayout.ts +5 -0
- package/src/components/rowpanels/renderFeaturePanel.ts +66 -0
- package/src/components/rowpanels/renderRowPanel.ts +41 -0
- package/src/components/rowpanels/renderStrip.ts +46 -0
- package/src/components/tracks/TrackBlocks.tsx +3 -5
- package/src/components/tracks/TrackTooltipContent.tsx +156 -0
- package/src/components/tracks/drawTracks.ts +17 -34
- package/src/components/tracks/useTrackHover.ts +42 -0
- package/src/components/tree/CladeLabels.tsx +77 -0
- package/src/components/tree/TreeCanvas.tsx +2 -0
- package/src/components/tree/TreeCanvasBlock.tsx +8 -32
- package/src/components/tree/TreeOverview.tsx +106 -0
- package/src/components/tree/TreeRuler.tsx +11 -22
- package/src/components/tree/cladeBrackets.ts +73 -0
- package/src/components/tree/dialogs/TreeNodeInfoDialog.tsx +2 -2
- package/src/components/tree/renderTreeCanvas.ts +240 -20
- package/src/components/tree/renderTreeOverview.ts +212 -0
- package/src/components/tree/useTreeHover.ts +2 -4
- package/src/constants.ts +56 -22
- package/src/createPaletteMap.ts +10 -9
- package/src/element.ts +321 -0
- package/src/featureFields.ts +19 -0
- package/src/flatToTree.ts +1 -2
- package/src/ggplotPalettes.ts +60 -0
- package/src/hierarchy.ts +77 -9
- package/src/index.ts +31 -7
- package/src/model/DataModel.ts +2 -5
- package/src/model/msaModel.ts +3 -2
- package/src/model/treeModel.ts +42 -0
- package/src/model.ts +1523 -560
- package/src/mount.tsx +32 -0
- package/src/neighborJoining.ts +8 -32
- package/src/renderToStaticMarkup.ts +13 -24
- package/src/renderToSvg.tsx +335 -68
- package/src/rowCoordinateCalculations.ts +4 -6
- package/src/scales.ts +47 -0
- package/src/seqPosToGlobalCol.ts +2 -3
- package/src/sequenceLogo.ts +6 -11
- package/src/stripDefault.ts +7 -14
- package/src/types.ts +258 -45
- package/src/umd.ts +4 -2
- package/src/useCanvasAutorun.ts +5 -10
- package/src/useMsaSvgFigure.ts +72 -0
- package/src/util.ts +15 -6
- package/src/version.ts +1 -1
- package/dist/components/msa/ColumnStats.js +0 -42
- package/dist/components/msa/ColumnStats.js.map +0 -1
- package/src/components/msa/ColumnStats.tsx +0 -54
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { useCallback, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { observer } from 'mobx-react'
|
|
4
|
+
|
|
5
|
+
import { useWheelScroll } from '../../useWheelScroll.ts'
|
|
6
|
+
import RowPanelBlock from './RowPanelBlock.tsx'
|
|
7
|
+
|
|
8
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
9
|
+
import type { ResolvedRowPanel } from '../../types.ts'
|
|
10
|
+
|
|
11
|
+
const RowPanel = observer(function ({
|
|
12
|
+
model,
|
|
13
|
+
panel,
|
|
14
|
+
}: {
|
|
15
|
+
model: MsaViewModel
|
|
16
|
+
panel: ResolvedRowPanel
|
|
17
|
+
}) {
|
|
18
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
19
|
+
const { height, blocksY, scrollY } = model
|
|
20
|
+
const onScrollY = useCallback(
|
|
21
|
+
(d: number) => {
|
|
22
|
+
model.doScrollY(d)
|
|
23
|
+
},
|
|
24
|
+
[model],
|
|
25
|
+
)
|
|
26
|
+
const { onMouseDown, onMouseUp } = useWheelScroll({ ref, onScrollY })
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
ref={ref}
|
|
31
|
+
data-testid={`rowpanel_${panel.id}`}
|
|
32
|
+
onMouseDown={onMouseDown}
|
|
33
|
+
onMouseUp={onMouseUp}
|
|
34
|
+
style={{
|
|
35
|
+
height,
|
|
36
|
+
width: panel.width,
|
|
37
|
+
flexShrink: 0,
|
|
38
|
+
overflow: 'hidden',
|
|
39
|
+
position: 'relative',
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
{/* one transform for the whole block set, matching TreeCanvas: the
|
|
43
|
+
strips scroll in lockstep with the tree and the alignment */}
|
|
44
|
+
<div
|
|
45
|
+
style={{
|
|
46
|
+
position: 'absolute',
|
|
47
|
+
top: 0,
|
|
48
|
+
left: 0,
|
|
49
|
+
transform: `translateY(${scrollY}px)`,
|
|
50
|
+
willChange: 'transform',
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
{blocksY.map(block => (
|
|
54
|
+
<RowPanelBlock
|
|
55
|
+
key={block}
|
|
56
|
+
model={model}
|
|
57
|
+
panel={panel}
|
|
58
|
+
offsetY={block}
|
|
59
|
+
/>
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const RowPanels = observer(function ({ model }: { model: MsaViewModel }) {
|
|
67
|
+
const { resolvedRowPanels } = model
|
|
68
|
+
return resolvedRowPanels.length > 0 ? (
|
|
69
|
+
<div style={{ display: 'flex', flexShrink: 0 }}>
|
|
70
|
+
{resolvedRowPanels.map(panel => (
|
|
71
|
+
<RowPanel key={panel.id} model={model} panel={panel} />
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
) : null
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
export default RowPanels
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { contrastTextFn } from '../../util.ts'
|
|
2
|
+
import { getVisibleLeaves } from '../getVisibleLeaves.ts'
|
|
3
|
+
import { drawFeatureSpans, spanRows } from '../msa/drawFeatureSpans.ts'
|
|
4
|
+
|
|
5
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
6
|
+
import type { ResolvedFeaturePanel, RowPanelSpan } from '../../types.ts'
|
|
7
|
+
import type { RenderCtx } from '../renderCtx.ts'
|
|
8
|
+
import type { Theme } from '@mui/material'
|
|
9
|
+
|
|
10
|
+
// a span keeps a pixel clear above and below it, so stacked lanes and adjacent
|
|
11
|
+
// rows read apart
|
|
12
|
+
const rowPadding = 2
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The spans of a `features` panel, drawn with the mark the alignment overlay
|
|
16
|
+
* draws: an arrow where the feature carries a strand, a box otherwise, labeled
|
|
17
|
+
* where the text fits. The x mapping is the panel's own, resolved in
|
|
18
|
+
* `resolvedRowPanels`; a row needing several lanes divides its height between
|
|
19
|
+
* them.
|
|
20
|
+
*/
|
|
21
|
+
export function renderFeaturePanel({
|
|
22
|
+
ctx,
|
|
23
|
+
model,
|
|
24
|
+
theme,
|
|
25
|
+
panel,
|
|
26
|
+
x,
|
|
27
|
+
offsetY,
|
|
28
|
+
blockSizeYOverride,
|
|
29
|
+
highResScaleFactorOverride,
|
|
30
|
+
}: {
|
|
31
|
+
ctx: RenderCtx
|
|
32
|
+
model: MsaViewModel
|
|
33
|
+
theme: Theme
|
|
34
|
+
panel: ResolvedFeaturePanel
|
|
35
|
+
x: number
|
|
36
|
+
offsetY: number
|
|
37
|
+
blockSizeYOverride?: number
|
|
38
|
+
highResScaleFactorOverride?: number
|
|
39
|
+
}) {
|
|
40
|
+
const { rowHeight, blockSize, highResScaleFactor } = model
|
|
41
|
+
const by = blockSizeYOverride ?? blockSize
|
|
42
|
+
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
43
|
+
ctx.resetTransform()
|
|
44
|
+
ctx.scale(k, k)
|
|
45
|
+
ctx.translate(x, rowHeight / 2 - offsetY)
|
|
46
|
+
|
|
47
|
+
drawFeatureSpans<RowPanelSpan>({
|
|
48
|
+
ctx,
|
|
49
|
+
rows: spanRows(
|
|
50
|
+
getVisibleLeaves({ model, offsetY, blockSizeY: by }),
|
|
51
|
+
panel.spans,
|
|
52
|
+
),
|
|
53
|
+
layout: {
|
|
54
|
+
height: laneCount => Math.max(1, rowHeight / laneCount - rowPadding),
|
|
55
|
+
top: (y, lane, h) =>
|
|
56
|
+
y - rowHeight + lane * (h + rowPadding) + rowPadding / 2,
|
|
57
|
+
headLength: h => h,
|
|
58
|
+
},
|
|
59
|
+
xOf: span => [span.xStart, span.xEnd],
|
|
60
|
+
colors: panel.colors,
|
|
61
|
+
labelOf: span => panel.labels?.get(span.annotation),
|
|
62
|
+
contrastText: contrastTextFn(theme),
|
|
63
|
+
xMin: 0,
|
|
64
|
+
xMax: panel.width,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { renderFeaturePanel } from './renderFeaturePanel.ts'
|
|
2
|
+
import { renderStrip } from './renderStrip.ts'
|
|
3
|
+
|
|
4
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
5
|
+
import type { ResolvedRowPanel } from '../../types.ts'
|
|
6
|
+
import type { RenderCtx } from '../renderCtx.ts'
|
|
7
|
+
import type { Theme } from '@mui/material'
|
|
8
|
+
|
|
9
|
+
interface PanelArgs {
|
|
10
|
+
ctx: RenderCtx
|
|
11
|
+
model: MsaViewModel
|
|
12
|
+
theme: Theme
|
|
13
|
+
panel: ResolvedRowPanel
|
|
14
|
+
x: number
|
|
15
|
+
offsetY: number
|
|
16
|
+
blockSizeYOverride?: number
|
|
17
|
+
highResScaleFactorOverride?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* One row panel, by kind. `x` is the panel's left edge in the context: the
|
|
22
|
+
* live view gives each panel a canvas of its own and passes 0, and the export
|
|
23
|
+
* packs every panel into one context and passes the panel's own offset.
|
|
24
|
+
*/
|
|
25
|
+
export function renderRowPanel({ panel, ...rest }: PanelArgs) {
|
|
26
|
+
if (panel.kind === 'features') {
|
|
27
|
+
renderFeaturePanel({ panel, ...rest })
|
|
28
|
+
} else {
|
|
29
|
+
renderStrip({ panel, ...rest })
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** every row panel side by side in one context, for the SVG export */
|
|
34
|
+
export function renderRowPanels({
|
|
35
|
+
model,
|
|
36
|
+
...rest
|
|
37
|
+
}: Omit<PanelArgs, 'panel' | 'x'>) {
|
|
38
|
+
for (const panel of model.resolvedRowPanels) {
|
|
39
|
+
renderRowPanel({ model, panel, x: panel.offsetX, ...rest })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getVisibleLeaves } from '../getVisibleLeaves.ts'
|
|
2
|
+
|
|
3
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
4
|
+
import type { ResolvedStripPanel } from '../../types.ts'
|
|
5
|
+
import type { RenderCtx } from '../renderCtx.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One cell per row of a `strip` panel, in the color its field takes through
|
|
9
|
+
* the panel's scale. A row the table has no value for leaves the cell empty.
|
|
10
|
+
*
|
|
11
|
+
* `x` is the panel's left edge in the context: the live view gives each panel
|
|
12
|
+
* a canvas of its own and passes 0, and the export packs every panel into one
|
|
13
|
+
* context and passes the panel's own offset.
|
|
14
|
+
*/
|
|
15
|
+
export function renderStrip({
|
|
16
|
+
ctx,
|
|
17
|
+
model,
|
|
18
|
+
panel,
|
|
19
|
+
x,
|
|
20
|
+
offsetY,
|
|
21
|
+
blockSizeYOverride,
|
|
22
|
+
highResScaleFactorOverride,
|
|
23
|
+
}: {
|
|
24
|
+
ctx: RenderCtx
|
|
25
|
+
model: MsaViewModel
|
|
26
|
+
panel: ResolvedStripPanel
|
|
27
|
+
x: number
|
|
28
|
+
offsetY: number
|
|
29
|
+
blockSizeYOverride?: number
|
|
30
|
+
highResScaleFactorOverride?: number
|
|
31
|
+
}) {
|
|
32
|
+
const { rowHeight, blockSize, highResScaleFactor } = model
|
|
33
|
+
const by = blockSizeYOverride ?? blockSize
|
|
34
|
+
ctx.resetTransform()
|
|
35
|
+
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
36
|
+
ctx.scale(k, k)
|
|
37
|
+
ctx.translate(x, -offsetY)
|
|
38
|
+
for (const leaf of getVisibleLeaves({ model, offsetY, blockSizeY: by })) {
|
|
39
|
+
const color = panel.colors.get(leaf.data.name)
|
|
40
|
+
if (color) {
|
|
41
|
+
ctx.fillStyle = color
|
|
42
|
+
// leaf.x is the row's center, at rowHeight*(index+0.5)
|
|
43
|
+
ctx.fillRect(0, leaf.x! - rowHeight / 2, panel.width, rowHeight)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -64,11 +64,9 @@ const TrackBlocks = observer(function ({
|
|
|
64
64
|
model: MsaViewModel
|
|
65
65
|
track: BasicTrack
|
|
66
66
|
}) {
|
|
67
|
-
// msaCanvasWidth, not msaAreaWidth:
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
// last columns appear in the tracks but not in the alignment, and the hovered
|
|
71
|
-
// -column indicator (sized to the canvas) stops short of the track it crosses
|
|
67
|
+
// msaCanvasWidth, not msaAreaWidth: tracks stop where the alignment canvas
|
|
68
|
+
// does, not 20px further under the vertical scrollbar, so their columns and
|
|
69
|
+
// the hover indicator line up with the alignment
|
|
72
70
|
const { blocksX, msaCanvasWidth, scrollX } = model
|
|
73
71
|
const { kind, height, data, arcs } = track.model
|
|
74
72
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { observer } from 'mobx-react'
|
|
4
|
+
|
|
5
|
+
import { columnLogoStack } from '../../sequenceLogo.ts'
|
|
6
|
+
import { barTrackValues } from './drawTracks.ts'
|
|
7
|
+
|
|
8
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
9
|
+
import type { BasicTrack } from '../../types.ts'
|
|
10
|
+
|
|
11
|
+
const pct = (n: number) => `${Math.round(n * 100)}%`
|
|
12
|
+
|
|
13
|
+
const MAX_PARTNERS = 8
|
|
14
|
+
|
|
15
|
+
function ColumnLabel({ model, col }: { model: MsaViewModel; col: number }) {
|
|
16
|
+
const { relativeTo } = model
|
|
17
|
+
const pos = relativeTo
|
|
18
|
+
? model.visibleColToSeqPosOneBased(relativeTo, col)
|
|
19
|
+
: undefined
|
|
20
|
+
return (
|
|
21
|
+
<b>
|
|
22
|
+
Column {col + 1}
|
|
23
|
+
{pos === undefined ? null : ` (${relativeTo}:${pos})`}
|
|
24
|
+
</b>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function BarTooltip({
|
|
29
|
+
model,
|
|
30
|
+
track,
|
|
31
|
+
col,
|
|
32
|
+
}: {
|
|
33
|
+
model: MsaViewModel
|
|
34
|
+
track: BasicTrack
|
|
35
|
+
col: number
|
|
36
|
+
}) {
|
|
37
|
+
const { id, name } = track.model
|
|
38
|
+
const value = barTrackValues(model, id)[col]
|
|
39
|
+
return value === undefined ? null : (
|
|
40
|
+
<div>
|
|
41
|
+
<ColumnLabel model={model} col={col} />
|
|
42
|
+
<br />
|
|
43
|
+
{name}: {value.toFixed(2)}
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function LogoTooltip({ model, col }: { model: MsaViewModel; col: number }) {
|
|
49
|
+
const stats = model.columnStatsAt(col)
|
|
50
|
+
if (!stats) {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
const { total, consensusLetter, consensusFraction, gapFraction } = stats
|
|
54
|
+
const stack = columnLogoStack(model.colStats, col, model.alphabetMaxBits)
|
|
55
|
+
const bits = stack.reduce((a, b) => a + b.bits, 0)
|
|
56
|
+
return (
|
|
57
|
+
<div>
|
|
58
|
+
<ColumnLabel model={model} col={col} /> ({total} seqs)
|
|
59
|
+
<br />
|
|
60
|
+
Information: {bits.toFixed(2)} bits
|
|
61
|
+
<br />
|
|
62
|
+
Consensus: {consensusLetter || '-'} ({pct(consensusFraction)})
|
|
63
|
+
<br />
|
|
64
|
+
Gaps: {pct(gapFraction)}
|
|
65
|
+
<br />
|
|
66
|
+
{stats.distribution.slice(0, 6).map(([letter, count]) => (
|
|
67
|
+
<span key={letter} style={{ marginRight: 8 }}>
|
|
68
|
+
{letter} {pct(count / total)}
|
|
69
|
+
</span>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function TextTooltip({
|
|
76
|
+
model,
|
|
77
|
+
track,
|
|
78
|
+
col,
|
|
79
|
+
}: {
|
|
80
|
+
model: MsaViewModel
|
|
81
|
+
track: BasicTrack
|
|
82
|
+
col: number
|
|
83
|
+
}) {
|
|
84
|
+
const { name, data } = track.model
|
|
85
|
+
const letter = data?.[col]
|
|
86
|
+
return letter === undefined ? null : (
|
|
87
|
+
<div>
|
|
88
|
+
<ColumnLabel model={model} col={col} />
|
|
89
|
+
<br />
|
|
90
|
+
{name}: {letter === ' ' ? 'blank' : letter}
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ArcTooltip({
|
|
96
|
+
model,
|
|
97
|
+
track,
|
|
98
|
+
col,
|
|
99
|
+
}: {
|
|
100
|
+
model: MsaViewModel
|
|
101
|
+
track: BasicTrack
|
|
102
|
+
col: number
|
|
103
|
+
}) {
|
|
104
|
+
const { name, arcs } = track.model
|
|
105
|
+
const partners = (arcs ?? [])
|
|
106
|
+
.filter(arc => arc.start === col || arc.end === col)
|
|
107
|
+
.map(arc => (arc.start === col ? arc.end : arc.start) + 1)
|
|
108
|
+
return partners.length === 0 ? null : (
|
|
109
|
+
<div>
|
|
110
|
+
<ColumnLabel model={model} col={col} />
|
|
111
|
+
<br />
|
|
112
|
+
{name}: pairs with {partners.slice(0, MAX_PARTNERS).join(', ')}
|
|
113
|
+
{partners.length > MAX_PARTNERS ? '...' : null}
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* One track's reading at the hovered column: a bar track's value, the logo's
|
|
120
|
+
* information content and composition, the columns an arc joins, the letter on a
|
|
121
|
+
* text track. `drawTrackBlock` dispatches on the same `kind`, so a new kind
|
|
122
|
+
* needs a case in both.
|
|
123
|
+
*/
|
|
124
|
+
const TrackTooltipContent = observer(function ({
|
|
125
|
+
model,
|
|
126
|
+
track,
|
|
127
|
+
col,
|
|
128
|
+
}: {
|
|
129
|
+
model: MsaViewModel
|
|
130
|
+
track: BasicTrack
|
|
131
|
+
col: number
|
|
132
|
+
}) {
|
|
133
|
+
switch (track.model.kind) {
|
|
134
|
+
case 'bar': {
|
|
135
|
+
return <BarTooltip model={model} track={track} col={col} />
|
|
136
|
+
}
|
|
137
|
+
case 'logo': {
|
|
138
|
+
return <LogoTooltip model={model} col={col} />
|
|
139
|
+
}
|
|
140
|
+
case 'text': {
|
|
141
|
+
return <TextTooltip model={model} track={track} col={col} />
|
|
142
|
+
}
|
|
143
|
+
case 'arc': {
|
|
144
|
+
return <ArcTooltip model={model} track={track} col={col} />
|
|
145
|
+
}
|
|
146
|
+
case 'ruler': {
|
|
147
|
+
return (
|
|
148
|
+
<div>
|
|
149
|
+
<ColumnLabel model={model} col={col} />
|
|
150
|
+
</div>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
export default TrackTooltipContent
|
|
@@ -41,8 +41,8 @@ export function drawConservationBars({
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
//
|
|
45
|
-
//
|
|
44
|
+
// bar-track values are model getters, off the plain track object, so the canvas
|
|
45
|
+
// autorun stays reactive
|
|
46
46
|
export function barTrackValues(model: MsaViewModel, trackId: string) {
|
|
47
47
|
const supplied = model.columnTrackContent.get(trackId)?.values
|
|
48
48
|
if (supplied) {
|
|
@@ -53,10 +53,8 @@ export function barTrackValues(model: MsaViewModel, trackId: string) {
|
|
|
53
53
|
: model.conservation
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// An arc's apex scales with
|
|
57
|
-
// and a
|
|
58
|
-
// than linear because a contact map's spans are heavily skewed towards short
|
|
59
|
-
// ones, which linear scaling flattens into a smear along the baseline.
|
|
56
|
+
// An arc's apex scales with the square root of its span. Contact-map spans skew
|
|
57
|
+
// short, and a linear scale flattens them along the baseline.
|
|
60
58
|
export function drawArcs({
|
|
61
59
|
ctx,
|
|
62
60
|
arcs,
|
|
@@ -145,17 +143,13 @@ export function drawTextTrackContent({
|
|
|
145
143
|
ctx.fillRect(x, 0, colWidth, rowHeight)
|
|
146
144
|
}
|
|
147
145
|
if (drawLetters) {
|
|
148
|
-
// a letter on a colored tile needs that tile's contrast color; otherwise
|
|
149
|
-
// it sits on the plain background and takes the theme's text color
|
|
150
146
|
ctx.fillStyle = contrastText(fill)
|
|
151
147
|
ctx.fillText(letter, x + colWidth / 2, rowHeight / 2)
|
|
152
148
|
}
|
|
153
149
|
}
|
|
154
150
|
}
|
|
155
151
|
|
|
156
|
-
//
|
|
157
|
-
// smallest 1/2/5 x 10^n that leaves room for a label, so a zoom changes how many
|
|
158
|
-
// numbers appear rather than crowding them.
|
|
152
|
+
// the smallest 1/2/5 x 10^n column step that leaves room for a label
|
|
159
153
|
export function rulerStep(colWidth: number, minPixels = 55) {
|
|
160
154
|
let step = 1
|
|
161
155
|
const mantissas = [1, 2, 5]
|
|
@@ -215,17 +209,11 @@ export function drawColumnRuler({
|
|
|
215
209
|
}
|
|
216
210
|
}
|
|
217
211
|
|
|
218
|
-
// Cap height as a fraction of the font size
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
// backend, so the ratio is a constant: the cap height of the sans-serif faces
|
|
223
|
-
// both backends use, within a percent or two. It only has to be consistent --
|
|
224
|
-
// the same ratio for every letter is what makes the slices sum to the stack
|
|
225
|
-
// height instead of drifting apart.
|
|
212
|
+
// Cap height as a fraction of the font size, within a percent or two for the
|
|
213
|
+
// sans-serif faces both backends use. `measureText` reports no vertical box in
|
|
214
|
+
// the SVG export backend, so one constant ratio scales every logo letter, and
|
|
215
|
+
// the slices sum to the stack height.
|
|
226
216
|
const CAP_HEIGHT_RATIO = 0.72
|
|
227
|
-
// letters below this many pixels are noise, and a stack that thin reads better
|
|
228
|
-
// as the bar chart it approximates
|
|
229
217
|
const MIN_LETTER_PX = 3
|
|
230
218
|
|
|
231
219
|
export function drawSequenceLogo({
|
|
@@ -256,8 +244,8 @@ export function drawSequenceLogo({
|
|
|
256
244
|
})
|
|
257
245
|
const end = Math.min(xEnd, colStats.numColumns)
|
|
258
246
|
|
|
259
|
-
// one reference size for
|
|
260
|
-
//
|
|
247
|
+
// one reference font size for the block; each letter's transform scales it to
|
|
248
|
+
// a height proportional to its frequency
|
|
261
249
|
const referenceFontSize = trackHeight
|
|
262
250
|
setFontSize(ctx, referenceFontSize)
|
|
263
251
|
ctx.textAlign = 'left'
|
|
@@ -270,9 +258,7 @@ export function drawSequenceLogo({
|
|
|
270
258
|
continue
|
|
271
259
|
}
|
|
272
260
|
const x = col * colWidth
|
|
273
|
-
//
|
|
274
|
-
// lands on top, which is the convention that makes a logo readable at a
|
|
275
|
-
// glance
|
|
261
|
+
// the stack is ascending, so the tallest letter lands on top
|
|
276
262
|
let bottom = trackHeight
|
|
277
263
|
for (const { letter, bits } of stack) {
|
|
278
264
|
const h = (bits / maxBits) * trackHeight
|
|
@@ -300,9 +286,9 @@ export function drawSequenceLogo({
|
|
|
300
286
|
|
|
301
287
|
/**
|
|
302
288
|
* Draw one track's content into a block of the alignment's column space, at
|
|
303
|
-
* `offsetY` within the ctx. Owns the transform, so
|
|
304
|
-
*
|
|
305
|
-
*
|
|
289
|
+
* `offsetY` within the ctx. Owns the transform, so the live canvas blocks and
|
|
290
|
+
* the SVG export draw into the same coordinate space. A new track kind needs a
|
|
291
|
+
* `kind`, a draw function above, and a case here.
|
|
306
292
|
*/
|
|
307
293
|
export function drawTrackBlock({
|
|
308
294
|
model,
|
|
@@ -397,8 +383,7 @@ export function drawTrackBlock({
|
|
|
397
383
|
drawColumnRuler({
|
|
398
384
|
ctx,
|
|
399
385
|
numColumns,
|
|
400
|
-
//
|
|
401
|
-
// drawn relative to one, which is what the rest of the view counts in
|
|
386
|
+
// with relativeTo set, number by the reference row's residues
|
|
402
387
|
label: relativeTo
|
|
403
388
|
? col => {
|
|
404
389
|
const pos = model.visibleColToSeqPosOneBased(relativeTo, col)
|
|
@@ -423,8 +408,6 @@ export function drawTrackBlock({
|
|
|
423
408
|
colorScheme: customColorScheme ?? modelColorScheme,
|
|
424
409
|
contrastText: contrastTextFn(theme),
|
|
425
410
|
bgColor,
|
|
426
|
-
// a text track is one alignment row tall, so it shows letters exactly
|
|
427
|
-
// when the alignment does
|
|
428
411
|
drawLetters: showMsaLetters,
|
|
429
412
|
colWidth,
|
|
430
413
|
rowHeight,
|
|
@@ -440,7 +423,7 @@ export function drawTrackBlock({
|
|
|
440
423
|
|
|
441
424
|
/**
|
|
442
425
|
* Every turned-on track stacked top to bottom, for the SVG export. The live
|
|
443
|
-
* view draws the same content one canvas block at a time
|
|
426
|
+
* view draws the same content one canvas block at a time in TrackBlocks.
|
|
444
427
|
*/
|
|
445
428
|
export function renderAllTracks({
|
|
446
429
|
model,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
4
|
+
import type React from 'react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Translates pointer events over a track's row into the hovered column and a
|
|
8
|
+
* tooltip anchor. The track shares the alignment's columns, so hovering it sets
|
|
9
|
+
* the model's mouse column too and the alignment's hover band follows along.
|
|
10
|
+
*
|
|
11
|
+
* `tooltip` off skips the anchor, so a track whose tooltip is turned off still
|
|
12
|
+
* drives the hover band without re-rendering on every mouse move.
|
|
13
|
+
*/
|
|
14
|
+
export function useTrackHover({
|
|
15
|
+
model,
|
|
16
|
+
tooltip,
|
|
17
|
+
}: {
|
|
18
|
+
model: MsaViewModel
|
|
19
|
+
tooltip: boolean
|
|
20
|
+
}) {
|
|
21
|
+
const [hover, setHover] = useState<{ col: number; x: number; y: number }>()
|
|
22
|
+
|
|
23
|
+
// the blocks are laid out in column space and scrolled by a transform on
|
|
24
|
+
// their container, so the pointer's column comes off the untransformed row
|
|
25
|
+
function onMouseMove(event: React.MouseEvent, el: HTMLElement) {
|
|
26
|
+
const { colWidth, scrollX, numColumns } = model
|
|
27
|
+
const { left } = el.getBoundingClientRect()
|
|
28
|
+
const col = Math.floor((event.clientX - left - scrollX) / colWidth)
|
|
29
|
+
const hit = col >= 0 && col < numColumns
|
|
30
|
+
model.setMousePos(hit ? col : undefined, undefined)
|
|
31
|
+
if (tooltip) {
|
|
32
|
+
setHover(hit ? { col, x: event.clientX, y: event.clientY } : undefined)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function onMouseLeave() {
|
|
37
|
+
setHover(undefined)
|
|
38
|
+
model.setMousePos(undefined, undefined)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { hover: tooltip ? hover : undefined, onMouseMove, onMouseLeave }
|
|
42
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { useTheme } from '@mui/material'
|
|
4
|
+
import { observer } from 'mobx-react'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
bracketGap,
|
|
8
|
+
cladeHeight,
|
|
9
|
+
cladeInGutter,
|
|
10
|
+
cladeLabelHorizontal,
|
|
11
|
+
cladeLabelOffset,
|
|
12
|
+
} from './cladeBrackets.ts'
|
|
13
|
+
|
|
14
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The label of each bracket, in the gutter beside its bar, where the canvas
|
|
18
|
+
* draws the bar itself. The text is DOM text, turned on its side for a clade
|
|
19
|
+
* shorter than the font, the way the strip headers are; the SVG export draws
|
|
20
|
+
* the same labels with a `rotate(-90)`.
|
|
21
|
+
*/
|
|
22
|
+
const CladeLabels = observer(function ({ model }: { model: MsaViewModel }) {
|
|
23
|
+
const {
|
|
24
|
+
resolvedClades,
|
|
25
|
+
cladeGutterWidth,
|
|
26
|
+
rowHeight,
|
|
27
|
+
treeAreaWidth,
|
|
28
|
+
fontSize,
|
|
29
|
+
} = model
|
|
30
|
+
const theme = useTheme()
|
|
31
|
+
if (cladeGutterWidth === 0) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
const gutterLeft = treeAreaWidth - cladeGutterWidth
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
{resolvedClades.map((clade, index) => {
|
|
38
|
+
const { label, markColor, rows } = clade
|
|
39
|
+
if (!label || !cladeInGutter(clade)) {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
const left = gutterLeft + cladeLabelOffset(clade)
|
|
43
|
+
const horizontal = cladeLabelHorizontal(clade, rowHeight, fontSize)
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
key={`${rows[0]}-${rows[1]}-${index}`}
|
|
47
|
+
title={label}
|
|
48
|
+
style={{
|
|
49
|
+
position: 'absolute',
|
|
50
|
+
left,
|
|
51
|
+
top: rows[0] * rowHeight,
|
|
52
|
+
width: treeAreaWidth - left - bracketGap,
|
|
53
|
+
height: cladeHeight(clade, rowHeight),
|
|
54
|
+
display: 'flex',
|
|
55
|
+
alignItems: 'center',
|
|
56
|
+
color: markColor ?? theme.palette.text.primary,
|
|
57
|
+
fontSize,
|
|
58
|
+
overflow: 'hidden',
|
|
59
|
+
textOverflow: 'ellipsis',
|
|
60
|
+
whiteSpace: 'nowrap',
|
|
61
|
+
pointerEvents: 'none',
|
|
62
|
+
// vertical-rl reads top to bottom, and the half turn makes it
|
|
63
|
+
// read upwards, which is what rotate(-90) exports
|
|
64
|
+
...(horizontal
|
|
65
|
+
? {}
|
|
66
|
+
: { writingMode: 'vertical-rl', transform: 'rotate(180deg)' }),
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{label}
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
})}
|
|
73
|
+
</>
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
export default CladeLabels
|
|
@@ -6,6 +6,7 @@ import { observer } from 'mobx-react'
|
|
|
6
6
|
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
7
7
|
import { useWheelScroll } from '../../useWheelScroll.ts'
|
|
8
8
|
import { referenceColor, treeHoverColor } from '../overlayColors.ts'
|
|
9
|
+
import CladeLabels from './CladeLabels.tsx'
|
|
9
10
|
import TreeCanvasBlock from './TreeCanvasBlock.tsx'
|
|
10
11
|
|
|
11
12
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -93,6 +94,7 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
93
94
|
{blocksY.map(block => (
|
|
94
95
|
<TreeCanvasBlock key={block} model={model} offsetY={block} />
|
|
95
96
|
))}
|
|
97
|
+
<CladeLabels model={model} />
|
|
96
98
|
</div>
|
|
97
99
|
<canvas
|
|
98
100
|
ref={mouseoverRef}
|