react-msaview 2.0.0 → 2.1.1

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 (149) hide show
  1. package/bundle/index.js +35 -33
  2. package/dist/UniprotTrack.js +6 -5
  3. package/dist/UniprotTrack.js.map +1 -1
  4. package/dist/colorSchemes.d.ts +3 -9
  5. package/dist/colorSchemes.js +8 -6
  6. package/dist/colorSchemes.js.map +1 -1
  7. package/dist/components/BoxTrack.d.ts +4 -3
  8. package/dist/components/BoxTrack.js +6 -137
  9. package/dist/components/BoxTrack.js.map +1 -1
  10. package/dist/components/BoxTrackBlock.d.ts +8 -0
  11. package/dist/components/BoxTrackBlock.js +136 -0
  12. package/dist/components/BoxTrackBlock.js.map +1 -0
  13. package/dist/components/Header.d.ts +2 -1
  14. package/dist/components/Header.js +29 -27
  15. package/dist/components/Header.js.map +1 -1
  16. package/dist/components/ImportForm.d.ts +2 -1
  17. package/dist/components/MSABlock.d.ts +8 -0
  18. package/dist/components/MSABlock.js +103 -0
  19. package/dist/components/MSABlock.js.map +1 -0
  20. package/dist/components/MSACanvas.d.ts +2 -1
  21. package/dist/components/MSACanvas.js +3 -101
  22. package/dist/components/MSACanvas.js.map +1 -1
  23. package/dist/components/MSAMouseoverCanvas.d.ts +6 -0
  24. package/dist/components/MSAMouseoverCanvas.js +52 -0
  25. package/dist/components/MSAMouseoverCanvas.js.map +1 -0
  26. package/dist/components/MSAView.d.ts +2 -1
  27. package/dist/components/MSAView.js +10 -36
  28. package/dist/components/MSAView.js.map +1 -1
  29. package/dist/components/MultiAlignmentSelector.d.ts +6 -0
  30. package/dist/components/MultiAlignmentSelector.js +13 -0
  31. package/dist/components/MultiAlignmentSelector.js.map +1 -0
  32. package/dist/components/ResizeHandles.d.ts +3 -2
  33. package/dist/components/Rubberband.d.ts +1 -1
  34. package/dist/components/Rubberband.js +0 -1
  35. package/dist/components/Rubberband.js.map +1 -1
  36. package/dist/components/Ruler.d.ts +2 -1
  37. package/dist/components/Ruler.js +2 -2
  38. package/dist/components/Ruler.js.map +1 -1
  39. package/dist/components/TextTrack.d.ts +2 -1
  40. package/dist/components/Track.d.ts +3 -2
  41. package/dist/components/Track.js +4 -3
  42. package/dist/components/Track.js.map +1 -1
  43. package/dist/components/TreeBranchMenu.d.ts +14 -0
  44. package/dist/components/TreeBranchMenu.js +26 -0
  45. package/dist/components/TreeBranchMenu.js.map +1 -0
  46. package/dist/components/TreeCanvas.d.ts +2 -1
  47. package/dist/components/TreeCanvas.js +2 -320
  48. package/dist/components/TreeCanvas.js.map +1 -1
  49. package/dist/components/TreeCanvasBlock.d.ts +7 -0
  50. package/dist/components/TreeCanvasBlock.js +252 -0
  51. package/dist/components/TreeCanvasBlock.js.map +1 -0
  52. package/dist/components/TreeMenu.d.ts +12 -0
  53. package/dist/components/TreeMenu.js +56 -0
  54. package/dist/components/TreeMenu.js.map +1 -0
  55. package/dist/components/TreeRuler.d.ts +2 -1
  56. package/dist/components/VerticalGuide.d.ts +2 -1
  57. package/dist/components/ZoomControls.d.ts +6 -0
  58. package/dist/components/ZoomControls.js +58 -0
  59. package/dist/components/ZoomControls.js.map +1 -0
  60. package/dist/components/dialogs/AboutDlg.d.ts +4 -0
  61. package/dist/components/{AboutDlg.js → dialogs/AboutDlg.js} +3 -3
  62. package/dist/components/dialogs/AboutDlg.js.map +1 -0
  63. package/dist/components/{AddTrackDlg.d.ts → dialogs/AddTrackDlg.d.ts} +3 -2
  64. package/dist/components/dialogs/AddTrackDlg.js.map +1 -0
  65. package/dist/components/{AnnotationDlg.d.ts → dialogs/AnnotationDlg.d.ts} +3 -2
  66. package/dist/components/dialogs/AnnotationDlg.js.map +1 -0
  67. package/dist/components/dialogs/DetailsDlg.d.ts +7 -0
  68. package/dist/components/{DetailsDlg.js → dialogs/DetailsDlg.js} +3 -2
  69. package/dist/components/dialogs/DetailsDlg.js.map +1 -0
  70. package/dist/components/{MoreInfoDlg.d.ts → dialogs/MoreInfoDlg.d.ts} +2 -1
  71. package/dist/components/dialogs/MoreInfoDlg.js.map +1 -0
  72. package/dist/components/dialogs/SettingsDlg.d.ts +7 -0
  73. package/dist/components/{SettingsDlg.js → dialogs/SettingsDlg.js} +4 -3
  74. package/dist/components/dialogs/SettingsDlg.js.map +1 -0
  75. package/dist/components/{TrackInfoDlg.d.ts → dialogs/TrackInfoDlg.d.ts} +2 -1
  76. package/dist/components/dialogs/TrackInfoDlg.js.map +1 -0
  77. package/dist/components/dialogs/TracklistDlg.d.ts +7 -0
  78. package/dist/components/{TracklistDlg.js → dialogs/TracklistDlg.js} +2 -2
  79. package/dist/components/dialogs/TracklistDlg.js.map +1 -0
  80. package/dist/components/util.js +0 -1
  81. package/dist/components/util.js.map +1 -1
  82. package/dist/model.d.ts +36 -39
  83. package/dist/model.js +25 -22
  84. package/dist/model.js.map +1 -1
  85. package/dist/parseNewick.js +2 -2
  86. package/dist/parseNewick.js.map +1 -1
  87. package/dist/parsers/FastaMSA.d.ts +1 -3
  88. package/dist/parsers/FastaMSA.js +2 -2
  89. package/dist/parsers/FastaMSA.js.map +1 -1
  90. package/dist/parsers/StockholmMSA.d.ts +3 -5
  91. package/dist/parsers/StockholmMSA.js +3 -3
  92. package/dist/parsers/StockholmMSA.js.map +1 -1
  93. package/dist/util.d.ts +5 -7
  94. package/dist/util.js +4 -2
  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/dist/version.js.map +1 -1
  99. package/package.json +9 -9
  100. package/src/UniprotTrack.ts +6 -7
  101. package/src/colorSchemes.ts +12 -9
  102. package/src/components/BoxTrack.tsx +6 -198
  103. package/src/components/BoxTrackBlock.tsx +198 -0
  104. package/src/components/Header.tsx +49 -60
  105. package/src/components/MSABlock.tsx +164 -0
  106. package/src/components/MSACanvas.tsx +3 -158
  107. package/src/components/MSAMouseoverCanvas.tsx +87 -0
  108. package/src/components/MSAView.tsx +19 -63
  109. package/src/components/MultiAlignmentSelector.tsx +33 -0
  110. package/src/components/Rubberband.tsx +0 -1
  111. package/src/components/Ruler.tsx +2 -1
  112. package/src/components/Track.tsx +9 -6
  113. package/src/components/TreeBranchMenu.tsx +67 -0
  114. package/src/components/TreeCanvas.tsx +2 -507
  115. package/src/components/TreeCanvasBlock.tsx +359 -0
  116. package/src/components/TreeMenu.tsx +105 -0
  117. package/src/components/ZoomControls.tsx +78 -0
  118. package/src/components/{AboutDlg.tsx → dialogs/AboutDlg.tsx} +3 -9
  119. package/src/components/{AddTrackDlg.tsx → dialogs/AddTrackDlg.tsx} +1 -1
  120. package/src/components/{AnnotationDlg.tsx → dialogs/AnnotationDlg.tsx} +2 -2
  121. package/src/components/{DetailsDlg.tsx → dialogs/DetailsDlg.tsx} +5 -5
  122. package/src/components/{SettingsDlg.tsx → dialogs/SettingsDlg.tsx} +6 -6
  123. package/src/components/{TracklistDlg.tsx → dialogs/TracklistDlg.tsx} +2 -4
  124. package/src/components/util.ts +0 -1
  125. package/src/declare.d.ts +0 -1
  126. package/src/model.ts +32 -29
  127. package/src/parseNewick.ts +2 -3
  128. package/src/parsers/FastaMSA.ts +3 -3
  129. package/src/parsers/StockholmMSA.ts +5 -5
  130. package/src/util.ts +8 -5
  131. package/src/version.ts +1 -1
  132. package/dist/components/AboutDlg.d.ts +0 -4
  133. package/dist/components/AboutDlg.js.map +0 -1
  134. package/dist/components/AddTrackDlg.js.map +0 -1
  135. package/dist/components/AnnotationDlg.js.map +0 -1
  136. package/dist/components/DetailsDlg.d.ts +0 -7
  137. package/dist/components/DetailsDlg.js.map +0 -1
  138. package/dist/components/MoreInfoDlg.js.map +0 -1
  139. package/dist/components/SettingsDlg.d.ts +0 -7
  140. package/dist/components/SettingsDlg.js.map +0 -1
  141. package/dist/components/TrackInfoDlg.js.map +0 -1
  142. package/dist/components/TracklistDlg.d.ts +0 -7
  143. package/dist/components/TracklistDlg.js.map +0 -1
  144. /package/dist/components/{AddTrackDlg.js → dialogs/AddTrackDlg.js} +0 -0
  145. /package/dist/components/{AnnotationDlg.js → dialogs/AnnotationDlg.js} +0 -0
  146. /package/dist/components/{MoreInfoDlg.js → dialogs/MoreInfoDlg.js} +0 -0
  147. /package/dist/components/{TrackInfoDlg.js → dialogs/TrackInfoDlg.js} +0 -0
  148. /package/src/components/{MoreInfoDlg.tsx → dialogs/MoreInfoDlg.tsx} +0 -0
  149. /package/src/components/{TrackInfoDlg.tsx → dialogs/TrackInfoDlg.tsx} +0 -0
