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.
Files changed (196) hide show
  1. package/bundle/index.js +99 -99
  2. package/bundle/index.js.map +4 -4
  3. package/dist/clustalX.js +22 -22
  4. package/dist/clustalX.js.map +1 -1
  5. package/dist/components/ConservationTrack.js +21 -16
  6. package/dist/components/ConservationTrack.js.map +1 -1
  7. package/dist/components/CopyButton.d.ts +9 -0
  8. package/dist/components/CopyButton.js +23 -0
  9. package/dist/components/CopyButton.js.map +1 -0
  10. package/dist/components/Loading.js +2 -2
  11. package/dist/components/Loading.js.map +1 -1
  12. package/dist/components/MSAView.js +18 -24
  13. package/dist/components/MSAView.js.map +1 -1
  14. package/dist/components/ResizeHandles.js +24 -40
  15. package/dist/components/ResizeHandles.js.map +1 -1
  16. package/dist/components/ScrollbarThumb.d.ts +14 -0
  17. package/dist/components/ScrollbarThumb.js +28 -0
  18. package/dist/components/ScrollbarThumb.js.map +1 -0
  19. package/dist/components/SequenceTextArea.js +3 -10
  20. package/dist/components/SequenceTextArea.js.map +1 -1
  21. package/dist/components/TextTrack.js +26 -21
  22. package/dist/components/TextTrack.js.map +1 -1
  23. package/dist/components/VerticalScrollbar.js +11 -25
  24. package/dist/components/VerticalScrollbar.js.map +1 -1
  25. package/dist/components/dialogs/FeatureDialog.js +3 -3
  26. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  27. package/dist/components/dialogs/{UserProvidedDomainsDialog.d.ts → InterProScanFileDialog.d.ts} +2 -2
  28. package/dist/components/dialogs/{UserProvidedDomainsDialog.js → InterProScanFileDialog.js} +6 -10
  29. package/dist/components/dialogs/InterProScanFileDialog.js.map +1 -0
  30. package/dist/components/dialogs/TrackInfoDialog.d.ts +2 -4
  31. package/dist/components/dialogs/TrackInfoDialog.js +8 -14
  32. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
  33. package/dist/components/header/FileMenu.js +3 -3
  34. package/dist/components/header/FileMenu.js.map +1 -1
  35. package/dist/components/header/GappynessSlider.js +1 -1
  36. package/dist/components/header/GappynessSlider.js.map +1 -1
  37. package/dist/components/header/{getDomainsMenu.d.ts → getAnnotationsMenu.d.ts} +1 -1
  38. package/dist/components/header/{getDomainsMenu.js → getAnnotationsMenu.js} +10 -9
  39. package/dist/components/header/getAnnotationsMenu.js.map +1 -0
  40. package/dist/components/header/settingsMenuItems.js +39 -72
  41. package/dist/components/header/settingsMenuItems.js.map +1 -1
  42. package/dist/components/import/ImportForm.js +1 -1
  43. package/dist/components/import/ImportForm.js.map +1 -1
  44. package/dist/components/import/ImportFormExamples.js +66 -57
  45. package/dist/components/import/ImportFormExamples.js.map +1 -1
  46. package/dist/components/minimap/Minimap.js +11 -22
  47. package/dist/components/minimap/Minimap.js.map +1 -1
  48. package/dist/components/minimap/MinimapSVG.d.ts +2 -1
  49. package/dist/components/minimap/MinimapSVG.js +3 -4
  50. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  51. package/dist/components/minimap/minimapLayout.d.ts +10 -1
  52. package/dist/components/minimap/minimapLayout.js +15 -10
  53. package/dist/components/minimap/minimapLayout.js.map +1 -1
  54. package/dist/components/msa/{DomainLegend.d.ts → AnnotationLegend.d.ts} +2 -2
  55. package/dist/components/msa/{DomainLegend.js → AnnotationLegend.js} +4 -4
  56. package/dist/components/msa/AnnotationLegend.js.map +1 -0
  57. package/dist/components/msa/MSACanvas.js +2 -2
  58. package/dist/components/msa/MSACanvas.js.map +1 -1
  59. package/dist/components/msa/MSACanvasBlock.js +19 -16
  60. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  61. package/dist/components/msa/MSAMouseoverCanvas.js +14 -8
  62. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  63. package/dist/components/msa/MSAPanel.js +2 -2
  64. package/dist/components/msa/MSAPanel.js.map +1 -1
  65. package/dist/components/msa/domainBandCursor.d.ts +17 -0
  66. package/dist/components/msa/domainBandCursor.js +39 -0
  67. package/dist/components/msa/domainBandCursor.js.map +1 -0
  68. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +9 -6
  69. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  70. package/dist/components/msa/renderMSABlock.js +14 -20
  71. package/dist/components/msa/renderMSABlock.js.map +1 -1
  72. package/dist/components/msa/renderMSAMouseover.js +32 -33
  73. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  74. package/dist/components/overlayColors.d.ts +2 -0
  75. package/dist/components/overlayColors.js +5 -0
  76. package/dist/components/overlayColors.js.map +1 -1
  77. package/dist/components/tree/TreeCanvas.js +29 -24
  78. package/dist/components/tree/TreeCanvas.js.map +1 -1
  79. package/dist/components/tree/TreeCanvasBlock.js +19 -12
  80. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  81. package/dist/components/tree/TreeRuler.js +5 -2
  82. package/dist/components/tree/TreeRuler.js.map +1 -1
  83. package/dist/components/tree/renderTreeCanvas.d.ts +0 -38
  84. package/dist/components/tree/renderTreeCanvas.js +17 -17
  85. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  86. package/dist/ggplotPalettes.d.ts +0 -1
  87. package/dist/ggplotPalettes.js +0 -3
  88. package/dist/ggplotPalettes.js.map +1 -1
  89. package/dist/hierarchy.d.ts +2 -6
  90. package/dist/hierarchy.js +4 -6
  91. package/dist/hierarchy.js.map +1 -1
  92. package/dist/index.d.ts +2 -2
  93. package/dist/launchInterProScan.d.ts +0 -23
  94. package/dist/launchInterProScan.js +2 -5
  95. package/dist/launchInterProScan.js.map +1 -1
  96. package/dist/measureTextCanvas.js +21 -6
  97. package/dist/measureTextCanvas.js.map +1 -1
  98. package/dist/model.d.ts +50 -100
  99. package/dist/model.js +209 -219
  100. package/dist/model.js.map +1 -1
  101. package/dist/neighborJoining.js +12 -19
  102. package/dist/neighborJoining.js.map +1 -1
  103. package/dist/parseAsn1.js +32 -43
  104. package/dist/parseAsn1.js.map +1 -1
  105. package/dist/renderToSvg.js +5 -2
  106. package/dist/renderToSvg.js.map +1 -1
  107. package/dist/rowCoordinateCalculations.d.ts +13 -17
  108. package/dist/rowCoordinateCalculations.js +45 -39
  109. package/dist/rowCoordinateCalculations.js.map +1 -1
  110. package/dist/seqPosToGlobalCol.d.ts +6 -19
  111. package/dist/seqPosToGlobalCol.js +6 -33
  112. package/dist/seqPosToGlobalCol.js.map +1 -1
  113. package/dist/types.d.ts +4 -11
  114. package/dist/useCanvasAutorun.d.ts +23 -1
  115. package/dist/useCanvasAutorun.js +20 -8
  116. package/dist/useCanvasAutorun.js.map +1 -1
  117. package/dist/version.d.ts +1 -1
  118. package/dist/version.js +1 -1
  119. package/package.json +5 -5
  120. package/src/clustalX.ts +22 -22
  121. package/src/colSpace.test.ts +27 -0
  122. package/src/components/ConservationTrack.tsx +7 -5
  123. package/src/components/CopyButton.tsx +44 -0
  124. package/src/components/Loading.tsx +10 -1
  125. package/src/components/MSAView.tsx +25 -32
  126. package/src/components/ResizeHandles.tsx +60 -69
  127. package/src/components/ScrollbarThumb.tsx +48 -0
  128. package/src/components/SequenceTextArea.tsx +3 -16
  129. package/src/components/TextTrack.tsx +7 -12
  130. package/src/components/VerticalScrollbar.tsx +19 -33
  131. package/src/components/dialogs/FeatureDialog.tsx +3 -4
  132. package/src/components/dialogs/{UserProvidedDomainsDialog.tsx → InterProScanFileDialog.tsx} +6 -14
  133. package/src/components/dialogs/TrackInfoDialog.tsx +18 -20
  134. package/src/components/header/FileMenu.tsx +3 -3
  135. package/src/components/header/GappynessSlider.tsx +4 -1
  136. package/src/components/header/{getDomainsMenu.ts → getAnnotationsMenu.ts} +10 -9
  137. package/src/components/header/settingsMenuItems.ts +40 -72
  138. package/src/components/import/ImportForm.tsx +1 -1
  139. package/src/components/import/ImportFormExamples.tsx +82 -133
  140. package/src/components/minimap/Minimap.tsx +23 -31
  141. package/src/components/minimap/MinimapSVG.tsx +32 -31
  142. package/src/components/minimap/minimapLayout.test.ts +60 -0
  143. package/src/components/minimap/minimapLayout.ts +15 -10
  144. package/src/components/msa/{DomainLegend.tsx → AnnotationLegend.tsx} +7 -3
  145. package/src/components/msa/MSACanvas.tsx +3 -10
  146. package/src/components/msa/MSACanvasBlock.tsx +16 -21
  147. package/src/components/msa/MSAMouseoverCanvas.tsx +11 -9
  148. package/src/components/msa/MSAPanel.tsx +2 -2
  149. package/src/components/msa/domainBandCursor.ts +41 -0
  150. package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +51 -0
  151. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +13 -8
  152. package/src/components/msa/renderMSABlock.ts +19 -23
  153. package/src/components/msa/renderMSAMouseover.ts +38 -35
  154. package/src/components/overlayColors.ts +6 -0
  155. package/src/components/tree/TreeCanvas.tsx +6 -4
  156. package/src/components/tree/TreeCanvasBlock.tsx +11 -12
  157. package/src/components/tree/TreeRuler.tsx +7 -2
  158. package/src/components/tree/renderTreeCanvas.test.ts +15 -7
  159. package/src/components/tree/renderTreeCanvas.ts +20 -17
  160. package/src/domainBands.test.ts +24 -0
  161. package/src/domainLetterColor.test.ts +80 -0
  162. package/src/ggplotPalettes.ts +0 -4
  163. package/src/hierarchy.test.ts +7 -7
  164. package/src/hierarchy.ts +6 -9
  165. package/src/index.ts +4 -1
  166. package/src/largeTree.test.ts +23 -2
  167. package/src/launchInterProScan.ts +3 -30
  168. package/src/loadErrorDisplay.test.tsx +102 -0
  169. package/src/measureTextCanvas.ts +22 -6
  170. package/src/model.ts +233 -244
  171. package/src/modelFilehandleLoaders.test.ts +137 -0
  172. package/src/neighborJoining.test.ts +12 -0
  173. package/src/neighborJoining.ts +12 -20
  174. package/src/parseAsn1.ts +32 -40
  175. package/src/{renderDomainLegendSvg.test.tsx → renderDomainsSvg.test.tsx} +44 -20
  176. package/src/renderMinimapSvg.test.tsx +109 -0
  177. package/src/renderToSvg.tsx +5 -2
  178. package/src/rowCoordinateCalculations.ts +48 -44
  179. package/src/seqPosToGlobalCol.test.ts +39 -51
  180. package/src/seqPosToGlobalCol.ts +6 -41
  181. package/src/types.ts +10 -15
  182. package/src/useCanvasAutorun.test.tsx +98 -0
  183. package/src/useCanvasAutorun.ts +30 -11
  184. package/src/version.ts +1 -1
  185. package/src/viewChrome.test.tsx +134 -0
  186. package/dist/components/dialogs/DomainDialog.d.ts +0 -6
  187. package/dist/components/dialogs/DomainDialog.js +0 -19
  188. package/dist/components/dialogs/DomainDialog.js.map +0 -1
  189. package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +0 -1
  190. package/dist/components/header/SettingsMenu.d.ts +0 -6
  191. package/dist/components/header/SettingsMenu.js +0 -40
  192. package/dist/components/header/SettingsMenu.js.map +0 -1
  193. package/dist/components/header/getDomainsMenu.js.map +0 -1
  194. package/dist/components/msa/DomainLegend.js.map +0 -1
  195. package/src/components/dialogs/DomainDialog.tsx +0 -45
  196. 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
