react-msaview 2.1.5 → 3.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 (176) hide show
  1. package/bundle/index.js +37 -255
  2. package/dist/DialogQueue.d.ts +25 -0
  3. package/dist/DialogQueue.js +46 -0
  4. package/dist/DialogQueue.js.map +1 -0
  5. package/dist/UniprotTrack.js.map +1 -1
  6. package/dist/colorSchemes.js.map +1 -1
  7. package/dist/components/BoxTrackBlock.js +3 -2
  8. package/dist/components/BoxTrackBlock.js.map +1 -1
  9. package/dist/components/Header.js +10 -13
  10. package/dist/components/Header.js.map +1 -1
  11. package/dist/components/ImportForm/ImportFormExamples.d.ts +6 -0
  12. package/dist/components/ImportForm/ImportFormExamples.js +50 -0
  13. package/dist/components/ImportForm/ImportFormExamples.js.map +1 -0
  14. package/dist/components/ImportForm/data/seq2.js.map +1 -0
  15. package/dist/components/{ImportForm.d.ts → ImportForm/index.d.ts} +1 -1
  16. package/dist/components/ImportForm/index.js +31 -0
  17. package/dist/components/ImportForm/index.js.map +1 -0
  18. package/dist/components/ImportForm/util.d.ts +3 -0
  19. package/dist/components/ImportForm/util.js +15 -0
  20. package/dist/components/ImportForm/util.js.map +1 -0
  21. package/dist/components/{MSABlock.d.ts → MSAPanel/MSABlock.d.ts} +1 -1
  22. package/dist/components/MSAPanel/MSABlock.js +46 -0
  23. package/dist/components/MSAPanel/MSABlock.js.map +1 -0
  24. package/dist/components/{MSACanvas.d.ts → MSAPanel/MSACanvas.d.ts} +1 -1
  25. package/dist/components/{MSACanvas.js → MSAPanel/MSACanvas.js} +10 -3
  26. package/dist/components/MSAPanel/MSACanvas.js.map +1 -0
  27. package/dist/components/{MSAMouseoverCanvas.d.ts → MSAPanel/MSAMouseoverCanvas.d.ts} +1 -1
  28. package/dist/components/MSAPanel/MSAMouseoverCanvas.js +50 -0
  29. package/dist/components/MSAPanel/MSAMouseoverCanvas.js.map +1 -0
  30. package/dist/components/MSAPanel/index.d.ts +5 -0
  31. package/dist/components/MSAPanel/index.js +9 -0
  32. package/dist/components/MSAPanel/index.js.map +1 -0
  33. package/dist/components/MSAPanel/renderMSABlock.d.ts +8 -0
  34. package/dist/components/MSAPanel/renderMSABlock.js +81 -0
  35. package/dist/components/MSAPanel/renderMSABlock.js.map +1 -0
  36. package/dist/components/MSAView.d.ts +2 -2
  37. package/dist/components/MSAView.js +26 -31
  38. package/dist/components/MSAView.js.map +1 -1
  39. package/dist/components/Minimap.d.ts +6 -0
  40. package/dist/components/Minimap.js +72 -0
  41. package/dist/components/Minimap.js.map +1 -0
  42. package/dist/components/OverviewRubberband.d.ts +8 -0
  43. package/dist/components/OverviewRubberband.js +185 -0
  44. package/dist/components/OverviewRubberband.js.map +1 -0
  45. package/dist/components/ResizeHandles.js.map +1 -1
  46. package/dist/components/Rubberband.js +13 -1
  47. package/dist/components/Rubberband.js.map +1 -1
  48. package/dist/components/TextTrack.js +3 -2
  49. package/dist/components/TextTrack.js.map +1 -1
  50. package/dist/components/Track.js +5 -5
  51. package/dist/components/Track.js.map +1 -1
  52. package/dist/components/{TreeBranchMenu.d.ts → TreePanel/TreeBranchMenu.d.ts} +1 -1
  53. package/dist/components/TreePanel/TreeBranchMenu.js.map +1 -0
  54. package/dist/components/{TreeCanvas.d.ts → TreePanel/TreeCanvas.d.ts} +1 -1
  55. package/dist/components/{TreeCanvas.js → TreePanel/TreeCanvas.js} +1 -1
  56. package/dist/components/TreePanel/TreeCanvas.js.map +1 -0
  57. package/dist/components/{TreeCanvasBlock.d.ts → TreePanel/TreeCanvasBlock.d.ts} +1 -1
  58. package/dist/components/TreePanel/TreeCanvasBlock.js +117 -0
  59. package/dist/components/TreePanel/TreeCanvasBlock.js.map +1 -0
  60. package/dist/components/{TreeMenu.d.ts → TreePanel/TreeMenu.d.ts} +1 -1
  61. package/dist/components/{TreeMenu.js → TreePanel/TreeMenu.js} +9 -5
  62. package/dist/components/TreePanel/TreeMenu.js.map +1 -0
  63. package/dist/components/{TreeRuler.d.ts → TreePanel/TreeRuler.d.ts} +1 -1
  64. package/dist/components/TreePanel/TreeRuler.js +8 -0
  65. package/dist/components/TreePanel/TreeRuler.js.map +1 -0
  66. package/dist/components/{dialogs → TreePanel/dialogs}/TreeNodeInfoDlg.d.ts +1 -1
  67. package/dist/components/TreePanel/dialogs/TreeNodeInfoDlg.js.map +1 -0
  68. package/dist/components/TreePanel/index.d.ts +6 -0
  69. package/dist/components/TreePanel/index.js +10 -0
  70. package/dist/components/TreePanel/index.js.map +1 -0
  71. package/dist/components/TreePanel/renderTreeCanvas.d.ts +36 -0
  72. package/dist/components/TreePanel/renderTreeCanvas.js +153 -0
  73. package/dist/components/TreePanel/renderTreeCanvas.js.map +1 -0
  74. package/dist/components/dialogs/{AboutDlg.js → AboutDialog.js} +1 -1
  75. package/dist/components/dialogs/AboutDialog.js.map +1 -0
  76. package/dist/components/dialogs/{AddTrackDlg.js → AddTrackDialog.js} +1 -1
  77. package/dist/components/dialogs/AddTrackDialog.js.map +1 -0
  78. package/dist/components/dialogs/{AnnotationDlg.js → AnnotationDialog.js} +1 -1
  79. package/dist/components/dialogs/AnnotationDialog.js.map +1 -0
  80. package/dist/components/dialogs/{MetadataDlg.js → MetadataDialog.js} +1 -1
  81. package/dist/components/dialogs/MetadataDialog.js.map +1 -0
  82. package/dist/components/dialogs/SettingsDialog.js +54 -0
  83. package/dist/components/dialogs/SettingsDialog.js.map +1 -0
  84. package/dist/components/dialogs/{TrackInfoDlg.js → TrackInfoDialog.js} +1 -1
  85. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -0
  86. package/dist/components/dialogs/{TracklistDlg.js → TracklistDialog.js} +1 -1
  87. package/dist/components/dialogs/TracklistDialog.js.map +1 -0
  88. package/dist/components/util.js.map +1 -1
  89. package/dist/layout.js.map +1 -1
  90. package/dist/model.d.ts +80 -47
  91. package/dist/model.js +130 -70
  92. package/dist/model.js.map +1 -1
  93. package/dist/parseNewick.js.map +1 -1
  94. package/dist/parsers/StockholmMSA.js.map +1 -1
  95. package/dist/util.js.map +1 -1
  96. package/dist/version.d.ts +1 -1
  97. package/dist/version.js +1 -1
  98. package/package.json +6 -4
  99. package/src/DialogQueue.ts +47 -0
  100. package/src/components/BoxTrackBlock.tsx +3 -1
  101. package/src/components/Header.tsx +9 -10
  102. package/src/components/ImportForm/ImportFormExamples.tsx +133 -0
  103. package/src/components/ImportForm/index.tsx +63 -0
  104. package/src/components/ImportForm/util.ts +20 -0
  105. package/src/components/MSAPanel/MSABlock.tsx +81 -0
  106. package/src/components/{MSACanvas.tsx → MSAPanel/MSACanvas.tsx} +17 -5
  107. package/src/components/MSAPanel/MSAMouseoverCanvas.tsx +92 -0
  108. package/src/components/MSAPanel/index.tsx +13 -0
  109. package/src/components/MSAPanel/renderMSABlock.ts +160 -0
  110. package/src/components/MSAView.tsx +36 -56
  111. package/src/components/Minimap.tsx +102 -0
  112. package/src/components/OverviewRubberband.tsx +283 -0
  113. package/src/components/Rubberband.tsx +14 -1
  114. package/src/components/TextTrack.tsx +3 -1
  115. package/src/components/Track.tsx +5 -5
  116. package/src/components/{TreeBranchMenu.tsx → TreePanel/TreeBranchMenu.tsx} +1 -1
  117. package/src/components/{TreeCanvas.tsx → TreePanel/TreeCanvas.tsx} +2 -3
  118. package/src/components/TreePanel/TreeCanvasBlock.tsx +190 -0
  119. package/src/components/{TreeMenu.tsx → TreePanel/TreeMenu.tsx} +10 -6
  120. package/src/components/{TreeRuler.tsx → TreePanel/TreeRuler.tsx} +3 -3
  121. package/src/components/{dialogs → TreePanel/dialogs}/TreeNodeInfoDlg.tsx +1 -1
  122. package/src/components/TreePanel/index.tsx +16 -0
  123. package/src/components/TreePanel/renderTreeCanvas.ts +245 -0
  124. package/src/components/dialogs/{SettingsDlg.tsx → SettingsDialog.tsx} +72 -47
  125. package/src/model.ts +157 -79
  126. package/src/version.ts +1 -1
  127. package/dist/components/ImportForm.js +0 -84
  128. package/dist/components/ImportForm.js.map +0 -1
  129. package/dist/components/MSABlock.js +0 -103
  130. package/dist/components/MSABlock.js.map +0 -1
  131. package/dist/components/MSACanvas.js.map +0 -1
  132. package/dist/components/MSAMouseoverCanvas.js +0 -61
  133. package/dist/components/MSAMouseoverCanvas.js.map +0 -1
  134. package/dist/components/Ruler.d.ts +0 -6
  135. package/dist/components/Ruler.js +0 -52
  136. package/dist/components/Ruler.js.map +0 -1
  137. package/dist/components/TreeBranchMenu.js.map +0 -1
  138. package/dist/components/TreeCanvas.js.map +0 -1
  139. package/dist/components/TreeCanvasBlock.js +0 -255
  140. package/dist/components/TreeCanvasBlock.js.map +0 -1
  141. package/dist/components/TreeMenu.js.map +0 -1
  142. package/dist/components/TreeRuler.js +0 -8
  143. package/dist/components/TreeRuler.js.map +0 -1
  144. package/dist/components/data/seq2.js.map +0 -1
  145. package/dist/components/dialogs/AboutDlg.js.map +0 -1
  146. package/dist/components/dialogs/AddTrackDlg.js.map +0 -1
  147. package/dist/components/dialogs/AnnotationDlg.js.map +0 -1
  148. package/dist/components/dialogs/MetadataDlg.js.map +0 -1
  149. package/dist/components/dialogs/SettingsDlg.js +0 -48
  150. package/dist/components/dialogs/SettingsDlg.js.map +0 -1
  151. package/dist/components/dialogs/TrackInfoDlg.js.map +0 -1
  152. package/dist/components/dialogs/TracklistDlg.js.map +0 -1
  153. package/dist/components/dialogs/TreeNodeInfoDlg.js.map +0 -1
  154. package/src/components/ImportForm.tsx +0 -192
  155. package/src/components/MSABlock.tsx +0 -164
  156. package/src/components/MSAMouseoverCanvas.tsx +0 -99
  157. package/src/components/Ruler.tsx +0 -123
  158. package/src/components/TreeCanvasBlock.tsx +0 -363
  159. /package/dist/components/{data → ImportForm/data}/seq2.d.ts +0 -0
  160. /package/dist/components/{data → ImportForm/data}/seq2.js +0 -0
  161. /package/dist/components/{TreeBranchMenu.js → TreePanel/TreeBranchMenu.js} +0 -0
  162. /package/dist/components/{dialogs → TreePanel/dialogs}/TreeNodeInfoDlg.js +0 -0
  163. /package/dist/components/dialogs/{AboutDlg.d.ts → AboutDialog.d.ts} +0 -0
  164. /package/dist/components/dialogs/{AddTrackDlg.d.ts → AddTrackDialog.d.ts} +0 -0
  165. /package/dist/components/dialogs/{AnnotationDlg.d.ts → AnnotationDialog.d.ts} +0 -0
  166. /package/dist/components/dialogs/{MetadataDlg.d.ts → MetadataDialog.d.ts} +0 -0
  167. /package/dist/components/dialogs/{SettingsDlg.d.ts → SettingsDialog.d.ts} +0 -0
  168. /package/dist/components/dialogs/{TrackInfoDlg.d.ts → TrackInfoDialog.d.ts} +0 -0
  169. /package/dist/components/dialogs/{TracklistDlg.d.ts → TracklistDialog.d.ts} +0 -0
  170. /package/src/components/{data → ImportForm/data}/seq2.ts +0 -0
  171. /package/src/components/dialogs/{AboutDlg.tsx → AboutDialog.tsx} +0 -0
  172. /package/src/components/dialogs/{AddTrackDlg.tsx → AddTrackDialog.tsx} +0 -0
  173. /package/src/components/dialogs/{AnnotationDlg.tsx → AnnotationDialog.tsx} +0 -0
  174. /package/src/components/dialogs/{MetadataDlg.tsx → MetadataDialog.tsx} +0 -0
  175. /package/src/components/dialogs/{TrackInfoDlg.tsx → TrackInfoDialog.tsx} +0 -0
  176. /package/src/components/dialogs/{TracklistDlg.tsx → TracklistDialog.tsx} +0 -0