@@ -1,20 +1,23 @@
1
- import React, { useState } from 'react'
2
- import { IconButton, Select, Typography } from '@mui/material'
1
+ import React, { Suspense, lazy, useState } from 'react'
2
+ import { IconButton, Typography } from '@mui/material'
3
3
  import { observer } from 'mobx-react'
4
4
 
5
5
  // locals
6
6
  import { MsaViewModel } from '../model'
7
- import SettingsDialog from './SettingsDlg'
8
- import AboutDialog from './AboutDlg'
9
- import DetailsDialog from './DetailsDlg'
10
- import TracklistDialog from './TracklistDlg'
11
7
 
12
8
  // icons
13
- import FolderOpenIcon from '@mui/icons-material/FolderOpen'
14
- import SettingsIcon from '@mui/icons-material/Settings'
15
- import HelpIcon from '@mui/icons-material/Help'
16
- import AssignmentIcon from '@mui/icons-material/Assignment'
17
- import ListIcon from '@mui/icons-material/List'
9
+ import FolderOpen from '@mui/icons-material/FolderOpen'
10
+ import Settings from '@mui/icons-material/Settings'
11
+ import Help from '@mui/icons-material/Help'
12
+ import Assignment from '@mui/icons-material/Assignment'
13
+ import List from '@mui/icons-material/List'
14
+ import ZoomControls from './ZoomControls'
15
+ import MultiAlignmentSelector from './MultiAlignmentSelector'
16
+
17
+ const SettingsDialog = lazy(() => import('./dialogs/SettingsDlg'))
18
+ const AboutDialog = lazy(() => import('./dialogs/AboutDlg'))
19
+ const DetailsDialog = lazy(() => import('./dialogs/DetailsDlg'))
20
+ const TracklistDialog = lazy(() => import('./dialogs/TracklistDlg'))
18
21
 