- label: 'Show column statistics on hover',
9
- type: 'checkbox',
10
- checked: showColumnStats,
11
- onClick: () => {
12
- model.setShowColumnStats(!showColumnStats)
13
- },
14
- },
15
- {
16
- label: 'Draw letters',
17
- type: 'checkbox',
18
- checked: drawMsaLetters,
19
- onClick: () => {
20
- model.setDrawMsaLetters(!drawMsaLetters)
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
- label: 'Show branch length',
53
- type: 'checkbox',
54
- checked: showBranchLen,
55
- onClick: () => {
56
- model.setShowBranchLen(!showBranchLen)
57
- },
58
- },
59
- {
60
- label: 'Show tree',
61
- type: 'checkbox',
62
- checked: drawTree,
63
- onClick: () => {
64
- model.setDrawTree(!drawTree)
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>InterProScan GFF file or URL (optional)</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
- function ListItem({
12
- onClick,
13
- model,
14
- children,
15
- }: {
16
- onClick: () => void
17
- model: MsaViewModel
18
- children: React.ReactNode
19
- }) {
20
- return (
21
- <li>
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 BASE = 'https://jbrowse.org/genomes/multiple_sequence_alignments'
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
- <ListItem
67
- model={model}
68
- onClick={() => {
69
- loadUris({
70
- tree: 'https://jbrowse.org/genomes/newicktrees/sarscov2phylo.pub.ft.nh',
71
- })
72
- }}
73
- >
74
- 230k COVID-19 samples (tree only)
75
- </ListItem>
76
- <ListItem
77
- model={model}
78
- onClick={() => {
79
- model.setData({ msa: smallMSA, tree: smallTree })
80
- }}
81
- >
82
- Small protein MSA+tree
83
- </ListItem>
84
- <ListItem
85
- model={model}
86
- onClick={() => {
87
- model.setData({ msa: smallMSAOnly })
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, useState } from 'react'
1
+ import React, { useCallback } from 'react'
2
2
 
3
3
  import { observer } from 'mobx-react'
4
4
 
5
- import { useDragScroll } from '../../useDragScroll.ts'
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 [hovered, setHovered] = useState(false)
12
- const { minimapHeight } = model
13
- const { unit, s, w, polygonHeight, polygonPoints } = getMinimapLayout(model)
14
- const fill = 'rgba(66, 119, 127, 0.3)'
11
+ const { minimapHeight, msaCanvasWidth } = model
12
+ const { unit, s, w, polygonHeight, polygonPoints } = getMinimapLayout(
13
+ model,
14
+ msaCanvasWidth,
15
+ )
15
16
 
16
- const { startDrag } = useDragScroll(
17
- 'x',
18
- useCallback(
19
- (delta: number, startScroll: number) => {
20
- model.setScrollX(startScroll - delta / unit)
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: '100%',
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
- <div
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} style={{ width: '100%' }}>
64
- <polygon fill={fill} points={polygonPoints} />
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(({ model }: { model: MsaViewModel }) => {
10
- const { msaAreaWidth } = model
11
- const { s, w, polygonPoints } = getMinimapLayout(model)
12
- const fillColor = 'rgb(66, 119, 127)'
13
- const fillOpacity = 0.3
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
- return (
16
- <>
17
- <rect
18
- x={0}
19
- y={0}
20
- width={msaAreaWidth}
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}
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
- points={polygonPoints}
32
+ stroke="#555"
39
33
  />
40
- </g>
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
- // viewport-rectangle and trapezoid geometry shared by the interactive Minimap
6
- // and the static MinimapSVG (used for SVG export)
7
- export function getMinimapLayout(model: MsaViewModel) {
8
- const { scrollX, msaAreaWidth, minimapHeight, colWidth, numColumns } = model
9
- const unit = msaAreaWidth / numColumns / colWidth
10
- const left = -scrollX
11
- const s = left * unit
12
- const e = (left + msaAreaWidth) * unit
13
- const w = Math.max(e - s, 20)
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} ${msaAreaWidth},${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 DomainLegend = observer(function ({ model }: { model: MsaViewModel }) {
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 domain key' : 'Expand domain key'}
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 DomainLegend
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
- MSA,
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: msaAreaWidth - verticalScrollbarWidth,
57
+ width: msaCanvasWidth,
65
58
  overflow: 'hidden',
66
59
  }}
67
60
  >