react-msaview 5.9.0 → 6.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/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/components/ConservationTrack.js +21 -16
- package/dist/components/ConservationTrack.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/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +18 -24
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/ResizeHandles.js +24 -40
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/ScrollbarThumb.d.ts +14 -0
- package/dist/components/ScrollbarThumb.js +28 -0
- package/dist/components/ScrollbarThumb.js.map +1 -0
- package/dist/components/SequenceTextArea.js +3 -10
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/TextTrack.js +26 -21
- package/dist/components/TextTrack.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/{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 +39 -72
- 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 +11 -22
- 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 +2 -2
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +19 -16
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +14 -8
- 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/renderBoxFeatureCanvasBlock.js +9 -6
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +14 -20
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +32 -33
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/overlayColors.d.ts +2 -0
- package/dist/components/overlayColors.js +5 -0
- package/dist/components/overlayColors.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +29 -24
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +19 -12
- 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.d.ts +0 -38
- package/dist/components/tree/renderTreeCanvas.js +17 -17
- package/dist/components/tree/renderTreeCanvas.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.d.ts +50 -100
- package/dist/model.js +209 -219
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +12 -19
- package/dist/neighborJoining.js.map +1 -1
- package/dist/parseAsn1.js +32 -43
- package/dist/parseAsn1.js.map +1 -1
- package/dist/renderToSvg.js +5 -2
- 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/seqPosToGlobalCol.d.ts +6 -19
- package/dist/seqPosToGlobalCol.js +6 -33
- package/dist/seqPosToGlobalCol.js.map +1 -1
- package/dist/types.d.ts +4 -11
- package/dist/useCanvasAutorun.d.ts +23 -1
- package/dist/useCanvasAutorun.js +20 -8
- package/dist/useCanvasAutorun.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -5
- package/src/clustalX.ts +22 -22
- package/src/colSpace.test.ts +27 -0
- package/src/components/ConservationTrack.tsx +7 -5
- package/src/components/CopyButton.tsx +44 -0
- package/src/components/Loading.tsx +10 -1
- package/src/components/MSAView.tsx +25 -32
- package/src/components/ResizeHandles.tsx +60 -69
- package/src/components/ScrollbarThumb.tsx +48 -0
- package/src/components/SequenceTextArea.tsx +3 -16
- package/src/components/TextTrack.tsx +7 -12
- package/src/components/VerticalScrollbar.tsx +19 -33
- package/src/components/dialogs/FeatureDialog.tsx +3 -4
- 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 +40 -72
- package/src/components/import/ImportForm.tsx +1 -1
- package/src/components/import/ImportFormExamples.tsx +82 -133
- package/src/components/minimap/Minimap.tsx +23 -31
- 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 +3 -10
- package/src/components/msa/MSACanvasBlock.tsx +16 -21
- package/src/components/msa/MSAMouseoverCanvas.tsx +11 -9
- package/src/components/msa/MSAPanel.tsx +2 -2
- package/src/components/msa/domainBandCursor.ts +41 -0
- package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +51 -0
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +13 -8
- package/src/components/msa/renderMSABlock.ts +19 -23
- package/src/components/msa/renderMSAMouseover.ts +38 -35
- package/src/components/overlayColors.ts +6 -0
- package/src/components/tree/TreeCanvas.tsx +6 -4
- package/src/components/tree/TreeCanvasBlock.tsx +11 -12
- package/src/components/tree/TreeRuler.tsx +7 -2
- package/src/components/tree/renderTreeCanvas.test.ts +15 -7
- package/src/components/tree/renderTreeCanvas.ts +20 -17
- 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/index.ts +4 -1
- package/src/largeTree.test.ts +23 -2
- package/src/launchInterProScan.ts +3 -30
- package/src/loadErrorDisplay.test.tsx +102 -0
- package/src/measureTextCanvas.ts +22 -6
- package/src/model.ts +233 -244
- package/src/modelFilehandleLoaders.test.ts +137 -0
- package/src/neighborJoining.test.ts +12 -0
- package/src/neighborJoining.ts +12 -20
- package/src/parseAsn1.ts +32 -40
- package/src/{renderDomainLegendSvg.test.tsx → renderDomainsSvg.test.tsx} +44 -20
- package/src/renderMinimapSvg.test.tsx +109 -0
- package/src/renderToSvg.tsx +5 -2
- package/src/rowCoordinateCalculations.ts +48 -44
- package/src/seqPosToGlobalCol.test.ts +39 -51
- package/src/seqPosToGlobalCol.ts +6 -41
- package/src/types.ts +10 -15
- package/src/useCanvasAutorun.test.tsx +98 -0
- package/src/useCanvasAutorun.ts +30 -11
- package/src/version.ts +1 -1
- package/src/viewChrome.test.tsx +134 -0
- 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/src/components/dialogs/DomainDialog.tsx +0 -45
- package/src/components/header/SettingsMenu.tsx +0 -57
|
@@ -1,41 +1,34 @@
|
|
|
1
1
|
import type { MsaViewModel } from '../../model.ts'
|
|
2
2
|
import type { MenuItem } from '@jbrowse/core/ui/Menu'
|
|
3
3
|
|
|
4
|
+
function toggle(label: string, checked: boolean, set: (arg: boolean) => void) {
|
|
5
|
+
return {
|
|
6
|
+
label,
|
|
7
|
+
type: 'checkbox' as const,
|
|
8
|
+
checked,
|
|
9
|
+
onClick: () => {
|
|
10
|
+
set(!checked)
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
export function msaSettingsMenuItems(model: MsaViewModel): MenuItem[] {
|
|
5
16
|
const { drawMsaLetters, hideGaps, bgColor, showColumnStats } = model
|
|
6
17
|
return [
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
label: 'Color letters instead of background of tiles',
|
|
25
|
-
type: 'checkbox',
|
|
26
|
-
checked: !bgColor,
|
|
27
|
-
onClick: () => {
|
|
28
|
-
model.setBgColor(!bgColor)
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
label: 'Enable hiding gappy columns?',
|
|
33
|
-
type: 'checkbox',
|
|
34
|
-
checked: hideGaps,
|
|
35
|
-
onClick: () => {
|
|
36
|
-
model.setHideGaps(!hideGaps)
|
|
37
|
-
},
|
|
38
|
-
},
|
|
18
|
+
toggle('Show column statistics on hover', showColumnStats, arg => {
|
|
19
|
+
model.setShowColumnStats(arg)
|
|
20
|
+
}),
|
|
21
|
+
toggle('Draw letters', drawMsaLetters, arg => {
|
|
22
|
+
model.setDrawMsaLetters(arg)
|
|
23
|
+
}),
|
|
24
|
+
// the checkbox reads as the inverse of the property it sets: bgColor draws
|
|
25
|
+
// the tile, and turning it off is what leaves the letter itself colored
|
|
26
|
+
toggle('Color letters instead of background of tiles', !bgColor, arg => {
|
|
27
|
+
model.setBgColor(!arg)
|
|
28
|
+
}),
|
|
29
|
+
toggle('Enable hiding gappy columns?', hideGaps, arg => {
|
|
30
|
+
model.setHideGaps(arg)
|
|
31
|
+
}),
|
|
39
32
|
]
|
|
40
33
|
}
|
|
41
34
|
|
|
@@ -48,45 +41,20 @@ export function treeSettingsMenuItems(model: MsaViewModel): MenuItem[] {
|
|
|
48
41
|
drawLabels,
|
|
49
42
|
} = model
|
|
50
43
|
return [
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
label: 'Draw clickable bubbles on tree branches',
|
|
69
|
-
type: 'checkbox',
|
|
70
|
-
checked: drawNodeBubbles,
|
|
71
|
-
onClick: () => {
|
|
72
|
-
model.setDrawNodeBubbles(!drawNodeBubbles)
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
label: 'Tree labels align right',
|
|
77
|
-
type: 'checkbox',
|
|
78
|
-
checked: labelsAlignRight,
|
|
79
|
-
onClick: () => {
|
|
80
|
-
model.setLabelsAlignRight(!labelsAlignRight)
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
label: 'Draw labels',
|
|
85
|
-
type: 'checkbox',
|
|
86
|
-
checked: drawLabels,
|
|
87
|
-
onClick: () => {
|
|
88
|
-
model.setDrawLabels(!drawLabels)
|
|
89
|
-
},
|
|
90
|
-
},
|
|
44
|
+
toggle('Show branch length', showBranchLen, arg => {
|
|
45
|
+
model.setShowBranchLen(arg)
|
|
46
|
+
}),
|
|
47
|
+
toggle('Show tree', drawTree, arg => {
|
|
48
|
+
model.setDrawTree(arg)
|
|
49
|
+
}),
|
|
50
|
+
toggle('Draw clickable bubbles on tree branches', drawNodeBubbles, arg => {
|
|
51
|
+
model.setDrawNodeBubbles(arg)
|
|
52
|
+
}),
|
|
53
|
+
toggle('Tree labels align right', labelsAlignRight, arg => {
|
|
54
|
+
model.setLabelsAlignRight(arg)
|
|
55
|
+
}),
|
|
56
|
+
toggle('Draw labels', drawLabels, arg => {
|
|
57
|
+
model.setDrawLabels(arg)
|
|
58
|
+
}),
|
|
91
59
|
]
|
|
92
60
|
}
|
|
@@ -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,34 +1,36 @@
|
|
|
1
|
-
import React, { useCallback
|
|
1
|
+
import React, { useCallback } from 'react'
|
|
2
2
|
|
|
3
3
|
import { observer } from 'mobx-react'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import ScrollbarThumb, { scrollbarThumbFill } from '../ScrollbarThumb.tsx'
|
|
6
6
|
import { MINIMAP_BAR_HEIGHT, getMinimapLayout } from './minimapLayout.ts'
|
|
7
7
|
|
|
8
8
|
import type { MsaViewModel } from '../../model.ts'
|
|
9
9
|
|
|
10
10
|
const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
|
|
11
|
-
const
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const { minimapHeight, msaCanvasWidth } = model
|
|
12
|
+
const { unit, s, w, polygonHeight, polygonPoints } = getMinimapLayout(
|
|
13
|
+
model,
|
|
14
|
+
msaCanvasWidth,
|
|
15
|
+
)
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
-
[model, unit],
|
|
23
|
-
),
|
|
17
|
+
const onDrag = useCallback(
|
|
18
|
+
(delta: number, startScroll: number) => {
|
|
19
|
+
model.setScrollX(startScroll - delta / unit)
|
|
20
|
+
},
|
|
21
|
+
[model, unit],
|
|
24
22
|
)
|
|
25
23
|
|
|
26
24
|
return (
|
|
25
|
+
// sized and placed to match the alignment canvas exactly: a minimap that
|
|
26
|
+
// spans a different width than the columns it maps puts its viewport
|
|
27
|
+
// rectangle over the wrong ones
|
|
27
28
|
<div
|
|
28
29
|
style={{
|
|
29
30
|
position: 'relative',
|
|
30
31
|
height: minimapHeight,
|
|
31
|
-
width:
|
|
32
|
+
width: msaCanvasWidth,
|
|
33
|
+
flexShrink: 0,
|
|
32
34
|
}}
|
|
33
35
|
>
|
|
34
36
|
<div
|
|
@@ -38,30 +40,20 @@ const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
38
40
|
border: '1px solid #555',
|
|
39
41
|
}}
|
|
40
42
|
/>
|
|
41
|
-
<
|
|
43
|
+
<ScrollbarThumb
|
|
44
|
+
axis="x"
|
|
45
|
+
getStart={() => model.scrollX}
|
|
46
|
+
onDrag={onDrag}
|
|
42
47
|
style={{
|
|
43
|
-
position: 'absolute',
|
|
44
48
|
top: 0,
|
|
45
49
|
left: Math.max(0, s),
|
|
46
|
-
background: hovered ? 'rgba(66,119,127,0.6)' : fill,
|
|
47
|
-
cursor: 'pointer',
|
|
48
50
|
height: MINIMAP_BAR_HEIGHT,
|
|
49
51
|
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
52
|
}}
|
|
61
53
|
/>
|
|
62
54
|
|
|
63
|
-
<svg height={polygonHeight}
|
|
64
|
-
<polygon fill={
|
|
55
|
+
<svg height={polygonHeight} width={msaCanvasWidth}>
|
|
56
|
+
<polygon fill={scrollbarThumbFill} points={polygonPoints} />
|
|
65
57
|
</svg>
|
|
66
58
|
</div>
|
|
67
59
|
)
|
|
@@ -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
|
}
|
|
@@ -7,7 +7,11 @@ import { observer } from 'mobx-react'
|
|
|
7
7
|
|
|
8
8
|
import type { MsaViewModel } from '../../model.ts'
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const AnnotationLegend = observer(function ({
|
|
11
|
+
model,
|
|
12
|
+
}: {
|
|
13
|
+
model: MsaViewModel
|
|
14
|
+
}) {
|
|
11
15
|
const {
|
|
12
16
|
actuallyShowDomains,
|
|
13
17
|
visibleDomainTypes: visible,
|
|
@@ -41,7 +45,7 @@ const DomainLegend = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
41
45
|
>
|
|
42
46
|
<IconButton
|
|
43
47
|
size="small"
|
|
44
|
-
title={expanded ? 'Collapse
|
|
48
|
+
title={expanded ? 'Collapse key' : 'Expand key'}
|
|
45
49
|
onClick={() => {
|
|
46
50
|
model.setShowDomainLegend(!expanded)
|
|
47
51
|
}}
|
|
@@ -76,4 +80,4 @@ const DomainLegend = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
76
80
|
) : null
|
|
77
81
|
})
|
|
78
82
|
|
|
79
|
-
export default
|
|
83
|
+
export default AnnotationLegend
|
|
@@ -9,15 +9,8 @@ import MSACanvasBlock from './MSACanvasBlock.tsx'
|
|
|
9
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
10
|
|
|
11
11
|
const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
verticalScrollbarWidth,
|
|
15
|
-
msaFilehandle,
|
|
16
|
-
height,
|
|
17
|
-
msaAreaWidth,
|
|
18
|
-
blocks2d,
|
|
19
|
-
scrollZoom,
|
|
20
|
-
} = model
|
|
12
|
+
const { MSA, msaFilehandle, height, msaCanvasWidth, blocks2d, scrollZoom } =
|
|
13
|
+
model
|
|
21
14
|
const ref = useRef<HTMLDivElement>(null)
|
|
22
15
|
const onScrollX = useCallback(
|
|
23
16
|
(d: number) => {
|
|
@@ -61,7 +54,7 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
61
54
|
style={{
|
|
62
55
|
position: 'relative',
|
|
63
56
|
height,
|
|
64
|
-
width:
|
|
57
|
+
width: msaCanvasWidth,
|
|
65
58
|
overflow: 'hidden',
|
|
66
59
|
}}
|
|
67
60
|
>
|