19
22
  const InfoArea = observer(({ model }: { model: MsaViewModel }) => {
20
23
  const { mouseOverRowName, mouseCol } = model
@@ -32,7 +35,6 @@ const Header = observer(({ model }: { model: MsaViewModel }) => {
32
35
  const [aboutDialogViz, setAboutDialogViz] = useState(false)
33
36
  const [detailsDialogViz, setDetailsDialogViz] = useState(false)
34
37
  const [tracklistDialogViz, setTracklistDialogViz] = useState(false)
35
- const { currentAlignment, alignmentNames } = model
36
38
 
37
39
  return (
38
40
  <div style={{ display: 'flex' }}>
@@ -51,67 +53,54 @@ const Header = observer(({ model }: { model: MsaViewModel }) => {
51
53
  }
52
54
  }}
53
55
  >
54
- <FolderOpenIcon />
56
+ <FolderOpen />
55
57
  </IconButton>
56
58
  <IconButton onClick={() => setSettingsDialogViz(true)}>
57
- <SettingsIcon />
59
+ <Settings />
58
60
  </IconButton>
59
61
  <IconButton onClick={() => setDetailsDialogViz(true)}>
60
- <AssignmentIcon />
62
+ <Assignment />
61
63
  </IconButton>
62
64
  <IconButton onClick={() => setTracklistDialogViz(true)}>
63
- <ListIcon />
65
+ <List />
64
66
  </IconButton>
65
- {settingsDialogViz ? (
66
- <SettingsDialog
67
- open
68
- model={model}
69
- onClose={() => setSettingsDialogViz(false)}
70
- />
71
- ) : null}
72
- {aboutDialogViz ? (
73
- <AboutDialog open onClose={() => setAboutDialogViz(false)} />
74
- ) : null}
75
- {detailsDialogViz ? (
76
- <DetailsDialog
77
- open
78
- model={model}
79
- onClose={() => setDetailsDialogViz(false)}
80
- />
81
- ) : null}
67
+ <Suspense fallback={null}>
68
+ {settingsDialogViz ? (
69
+ <SettingsDialog
70
+ model={model}
71
+ onClose={() => setSettingsDialogViz(false)}
72
+ />
73
+ ) : null}
74
+ {aboutDialogViz ? (
75
+ <AboutDialog onClose={() => setAboutDialogViz(false)} />
76
+ ) : null}
77
+ {detailsDialogViz ? (
78
+ <DetailsDialog
79
+ model={model}
80
+ onClose={() => setDetailsDialogViz(false)}
81
+ />
82
+ ) : null}
82
83
 
83
- {tracklistDialogViz ? (
84
- <TracklistDialog
85
- open
86
- model={model}
87
- onClose={() => setTracklistDialogViz(false)}
88
- />
89
- ) : null}
90
- {alignmentNames.length > 0 ? (
91
- <Select
92
- native
93
- value={currentAlignment}
94
- size="small"
95
- onChange={event => {
96
- model.setCurrentAlignment(+(event.target.value as string))
97
- model.setScrollX(0)
98
- model.setScrollY(0)
99
- }}
100
- >
101
- {alignmentNames.map((option, index) => (
102
- <option key={`${option}-${index}`} value={index}>
103
- {option}
104
- </option>
105
- ))}
106
- </Select>
107
- ) : null}
84
+ {tracklistDialogViz ? (
85
+ <TracklistDialog
86
+ model={model}
87
+ onClose={() => setTracklistDialogViz(false)}
88
+ />
89
+ ) : null}
90
+ </Suspense>
91
+ <MultiAlignmentSelector model={model} />
92
+ <ZoomControls model={model} />
108
93
  <InfoArea model={model} />
109
- <div style={{ flex: 1 }} />
94
+ <Spacer />
110
95
  <IconButton onClick={() => setAboutDialogViz(true)}>
111
- <HelpIcon />
96
+ <Help />
112
97
  </IconButton>
113
98
  </div>
114
99
  )
115
100
  })
116
101
 
102
+ function Spacer() {
103
+ return <div style={{ flex: 1 }} />
104
+ }
105
+
117
106
  export default Header
@@ -0,0 +1,164 @@
1
+ import React, { useEffect, useRef, useMemo } from 'react'
2
+ import { useTheme } from '@mui/material'
3
+ import { observer } from 'mobx-react'
4
+
5
+ // locals
6
+ import { MsaViewModel } from '../model'
7
+ import { colorContrast } from '../util'
8
+ import { getClustalXColor, getPercentIdentityColor } from '../colorSchemes'
9
+
10
+ const MSABlock = observer(function ({
11
+ model,
12
+ offsetX,
13
+ offsetY,
14
+ }: {
15
+ model: MsaViewModel
16
+ offsetX: number
17
+ offsetY: number
18
+ }) {
19
+ const {
20
+ MSA,
21
+ colWidth,
22
+ bgColor,
23
+ columns,
24
+ rowHeight,
25
+ scrollY,
26
+ scrollX,
27
+ hierarchy,
28
+ colorScheme,
29
+ colorSchemeName,
30
+ blockSize,
31
+ highResScaleFactor,
32
+ colStats,
33
+ } = model
34
+ const theme = useTheme()
35
+
36
+ const contrastScheme = useMemo(
37
+ () => colorContrast(colorScheme, theme),
38
+ [colorScheme, theme],
39
+ )
40
+
41
+ const ref = useRef<HTMLCanvasElement>(null)
42
+ useEffect(() => {
43
+ if (!ref.current) {
44
+ return
45
+ }
46
+
47
+ const ctx = ref.current.getContext('2d')
48
+ if (!ctx) {
49
+ return
50
+ }
51
+
52
+ ctx.resetTransform()
53
+ ctx.scale(highResScaleFactor, highResScaleFactor)
54
+ ctx.clearRect(0, 0, blockSize, blockSize)
55
+ ctx.translate(-offsetX, rowHeight / 2 - offsetY)
56
+ ctx.textAlign = 'center'
57
+ ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
58
+
59
+ const leaves = hierarchy.leaves()
60
+ const b = blockSize
61
+
62
+ // slice vertical rows, e.g. tree leaves, avoid negative slice
63
+ const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
64
+ const yEnd = Math.max(0, Math.ceil((offsetY + b + rowHeight) / rowHeight))
65
+
66
+ // slice horizontal visible letters, avoid negative slice
67
+ const xStart = Math.max(0, Math.floor(offsetX / colWidth))
68
+ const xEnd = Math.max(0, Math.ceil((offsetX + b) / colWidth))
69
+ const visibleLeaves = leaves.slice(yStart, yEnd)
70
+ for (const node of visibleLeaves) {
71
+ const {
72
+ // @ts-expect-error
73
+ x: y,
74
+ data: { name },
75
+ } = node
76
+
77
+ const str = columns[name]?.slice(xStart, xEnd)
78
+ for (let i = 0; i < str?.length; i++) {
79
+ const letter = str[i]
80
+ const color =
81
+ colorSchemeName === 'clustalx_protein_dynamic'
82
+ ? getClustalXColor(colStats[xStart + i], model, name, xStart + i)
83
+ : colorSchemeName === 'percent_identity_dynamic'
84
+ ? getPercentIdentityColor(
85
+ colStats[xStart + i],
86
+ model,
87
+ name,
88
+ xStart + i,
89
+ )
90
+ : colorScheme[letter.toUpperCase()]
91
+ if (bgColor) {
92
+ const x = i * colWidth + offsetX - (offsetX % colWidth)
93
+ ctx.fillStyle = color || 'white'
94
+ ctx.fillRect(x, y - rowHeight, colWidth, rowHeight)
95
+ }
96
+ }
97
+ }
98
+
99
+ if (rowHeight >= 5 && colWidth > rowHeight / 2) {
100
+ for (const node of visibleLeaves) {
101
+ const {
102
+ // @ts-expect-error
103
+ x: y,
104
+ data: { name },
105
+ } = node
106
+
107
+ const str = columns[name]?.slice(xStart, xEnd)
108
+ for (let i = 0; i < str?.length; i++) {
109
+ const letter = str[i]
110
+ const color = colorScheme[letter.toUpperCase()]
111
+ const contrast = contrastScheme[letter.toUpperCase()] || 'black'
112
+ const x = i * colWidth + offsetX - (offsetX % colWidth)
113
+
114
+ // note: -rowHeight/4 matches +rowHeight/4 in tree
115
+ ctx.fillStyle = bgColor ? contrast : color || 'black'
116
+ ctx.fillText(letter, x + colWidth / 2, y - rowHeight / 4)
117
+ }
118
+ }
119
+ }
120
+ // eslint-disable-next-line react-hooks/exhaustive-deps
121
+ }, [
122
+ MSA,
123
+ highResScaleFactor,
124
+ columns,
125
+ colorScheme,
126
+ contrastScheme,
127
+ bgColor,
128
+ rowHeight,
129
+ colWidth,
130
+ hierarchy,
131
+ offsetX,
132
+ offsetY,
133
+ blockSize,
134
+ ])
135
+ return (
136
+ <canvas
137
+ ref={ref}
138
+ onMouseMove={event => {
139
+ if (!ref.current) {
140
+ return
141
+ }
142
+ const { left, top } = ref.current.getBoundingClientRect()
143
+ const mouseX = event.clientX - left + offsetX
144
+ const mouseY = event.clientY - top + offsetY
145
+ model.setMousePos(
146
+ Math.floor(mouseX / colWidth) + 1,
147
+ Math.floor(mouseY / rowHeight),
148
+ )
149
+ }}
150
+ onMouseLeave={() => model.setMousePos()}
151
+ width={blockSize * highResScaleFactor}
152
+ height={blockSize * highResScaleFactor}
153
+ style={{
154
+ position: 'absolute',
155
+ top: scrollY + offsetY,
156
+ left: scrollX + offsetX,
157
+ width: blockSize,
158
+ height: blockSize,
159
+ }}
160
+ />
161
+ )
162
+ })
163
+
164
+ export default MSABlock
@@ -1,166 +1,11 @@
1
- import React, { useEffect, useState, useRef, useMemo } from 'react'
2
- import { Typography, CircularProgress, useTheme } from '@mui/material'
1
+ import React, { useEffect, useState, useRef } from 'react'
2
+ import { Typography, CircularProgress } from '@mui/material'
3
3
  import { observer } from 'mobx-react'
4
4
  import normalizeWheel from 'normalize-wheel'
5
5
 
6
6
  // locals
7
7
  import { MsaViewModel } from '../model'
8
- import { colorContrast } from '../util'
9
- import { getClustalXColor, getPercentIdentityColor } from '../colorSchemes'
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
- MSA,
22
- colWidth,
23
- bgColor,
24
- columns,
25
- rowHeight,
26
- scrollY,
27
- scrollX,
28
- hierarchy,
29
- colorScheme,
30
- colorSchemeName,
31
- blockSize,
32
- highResScaleFactor,
33
- colStats,
34
- } = model
35
- const theme = useTheme()
36
-
37
- const contrastScheme = useMemo(
38
- () => colorContrast(colorScheme, theme),
39
- [colorScheme, theme],
40
- )
41
-
42
- const ref = useRef<HTMLCanvasElement>(null)
43
- useEffect(() => {
44
- if (!ref.current) {
45
- return
46
- }
47
-
48
- const ctx = ref.current.getContext('2d')
49
- if (!ctx) {
50
- return
51
- }
52
-
53
- ctx.resetTransform()
54
- ctx.scale(highResScaleFactor, highResScaleFactor)
55
- ctx.clearRect(0, 0, blockSize, blockSize)
56
- ctx.translate(-offsetX, rowHeight / 2 - offsetY)
57
- ctx.textAlign = 'center'
58
- ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
59
-
60
- const leaves = hierarchy.leaves()
61
- const b = blockSize
62
-
63
- // slice vertical rows, e.g. tree leaves, avoid negative slice
64
- const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
65
- const yEnd = Math.max(0, Math.ceil((offsetY + b + rowHeight) / rowHeight))
66
-
67
- // slice horizontal visible letters, avoid negative slice
68
- const xStart = Math.max(0, Math.floor(offsetX / colWidth))
69
- const xEnd = Math.max(0, Math.ceil((offsetX + b) / colWidth))
70
- const visibleLeaves = leaves.slice(yStart, yEnd)
71
- visibleLeaves.forEach(node => {
72
- const {
73
- // @ts-expect-error
74
- x: y,
75
- data: { name },
76
- } = node
77
-
78
- const str = columns[name]?.slice(xStart, xEnd)
79
- for (let i = 0; i < str?.length; i++) {
80
- const letter = str[i]
81
- const color =
82
- colorSchemeName === 'clustalx_protein_dynamic'
83
- ? getClustalXColor(colStats[xStart + i], model, name, xStart + i)
84
- : colorSchemeName === 'percent_identity_dynamic'
85
- ? getPercentIdentityColor(
86
- colStats[xStart + i],
87
- model,
88
- name,
89
- xStart + i,
90
- )
91
- : colorScheme[letter.toUpperCase()]
92
- if (bgColor) {
93
- const x = i * colWidth + offsetX - (offsetX % colWidth)
94
- ctx.fillStyle = color || 'white'
95
- ctx.fillRect(x, y - rowHeight, colWidth, rowHeight)
96
- }
97
- }
98
- })
99
-
100
- if (rowHeight >= 10 && colWidth >= rowHeight / 2) {
101
- visibleLeaves.forEach(node => {
102
- const {
103
- // @ts-expect-error
104
- x: y,
105
- data: { name },
106
- } = node
107
-
108
- const str = columns[name]?.slice(xStart, xEnd)
109
- for (let i = 0; i < str?.length; i++) {
110
- const letter = str[i]
111
- const color = colorScheme[letter.toUpperCase()]
112
- const contrast = contrastScheme[letter.toUpperCase()] || 'black'
113
- const x = i * colWidth + offsetX - (offsetX % colWidth)
114
-
115
- // note: -rowHeight/4 matches +rowHeight/4 in tree
116
- ctx.fillStyle = bgColor ? contrast : color || 'black'
117
- ctx.fillText(letter, x + colWidth / 2, y - rowHeight / 4)
118
- }
119
- })
120
- }
121
- // eslint-disable-next-line react-hooks/exhaustive-deps
122
- }, [
123
- MSA,
124
- highResScaleFactor,
125
- columns,
126
- colorScheme,
127
- contrastScheme,
128
- bgColor,
129
- rowHeight,
130
- colWidth,
131
- hierarchy,
132
- offsetX,
133
- offsetY,
134
- blockSize,
135
- ])
136
- return (
137
- <canvas
138
- ref={ref}
139
- onMouseMove={event => {
140
- if (!ref.current) {
141
- return
142
- }
143
- const { left, top } = ref.current.getBoundingClientRect()
144
- const mouseX = event.clientX - left
145
- const mouseY = event.clientY - top
146
- model.setMousePos(
147
- Math.floor((mouseX + offsetX) / colWidth) + 1,
148
- Math.floor((mouseY + offsetY) / rowHeight),
149
- )
150
- }}
151
- onMouseLeave={() => model.setMousePos()}
152
- width={blockSize * highResScaleFactor}
153
- height={blockSize * highResScaleFactor}
154
- style={{
155
- position: 'absolute',
156
- top: scrollY + offsetY,
157
- left: scrollX + offsetX,
158
- width: blockSize,
159
- height: blockSize,
160
- }}
161
- />
162
- )
163
- })
8
+ import MSABlock from './MSABlock'
164
9
 