@@ -0,0 +1,133 @@
1
+ import React from 'react'
2
+ import { Typography, Link } from '@mui/material'
3
+ import { observer } from 'mobx-react'
4
+
5
+ // locals
6
+ import { MsaViewModel } from '../../model'
7
+ import { smallTree, smallMSA, smallMSAOnly } from './data/seq2'
8
+ import { load } from './util'
9
+
10
+ const ListItem = ({
11
+ onClick,
12
+ model,
13
+ children,
14
+ }: {
15
+ onClick: () => void
16
+ model: MsaViewModel
17
+ children: React.ReactNode
18
+ }) => (
19
+ <li>
20
+ <Link
21
+ onClick={event => {
22
+ model.setError(undefined)
23
+ event.preventDefault()
24
+ onClick()
25
+ }}
26
+ href="#"
27
+ >
28
+ <Typography display="inline">{children}</Typography>
29
+ </Link>
30
+ </li>
31
+ )
32
+
33
+ const ImportFormExamples = observer(function ({
34
+ model,
35
+ }: {
36
+ model: MsaViewModel
37
+ }) {
38
+ return (
39
+ <ul>
40
+ <ListItem
41
+ model={model}
42
+ onClick={() =>
43
+ load(model, undefined, {
44
+ uri: 'https://jbrowse.org/genomes/newick_trees/sarscov2phylo.pub.ft.nh',
45
+ locationType: 'UriLocation',
46
+ })
47
+ }
48
+ >
49
+ 230k COVID-19 samples (tree only)
50
+ </ListItem>
51
+ <ListItem
52
+ model={model}
53
+ onClick={() => {
54
+ model.setData({ msa: smallMSA, tree: smallTree })
55
+ }}
56
+ >
57
+ Small protein MSA+tree
58
+ </ListItem>
59
+ <ListItem
60
+ model={model}
61
+ onClick={() => {
62
+ model.setData({ msa: smallMSAOnly })
63
+ }}
64
+ >
65
+ Small MSA only
66
+ </ListItem>
67
+ <ListItem
68
+ model={model}
69
+ onClick={() =>
70
+ load(model, {
71
+ uri: 'https://jbrowse.org/genomes/multiple_sequence_alignments/pfam-cov2.stock',
72
+ locationType: 'UriLocation',
73
+ })
74
+ }
75
+ >
76
+ PFAM SARS-CoV2 multi-stockholm
77
+ </ListItem>
78
+ <ListItem
79
+ model={model}
80
+ onClick={() =>
81
+ load(model, {
82
+ uri: 'https://jbrowse.org/genomes/multiple_sequence_alignments/Lysine.stock',
83
+ locationType: 'UriLocation',
84
+ })
85
+ }
86
+ >
87
+ Lysine stockholm file
88
+ </ListItem>
89
+ <ListItem
90
+ model={model}
91
+ onClick={() =>
92
+ load(model, {
93
+ uri: 'https://jbrowse.org/genomes/multiple_sequence_alignments/PF01601_full.txt',
94
+ locationType: 'UriLocation',
95
+ })
96
+ }
97
+ >
98
+ PF01601 stockholm file (SARS-CoV2 spike protein)
99
+ </ListItem>
100
+ <ListItem
101
+ model={model}
102
+ onClick={() =>
103
+ load(model, {
104
+ uri: 'https://jbrowse.org/genomes/multiple_sequence_alignments/europe_covid.fa',
105
+ locationType: 'UriLocation',
106
+ })
107
+ }
108
+ >
109
+ Europe COVID full genomes (LR883044.1 and 199 other sequences)
110
+ </ListItem>
111
+ <ListItem
112
+ model={model}
113
+ onClick={() =>
114
+ load(
115
+ model,
116
+ {
117
+ uri: '/rhv_test-only.aligned_with_mafft_auto.fa',
118
+ locationType: 'UriLocation',
119
+ },
120
+ {
121
+ uri: 'https://jbrowse.org/genomes/multiple_sequence_alignments/rhv_test-only.aligned_with_mafft_auto.nh',
122
+ locationType: 'UriLocation',
123
+ },
124
+ )
125
+ }
126
+ >
127
+ MAFFT+VeryFastTree(17.9k samples)
128
+ </ListItem>
129
+ </ul>
130
+ )
131
+ })
132
+
133
+ export default ImportFormExamples
@@ -0,0 +1,63 @@
1
+ import React, { useState } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { Button, Container, Grid, Typography } from '@mui/material'
4
+ import { FileSelector } from '@jbrowse/core/ui'
5
+ import { FileLocation } from '@jbrowse/core/util/types'
6
+
7
+ // locals
8
+ import { MsaViewModel } from '../../model'
9
+ import { load } from './util'
10
+ import ImportFormExamples from './ImportFormExamples'
11
+
12
+ export default observer(({ model }: { model: MsaViewModel }) => {
13
+ const [msaFile, setMsaFile] = useState<FileLocation>()
14
+ const [treeFile, setTreeFile] = useState<FileLocation>()
15
+ const { error } = model
16
+
17
+ return (
18
+ <Container>
19
+ <div style={{ width: '50%' }}>
20
+ {error ? (
21
+ <div style={{ padding: 20 }}>
22
+ <Typography color="error">Error: {`${error}`}</Typography>
23
+ </div>
24
+ ) : null}
25
+ <Typography>
26
+ Open an MSA file (stockholm or clustal format) and/or a tree file
27
+ (newick format).
28
+ </Typography>
29
+ <Typography color="error">
30
+ Note: you can open up just an MSA or just a tree, both are not
31
+ required. Some MSA files e.g. stockholm format have an embedded tree
32
+ also and this is fine, and opening a separate tree file is not
33
+ required.
34
+ </Typography>
35
+ </div>
36
+
37
+ <Grid container spacing={10} justifyContent="center" alignItems="center">
38
+ <Grid item>
39
+ <Typography>MSA file or URL</Typography>
40
+ <FileSelector location={msaFile} setLocation={setMsaFile} />
41
+ <Typography>Tree file or URL</Typography>
42
+ <FileSelector location={treeFile} setLocation={setTreeFile} />
43
+ </Grid>
44
+
45
+ <Grid item>
46
+ <Button
47
+ onClick={() => load(model, msaFile, treeFile)}
48
+ variant="contained"
49
+ color="primary"
50
+ disabled={!msaFile && !treeFile}
51
+ >
52
+ Open
53
+ </Button>
54
+ </Grid>
55
+
56
+ <Grid item>
57
+ <Typography>Examples</Typography>
58
+ <ImportFormExamples model={model} />
59
+ </Grid>
60
+ </Grid>
61
+ </Container>
62
+ )
63
+ })
@@ -0,0 +1,20 @@
1
+ import { FileLocation } from '@jbrowse/core/util'
2
+ import { MsaViewModel } from '../../model'
3
+
4
+ export async function load(
5
+ model: MsaViewModel,
6
+ msaFile?: FileLocation,
7
+ treeFile?: FileLocation,
8
+ ) {
9
+ model.setError(undefined)
10
+ try {
11
+ if (msaFile) {
12
+ await model.setMSAFilehandle(msaFile)
13
+ }
14
+ if (treeFile) {
15
+ await model.setTreeFilehandle(treeFile)
16
+ }
17
+ } catch (e) {
18
+ model.setError(e)
19
+ }
20
+ }
@@ -0,0 +1,81 @@
1
+ import React, { useEffect, useRef, useMemo } from 'react'
2
+ import { autorun } from 'mobx'
3
+ import { useTheme } from '@mui/material'
4
+ import { observer } from 'mobx-react'
5
+
6
+ // locals
7
+ import { renderBlock } from './renderMSABlock'
8
+ import { MsaViewModel } from '../../model'
9
+ import { colorContrast } from '../../util'
10
+
11
+ const MSABlock = observer(function ({
12
+ model,
13
+ offsetX,
14
+ offsetY,
15
+ }: {
16
+ model: MsaViewModel
17
+ offsetX: number
18
+ offsetY: number
19
+ }) {
20
+ const {
21
+ colWidth,
22
+ rowHeight,
23
+ scrollY,
24
+ scrollX,
25
+ colorScheme,
26
+ blockSize,
27
+ highResScaleFactor,
28
+ } = model
29
+ const theme = useTheme()
30
+
31
+ const contrastScheme = useMemo(
32
+ () => colorContrast(colorScheme, theme),
33
+ [colorScheme, theme],
34
+ )
35
+
36
+ const ref = useRef<HTMLCanvasElement>(null)
37
+ useEffect(() => {
38
+ const ctx = ref.current?.getContext('2d')
39
+ if (!ctx) {
40
+ return
41
+ }
42
+ return autorun(() => {
43
+ renderBlock({
44
+ ctx,
45
+ offsetX,
46
+ offsetY,
47
+ contrastScheme,
48
+ model,
49
+ })
50
+ })
51
+ }, [model, offsetX, offsetY, contrastScheme])
52
+ return (
53
+ <canvas
54
+ ref={ref}
55
+ onMouseMove={event => {
56
+ if (!ref.current) {
57
+ return
58
+ }
59
+ const { left, top } = ref.current.getBoundingClientRect()
60
+ const mouseX = event.clientX - left + offsetX
61
+ const mouseY = event.clientY - top + offsetY
62
+ model.setMousePos(
63
+ Math.floor(mouseX / colWidth) + 1,
64
+ Math.floor(mouseY / rowHeight),
65
+ )
66
+ }}
67
+ onMouseLeave={() => model.setMousePos()}
68
+ width={blockSize * highResScaleFactor}
69
+ height={blockSize * highResScaleFactor}
70
+ style={{
71
+ position: 'absolute',
72
+ top: scrollY + offsetY,
73
+ left: scrollX + offsetX,
74
+ width: blockSize,
75
+ height: blockSize,
76
+ }}
77
+ />
78
+ )
79
+ })
80
+
81
+ export default MSABlock
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react'
4
4
  import normalizeWheel from 'normalize-wheel'
