react-msaview 5.10.0 → 6.1.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/bundle/index.js +99 -99
- package/bundle/index.js.map +4 -4
- package/dist/clustalX.js +22 -22
- package/dist/clustalX.js.map +1 -1
- package/dist/columnCounts.d.ts +7 -0
- package/dist/columnCounts.js +18 -0
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/CopyButton.d.ts +9 -0
- package/dist/components/CopyButton.js +23 -0
- package/dist/components/CopyButton.js.map +1 -0
- package/dist/components/DragHandle.d.ts +25 -0
- package/dist/components/DragHandle.js +49 -0
- package/dist/components/DragHandle.js.map +1 -0
- package/dist/components/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +2 -2
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/ResizeHandles.d.ts +4 -2
- package/dist/components/ResizeHandles.js +35 -54
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/SequenceTextArea.js +3 -10
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +11 -25
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/FeatureDialog.js +3 -3
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/dialogs/InterProScanDialog.js +52 -129
- package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
- package/dist/components/dialogs/{UserProvidedDomainsDialog.d.ts → InterProScanFileDialog.d.ts} +2 -2
- package/dist/components/dialogs/{UserProvidedDomainsDialog.js → InterProScanFileDialog.js} +6 -10
- package/dist/components/dialogs/InterProScanFileDialog.js.map +1 -0
- package/dist/components/dialogs/TrackInfoDialog.d.ts +2 -4
- package/dist/components/dialogs/TrackInfoDialog.js +8 -14
- package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
- package/dist/components/header/FileMenu.js +3 -3
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/GappynessSlider.js +1 -1
- package/dist/components/header/GappynessSlider.js.map +1 -1
- package/dist/components/header/{getDomainsMenu.d.ts → getAnnotationsMenu.d.ts} +1 -1
- package/dist/components/header/{getDomainsMenu.js → getAnnotationsMenu.js} +10 -9
- package/dist/components/header/getAnnotationsMenu.js.map +1 -0
- package/dist/components/header/settingsMenuItems.js +15 -0
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/ImportForm.js +1 -1
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +66 -57
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/minimap/Minimap.js +40 -23
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.d.ts +2 -1
- package/dist/components/minimap/MinimapSVG.js +3 -4
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/minimap/minimapLayout.d.ts +10 -1
- package/dist/components/minimap/minimapLayout.js +15 -10
- package/dist/components/minimap/minimapLayout.js.map +1 -1
- package/dist/components/msa/{DomainLegend.d.ts → AnnotationLegend.d.ts} +2 -2
- package/dist/components/msa/{DomainLegend.js → AnnotationLegend.js} +4 -4
- package/dist/components/msa/AnnotationLegend.js.map +1 -0
- package/dist/components/msa/MSACanvas.js +9 -3
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +12 -3
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +1 -2
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/MSAPanel.js +2 -2
- package/dist/components/msa/MSAPanel.js.map +1 -1
- package/dist/components/msa/domainBandCursor.d.ts +17 -0
- package/dist/components/msa/domainBandCursor.js +39 -0
- package/dist/components/msa/domainBandCursor.js.map +1 -0
- package/dist/components/msa/msaRaster.d.ts +63 -0
- package/dist/components/msa/msaRaster.js +252 -0
- package/dist/components/msa/msaRaster.js.map +1 -0
- package/dist/components/msa/renderMSABlock.d.ts +2 -1
- package/dist/components/msa/renderMSABlock.js +27 -41
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +1 -2
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/msa/tileColor.d.ts +6 -0
- package/dist/components/msa/tileColor.js +35 -0
- package/dist/components/msa/tileColor.js.map +1 -0
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/tracks/TrackBlocks.d.ts +14 -0
- package/dist/components/tracks/TrackBlocks.js +65 -0
- package/dist/components/tracks/TrackBlocks.js.map +1 -0
- package/dist/components/tracks/{renderTracksSvg.d.ts → drawTracks.d.ts} +22 -10
- package/dist/components/tracks/drawTracks.js +193 -0
- package/dist/components/tracks/drawTracks.js.map +1 -0
- package/dist/components/tree/TreeCanvas.js +8 -2
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +7 -4
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeRuler.js +5 -2
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +5 -2
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -1
- package/dist/ggplotPalettes.d.ts +0 -1
- package/dist/ggplotPalettes.js +0 -3
- package/dist/ggplotPalettes.js.map +1 -1
- package/dist/hierarchy.d.ts +2 -6
- package/dist/hierarchy.js +4 -6
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/launchInterProScan.d.ts +0 -23
- package/dist/launchInterProScan.js +2 -5
- package/dist/launchInterProScan.js.map +1 -1
- package/dist/measureTextCanvas.js +21 -6
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/DataModel.d.ts +1 -4
- package/dist/model/DataModel.js +8 -10
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model.d.ts +86 -107
- package/dist/model.js +238 -162
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +34 -18
- package/dist/neighborJoining.js.map +1 -1
- package/dist/renderTestEnv.d.ts +43 -0
- package/dist/renderTestEnv.js +88 -0
- package/dist/renderTestEnv.js.map +1 -0
- package/dist/renderToSvg.js +3 -3
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +13 -17
- package/dist/rowCoordinateCalculations.js +45 -39
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/sequenceLogo.d.ts +28 -0
- package/dist/sequenceLogo.js +47 -0
- package/dist/sequenceLogo.js.map +1 -0
- package/dist/types.d.ts +6 -11
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -5
- package/src/clustalX.ts +22 -22
- package/src/colSpace.test.ts +27 -0
- package/src/columnCounts.test.ts +15 -0
- package/src/columnCounts.ts +19 -0
- package/src/components/CopyButton.tsx +44 -0
- package/src/components/DragHandle.tsx +81 -0
- package/src/components/Loading.tsx +10 -1
- package/src/components/MSAView.tsx +2 -3
- package/src/components/ResizeHandles.tsx +70 -87
- package/src/components/SequenceTextArea.tsx +3 -16
- package/src/components/VerticalScrollbar.tsx +20 -33
- package/src/components/dialogs/FeatureDialog.tsx +3 -4
- package/src/components/dialogs/InterProScanDialog.tsx +54 -131
- package/src/components/dialogs/{UserProvidedDomainsDialog.tsx → InterProScanFileDialog.tsx} +6 -14
- package/src/components/dialogs/TrackInfoDialog.tsx +18 -20
- package/src/components/header/FileMenu.tsx +3 -3
- package/src/components/header/GappynessSlider.tsx +4 -1
- package/src/components/header/{getDomainsMenu.ts → getAnnotationsMenu.ts} +10 -9
- package/src/components/header/settingsMenuItems.ts +18 -0
- package/src/components/import/ImportForm.tsx +1 -1
- package/src/components/import/ImportFormExamples.tsx +82 -133
- package/src/components/minimap/Minimap.tsx +67 -32
- package/src/components/minimap/MinimapSVG.tsx +32 -31
- package/src/components/minimap/minimapLayout.test.ts +60 -0
- package/src/components/minimap/minimapLayout.ts +15 -10
- package/src/components/msa/{DomainLegend.tsx → AnnotationLegend.tsx} +7 -3
- package/src/components/msa/MSACanvas.tsx +24 -11
- package/src/components/msa/MSACanvasBlock.tsx +13 -3
- package/src/components/msa/MSAMouseoverCanvas.tsx +1 -3
- package/src/components/msa/MSAPanel.tsx +2 -2
- package/src/components/msa/domainBandCursor.ts +41 -0
- package/src/components/msa/msaRaster.test.ts +145 -0
- package/src/components/msa/msaRaster.ts +350 -0
- package/src/components/msa/renderMSABlock.ts +34 -43
- package/src/components/msa/renderMSAMouseover.ts +1 -3
- package/src/components/msa/tileColor.ts +37 -0
- package/src/components/renderCtx.ts +2 -0
- package/src/components/tracks/TrackBlocks.tsx +111 -0
- package/src/components/tracks/drawTracks.ts +323 -0
- package/src/components/tree/TreeCanvas.tsx +16 -4
- package/src/components/tree/TreeCanvasBlock.tsx +18 -11
- package/src/components/tree/TreeRuler.tsx +7 -2
- package/src/components/tree/renderTreeCanvas.test.ts +15 -7
- package/src/components/tree/renderTreeCanvas.ts +5 -1
- package/src/constants.ts +10 -0
- package/src/domainBands.test.ts +24 -0
- package/src/domainLetterColor.test.ts +80 -0
- package/src/ggplotPalettes.ts +0 -4
- package/src/hierarchy.test.ts +7 -7
- package/src/hierarchy.ts +6 -9
- package/src/impgRender.test.tsx +2 -57
- package/src/index.ts +4 -1
- package/src/largeTree.test.ts +39 -4
- package/src/launchInterProScan.ts +3 -30
- package/src/loadErrorDisplay.test.tsx +102 -0
- package/src/measureTextCanvas.ts +22 -6
- package/src/model/DataModel.ts +11 -11
- package/src/model.ts +262 -172
- package/src/neighborJoining.ts +36 -18
- package/src/renderDomainsSvg.test.tsx +2 -57
- package/src/renderMinimapSvg.test.tsx +55 -0
- package/src/renderSequenceLogoSvg.test.tsx +176 -0
- package/src/renderTestEnv.ts +96 -0
- package/src/renderToSvg.tsx +3 -3
- package/src/rowCoordinateCalculations.ts +48 -44
- package/src/sequenceLogo.test.ts +88 -0
- package/src/sequenceLogo.ts +63 -0
- package/src/types.ts +17 -16
- package/src/version.ts +1 -1
- package/src/viewChrome.test.tsx +134 -0
- package/dist/components/ConservationTrack.d.ts +0 -8
- package/dist/components/ConservationTrack.js +0 -49
- package/dist/components/ConservationTrack.js.map +0 -1
- package/dist/components/TextTrack.d.ts +0 -8
- package/dist/components/TextTrack.js +0 -58
- package/dist/components/TextTrack.js.map +0 -1
- package/dist/components/dialogs/DomainDialog.d.ts +0 -6
- package/dist/components/dialogs/DomainDialog.js +0 -19
- package/dist/components/dialogs/DomainDialog.js.map +0 -1
- package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +0 -1
- package/dist/components/header/SettingsMenu.d.ts +0 -6
- package/dist/components/header/SettingsMenu.js +0 -40
- package/dist/components/header/SettingsMenu.js.map +0 -1
- package/dist/components/header/getDomainsMenu.js.map +0 -1
- package/dist/components/msa/DomainLegend.js.map +0 -1
- package/dist/components/tracks/renderTracksSvg.js +0 -126
- package/dist/components/tracks/renderTracksSvg.js.map +0 -1
- package/src/components/ConservationTrack.tsx +0 -99
- package/src/components/TextTrack.tsx +0 -103
- package/src/components/dialogs/DomainDialog.tsx +0 -45
- package/src/components/header/SettingsMenu.tsx +0 -57
- package/src/components/tracks/renderTracksSvg.ts +0 -247
|
@@ -7,7 +7,7 @@ import Menu from '@mui/icons-material/Menu'
|
|
|
7
7
|
import PhotoCamera from '@mui/icons-material/PhotoCamera'
|
|
8
8
|
import { observer } from 'mobx-react'
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { getAnnotationsMenu } from './getAnnotationsMenu.ts'
|
|
11
11
|
|
|
12
12
|
import type { MsaViewModel } from '../../model.ts'
|
|
13
13
|
|
|
@@ -41,9 +41,9 @@ const FileMenu = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
41
41
|
},
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
|
-
label: '
|
|
44
|
+
label: 'Annotations',
|
|
45
45
|
type: 'subMenu',
|
|
46
|
-
subMenu:
|
|
46
|
+
subMenu: getAnnotationsMenu({
|
|
47
47
|
model,
|
|
48
48
|
}),
|
|
49
49
|
},
|
|
@@ -16,8 +16,11 @@ const GappynessSlider = observer(function GappynessSlider({
|
|
|
16
16
|
}
|
|
17
17
|
return (
|
|
18
18
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
19
|
+
{/* the threshold is inclusive: blanks are columns whose gap count is at
|
|
20
|
+
least ceil(allowedGappyness% of the rows), so at 100 it hides the
|
|
21
|
+
columns that are entirely gaps rather than nothing */}
|
|
19
22
|
<Typography style={{ whiteSpace: 'nowrap' }}>
|
|
20
|
-
Hide columns w/ &
|
|
23
|
+
Hide columns w/ ≥{allowedGappyness}% gaps
|
|
21
24
|
</Typography>
|
|
22
25
|
<Slider
|
|
23
26
|
// Named, because the class alone is not enough to find it from outside.
|
|
@@ -9,22 +9,23 @@ import Visibility from '@mui/icons-material/Visibility'
|
|
|
9
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
10
|
|
|
11
11
|
const FeatureFilterDialog = lazy(() => import('../dialogs/FeatureDialog.tsx'))
|
|
12
|
-
const
|
|
13
|
-
() => import('../dialogs/
|
|
12
|
+
const InterProScanFileDialog = lazy(
|
|
13
|
+
() => import('../dialogs/InterProScanFileDialog.tsx'),
|
|
14
14
|
)
|
|
15
15
|
const InterProScanDialog = lazy(
|
|
16
16
|
() => import('../dialogs/InterProScanDialog.tsx'),
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
-
export function
|
|
19
|
+
export function getAnnotationsMenu({ model }: { model: MsaViewModel }) {
|
|
20
20
|
const { showDomains, actuallyShowDomains, subFeatureRows, noDomains } = model
|
|
21
|
+
const noneLoaded = noDomains ? ' (none loaded)' : ''
|
|
21
22
|
return [
|
|
22
23
|
{
|
|
23
|
-
label: 'Open
|
|
24
|
+
label: 'Open InterProScan results...',
|
|
24
25
|
icon: FolderOpen,
|
|
25
26
|
onClick: () => {
|
|
26
27
|
model.queueDialog(handleClose => [
|
|
27
|
-
|
|
28
|
+
InterProScanFileDialog,
|
|
28
29
|
{
|
|
29
30
|
handleClose,
|
|
30
31
|
model,
|
|
@@ -33,7 +34,7 @@ export function getDomainMenu({ model }: { model: MsaViewModel }) {
|
|
|
33
34
|
},
|
|
34
35
|
},
|
|
35
36
|
{
|
|
36
|
-
label: 'Query InterProScan
|
|
37
|
+
label: 'Query InterProScan...',
|
|
37
38
|
icon: Search,
|
|
38
39
|
onClick: () => {
|
|
39
40
|
model.queueDialog(handleClose => [
|
|
@@ -46,7 +47,7 @@ export function getDomainMenu({ model }: { model: MsaViewModel }) {
|
|
|
46
47
|
},
|
|
47
48
|
},
|
|
48
49
|
{
|
|
49
|
-
label: `Show
|
|
50
|
+
label: `Show annotations${noneLoaded}`,
|
|
50
51
|
disabled: noDomains,
|
|
51
52
|
icon: Visibility,
|
|
52
53
|
checked: actuallyShowDomains,
|
|
@@ -56,7 +57,7 @@ export function getDomainMenu({ model }: { model: MsaViewModel }) {
|
|
|
56
57
|
},
|
|
57
58
|
},
|
|
58
59
|
{
|
|
59
|
-
label: `Use sub-row layout${
|
|
60
|
+
label: `Use sub-row layout${noneLoaded}`,
|
|
60
61
|
disabled: noDomains,
|
|
61
62
|
checked: actuallyShowDomains ? subFeatureRows : false,
|
|
62
63
|
icon: Sort,
|
|
@@ -66,7 +67,7 @@ export function getDomainMenu({ model }: { model: MsaViewModel }) {
|
|
|
66
67
|
},
|
|
67
68
|
},
|
|
68
69
|
{
|
|
69
|
-
label: `Filter
|
|
70
|
+
label: `Filter annotations${noneLoaded}`,
|
|
70
71
|
icon: FilterAlt,
|
|
71
72
|
disabled: noDomains,
|
|
72
73
|
onClick: () => {
|
|
@@ -12,9 +12,27 @@ function toggle(label: string, checked: boolean, set: (arg: boolean) => void) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// Every track the alignment can show, with its current state. Without this the
|
|
16
|
+
// "Close" item on a track's own dropdown is a one-way door -- there is nowhere
|
|
17
|
+
// else in the UI to turn a track back on.
|
|
18
|
+
function tracksSubMenu(model: MsaViewModel): MenuItem[] {
|
|
19
|
+
const shown = new Set(model.turnedOnTracks.map(t => t.model.id))
|
|
20
|
+
return model.tracks.map(({ model: { id, name } }) =>
|
|
21
|
+
toggle(name, shown.has(id), () => {
|
|
22
|
+
model.toggleTrack(id)
|
|
23
|
+
}),
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
export function msaSettingsMenuItems(model: MsaViewModel): MenuItem[] {
|
|
16
28
|
const { drawMsaLetters, hideGaps, bgColor, showColumnStats } = model
|
|
17
29
|
return [
|
|
30
|
+
{
|
|
31
|
+
label: 'Tracks',
|
|
32
|
+
type: 'subMenu' as const,
|
|
33
|
+
subMenu: tracksSubMenu(model),
|
|
34
|
+
},
|
|
35
|
+
{ type: 'divider' as const },
|
|
18
36
|
toggle('Show column statistics on hover', showColumnStats, arg => {
|
|
19
37
|
model.setShowColumnStats(arg)
|
|
20
38
|
}),
|
|
@@ -50,7 +50,7 @@ const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
50
50
|
<FileSelector location={treeFile} setLocation={setTreeFile} />
|
|
51
51
|
</div>
|
|
52
52
|
<div>
|
|
53
|
-
<Typography>
|
|
53
|
+
<Typography>Annotation GFF file or URL (optional)</Typography>
|
|
54
54
|
<FileSelector location={gffFile} setLocation={setGffFile} />
|
|
55
55
|
</div>
|
|
56
56
|
</div>
|
|
@@ -8,149 +8,98 @@ import { load } from './util.ts'
|
|
|
8
8
|
|
|
9
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<Link
|
|
23
|
-
onClick={event => {
|
|
24
|
-
model.setError(undefined)
|
|
25
|
-
event.preventDefault()
|
|
26
|
-
onClick()
|
|
27
|
-
}}
|
|
28
|
-
href="#"
|
|
29
|
-
>
|
|
30
|
-
<Typography component="span">{children}</Typography>
|
|
31
|
-
</Link>
|
|
32
|
-
</li>
|
|
33
|
-
)
|
|
11
|
+
const BASE = 'https://jbrowse.org/genomes/multiple_sequence_alignments'
|
|
12
|
+
const TREES = 'https://jbrowse.org/genomes/newicktrees'
|
|
13
|
+
|
|
14
|
+
// each example either points at remote files (msa/tree/gff urls) or carries its
|
|
15
|
+
// data inline, which is what the two bundled small ones do
|
|
16
|
+
interface Example {
|
|
17
|
+
label: string
|
|
18
|
+
msa?: string
|
|
19
|
+
tree?: string
|
|
20
|
+
gff?: string
|
|
21
|
+
inline?: { msa: string; tree?: string }
|
|
34
22
|
}
|
|
35
23
|
|
|
36
|
-
const
|
|
24
|
+
const examples: Example[] = [
|
|
25
|
+
{
|
|
26
|
+
label: '230k COVID-19 samples (tree only)',
|
|
27
|
+
tree: `${TREES}/sarscov2phylo.pub.ft.nh`,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: 'Small protein MSA+tree',
|
|
31
|
+
inline: { msa: smallMSA, tree: smallTree },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: 'Small MSA only',
|
|
35
|
+
inline: { msa: smallMSAOnly },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: 'PFAM SARS-CoV2 multi-stockholm',
|
|
39
|
+
msa: `${BASE}/pfam-cov2.stock`,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: 'PFAM SARS-CoV2 multi-stockholm w/ domains loaded',
|
|
43
|
+
msa: `${BASE}/pfam-cov2.stock`,
|
|
44
|
+
gff: `${BASE}/pfam-cov2-domains.gff`,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: 'Lysine stockholm file',
|
|
48
|
+
msa: `${BASE}/Lysine.stock`,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'PF01601 stockholm file (SARS-CoV2 spike protein)',
|
|
52
|
+
msa: `${BASE}/PF01601_full.txt`,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: 'Europe COVID full genomes (LR883044.1 and 199 other sequences)',
|
|
56
|
+
msa: `${BASE}/europe_covid.fa`,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: 'MAFFT+VeryFastTree(17.9k samples)',
|
|
60
|
+
msa: `${BASE}/rhv_test-only.aligned_with_mafft_auto.fa`,
|
|
61
|
+
tree: `${BASE}/rhv_test-only.aligned_with_mafft_auto.nh`,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
label: 'Human BLAST results mfa',
|
|
65
|
+
msa: 'https://jbrowse.org/demos/ttc39a.mfa',
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
function uri(url?: string) {
|
|
70
|
+
return url ? { uri: url, locationType: 'UriLocation' as const } : undefined
|
|
71
|
+
}
|
|
37
72
|
|
|
38
73
|
const ImportFormExamples = observer(function ({
|
|
39
74
|
model,
|
|
40
75
|
}: {
|
|
41
76
|
model: MsaViewModel
|
|
42
77
|
}) {
|
|
43
|
-
function loadUris({
|
|
44
|
-
msa,
|
|
45
|
-
tree,
|
|
46
|
-
gff,
|
|
47
|
-
}: {
|
|
48
|
-
msa?: string
|
|
49
|
-
tree?: string
|
|
50
|
-
gff?: string
|
|
51
|
-
}) {
|
|
52
|
-
try {
|
|
53
|
-
load(
|
|
54
|
-
model,
|
|
55
|
-
msa ? { uri: msa, locationType: 'UriLocation' } : undefined,
|
|
56
|
-
tree ? { uri: tree, locationType: 'UriLocation' } : undefined,
|
|
57
|
-
gff ? { uri: gff, locationType: 'UriLocation' } : undefined,
|
|
58
|
-
)
|
|
59
|
-
} catch (e) {
|
|
60
|
-
console.error(e)
|
|
61
|
-
model.setError(e)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
78
|
return (
|
|
65
79
|
<ul>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
>
|
|
90
|
-
Small MSA only
|
|
91
|
-
</ListItem>
|
|
92
|
-
<ListItem
|
|
93
|
-
model={model}
|
|
94
|
-
onClick={() => {
|
|
95
|
-
loadUris({ msa: `${BASE}/pfam-cov2.stock` })
|
|
96
|
-
}}
|
|
97
|
-
>
|
|
98
|
-
PFAM SARS-CoV2 multi-stockholm
|
|
99
|
-
</ListItem>
|
|
100
|
-
<ListItem
|
|
101
|
-
model={model}
|
|
102
|
-
onClick={() => {
|
|
103
|
-
loadUris({
|
|
104
|
-
msa: `${BASE}/pfam-cov2.stock`,
|
|
105
|
-
gff: `${BASE}/pfam-cov2-domains.gff`,
|
|
106
|
-
})
|
|
107
|
-
}}
|
|
108
|
-
>
|
|
109
|
-
PFAM SARS-CoV2 multi-stockholm w/ domains loaded
|
|
110
|
-
</ListItem>
|
|
111
|
-
<ListItem
|
|
112
|
-
model={model}
|
|
113
|
-
onClick={() => {
|
|
114
|
-
loadUris({ msa: `${BASE}/Lysine.stock` })
|
|
115
|
-
}}
|
|
116
|
-
>
|
|
117
|
-
Lysine stockholm file
|
|
118
|
-
</ListItem>
|
|
119
|
-
<ListItem
|
|
120
|
-
model={model}
|
|
121
|
-
onClick={() => {
|
|
122
|
-
loadUris({ msa: `${BASE}/PF01601_full.txt` })
|
|
123
|
-
}}
|
|
124
|
-
>
|
|
125
|
-
PF01601 stockholm file (SARS-CoV2 spike protein)
|
|
126
|
-
</ListItem>
|
|
127
|
-
<ListItem
|
|
128
|
-
model={model}
|
|
129
|
-
onClick={() => {
|
|
130
|
-
loadUris({ msa: `${BASE}/europe_covid.fa` })
|
|
131
|
-
}}
|
|
132
|
-
>
|
|
133
|
-
Europe COVID full genomes (LR883044.1 and 199 other sequences)
|
|
134
|
-
</ListItem>
|
|
135
|
-
<ListItem
|
|
136
|
-
model={model}
|
|
137
|
-
onClick={() => {
|
|
138
|
-
loadUris({
|
|
139
|
-
msa: `${BASE}/rhv_test-only.aligned_with_mafft_auto.fa`,
|
|
140
|
-
tree: `${BASE}/rhv_test-only.aligned_with_mafft_auto.nh`,
|
|
141
|
-
})
|
|
142
|
-
}}
|
|
143
|
-
>
|
|
144
|
-
MAFFT+VeryFastTree(17.9k samples)
|
|
145
|
-
</ListItem>
|
|
146
|
-
<ListItem
|
|
147
|
-
model={model}
|
|
148
|
-
onClick={() => {
|
|
149
|
-
loadUris({ msa: 'https://jbrowse.org/demos/ttc39a.mfa' })
|
|
150
|
-
}}
|
|
151
|
-
>
|
|
152
|
-
Human BLAST results mfa
|
|
153
|
-
</ListItem>
|
|
80
|
+
{examples.map(({ label, msa, tree, gff, inline }) => (
|
|
81
|
+
<li key={label}>
|
|
82
|
+
<Link
|
|
83
|
+
href="#"
|
|
84
|
+
onClick={event => {
|
|
85
|
+
event.preventDefault()
|
|
86
|
+
model.setError(undefined)
|
|
87
|
+
try {
|
|
88
|
+
if (inline) {
|
|
89
|
+
model.setData(inline)
|
|
90
|
+
} else {
|
|
91
|
+
load(model, uri(msa), uri(tree), uri(gff))
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error(e)
|
|
95
|
+
model.setError(e)
|
|
96
|
+
}
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<Typography component="span">{label}</Typography>
|
|
100
|
+
</Link>
|
|
101
|
+
</li>
|
|
102
|
+
))}
|
|
154
103
|
</ul>
|
|
155
104
|
)
|
|
156
105
|
})
|
|
@@ -1,67 +1,102 @@
|
|
|
1
|
-
import React, { useCallback
|
|
1
|
+
import React, { useCallback } from 'react'
|
|
2
2
|
|
|
3
|
+
import { useTheme } from '@mui/material'
|
|
3
4
|
import { observer } from 'mobx-react'
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
+
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
7
|
+
import DragHandle, { scrollbarThumbFill } from '../DragHandle.tsx'
|
|
8
|
+
import { msaThumbnail } from '../msa/msaRaster.ts'
|
|
6
9
|
import { MINIMAP_BAR_HEIGHT, getMinimapLayout } from './minimapLayout.ts'
|
|
7
10
|
|
|
8
11
|
import type { MsaViewModel } from '../../model.ts'
|
|
9
12
|
|
|
10
13
|
const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const { unit, s, w, polygonHeight, polygonPoints } = getMinimapLayout(
|
|
14
|
-
|
|
14
|
+
const { minimapHeight, msaCanvasWidth, highResScaleFactor } = model
|
|
15
|
+
const theme = useTheme()
|
|
16
|
+
const { unit, s, w, polygonHeight, polygonPoints } = getMinimapLayout(
|
|
17
|
+
model,
|
|
18
|
+
msaCanvasWidth,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
// the whole alignment, sampled down to the bar: it says where the conserved
|
|
22
|
+
// blocks and the gappy stretches are, which is what a scroll aims at
|
|
23
|
+
const barWidth = Math.round(msaCanvasWidth * highResScaleFactor)
|
|
24
|
+
const barHeight = Math.round(MINIMAP_BAR_HEIGHT * highResScaleFactor)
|
|
25
|
+
const ref = useCanvasAutorun({
|
|
26
|
+
draw: ctx => {
|
|
27
|
+
ctx.resetTransform()
|
|
28
|
+
ctx.clearRect(0, 0, barWidth, barHeight)
|
|
29
|
+
const thumbnail = msaThumbnail({
|
|
30
|
+
model,
|
|
31
|
+
theme,
|
|
32
|
+
height: MINIMAP_BAR_HEIGHT,
|
|
33
|
+
})
|
|
34
|
+
if (thumbnail) {
|
|
35
|
+
ctx.imageSmoothingEnabled = true
|
|
36
|
+
ctx.drawImage(
|
|
37
|
+
thumbnail,
|
|
38
|
+
0,
|
|
39
|
+
0,
|
|
40
|
+
thumbnail.width,
|
|
41
|
+
thumbnail.height,
|
|
42
|
+
0,
|
|
43
|
+
0,
|
|
44
|
+
barWidth,
|
|
45
|
+
barHeight,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
width: barWidth,
|
|
50
|
+
height: barHeight,
|
|
51
|
+
deps: [model, theme],
|
|
52
|
+
})
|
|
15
53
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
-
[model, unit],
|
|
23
|
-
),
|
|
54
|
+
const onDrag = useCallback(
|
|
55
|
+
(delta: number, startScroll: number) => {
|
|
56
|
+
model.setScrollX(startScroll - delta / unit)
|
|
57
|
+
},
|
|
58
|
+
[model, unit],
|
|
24
59
|
)
|
|
25
60
|
|
|
26
61
|
return (
|
|
62
|
+
// sized and placed to match the alignment canvas exactly: a minimap that
|
|
63
|
+
// spans a different width than the columns it maps puts its viewport
|
|
64
|
+
// rectangle over the wrong ones
|
|
27
65
|
<div
|
|
28
66
|
style={{
|
|
29
67
|
position: 'relative',
|
|
30
68
|
height: minimapHeight,
|
|
31
|
-
width:
|
|
69
|
+
width: msaCanvasWidth,
|
|
70
|
+
flexShrink: 0,
|
|
32
71
|
}}
|
|
33
72
|
>
|
|
34
|
-
<
|
|
73
|
+
<canvas
|
|
74
|
+
ref={ref}
|
|
75
|
+
width={barWidth}
|
|
76
|
+
height={barHeight}
|
|
35
77
|
style={{
|
|
78
|
+
display: 'block',
|
|
79
|
+
width: msaCanvasWidth,
|
|
36
80
|
height: MINIMAP_BAR_HEIGHT,
|
|
37
81
|
boxSizing: 'border-box',
|
|
38
82
|
border: '1px solid #555',
|
|
39
83
|
}}
|
|
40
84
|
/>
|
|
41
|
-
<
|
|
85
|
+
<DragHandle
|
|
86
|
+
axis="x"
|
|
87
|
+
variant="thumb"
|
|
88
|
+
getStart={() => model.scrollX}
|
|
89
|
+
onDrag={onDrag}
|
|
42
90
|
style={{
|
|
43
|
-
position: 'absolute',
|
|
44
91
|
top: 0,
|
|
45
92
|
left: Math.max(0, s),
|
|
46
|
-
background: hovered ? 'rgba(66,119,127,0.6)' : fill,
|
|
47
|
-
cursor: 'pointer',
|
|
48
93
|
height: MINIMAP_BAR_HEIGHT,
|
|
49
94
|
width: w,
|
|
50
|
-
zIndex: 100,
|
|
51
|
-
}}
|
|
52
|
-
onMouseOver={() => {
|
|
53
|
-
setHovered(true)
|
|
54
|
-
}}
|
|
55
|
-
onMouseOut={() => {
|
|
56
|
-
setHovered(false)
|
|
57
|
-
}}
|
|
58
|
-
onMouseDown={event => {
|
|
59
|
-
startDrag(event, model.scrollX)
|
|
60
95
|
}}
|
|
61
96
|
/>
|
|
62
97
|
|
|
63
|
-
<svg height={polygonHeight}
|
|
64
|
-
<polygon fill={
|
|
98
|
+
<svg height={polygonHeight} width={msaCanvasWidth}>
|
|
99
|
+
<polygon fill={scrollbarThumbFill} points={polygonPoints} />
|
|
65
100
|
</svg>
|
|
66
101
|
</div>
|
|
67
102
|
)
|
|
@@ -6,40 +6,41 @@ import { MINIMAP_BAR_HEIGHT, getMinimapLayout } from './minimapLayout.ts'
|
|
|
6
6
|
|
|
7
7
|
import type { MsaViewModel } from '../../model.ts'
|
|
8
8
|
|
|
9
|
-
const MinimapSVG = observer(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const MinimapSVG = observer(
|
|
10
|
+
({ model, width }: { model: MsaViewModel; width: number }) => {
|
|
11
|
+
const { s, w, polygonPoints } = getMinimapLayout(model, width)
|
|
12
|
+
const fillColor = 'rgb(66, 119, 127)'
|
|
13
|
+
const fillOpacity = 0.3
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
fill={fillColor}
|
|
31
|
-
fillOpacity={fillOpacity}
|
|
32
|
-
stroke="#555"
|
|
33
|
-
/>
|
|
34
|
-
<g transform={`translate(0 ${MINIMAP_BAR_HEIGHT})`}>
|
|
35
|
-
<polygon
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<rect
|
|
18
|
+
x={0}
|
|
19
|
+
y={0}
|
|
20
|
+
width={width}
|
|
21
|
+
height={MINIMAP_BAR_HEIGHT}
|
|
22
|
+
stroke="#555"
|
|
23
|
+
fill="none"
|
|
24
|
+
/>
|
|
25
|
+
<rect
|
|
26
|
+
x={Math.max(0, s)}
|
|
27
|
+
y={0}
|
|
28
|
+
width={w}
|
|
29
|
+
height={MINIMAP_BAR_HEIGHT}
|
|
36
30
|
fill={fillColor}
|
|
37
31
|
fillOpacity={fillOpacity}
|
|
38
|
-
|
|
32
|
+
stroke="#555"
|
|
39
33
|
/>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
34
|
+
<g transform={`translate(0 ${MINIMAP_BAR_HEIGHT})`}>
|
|
35
|
+
<polygon
|
|
36
|
+
fill={fillColor}
|
|
37
|
+
fillOpacity={fillOpacity}
|
|
38
|
+
points={polygonPoints}
|
|
39
|
+
/>
|
|
40
|
+
</g>
|
|
41
|
+
</>
|
|
42
|
+
)
|
|
43
|
+
},
|
|
44
|
+
)
|
|
44
45
|
|
|
45
46
|
export default MinimapSVG
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { expect, test } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import MSAModelF from '../../model.ts'
|
|
5
|
+
import { MINIMAP_BAR_HEIGHT, getMinimapLayout } from './minimapLayout.ts'
|
|
6
|
+
|
|
7
|
+
// wide enough to overflow horizontally, tall enough to raise the vertical
|
|
8
|
+
// scrollbar — which takes 20px out of the width the alignment actually draws on
|
|
9
|
+
function makeModel() {
|
|
10
|
+
const names = Array.from({ length: 60 }, (_, i) => `s${i}`)
|
|
11
|
+
const model = MSAModelF().create({
|
|
12
|
+
type: 'MsaView',
|
|
13
|
+
msaFormat: 'fasta',
|
|
14
|
+
height: 400,
|
|
15
|
+
data: { msa: names.map(n => `>${n}\n${'ACGT'.repeat(50)}`).join('\n') },
|
|
16
|
+
})
|
|
17
|
+
model.setWidth(1000)
|
|
18
|
+
return model
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('the viewport rectangle scales by the canvas width, not the msa area', () => {
|
|
22
|
+
const model = makeModel()
|
|
23
|
+
expect(model.showVerticalScrollbar).toBe(true)
|
|
24
|
+
expect(model.msaCanvasWidth).toBe(model.msaAreaWidth - 20)
|
|
25
|
+
|
|
26
|
+
const { unit, s, w } = getMinimapLayout(model, model.msaCanvasWidth)
|
|
27
|
+
expect(unit).toBeCloseTo(model.msaCanvasWidth / model.totalWidth)
|
|
28
|
+
// unscrolled, the rectangle starts at the left edge and covers the fraction
|
|
29
|
+
// of the alignment on screen. Sizing it by msaAreaWidth instead overstated
|
|
30
|
+
// that fraction by the scrollbar's width.
|
|
31
|
+
expect(s).toBeCloseTo(0)
|
|
32
|
+
expect(w).toBeCloseTo(
|
|
33
|
+
(model.msaCanvasWidth / model.totalWidth) * model.msaCanvasWidth,
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('the rectangle tracks scrollX and the trapezoid spans the given width', () => {
|
|
38
|
+
const model = makeModel()
|
|
39
|
+
model.setScrollX(-300)
|
|
40
|
+
|
|
41
|
+
const width = 400
|
|
42
|
+
const { s, polygonHeight, polygonPoints } = getMinimapLayout(model, width)
|
|
43
|
+
expect(s).toBeCloseTo((300 / model.totalWidth) * width)
|
|
44
|
+
expect(polygonHeight).toBe(model.minimapHeight - MINIMAP_BAR_HEIGHT)
|
|
45
|
+
// bottom edge spans the full minimap, top edge is the viewport rectangle
|
|
46
|
+
expect(
|
|
47
|
+
polygonPoints.endsWith(`0,${polygonHeight} ${width},${polygonHeight}`),
|
|
48
|
+
).toBe(true)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('an empty alignment collapses to a zero-width unit instead of dividing by zero', () => {
|
|
52
|
+
const model = MSAModelF().create({ type: 'MsaView' })
|
|
53
|
+
model.setWidth(1000)
|
|
54
|
+
expect(model.totalWidth).toBe(0)
|
|
55
|
+
const { unit, s, w } = getMinimapLayout(model, 400)
|
|
56
|
+
expect(unit).toBe(0)
|
|
57
|
+
expect(s).toBeCloseTo(0)
|
|
58
|
+
// still clamped to the minimum grabbable width
|
|
59
|
+
expect(w).toBe(20)
|
|
60
|
+
})
|
|
@@ -2,16 +2,21 @@ import type { MsaViewModel } from '../../model.ts'
|
|
|
2
2
|
|
|
3
3
|
export const MINIMAP_BAR_HEIGHT = 12
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Viewport-rectangle and trapezoid geometry for a minimap drawn `minimapWidth`
|
|
7
|
+
* pixels wide, shared by the interactive Minimap and the static MinimapSVG.
|
|
8
|
+
*
|
|
9
|
+
* The width is a parameter rather than read off the model because the two
|
|
10
|
+
* differ: on screen the minimap sits over the alignment canvas, while the SVG
|
|
11
|
+
* export lays the whole figure out on its own grid. What the rectangle *marks*
|
|
12
|
+
* is always the on-screen viewport, so that part comes from the model.
|
|
13
|
+
*/
|
|
14
|
+
export function getMinimapLayout(model: MsaViewModel, minimapWidth: number) {
|
|
15
|
+
const { scrollX, minimapHeight, totalWidth, msaCanvasWidth } = model
|
|
16
|
+
const unit = totalWidth > 0 ? minimapWidth / totalWidth : 0
|
|
17
|
+
const s = -scrollX * unit
|
|
18
|
+
const w = Math.max(msaCanvasWidth * unit, 20)
|
|
14
19
|
const polygonHeight = minimapHeight - MINIMAP_BAR_HEIGHT
|
|
15
|
-
const polygonPoints = `${s + w},0 ${s},0 0,${polygonHeight} ${
|
|
20
|
+
const polygonPoints = `${s + w},0 ${s},0 0,${polygonHeight} ${minimapWidth},${polygonHeight}`
|
|
16
21
|
return { unit, s, w, polygonHeight, polygonPoints }
|
|
17
22
|
}
|