165
10
  const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
166
11
  const { MSA, msaFilehandle, height, msaAreaWidth, blocks2d } = model
@@ -0,0 +1,87 @@
1
+ import React, { useEffect, useRef } from 'react'
2
+ import { observer } from 'mobx-react'
3
+
4
+ // locals
5
+ import { MsaViewModel } from '../model'
6
+ import { sum } from '@jbrowse/core/util'
7
+
8
+ const MSAMouseoverCanvas = observer(function ({
9
+ model,
10
+ }: {
11
+ model: MsaViewModel
12
+ }) {
13
+ const ref = useRef<HTMLCanvasElement>(null)
14
+ const {
15
+ height,
16
+ width,
17
+ treeAreaWidth,
18
+ resizeHandleWidth,
19
+ rulerHeight,
20
+ turnedOnTracks,
21
+ scrollX,
22
+ scrollY,
23
+ mouseCol,
24
+ mouseRow,
25
+ rowHeight,
26
+ colWidth,
27
+ } = model
28
+ const totalTrackAreaHeight = sum(turnedOnTracks.map(r => r.model.height))
29
+ useEffect(() => {
30
+ if (!ref.current) {
31
+ return
32
+ }
33
+
34
+ const ctx = ref.current.getContext('2d')
35
+ if (!ctx) {
36
+ return
37
+ }
38
+
39
+ ctx.resetTransform()
40
+ ctx.clearRect(0, 0, width, height)
41
+
42
+ ctx.fillStyle = 'rgba(0,0,0,0.15)'
43
+ if (mouseCol !== undefined) {
44
+ const x =
45
+ (mouseCol - 1) * colWidth + scrollX + treeAreaWidth + resizeHandleWidth
46
+
47
+ ctx.fillRect(x, 0, colWidth, height)
48
+ }
49
+ if (mouseRow !== undefined) {
50
+ const y =
51
+ mouseRow * rowHeight + scrollY + rulerHeight + totalTrackAreaHeight
52
+ ctx.fillRect(treeAreaWidth + resizeHandleWidth, y, width, rowHeight)
53
+ }
54
+ }, [
55
+ mouseCol,
56
+ colWidth,
57
+ scrollY,
58
+ totalTrackAreaHeight,
59
+ mouseRow,
60
+ rowHeight,
61
+ rulerHeight,
62
+ scrollX,
63
+ height,
64
+ resizeHandleWidth,
65
+ treeAreaWidth,
66
+ width,
67
+ ])
68
+
69
+ return (
70
+ <canvas
71
+ ref={ref}
72
+ width={width}
73
+ height={height}
74
+ style={{
75
+ position: 'absolute',
76
+ top: 0,
77
+ left: 0,
78
+ width,
79
+ height,
80
+ zIndex: 1000,
81
+ pointerEvents: 'none',
82
+ }}
83
+ />
84
+ )
85
+ })
86
+
87
+ export default MSAMouseoverCanvas
@@ -1,4 +1,4 @@
1
- import React, { useRef, useEffect } from 'react'
1
+ import React, { lazy, Suspense } from 'react'
2
2
 