5
5
 
6
6
  // locals
7
- import { MsaViewModel } from '../model'
7
+ import { MsaViewModel } from '../../model'
8
8
  import MSABlock from './MSABlock'
9
9
 
10
10
  const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
@@ -121,10 +121,7 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
121
121
  }}
122
122
  >
123
123
  {!MSA && !msaFilehandle ? null : !MSA ? (
124
- <div style={{ position: 'absolute', left: '50%', top: '50%' }}>
125
- <CircularProgress />
126
- <Typography>Loading...</Typography>
127
- </div>
124
+ <Loading />
128
125
  ) : (
129
126
  blocks2d.map(([bx, by]) => (
130
127
  <MSABlock
@@ -139,4 +136,19 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
139
136
  )
140
137
  })
141
138
 
139
+ function Loading() {
140
+ return (
141
+ <div
142
+ style={{
143
+ position: 'absolute',
144
+ left: '50%',
145
+ top: '50%',
146
+ }}
147
+ >
148
+ <CircularProgress />
149
+ <Typography>Loading...</Typography>
150
+ </div>
151
+ )
152
+ }
153
+
142
154
  export default MSACanvas
@@ -0,0 +1,92 @@
1
+ import React, { useEffect, useRef } from 'react'
2
+ import { observer } from 'mobx-react'
3
+
4
+ // locals
5
+ import { MsaViewModel } from '../../model'
6
+ import { autorun } from 'mobx'
7
+
8
+ function renderMouseover({
9
+ ctx,
10
+ model,
11
+ }: {
12
+ ctx: CanvasRenderingContext2D
13
+ model: MsaViewModel
14
+ }) {
15
+ const {
16
+ mouseCol,
17
+ colWidth,
18
+ treeAreaWidth,
19
+ resizeHandleWidth,
20
+ width,
21
+ height,
22
+ rowHeight,
23
+ scrollX,
24
+ scrollY,
25
+ mouseRow,
26
+ // @ts-expect-error
27
+ mouseCol2,
28
+ minimapHeight,
29
+ totalTrackAreaHeight,
30
+ } = model
31
+ ctx.resetTransform()
32
+ ctx.clearRect(0, 0, width, height)
33
+
34
+ if (mouseCol !== undefined) {
35
+ ctx.fillStyle = 'rgba(0,0,0,0.15)'
36
+ const x =
37
+ (mouseCol - 1) * colWidth + scrollX + treeAreaWidth + resizeHandleWidth
38
+
39
+ ctx.fillRect(x, minimapHeight, colWidth, height)
40
+ }
41
+
42
+ if (mouseRow !== undefined) {
43
+ ctx.fillStyle = 'rgba(0,0,0,0.15)'
44
+ const y =
45
+ mouseRow * rowHeight + scrollY + minimapHeight + totalTrackAreaHeight
46
+ ctx.fillRect(treeAreaWidth + resizeHandleWidth, y, width, rowHeight)
47
+ }
48
+ if (mouseCol2 !== undefined) {
49
+ ctx.fillStyle = 'rgba(255,255,0,0.2)'
50
+ const x =
51
+ (mouseCol2 - 1) * colWidth + scrollX + treeAreaWidth + resizeHandleWidth
52
+
53
+ ctx.fillRect(x, 0, colWidth, height)
54
+ }
55
+ }
56
+
57
+ const MSAMouseoverCanvas = observer(function ({
58
+ model,
59
+ }: {
60
+ model: MsaViewModel
61
+ }) {
62
+ const ref = useRef<HTMLCanvasElement>(null)
63
+ const { height, width } = model
64
+ useEffect(() => {
65
+ const ctx = ref.current?.getContext('2d')
66
+ if (!ctx) {
67
+ return
68
+ }
69
+ return autorun(() => {
70
+ renderMouseover({ ctx, model })
71
+ })
72
+ }, [model])
73
+
74
+ return (
75
+ <canvas
76
+ ref={ref}
77
+ width={width}
78
+ height={height}
79
+ style={{
80
+ position: 'absolute',
81
+ top: 0,
82
+ left: 0,
83
+ width,
84
+ height,
85
+ zIndex: 1000,
86
+ pointerEvents: 'none',
87
+ }}
88
+ />
89
+ )
90
+ })
91
+
92
+ export default MSAMouseoverCanvas
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import MSACanvas from './MSACanvas'
3
+ import MSAMouseoverCanvas from './MSAMouseoverCanvas'
4
+ import { MsaViewModel } from '../../model'
5
+
6
+ export default function MSAPanel({ model }: { model: MsaViewModel }) {
7
+ return (
8
+ <>
9
+ <MSACanvas model={model} />
10
+ <MSAMouseoverCanvas model={model} />
11
+ </>
12
+ )
13
+ }
@@ -0,0 +1,160 @@
1
+ // locals
2
+ import { MsaViewModel } from '../../model'
3
+ import { getClustalXColor, getPercentIdentityColor } from '../../colorSchemes'
4
+ import { NodeWithIdsAndLength } from '../../util'
5
+ import { HierarchyNode } from 'd3-hierarchy'
6
+
7
+ export function renderBlock({
8
+ model,
9
+ offsetX,
10
+ offsetY,
11
+ contrastScheme,
12
+ ctx,
13
+ }: {
14
+ offsetX: number
15
+ offsetY: number
16
+ model: MsaViewModel
17
+ contrastScheme: Record<string, string>
18
+ ctx: CanvasRenderingContext2D
19
+ }) {
20
+ const {
21
+ hierarchy,
22
+ colWidth,
23
+ blockSize,
24
+ rowHeight,
25
+ fontSize,
26
+ highResScaleFactor,
27
+ } = model
28
+ ctx.resetTransform()
29
+ ctx.scale(highResScaleFactor, highResScaleFactor)
30
+ ctx.clearRect(0, 0, blockSize, blockSize)
31
+ ctx.translate(-offsetX, rowHeight / 2 - offsetY)
32
+ ctx.textAlign = 'center'
33
+ ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
34
+
35
+ const leaves = hierarchy.leaves()
36
+ const b = blockSize
37
+
38
+ const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
39
+ const yEnd = Math.max(0, Math.ceil((offsetY + b + rowHeight) / rowHeight))
40
+ const xStart = Math.max(0, Math.floor(offsetX / colWidth))
41
+ const xEnd = Math.max(0, Math.ceil((offsetX + b) / colWidth))
42
+ const visibleLeaves = leaves.slice(yStart, yEnd)
43
+
44
+ drawTiles({
45
+ model,
46
+ ctx,
47
+ offsetX,
48
+ offsetY,
49
+ xStart,
50
+ xEnd,
51
+ visibleLeaves,
52
+ })
53
+ drawText({
54
+ model,
55
+ ctx,
56
+ offsetX,
57
+ contrastScheme,
58
+ xStart,
59
+ xEnd,
60
+ visibleLeaves,
61
+ })
62
+ }
63
+
64
+ function drawTiles({
65
+ model,
66
+ offsetX,
67
+ ctx,
68
+ visibleLeaves,
69
+ xStart,
70
+ xEnd,
71
+ }: {
72
+ model: MsaViewModel
73
+ offsetX: number
74
+ offsetY: number
75
+ ctx: CanvasRenderingContext2D
76
+ visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
77
+ xStart: number
78
+ xEnd: number
79
+ }) {
80
+ const {
81
+ bgColor,
82
+ colorSchemeName,
83
+ colorScheme,
84
+ colStats,
85
+ columns,
86
+ colWidth,
87
+ rowHeight,
88
+ } = model
89
+
90
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
91
+ for (const node of visibleLeaves) {
92
+ const {
93
+ // @ts-expect-error
94
+ x: y,
95
+ data: { name },
96
+ } = node
97
+
98
+ const str = columns[name]?.slice(xStart, xEnd)
99
+ for (let i = 0; i < str?.length; i++) {
100
+ const letter = str[i]
101
+ const color =
102
+ colorSchemeName === 'clustalx_protein_dynamic'
103
+ ? getClustalXColor(colStats[xStart + i], model, name, xStart + i)
104
+ : colorSchemeName === 'percent_identity_dynamic'
105
+ ? getPercentIdentityColor(
106
+ colStats[xStart + i],
107
+ model,
108
+ name,
109
+ xStart + i,
110
+ )
111
+ : colorScheme[letter.toUpperCase()]
112
+ if (bgColor) {
113
+ const x = i * colWidth + offsetX - (offsetX % colWidth)
114
+ ctx.fillStyle = color || 'white'
115
+ ctx.fillRect(x, y - rowHeight, colWidth, rowHeight)
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ function drawText({
122
+ model,
123
+ offsetX,
124
+ contrastScheme,
125
+ ctx,
126
+ visibleLeaves,
127
+ xStart,
128
+ xEnd,
129
+ }: {
130
+ offsetX: number
131
+ model: MsaViewModel
132
+ contrastScheme: Record<string, string>
133
+ ctx: CanvasRenderingContext2D
134
+ visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
135
+ xStart: number
136
+ xEnd: number
137
+ }) {
138
+ const { bgColor, colorScheme, columns, colWidth, rowHeight } = model
139
+ if (rowHeight >= 5 && colWidth > rowHeight / 2) {
140
+ for (const node of visibleLeaves) {
141
+ const {
142
+ // @ts-expect-error
143
+ x: y,
144
+ data: { name },
145
+ } = node
146
+
147
+ const str = columns[name]?.slice(xStart, xEnd)
148
+ for (let i = 0; i < str?.length; i++) {
149
+ const letter = str[i]
150
+ const color = colorScheme[letter.toUpperCase()]
151
+ const contrast = contrastScheme[letter.toUpperCase()] || 'black'
152
+ const x = i * colWidth + offsetX - (offsetX % colWidth)
153
+
154
+ // note: -rowHeight/4 matches +rowHeight/4 in tree
155
+ ctx.fillStyle = bgColor ? contrast : color || 'black'
156
+ ctx.fillText(letter, x + colWidth / 2, y - rowHeight / 4)
157
+ }
158
+ }
159
+ }
160
+ }