react-msaview 5.4.1 → 5.6.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/LICENSE +21 -0
- package/bundle/index.js +487 -149
- package/bundle/index.js.map +7 -1
- package/dist/colorSchemes.js +11 -7
- package/dist/colorSchemes.js.map +1 -1
- package/dist/components/ConservationTrack.js +20 -26
- package/dist/components/ConservationTrack.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +13 -1
- package/dist/components/MSAViewer.js +13 -7
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/SequenceTextArea.js +1 -1
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/TextTrack.js +20 -27
- package/dist/components/TextTrack.js.map +1 -1
- package/dist/components/Track.js +9 -6
- package/dist/components/Track.js.map +1 -1
- package/dist/components/dialogs/FeatureDialog.js +27 -23
- package/dist/components/dialogs/FeatureDialog.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/ColorSchemeMenu.js +1 -1
- package/dist/components/header/ColorSchemeMenu.js.map +1 -1
- package/dist/components/header/FileMenu.js +1 -16
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/Header.js +6 -5
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderInfoArea.js +1 -1
- package/dist/components/header/HeaderInfoArea.js.map +1 -1
- package/dist/components/header/HeaderStatusArea.js +1 -1
- package/dist/components/header/HeaderStatusArea.js.map +1 -1
- package/dist/components/header/MSASettingsMenu.js +1 -1
- package/dist/components/header/MSASettingsMenu.js.map +1 -1
- package/dist/components/header/MultiAlignmentSelector.js +0 -0
- package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.js +1 -1
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/ZoomControls.js +14 -12
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/ZoomMenu.js +1 -10
- package/dist/components/header/ZoomMenu.js.map +1 -1
- package/dist/components/header/settingsMenuItems.js +9 -1
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/{header/HeaderMenu.d.ts → msa/ColumnStats.d.ts} +3 -3
- package/dist/components/msa/ColumnStats.js +42 -0
- package/dist/components/msa/ColumnStats.js.map +1 -0
- package/dist/components/msa/DomainLegend.d.ts +6 -0
- package/dist/components/msa/DomainLegend.js +41 -0
- package/dist/components/msa/DomainLegend.js.map +1 -0
- package/dist/components/msa/MSACanvasBlock.js +41 -29
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +6 -12
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/MSAPanel.js +3 -1
- package/dist/components/msa/MSAPanel.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +60 -14
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +3 -3
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +33 -6
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/overlayColors.d.ts +3 -0
- package/dist/components/overlayColors.js +11 -0
- package/dist/components/overlayColors.js.map +1 -0
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/tracks/renderTracksSvg.d.ts +6 -3
- package/dist/components/tracks/renderTracksSvg.js +20 -11
- package/dist/components/tracks/renderTracksSvg.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +31 -38
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +12 -47
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/clickMap.d.ts +24 -0
- package/dist/components/tree/clickMap.js +33 -0
- package/dist/components/tree/clickMap.js.map +1 -0
- package/dist/components/tree/renderTreeCanvas.d.ts +1 -21
- package/dist/components/tree/renderTreeCanvas.js +8 -7
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +17 -0
- package/dist/constants.js.map +1 -1
- package/dist/createPaletteMap.d.ts +5 -2
- package/dist/createPaletteMap.js +15 -5
- package/dist/createPaletteMap.js.map +1 -1
- package/dist/fetchUtils.d.ts +2 -1
- package/dist/fetchUtils.js +3 -2
- package/dist/fetchUtils.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/launchInterProScan.d.ts +2 -0
- package/dist/launchInterProScan.js.map +1 -1
- package/dist/model/msaModel.d.ts +25 -2
- package/dist/model/msaModel.js +15 -3
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.d.ts +46 -5
- package/dist/model/treeModel.js +20 -7
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +641 -133
- package/dist/model.js +191 -77
- package/dist/model.js.map +1 -1
- package/dist/propertyConservation.d.ts +7 -0
- package/dist/propertyConservation.js +66 -0
- package/dist/propertyConservation.js.map +1 -0
- package/dist/renderToSvg.js +104 -74
- package/dist/renderToSvg.js.map +1 -1
- package/dist/types.d.ts +4 -1
- package/dist/{webpack.js → umd.js} +1 -1
- package/dist/umd.js.map +1 -0
- package/dist/useCanvasAutorun.d.ts +2 -0
- package/dist/useCanvasAutorun.js +23 -0
- package/dist/useCanvasAutorun.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -8
- package/src/colorSchemes.docs.test.ts +55 -0
- package/src/colorSchemes.ts +12 -7
- package/src/columnStats.test.ts +94 -0
- package/src/components/ConservationTrack.tsx +17 -15
- package/src/components/MSAViewer.tsx +42 -18
- package/src/components/SequenceTextArea.tsx +1 -1
- package/src/components/TextTrack.tsx +7 -11
- package/src/components/Track.tsx +9 -6
- package/src/components/dialogs/FeatureDialog.tsx +62 -48
- package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
- package/src/components/header/ColorSchemeMenu.tsx +1 -0
- package/src/components/header/FileMenu.tsx +1 -15
- package/src/components/header/Header.tsx +11 -8
- package/src/components/header/HeaderInfoArea.tsx +1 -1
- package/src/components/header/HeaderStatusArea.tsx +1 -1
- package/src/components/header/MSASettingsMenu.tsx +1 -0
- package/src/components/header/MultiAlignmentSelector.tsx +0 -0
- package/src/components/header/TreeSettingsMenu.tsx +1 -0
- package/src/components/header/ZoomControls.tsx +24 -17
- package/src/components/header/ZoomMenu.tsx +1 -10
- package/src/components/header/settingsMenuItems.ts +9 -1
- package/src/components/msa/ColumnStats.tsx +54 -0
- package/src/components/msa/DomainLegend.tsx +78 -0
- package/src/components/msa/MSACanvasBlock.tsx +46 -25
- package/src/components/msa/MSAMouseoverCanvas.tsx +10 -13
- package/src/components/msa/MSAPanel.tsx +2 -0
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +75 -17
- package/src/components/msa/renderMSABlock.ts +3 -3
- package/src/components/msa/renderMSAMouseover.ts +34 -6
- package/src/components/overlayColors.ts +12 -0
- package/src/components/renderCtx.ts +1 -0
- package/src/components/tracks/renderTracksSvg.ts +25 -11
- package/src/components/tree/TreeCanvas.tsx +47 -54
- package/src/components/tree/TreeCanvasBlock.tsx +10 -68
- package/src/components/tree/clickMap.ts +55 -0
- package/src/components/tree/renderTreeCanvas.ts +10 -34
- package/src/constants.ts +19 -0
- package/src/createPaletteMap.ts +18 -5
- package/src/fetchUtils.ts +6 -3
- package/src/index.ts +1 -1
- package/src/launchInterProScan.ts +3 -1
- package/src/model/msaModel.ts +21 -3
- package/src/model/treeModel.ts +21 -7
- package/src/model.ts +221 -119
- package/src/propertyConservation.test.ts +47 -0
- package/src/propertyConservation.ts +73 -0
- package/src/renderDomainLegendSvg.test.tsx +134 -0
- package/src/renderToSvg.tsx +210 -147
- package/src/segmentOverlay.test.ts +70 -0
- package/src/stripDefaultSnapshot.test.ts +54 -0
- package/src/types.ts +7 -1
- package/src/useCanvasAutorun.ts +28 -0
- package/src/version.ts +1 -1
- package/bundle/index.js.LICENSE.txt +0 -100
- package/dist/components/dialogs/SettingsDialog.d.ts +0 -7
- package/dist/components/dialogs/SettingsDialog.js +0 -100
- package/dist/components/dialogs/SettingsDialog.js.map +0 -1
- package/dist/components/header/HeaderMenu.js +0 -27
- package/dist/components/header/HeaderMenu.js.map +0 -1
- package/dist/components/util.d.ts +0 -1
- package/dist/components/util.js +0 -7
- package/dist/components/util.js.map +0 -1
- package/dist/webpack.js.map +0 -1
- package/src/components/dialogs/SettingsDialog.tsx +0 -234
- package/src/components/header/HeaderMenu.tsx +0 -38
- package/src/components/util.ts +0 -9
- /package/dist/{webpack.d.ts → umd.d.ts} +0 -0
- /package/src/{webpack.ts → umd.ts} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
+
import Mouse from '@mui/icons-material/Mouse'
|
|
3
4
|
import ZoomIn from '@mui/icons-material/ZoomIn'
|
|
4
|
-
import ZoomInMap from '@mui/icons-material/ZoomInMap'
|
|
5
5
|
import ZoomOut from '@mui/icons-material/ZoomOut'
|
|
6
|
-
import { IconButton, ToggleButton } from '@mui/material'
|
|
6
|
+
import { IconButton, ToggleButton, Tooltip } from '@mui/material'
|
|
7
7
|
import { observer } from 'mobx-react'
|
|
8
8
|
|
|
9
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -15,23 +15,30 @@ const ZoomControls = observer(function ZoomControls({
|
|
|
15
15
|
}) {
|
|
16
16
|
return (
|
|
17
17
|
<>
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
<Tooltip title="Zoom in">
|
|
19
|
+
<IconButton
|
|
20
|
+
aria-label="Zoom in"
|
|
21
|
+
onClick={() => {
|
|
22
|
+
model.zoomIn()
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<ZoomIn />
|
|
26
|
+
</IconButton>
|
|
27
|
+
</Tooltip>
|
|
28
|
+
<Tooltip title="Zoom out">
|
|
29
|
+
<IconButton
|
|
30
|
+
aria-label="Zoom out"
|
|
31
|
+
onClick={() => {
|
|
32
|
+
model.zoomOut()
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
<ZoomOut />
|
|
36
|
+
</IconButton>
|
|
37
|
+
</Tooltip>
|
|
32
38
|
<ToggleButton
|
|
33
39
|
value="scrollZoom"
|
|
34
40
|
selected={model.scrollZoom}
|
|
41
|
+
aria-label="Toggle scroll-to-zoom"
|
|
35
42
|
title="Toggle scroll-to-zoom (zoom on plain mouse wheel; ctrl+wheel always zooms)"
|
|
36
43
|
size="small"
|
|
37
44
|
sx={{ border: 'none' }}
|
|
@@ -39,7 +46,7 @@ const ZoomControls = observer(function ZoomControls({
|
|
|
39
46
|
model.setScrollZoom(!model.scrollZoom)
|
|
40
47
|
}}
|
|
41
48
|
>
|
|
42
|
-
<
|
|
49
|
+
<Mouse />
|
|
43
50
|
</ToggleButton>
|
|
44
51
|
</>
|
|
45
52
|
)
|
|
@@ -10,6 +10,7 @@ import type { MsaViewModel } from '../../model.ts'
|
|
|
10
10
|
const ZoomMenu = observer(function ({ model }: { model: MsaViewModel }) {
|
|
11
11
|
return (
|
|
12
12
|
<CascadingMenuButton
|
|
13
|
+
tooltip="Fit / zoom options"
|
|
13
14
|
menuItems={[
|
|
14
15
|
{
|
|
15
16
|
label: 'Fit both vertically/horizontally',
|
|
@@ -37,16 +38,6 @@ const ZoomMenu = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
37
38
|
model.resetZoom()
|
|
38
39
|
},
|
|
39
40
|
},
|
|
40
|
-
{
|
|
41
|
-
label: 'Scroll zoom',
|
|
42
|
-
type: 'checkbox',
|
|
43
|
-
checked: model.scrollZoom,
|
|
44
|
-
onClick: () => {
|
|
45
|
-
model.setScrollZoom(!model.scrollZoom)
|
|
46
|
-
},
|
|
47
|
-
helpText:
|
|
48
|
-
'When enabled, scrolling the mouse wheel zooms in and out without holding ctrl. Hold shift to pan instead. Ctrl/⌘+wheel always zooms.',
|
|
49
|
-
},
|
|
50
41
|
]}
|
|
51
42
|
>
|
|
52
43
|
<MoreVert />
|
|
@@ -2,8 +2,16 @@ import type { MsaViewModel } from '../../model.ts'
|
|
|
2
2
|
import type { MenuItem } from '@jbrowse/core/ui/Menu'
|
|
3
3
|
|
|
4
4
|
export function msaSettingsMenuItems(model: MsaViewModel): MenuItem[] {
|
|
5
|
-
const { drawMsaLetters, hideGaps, bgColor } = model
|
|
5
|
+
const { drawMsaLetters, hideGaps, bgColor, showColumnStats } = model
|
|
6
6
|
return [
|
|
7
|
+
{
|
|
8
|
+
label: 'Show column statistics on hover',
|
|
9
|
+
type: 'checkbox',
|
|
10
|
+
checked: showColumnStats,
|
|
11
|
+
onClick: () => {
|
|
12
|
+
model.setShowColumnStats(!showColumnStats)
|
|
13
|
+
},
|
|
14
|
+
},
|
|
7
15
|
{
|
|
8
16
|
label: 'Draw letters',
|
|
9
17
|
type: 'checkbox',
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { observer } from 'mobx-react'
|
|
4
|
+
|
|
5
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
6
|
+
|
|
7
|
+
const pct = (n: number) => `${Math.round(n * 100)}%`
|
|
8
|
+
|
|
9
|
+
const ColumnStats = observer(function ({ model }: { model: MsaViewModel }) {
|
|
10
|
+
const stats = model.mouseOverColumnStats
|
|
11
|
+
if (!stats) {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
const {
|
|
15
|
+
col,
|
|
16
|
+
total,
|
|
17
|
+
conservation,
|
|
18
|
+
propertyConservation,
|
|
19
|
+
consensusLetter,
|
|
20
|
+
consensusFraction,
|
|
21
|
+
gapFraction,
|
|
22
|
+
distribution,
|
|
23
|
+
} = stats
|
|
24
|
+
const top = distribution.slice(0, 6)
|
|
25
|
+
return (
|
|
26
|
+
<div>
|
|
27
|
+
<b>Column {col + 1}</b> ({total} seqs)
|
|
28
|
+
<br />
|
|
29
|
+
Consensus: {consensusLetter || '-'} ({pct(consensusFraction)})
|
|
30
|
+
<br />
|
|
31
|
+
Conservation: {conservation.toFixed(2)}
|
|
32
|
+
{propertyConservation === undefined ? null : (
|
|
33
|
+
<>
|
|
34
|
+
<br />
|
|
35
|
+
Property conservation: {propertyConservation.toFixed(2)}
|
|
36
|
+
</>
|
|
37
|
+
)}
|
|
38
|
+
<br />
|
|
39
|
+
Gaps: {pct(gapFraction)}
|
|
40
|
+
{top.length > 0 ? (
|
|
41
|
+
<>
|
|
42
|
+
<br />
|
|
43
|
+
{top.map(([letter, count]) => (
|
|
44
|
+
<span key={letter} style={{ marginRight: 8 }}>
|
|
45
|
+
{letter} {pct(count / total)}
|
|
46
|
+
</span>
|
|
47
|
+
))}
|
|
48
|
+
</>
|
|
49
|
+
) : null}
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
export default ColumnStats
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import ExpandLess from '@mui/icons-material/ExpandLess'
|
|
4
|
+
import ExpandMore from '@mui/icons-material/ExpandMore'
|
|
5
|
+
import { IconButton, Paper, Typography } from '@mui/material'
|
|
6
|
+
import { observer } from 'mobx-react'
|
|
7
|
+
|
|
8
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
9
|
+
|
|
10
|
+
const DomainLegend = observer(function ({ model }: { model: MsaViewModel }) {
|
|
11
|
+
const [expanded, setExpanded] = useState(true)
|
|
12
|
+
const {
|
|
13
|
+
actuallyShowDomains,
|
|
14
|
+
visibleDomainTypes: visible,
|
|
15
|
+
fillPalette,
|
|
16
|
+
} = model
|
|
17
|
+
const ExpandIcon = expanded ? ExpandLess : ExpandMore
|
|
18
|
+
|
|
19
|
+
return actuallyShowDomains && visible.length > 0 ? (
|
|
20
|
+
<Paper
|
|
21
|
+
elevation={3}
|
|
22
|
+
style={{
|
|
23
|
+
position: 'absolute',
|
|
24
|
+
top: 4,
|
|
25
|
+
right: 4,
|
|
26
|
+
zIndex: 100,
|
|
27
|
+
maxWidth: 260,
|
|
28
|
+
maxHeight: '60%',
|
|
29
|
+
display: 'flex',
|
|
30
|
+
flexDirection: 'column',
|
|
31
|
+
opacity: 0.95,
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
<div
|
|
35
|
+
style={{
|
|
36
|
+
display: 'flex',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
justifyContent: 'flex-end',
|
|
39
|
+
padding: '2px 4px 2px 8px',
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<IconButton
|
|
43
|
+
size="small"
|
|
44
|
+
onClick={() => {
|
|
45
|
+
setExpanded(!expanded)
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<ExpandIcon fontSize="small" />
|
|
49
|
+
</IconButton>
|
|
50
|
+
</div>
|
|
51
|
+
{expanded ? (
|
|
52
|
+
<div style={{ overflow: 'auto', padding: '0 8px 6px' }}>
|
|
53
|
+
{visible.map(({ accession, name }) => (
|
|
54
|
+
<div
|
|
55
|
+
key={accession}
|
|
56
|
+
style={{ display: 'flex', alignItems: 'center', gap: 6 }}
|
|
57
|
+
title={accession}
|
|
58
|
+
>
|
|
59
|
+
<div
|
|
60
|
+
style={{
|
|
61
|
+
width: 12,
|
|
62
|
+
height: 12,
|
|
63
|
+
flexShrink: 0,
|
|
64
|
+
background: fillPalette[accession],
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
<Typography variant="caption" noWrap>
|
|
68
|
+
{name}
|
|
69
|
+
</Typography>
|
|
70
|
+
</div>
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
) : null}
|
|
74
|
+
</Paper>
|
|
75
|
+
) : null
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
export default DomainLegend
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { BaseTooltip } from '@jbrowse/core/ui'
|
|
4
|
-
import { autorun } from 'mobx'
|
|
5
4
|
import { observer } from 'mobx-react'
|
|
6
5
|
|
|
6
|
+
import ColumnStats from './ColumnStats.tsx'
|
|
7
7
|
import { renderBoxFeatureCanvasBlock } from './renderBoxFeatureCanvasBlock.ts'
|
|
8
8
|
import { renderMSABlock } from './renderMSABlock.ts'
|
|
9
|
+
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
9
10
|
import { useColorContrast } from '../../useColorContrast.ts'
|
|
10
11
|
|
|
11
12
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -47,13 +48,8 @@ const MSACanvasBlock = observer(function ({
|
|
|
47
48
|
} = model
|
|
48
49
|
const { theme, contrastScheme } = useColorContrast(colorScheme)
|
|
49
50
|
|
|
50
|
-
const ref =
|
|
51
|
-
|
|
52
|
-
const ctx = ref.current?.getContext('2d')
|
|
53
|
-
if (!ctx) {
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
return autorun(() => {
|
|
51
|
+
const ref = useCanvasAutorun(
|
|
52
|
+
ctx => {
|
|
57
53
|
ctx.resetTransform()
|
|
58
54
|
ctx.clearRect(
|
|
59
55
|
0,
|
|
@@ -77,19 +73,20 @@ const MSACanvasBlock = observer(function ({
|
|
|
77
73
|
contrastScheme,
|
|
78
74
|
model,
|
|
79
75
|
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
},
|
|
77
|
+
[
|
|
78
|
+
model,
|
|
79
|
+
offsetX,
|
|
80
|
+
offsetY,
|
|
81
|
+
theme,
|
|
82
|
+
blockSize,
|
|
83
|
+
highResScaleFactor,
|
|
84
|
+
contrastScheme,
|
|
85
|
+
],
|
|
86
|
+
)
|
|
90
87
|
|
|
91
88
|
const [mousePosition, setMousePosition] = useState<{ x: number; y: number }>()
|
|
92
|
-
const { hoveredInsertion } = model
|
|
89
|
+
const { hoveredInsertion, mouseOverDomains, showColumnStats } = model
|
|
93
90
|
|
|
94
91
|
return (
|
|
95
92
|
<>
|
|
@@ -116,12 +113,16 @@ const MSACanvasBlock = observer(function ({
|
|
|
116
113
|
} else {
|
|
117
114
|
model.setMousePos(undefined, undefined)
|
|
118
115
|
}
|
|
119
|
-
// only track client coords when
|
|
120
|
-
//
|
|
116
|
+
// only track client coords when there's something to show in the
|
|
117
|
+
// tooltip: an insertion, a domain under the cursor, or column stats
|
|
118
|
+
// (when that option is enabled). avoids a re-render on every move
|
|
119
|
+
// when nothing would be shown
|
|
120
|
+
const hasTooltip =
|
|
121
|
+
model.hoveredInsertion ||
|
|
122
|
+
model.mouseOverDomains.length > 0 ||
|
|
123
|
+
(model.showColumnStats && !!model.mouseOverColumnStats)
|
|
121
124
|
setMousePosition(
|
|
122
|
-
|
|
123
|
-
? { x: event.clientX, y: event.clientY }
|
|
124
|
-
: undefined,
|
|
125
|
+
hasTooltip ? { x: event.clientX, y: event.clientY } : undefined,
|
|
125
126
|
)
|
|
126
127
|
}
|
|
127
128
|
}}
|
|
@@ -167,6 +168,26 @@ const MSACanvasBlock = observer(function ({
|
|
|
167
168
|
: hoveredInsertion.letters}
|
|
168
169
|
</BaseTooltip>
|
|
169
170
|
) : null}
|
|
171
|
+
{!hoveredInsertion &&
|
|
172
|
+
mousePosition &&
|
|
173
|
+
(mouseOverDomains.length > 0 || showColumnStats) ? (
|
|
174
|
+
<BaseTooltip
|
|
175
|
+
clientPoint={{ x: mousePosition.x, y: mousePosition.y + 15 }}
|
|
176
|
+
>
|
|
177
|
+
{mouseOverDomains.map(d => (
|
|
178
|
+
<div key={`${d.accession}-${d.start}-${d.end}`}>
|
|
179
|
+
<b>{d.name}</b> ({d.accession}) {d.start}-{d.end}
|
|
180
|
+
{d.description ? (
|
|
181
|
+
<>
|
|
182
|
+
<br />
|
|
183
|
+
{d.description}
|
|
184
|
+
</>
|
|
185
|
+
) : null}
|
|
186
|
+
</div>
|
|
187
|
+
))}
|
|
188
|
+
{showColumnStats ? <ColumnStats model={model} /> : null}
|
|
189
|
+
</BaseTooltip>
|
|
190
|
+
) : null}
|
|
170
191
|
</>
|
|
171
192
|
)
|
|
172
193
|
})
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react'
|
|
2
2
|
|
|
3
3
|
import { isAlive } from '@jbrowse/mobx-state-tree'
|
|
4
|
-
import { autorun } from 'mobx'
|
|
5
4
|
import { observer } from 'mobx-react'
|
|
6
5
|
|
|
7
6
|
import { renderMouseover } from './renderMSAMouseover.ts'
|
|
7
|
+
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
8
8
|
|
|
9
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
10
|
|
|
@@ -13,20 +13,17 @@ const MSAMouseoverCanvas = observer(function ({
|
|
|
13
13
|
}: {
|
|
14
14
|
model: MsaViewModel
|
|
15
15
|
}) {
|
|
16
|
-
const ref = useRef<HTMLCanvasElement>(null)
|
|
17
16
|
const { height, msaAreaWidth, verticalScrollbarWidth, highResScaleFactor } =
|
|
18
17
|
model
|
|
19
18
|
const width = msaAreaWidth - verticalScrollbarWidth
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
: undefined
|
|
29
|
-
}, [model])
|
|
19
|
+
const ref = useCanvasAutorun(
|
|
20
|
+
ctx => {
|
|
21
|
+
if (isAlive(model)) {
|
|
22
|
+
renderMouseover({ ctx, model })
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
[model],
|
|
26
|
+
)
|
|
30
27
|
|
|
31
28
|
return (
|
|
32
29
|
<canvas
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { observer } from 'mobx-react'
|
|
4
4
|
|
|
5
|
+
import DomainLegend from './DomainLegend.tsx'
|
|
5
6
|
import MSACanvas from './MSACanvas.tsx'
|
|
6
7
|
import MSAMouseoverCanvas from './MSAMouseoverCanvas.tsx'
|
|
7
8
|
|
|
@@ -13,6 +14,7 @@ const MSAPanel = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
13
14
|
<div style={{ position: 'relative' }}>
|
|
14
15
|
<MSACanvas model={model} />
|
|
15
16
|
<MSAMouseoverCanvas model={model} />
|
|
17
|
+
<DomainLegend model={model} />
|
|
16
18
|
</div>
|
|
17
19
|
)
|
|
18
20
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getVisibleLeaves } from './getVisibleLeaves.ts'
|
|
2
|
+
import { subFeatureRowHeight } from '../../constants.ts'
|
|
2
3
|
|
|
3
4
|
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
4
5
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -53,38 +54,95 @@ function drawTiles({
|
|
|
53
54
|
rowHeight,
|
|
54
55
|
fillPalette,
|
|
55
56
|
strokePalette,
|
|
57
|
+
segmentLabels,
|
|
58
|
+
showMsaLetters,
|
|
56
59
|
tidyFilteredGatheredInterProAnnotations,
|
|
57
60
|
} = model
|
|
61
|
+
const h = subFeatureRows ? subFeatureRowHeight : rowHeight
|
|
62
|
+
// exon numbers label the bands only when residue letters aren't drawn (zoomed
|
|
63
|
+
// out); when letters show, the alternating shades alone mark the boundaries
|
|
64
|
+
// and a number would collide with the sequence
|
|
65
|
+
const drawSegmentLabels = !showMsaLetters && !subFeatureRows && h >= 9
|
|
58
66
|
|
|
59
67
|
for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
|
|
60
68
|
const node = visibleLeaves[i]!
|
|
61
|
-
const {
|
|
62
|
-
|
|
63
|
-
data: { name },
|
|
64
|
-
} = node
|
|
65
|
-
const y = x!
|
|
66
|
-
|
|
69
|
+
const { name } = node.data
|
|
70
|
+
const y = node.x!
|
|
67
71
|
const entry = tidyFilteredGatheredInterProAnnotations[name]
|
|
68
72
|
|
|
69
73
|
if (entry) {
|
|
70
74
|
for (let j = 0, l2 = entry.length; j < l2; j++) {
|
|
71
|
-
const { start, end, accession } = entry[j]!
|
|
75
|
+
const { start, end, accession, strand } = entry[j]!
|
|
72
76
|
// Convert sequence positions to visible column positions
|
|
73
77
|
// seqPos is 1-based from InterPro, so subtract 1 for 0-based
|
|
74
78
|
const m1 = model.seqPosToVisibleCol(name, start - 1)
|
|
75
79
|
const m2 = model.seqPosToVisibleCol(name, end)
|
|
76
|
-
if (m1
|
|
77
|
-
|
|
80
|
+
if (m1 !== undefined && m2 !== undefined) {
|
|
81
|
+
const x = m1 * colWidth
|
|
82
|
+
const t = y - rowHeight + (subFeatureRows ? j * h : 0)
|
|
83
|
+
const lw = colWidth * (m2 - m1)
|
|
84
|
+
ctx.fillStyle = fillPalette[accession]!
|
|
85
|
+
ctx.strokeStyle = strokePalette[accession]!
|
|
86
|
+
if (strand === undefined) {
|
|
87
|
+
ctx.fillRect(x, t, lw, h)
|
|
88
|
+
ctx.strokeRect(x, t, lw, h)
|
|
89
|
+
// segment (exon) number drawn once per band, on the top visible row,
|
|
90
|
+
// so it reads as a column header for the whole band
|
|
91
|
+
const label = drawSegmentLabels
|
|
92
|
+
? segmentLabels.get(accession)
|
|
93
|
+
: undefined
|
|
94
|
+
if (label !== undefined && i === 0) {
|
|
95
|
+
const fontSize = Math.min(h - 2, 11)
|
|
96
|
+
ctx.font = `${fontSize}px sans-serif`
|
|
97
|
+
if (ctx.measureText(label).width + 2 <= lw) {
|
|
98
|
+
ctx.fillStyle = '#222'
|
|
99
|
+
ctx.textAlign = 'center'
|
|
100
|
+
ctx.textBaseline = 'middle'
|
|
101
|
+
ctx.fillText(label, x + lw / 2, t + h / 2)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
drawGeneArrow({ ctx, x, t, w: lw, h, strand })
|
|
106
|
+
}
|
|
78
107
|
}
|
|
79
|
-
const x = m1 * colWidth
|
|
80
|
-
ctx.fillStyle = fillPalette[accession]!
|
|
81
|
-
ctx.strokeStyle = strokePalette[accession]!
|
|
82
|
-
const h = subFeatureRows ? 4 : rowHeight
|
|
83
|
-
const t = y - rowHeight + (subFeatureRows ? j * h : 0)
|
|
84
|
-
const lw = colWidth * (m2 - m1)
|
|
85
|
-
ctx.fillRect(x, t, lw, h)
|
|
86
|
-
ctx.strokeRect(x, t, lw, h)
|
|
87
108
|
}
|
|
88
109
|
}
|
|
89
110
|
}
|
|
90
111
|
}
|
|
112
|
+
|
|
113
|
+
// A gene arrow: a full-width rectangular body spanning the feature's
|
|
114
|
+
// start..end columns, plus a triangular head that points *beyond* that end in
|
|
115
|
+
// the strand direction (right for +, left for -). Keeping the body aligned to
|
|
116
|
+
// the exact start/end columns means + and - strand features read as having the
|
|
117
|
+
// same boundaries; the arrow lives outside them purely to show transcription
|
|
118
|
+
// direction, preserving the column-by-column homology down the rows.
|
|
119
|
+
function drawGeneArrow({
|
|
120
|
+
ctx,
|
|
121
|
+
x,
|
|
122
|
+
t,
|
|
123
|
+
w,
|
|
124
|
+
h,
|
|
125
|
+
strand,
|
|
126
|
+
}: {
|
|
127
|
+
ctx: RenderCtx
|
|
128
|
+
x: number
|
|
129
|
+
t: number
|
|
130
|
+
w: number
|
|
131
|
+
h: number
|
|
132
|
+
strand: number
|
|
133
|
+
}) {
|
|
134
|
+
// body spans bodyStart..bodyEnd (the exact columns); the head extends one
|
|
135
|
+
// row-height past bodyEnd in the strand direction
|
|
136
|
+
const dir = strand > 0 ? 1 : -1
|
|
137
|
+
const bodyStart = strand > 0 ? x : x + w
|
|
138
|
+
const bodyEnd = strand > 0 ? x + w : x
|
|
139
|
+
ctx.beginPath()
|
|
140
|
+
ctx.moveTo(bodyStart, t)
|
|
141
|
+
ctx.lineTo(bodyEnd, t)
|
|
142
|
+
ctx.lineTo(bodyEnd + dir * h, t + h / 2)
|
|
143
|
+
ctx.lineTo(bodyEnd, t + h)
|
|
144
|
+
ctx.lineTo(bodyStart, t + h)
|
|
145
|
+
ctx.closePath()
|
|
146
|
+
ctx.fill()
|
|
147
|
+
ctx.stroke()
|
|
148
|
+
}
|
|
@@ -46,6 +46,7 @@ export function renderMSABlock({
|
|
|
46
46
|
ctx.scale(k, k)
|
|
47
47
|
ctx.translate(-offsetX, rowHeight / 2 - offsetY)
|
|
48
48
|
ctx.textAlign = 'center'
|
|
49
|
+
ctx.textBaseline = 'alphabetic'
|
|
49
50
|
setFontSize(ctx, fontSize, !bgColor)
|
|
50
51
|
|
|
51
52
|
const { xStart, xEnd } = visibleColRange({
|
|
@@ -124,8 +125,7 @@ function drawTilesAndText({
|
|
|
124
125
|
|
|
125
126
|
const isClustalX = colorSchemeName === 'clustalx_protein_dynamic'
|
|
126
127
|
const isPercentIdentity = colorSchemeName === 'percent_identity_dynamic'
|
|
127
|
-
|
|
128
|
-
if (!drawBgTiles && !showMsaLetters) {
|
|
128
|
+
if (!bgColor && !showMsaLetters) {
|
|
129
129
|
return
|
|
130
130
|
}
|
|
131
131
|
const offsetXAligned = xStart * colWidth
|
|
@@ -159,7 +159,7 @@ function drawTilesAndText({
|
|
|
159
159
|
color = colorScheme[letter]
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
if (
|
|
162
|
+
if (bgColor) {
|
|
163
163
|
ctx.fillStyle = isMatchingReference
|
|
164
164
|
? theme.palette.action.hover
|
|
165
165
|
: color || theme.palette.background.default
|
|
@@ -1,9 +1,31 @@
|
|
|
1
|
+
import { multiRowHoverColor, referenceColor } from '../overlayColors.ts'
|
|
2
|
+
|
|
1
3
|
import type { MsaViewModel } from '../../model.ts'
|
|
2
4
|
|
|
3
5
|
const hoverColor = 'rgba(0,0,0,0.15)'
|
|
4
6
|
const highlightColor = 'rgba(128,128,0,0.2)'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
// the persistent highlightColumns overlay: a stronger fill plus a solid border
|
|
8
|
+
// so a domain/motif band reads clearly over the colored alignment cells (the
|
|
9
|
+
// faint hover-style wash alone is invisible against clustalx coloring)
|
|
10
|
+
const highlightColumnsFill = 'rgba(255,140,0,0.28)'
|
|
11
|
+
const highlightColumnsBorder = 'rgba(210,90,0,0.95)'
|
|
12
|
+
|
|
13
|
+
// Collapse sorted column indices into contiguous [start,end] runs so a run of
|
|
14
|
+
// highlighted columns draws as one bordered band rather than per-column slices
|
|
15
|
+
// (which would draw internal borders).
|
|
16
|
+
function contiguousRuns(columns: number[]) {
|
|
17
|
+
const sorted = [...columns].sort((a, b) => a - b)
|
|
18
|
+
const runs: { start: number; end: number }[] = []
|
|
19
|
+
for (const col of sorted) {
|
|
20
|
+
const last = runs.at(-1)
|
|
21
|
+
if (last && col === last.end + 1) {
|
|
22
|
+
last.end = col
|
|
23
|
+
} else {
|
|
24
|
+
runs.push({ start: col, end: col })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return runs
|
|
28
|
+
}
|
|
7
29
|
|
|
8
30
|
export function renderMouseover({
|
|
9
31
|
ctx,
|
|
@@ -54,11 +76,17 @@ export function renderMouseover({
|
|
|
54
76
|
}
|
|
55
77
|
}
|
|
56
78
|
|
|
57
|
-
// Highlight multiple columns
|
|
79
|
+
// Highlight multiple columns — draw each contiguous run as a filled, bordered
|
|
80
|
+
// band so a domain/motif highlight is clearly visible over the alignment
|
|
58
81
|
if (highlightedColumns?.length) {
|
|
59
|
-
ctx.
|
|
60
|
-
for (const
|
|
61
|
-
|
|
82
|
+
ctx.lineWidth = 2
|
|
83
|
+
for (const { start, end } of contiguousRuns(highlightedColumns)) {
|
|
84
|
+
const x = start * colWidth + scrollX
|
|
85
|
+
const w = (end - start + 1) * colWidth
|
|
86
|
+
ctx.fillStyle = highlightColumnsFill
|
|
87
|
+
ctx.fillRect(x, 0, w, height)
|
|
88
|
+
ctx.strokeStyle = highlightColumnsBorder
|
|
89
|
+
ctx.strokeRect(x, 0, w, height)
|
|
62
90
|
}
|
|
63
91
|
}
|
|
64
92
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Shared highlight colors for the MSA/tree hover and selection overlay canvases.
|
|
2
|
+
// Kept in one place so the reference-row color (drawn identically over both the
|
|
3
|
+
// tree and the alignment) can't drift between the two surfaces.
|
|
4
|
+
|
|
5
|
+
// reference (relativeTo) row, drawn over both the tree and the alignment
|
|
6
|
+
export const referenceColor = 'rgba(0,128,255,0.3)'
|
|
7
|
+
|
|
8
|
+
// tree-node descendant rows highlighted on hover. The alpha differs by surface
|
|
9
|
+
// intentionally: the tree background is plain so it can take a stronger wash,
|
|
10
|
+
// while the alignment cells are already colored so a lighter wash reads better.
|
|
11
|
+
export const treeHoverColor = 'rgba(255,165,0,0.2)'
|
|
12
|
+
export const multiRowHoverColor = 'rgba(255,165,0,0.15)'
|