3
3
  import { observer } from 'mobx-react'
4
4
  import { Typography } from '@mui/material'
@@ -12,61 +12,13 @@ import Ruler from './Ruler'
12
12
  import TreeRuler from './TreeRuler'
13
13
  import Header from './Header'
14
14
  import Track from './Track'
15
- import AnnotationDialog from './AnnotationDlg'
16
15
 
17
16
  import { HorizontalResizeHandle, VerticalResizeHandle } from './ResizeHandles'
18
17
  import { MsaViewModel } from '../model'
18
+ import MSAMouseoverCanvas from './MSAMouseoverCanvas'
19
19
 
20
- const MouseoverCanvas = observer(function ({ model }: { model: MsaViewModel }) {
21
- const ref = useRef<HTMLCanvasElement>(null)
22
- const {
23
- height,
24
- width,
25
- treeAreaWidth,
26
- resizeHandleWidth,
27
- scrollX,
28
- mouseCol,
29
- colWidth,
30
- } = model
20
+ const AnnotationDialog = lazy(() => import('./dialogs/AnnotationDlg'))
31
21
 
32
- useEffect(() => {
33
- if (!ref.current) {
34
- return
35
- }
36
-
37
- const ctx = ref.current.getContext('2d')
38
- if (!ctx) {
39
- return
40
- }
41
-
42
- ctx.resetTransform()
43
- ctx.clearRect(0, 0, width, height)
44
-
45
- if (mouseCol !== undefined) {
46
- const x =
47
- (mouseCol - 1) * colWidth + scrollX + treeAreaWidth + resizeHandleWidth
48
- ctx.fillStyle = 'rgba(100,100,100,0.5)'
49
- ctx.fillRect(x, 0, colWidth, height)
50
- }
51
- }, [mouseCol, colWidth, scrollX, height, resizeHandleWidth, treeAreaWidth, width])
52
-
53
- return (
54
- <canvas
55
- ref={ref}
56
- width={width}
57
- height={height}
58
- style={{
59
- position: 'absolute',
60
- top: 0,
61
- left: 0,
62
- width,
63
- height,
64
- zIndex: 1000,
65
- pointerEvents: 'none',
66
- }}
67
- />
68
- )
69
- })
70
22
  export default observer(function ({ model }: { model: MsaViewModel }) {
71
23
  const { done, initialized, treeAreaWidth, height, turnedOnTracks } = model
72
24
 
@@ -102,7 +54,7 @@ export default observer(function ({ model }: { model: MsaViewModel }) {
102
54
  </div>
103
55
  <VerticalResizeHandle model={model} />
104
56
  <MSACanvas model={model} />
105
- <MouseoverCanvas model={model} />
57
+ <MSAMouseoverCanvas model={model} />
106
58
  </div>
107
59
  </div>
108
60
  </div>
@@ -112,20 +64,24 @@ export default observer(function ({ model }: { model: MsaViewModel }) {
112
64
  )}
113
65
 
114
66
  {model.DialogComponent ? (
115
- <model.DialogComponent
116
- {...(model.DialogProps || {})}
117
- onClose={() => {
118
- model.setDialogComponent(undefined, undefined)
119
- }}
120
- />
67
+ <Suspense fallback={null}>
68
+ <model.DialogComponent
69
+ {...(model.DialogProps || {})}
70
+ onClose={() => {
71
+ model.setDialogComponent(undefined, undefined)
72
+ }}
73
+ />
74
+ </Suspense>
121
75
  ) : null}
122
76
 
123
77
  {model.annotPos ? (
124
- <AnnotationDialog
125
- data={model.annotPos}
126
- model={model}
127
- onClose={() => model.clearAnnotPos()}
128
- />
78
+ <Suspense fallback={null}>
79
+ <AnnotationDialog
80
+ data={model.annotPos}
81
+ model={model}
82
+ onClose={() => model.clearAnnotPos()}
83
+ />
84
+ </Suspense>
129
85
  ) : null}
130
86
  </div>
131
87
  )