react-msaview 5.1.1 → 5.2.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 (190) hide show
  1. package/README.md +41 -8
  2. package/bundle/index.js +17 -17
  3. package/bundle/index.js.LICENSE.txt +1 -1
  4. package/bundle/index.js.map +1 -1
  5. package/dist/calculateBlocks.d.ts +4 -10
  6. package/dist/calculateBlocks.js +12 -20
  7. package/dist/calculateBlocks.js.map +1 -1
  8. package/dist/components/ConservationTrack.js +20 -37
  9. package/dist/components/ConservationTrack.js.map +1 -1
  10. package/dist/components/MSAView.js +8 -1
  11. package/dist/components/MSAView.js.map +1 -1
  12. package/dist/components/ResizeHandles.d.ts +3 -0
  13. package/dist/components/ResizeHandles.js +60 -105
  14. package/dist/components/ResizeHandles.js.map +1 -1
  15. package/dist/components/TextTrack.js +25 -34
  16. package/dist/components/TextTrack.js.map +1 -1
  17. package/dist/components/Track.js +1 -1
  18. package/dist/components/Track.js.map +1 -1
  19. package/dist/components/VerticalScrollbar.js +2 -2
  20. package/dist/components/VerticalScrollbar.js.map +1 -1
  21. package/dist/components/dialogs/ExportSVGDialog.js +20 -20
  22. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  23. package/dist/components/dialogs/InterProScanDialog.js +6 -1
  24. package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
  25. package/dist/components/dialogs/UserProvidedDomainsDialog.js +1 -2
  26. package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +1 -1
  27. package/dist/components/header/ColorSchemeMenu.js +1 -1
  28. package/dist/components/header/ColorSchemeMenu.js.map +1 -1
  29. package/dist/components/header/FileMenu.js +3 -2
  30. package/dist/components/header/FileMenu.js.map +1 -1
  31. package/dist/components/header/Header.js +1 -1
  32. package/dist/components/header/HeaderInfoArea.js +28 -8
  33. package/dist/components/header/HeaderInfoArea.js.map +1 -1
  34. package/dist/components/header/MSASettingsMenu.js +1 -1
  35. package/dist/components/header/MSASettingsMenu.js.map +1 -1
  36. package/dist/components/header/TreeSettingsMenu.js +1 -1
  37. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  38. package/dist/components/header/ZoomControls.js +7 -2
  39. package/dist/components/header/ZoomControls.js.map +1 -1
  40. package/dist/components/header/ZoomMenu.js +9 -0
  41. package/dist/components/header/ZoomMenu.js.map +1 -1
  42. package/dist/components/header/settingsMenuItems.js +24 -8
  43. package/dist/components/header/settingsMenuItems.js.map +1 -1
  44. package/dist/components/minimap/Minimap.js +6 -17
  45. package/dist/components/minimap/Minimap.js.map +1 -1
  46. package/dist/components/minimap/MinimapSVG.js +7 -17
  47. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  48. package/dist/components/minimap/minimapLayout.d.ts +9 -0
  49. package/dist/components/minimap/minimapLayout.js +15 -0
  50. package/dist/components/minimap/minimapLayout.js.map +1 -0
  51. package/dist/components/msa/MSACanvas.js +6 -1
  52. package/dist/components/msa/MSACanvas.js.map +1 -1
  53. package/dist/components/msa/MSACanvasBlock.js +5 -1
  54. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  55. package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
  56. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  57. package/dist/components/msa/getVisibleLeaves.d.ts +6 -0
  58. package/dist/components/msa/getVisibleLeaves.js +9 -0
  59. package/dist/components/msa/getVisibleLeaves.js.map +1 -0
  60. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +2 -1
  61. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +5 -6
  62. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  63. package/dist/components/msa/renderMSABlock.d.ts +2 -1
  64. package/dist/components/msa/renderMSABlock.js +100 -88
  65. package/dist/components/msa/renderMSABlock.js.map +1 -1
  66. package/dist/components/msa/renderMSAMouseover.js +5 -4
  67. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  68. package/dist/components/msa/visibleColRange.d.ts +8 -0
  69. package/dist/components/msa/visibleColRange.js +10 -0
  70. package/dist/components/msa/visibleColRange.js.map +1 -0
  71. package/dist/components/renderCtx.d.ts +7 -0
  72. package/dist/components/renderCtx.js +2 -0
  73. package/dist/components/renderCtx.js.map +1 -0
  74. package/dist/components/tracks/renderTracksSvg.d.ts +6 -5
  75. package/dist/components/tracks/renderTracksSvg.js +13 -5
  76. package/dist/components/tracks/renderTracksSvg.js.map +1 -1
  77. package/dist/components/tree/TreeCanvas.js.map +1 -1
  78. package/dist/components/tree/TreeCanvasBlock.js +2 -10
  79. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  80. package/dist/components/tree/renderTreeCanvas.d.ts +5 -4
  81. package/dist/components/tree/renderTreeCanvas.js +18 -26
  82. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  83. package/dist/components/util.d.ts +0 -14
  84. package/dist/components/util.js +0 -58
  85. package/dist/components/util.js.map +1 -1
  86. package/dist/constants.d.ts +4 -0
  87. package/dist/constants.js +7 -0
  88. package/dist/constants.js.map +1 -1
  89. package/dist/flatToTree.js +6 -0
  90. package/dist/flatToTree.js.map +1 -1
  91. package/dist/hierarchy.d.ts +3 -1
  92. package/dist/hierarchy.js +142 -89
  93. package/dist/hierarchy.js.map +1 -1
  94. package/dist/index.d.ts +1 -0
  95. package/dist/index.js +8 -0
  96. package/dist/index.js.map +1 -1
  97. package/dist/launchInterProScan.js +22 -9
  98. package/dist/launchInterProScan.js.map +1 -1
  99. package/dist/measureTextCanvas.js +2 -1
  100. package/dist/measureTextCanvas.js.map +1 -1
  101. package/dist/model.d.ts +234 -176
  102. package/dist/model.js +285 -259
  103. package/dist/model.js.map +1 -1
  104. package/dist/parseAsn1.js +8 -2
  105. package/dist/parseAsn1.js.map +1 -1
  106. package/dist/renderToSvg.d.ts +1 -1
  107. package/dist/renderToSvg.js +43 -46
  108. package/dist/renderToSvg.js.map +1 -1
  109. package/dist/rowCoordinateCalculations.d.ts +0 -9
  110. package/dist/rowCoordinateCalculations.js +0 -20
  111. package/dist/rowCoordinateCalculations.js.map +1 -1
  112. package/dist/setFontSize.d.ts +3 -0
  113. package/dist/setFontSize.js +14 -0
  114. package/dist/setFontSize.js.map +1 -0
  115. package/dist/useDevicePixelRatio.d.ts +1 -0
  116. package/dist/useDevicePixelRatio.js +27 -0
  117. package/dist/useDevicePixelRatio.js.map +1 -0
  118. package/dist/useWheelScroll.d.ts +3 -1
  119. package/dist/useWheelScroll.js +64 -22
  120. package/dist/useWheelScroll.js.map +1 -1
  121. package/dist/util.d.ts +5 -0
  122. package/dist/util.js +59 -1
  123. package/dist/util.js.map +1 -1
  124. package/dist/version.d.ts +1 -1
  125. package/dist/version.js +1 -1
  126. package/dist/webpack.js +0 -1
  127. package/dist/webpack.js.map +1 -1
  128. package/dist/wheelZoom.d.ts +4 -0
  129. package/dist/wheelZoom.js +43 -0
  130. package/dist/wheelZoom.js.map +1 -0
  131. package/package.json +5 -5
  132. package/src/calculateBlocks.ts +18 -51
  133. package/src/components/ConservationTrack.tsx +20 -45
  134. package/src/components/MSAView.tsx +10 -1
  135. package/src/components/ResizeHandles.tsx +101 -113
  136. package/src/components/TextTrack.tsx +31 -38
  137. package/src/components/Track.tsx +1 -1
  138. package/src/components/VerticalScrollbar.tsx +2 -2
  139. package/src/components/dialogs/ExportSVGDialog.tsx +27 -17
  140. package/src/components/dialogs/InterProScanDialog.tsx +6 -1
  141. package/src/components/dialogs/UserProvidedDomainsDialog.tsx +1 -2
  142. package/src/components/header/ColorSchemeMenu.tsx +1 -0
  143. package/src/components/header/FileMenu.tsx +3 -1
  144. package/src/components/header/Header.tsx +1 -1
  145. package/src/components/header/HeaderInfoArea.tsx +32 -7
  146. package/src/components/header/MSASettingsMenu.tsx +1 -0
  147. package/src/components/header/TreeSettingsMenu.tsx +1 -0
  148. package/src/components/header/ZoomControls.tsx +14 -1
  149. package/src/components/header/ZoomMenu.tsx +10 -0
  150. package/src/components/header/settingsMenuItems.ts +24 -8
  151. package/src/components/minimap/Minimap.tsx +6 -20
  152. package/src/components/minimap/MinimapSVG.tsx +10 -27
  153. package/src/components/minimap/minimapLayout.ts +17 -0
  154. package/src/components/msa/MSACanvas.tsx +9 -0
  155. package/src/components/msa/MSACanvasBlock.tsx +7 -1
  156. package/src/components/msa/MSAMouseoverCanvas.tsx +4 -3
  157. package/src/components/msa/getVisibleLeaves.ts +21 -0
  158. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +9 -9
  159. package/src/components/msa/renderMSABlock.ts +123 -125
  160. package/src/components/msa/renderMSAMouseover.ts +5 -4
  161. package/src/components/msa/visibleColRange.ts +17 -0
  162. package/src/components/renderCtx.ts +29 -0
  163. package/src/components/tracks/renderTracksSvg.ts +20 -10
  164. package/src/components/tree/TreeCanvas.tsx +3 -1
  165. package/src/components/tree/TreeCanvasBlock.tsx +2 -12
  166. package/src/components/tree/renderTreeCanvas.ts +27 -30
  167. package/src/components/util.ts +0 -81
  168. package/src/constants.ts +8 -0
  169. package/src/flatToTree.test.ts +30 -0
  170. package/src/flatToTree.ts +10 -1
  171. package/src/hierarchy.test.ts +53 -1
  172. package/src/hierarchy.ts +153 -100
  173. package/src/index.ts +8 -0
  174. package/src/launchInterProScan.ts +33 -11
  175. package/src/measureTextCanvas.ts +3 -1
  176. package/src/model.ts +359 -306
  177. package/src/parseAsn1.test.ts +6 -0
  178. package/src/parseAsn1.ts +11 -3
  179. package/src/renderToSvg.tsx +99 -95
  180. package/src/rowCoordinateCalculations.test.ts +0 -51
  181. package/src/rowCoordinateCalculations.ts +0 -23
  182. package/src/setFontSize.test.ts +33 -0
  183. package/src/setFontSize.ts +20 -0
  184. package/src/useDevicePixelRatio.ts +29 -0
  185. package/src/useWheelScroll.ts +79 -21
  186. package/src/util.ts +60 -1
  187. package/src/version.ts +1 -1
  188. package/src/webpack.ts +0 -1
  189. package/src/wheelZoom.test.ts +46 -0
  190. package/src/wheelZoom.ts +47 -0
@@ -1,6 +1,12 @@
1
+ import { getVisibleLeaves } from './getVisibleLeaves.ts'
2
+ import { visibleColRange } from './visibleColRange.ts'
3
+ import { setFontSize } from '../../setFontSize.ts'
4
+ import { adjustColorForContrast } from '../../util.ts'
5
+
1
6
  import type { HierarchyNode } from '../../hierarchy.ts'
2
7
  import type { MsaViewModel } from '../../model.ts'
3
8
  import type { NodeWithIdsAndLength } from '../../types.ts'
9
+ import type { RenderCtx } from '../renderCtx.ts'
4
10
  import type { Theme } from '@mui/material'
5
11
 
6
12
  export function renderMSABlock({
@@ -19,7 +25,7 @@ export function renderMSABlock({
19
25
  theme: Theme
20
26
  model: MsaViewModel
21
27
  contrastScheme: Record<string, string>
22
- ctx: CanvasRenderingContext2D
28
+ ctx: RenderCtx
23
29
  highResScaleFactorOverride?: number
24
30
  blockSizeXOverride?: number
25
31
  blockSizeYOverride?: number
@@ -31,50 +37,51 @@ export function renderMSABlock({
31
37
  fontSize,
32
38
  highResScaleFactor,
33
39
  actuallyShowDomains,
34
- leaves,
35
40
  bgColor,
36
41
  } = model
37
- const k = highResScaleFactorOverride || highResScaleFactor
38
- const bx = blockSizeXOverride || blockSize
39
- const by = blockSizeYOverride || blockSize
42
+ const k = highResScaleFactorOverride ?? highResScaleFactor
43
+ const bx = blockSizeXOverride ?? blockSize
44
+ const by = blockSizeYOverride ?? blockSize
40
45
  ctx.resetTransform()
41
46
  ctx.scale(k, k)
42
47
  ctx.translate(-offsetX, rowHeight / 2 - offsetY)
43
48
  ctx.textAlign = 'center'
44
- // match an optional existing "bold " so re-renders don't accumulate it
45
- // (an invalid "bold bold 16px" string would be silently ignored by canvas,
46
- // freezing the font size against zoom changes)
47
- ctx.font = ctx.font.replace(
48
- /(?:bold )?\d+px/,
49
- `${bgColor ? '' : 'bold '}${fontSize}px`,
50
- )
49
+ setFontSize(ctx, fontSize, !bgColor)
51
50
 
52
- const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
53
- const yEnd = Math.max(0, Math.ceil((offsetY + by + rowHeight) / rowHeight))
54
- const xStart = Math.max(0, Math.floor(offsetX / colWidth))
55
- const xEnd = Math.max(0, Math.ceil((offsetX + bx) / colWidth))
56
- const visibleLeaves = leaves.slice(yStart, yEnd)
51
+ const { xStart, xEnd } = visibleColRange({
52
+ offsetX,
53
+ blockWidth: bx,
54
+ colWidth,
55
+ })
56
+ const visibleLeaves = getVisibleLeaves({ model, offsetY, blockSizeY: by })
57
+ const { relativeTo, columns } = model
58
+ const referenceSeq = relativeTo
59
+ ? columns.get(relativeTo)?.slice(xStart, xEnd)
60
+ : null
57
61
 
58
62
  if (!actuallyShowDomains) {
59
- drawTiles({
63
+ drawTilesAndText({
60
64
  model,
61
65
  ctx,
62
66
  theme,
67
+ contrastScheme,
63
68
  offsetX,
64
69
  xStart,
65
70
  xEnd,
66
71
  visibleLeaves,
72
+ referenceSeq,
73
+ })
74
+ } else {
75
+ drawText({
76
+ model,
77
+ ctx,
78
+ offsetX,
79
+ xStart,
80
+ xEnd,
81
+ visibleLeaves,
82
+ referenceSeq,
67
83
  })
68
84
  }
69
- drawText({
70
- model,
71
- ctx,
72
- offsetX,
73
- contrastScheme,
74
- xStart,
75
- xEnd,
76
- visibleLeaves,
77
- })
78
85
  drawInsertionIndicators({
79
86
  model,
80
87
  ctx,
@@ -85,22 +92,26 @@ export function renderMSABlock({
85
92
  ctx.resetTransform()
86
93
  }
87
94
 
88
- function drawTiles({
95
+ function drawTilesAndText({
89
96
  model,
90
97
  offsetX,
91
98
  ctx,
92
- visibleLeaves,
93
99
  theme,
100
+ contrastScheme,
101
+ visibleLeaves,
94
102
  xStart,
95
103
  xEnd,
104
+ referenceSeq,
96
105
  }: {
97
106
  model: MsaViewModel
98
107
  offsetX: number
99
108
  theme: Theme
100
- ctx: CanvasRenderingContext2D
109
+ ctx: RenderCtx
110
+ contrastScheme: Record<string, string>
101
111
  visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
102
112
  xStart: number
103
113
  xEnd: number
114
+ referenceSeq: string | null | undefined
104
115
  }) {
105
116
  const {
106
117
  bgColor,
@@ -110,131 +121,118 @@ function drawTiles({
110
121
  colWidth,
111
122
  rowHeight,
112
123
  relativeTo,
124
+ showMsaLetters,
125
+ colClustalX,
126
+ colConsensus,
113
127
  } = model
114
128
 
115
- // Get reference sequence if relativeTo is set
116
- const referenceSeq = relativeTo
117
- ? columns[relativeTo]?.slice(xStart, xEnd)
118
- : null
119
-
120
129
  const isClustalX = colorSchemeName === 'clustalx_protein_dynamic'
121
130
  const isPercentIdentity = colorSchemeName === 'percent_identity_dynamic'
131
+ const drawBgTiles = bgColor || isClustalX || isPercentIdentity
132
+ if (!drawBgTiles && !showMsaLetters) {
133
+ return
134
+ }
122
135
  const offsetXAligned = offsetX - (offsetX % colWidth)
136
+ const halfColWidth = colWidth / 2
137
+ const quarterRowHeight = rowHeight / 4
123
138
 
124
139
  for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
125
140
  const node = visibleLeaves[i]!
126
- const {
127
- data: { name },
128
- } = node
141
+ const { name } = node.data
129
142
  const y = node.x!
130
- const str = columns[name]?.slice(xStart, xEnd)
131
- if (str) {
132
- for (let j = 0, l2 = str.length; j < l2; j++) {
133
- const letter = str[j]!
143
+ const str = columns.get(name)?.slice(xStart, xEnd)
144
+ if (!str) {
145
+ continue
146
+ }
147
+ const tileY = y - rowHeight
148
+ const textY = y - quarterRowHeight
134
149
 
135
- // Use a muted background for positions that match reference
136
- const isMatchingReference =
137
- referenceSeq && name !== relativeTo && letter === referenceSeq[j]
150
+ for (let j = 0, l2 = str.length; j < l2; j++) {
151
+ const letter = str[j]!
152
+ const x = j * colWidth + offsetXAligned
153
+ const isMatchingReference =
154
+ referenceSeq && name !== relativeTo && letter === referenceSeq[j]
138
155
 
139
- let color: string | undefined
140
- if (isClustalX) {
141
- color = model.colClustalX[xStart + j]![letter]
142
- } else if (isPercentIdentity) {
143
- const consensus = model.colConsensus[xStart + j]!
144
- color = letter === consensus.letter ? consensus.color : undefined
145
- } else {
146
- color = colorScheme[letter.toUpperCase()]
147
- }
148
- if (bgColor || isClustalX || isPercentIdentity) {
149
- // Use a very light background for matching positions in relative mode
150
- const finalColor = isMatchingReference
151
- ? theme.palette.action.hover
152
- : color || theme.palette.background.default
153
- ctx.fillStyle = finalColor
154
- ctx.fillRect(
155
- j * colWidth + offsetXAligned,
156
- y - rowHeight,
157
- colWidth,
158
- rowHeight,
159
- )
160
- }
156
+ let color: string | undefined
157
+ if (isClustalX) {
158
+ color = colClustalX[xStart + j]![letter]
159
+ } else if (isPercentIdentity) {
160
+ const consensus = colConsensus[xStart + j]!
161
+ color = letter === consensus.letter ? consensus.color : undefined
162
+ } else {
163
+ color = colorScheme[letter]
164
+ }
165
+
166
+ if (drawBgTiles) {
167
+ ctx.fillStyle = isMatchingReference
168
+ ? theme.palette.action.hover
169
+ : color || theme.palette.background.default
170
+ ctx.fillRect(x, tileY, colWidth, rowHeight)
171
+ }
172
+
173
+ if (showMsaLetters) {
174
+ ctx.fillStyle = bgColor
175
+ ? (contrastScheme[letter] ?? 'black')
176
+ : color
177
+ ? adjustColorForContrast(color, theme.palette.background.default)
178
+ : theme.palette.text.primary
179
+ ctx.fillText(
180
+ isMatchingReference ? '.' : letter,
181
+ x + halfColWidth,
182
+ textY,
183
+ )
161
184
  }
162
185
  }
163
186
  }
164
187
  }
165
188
 
189
+ // When domains are shown the background tiles come from renderBoxFeatureCanvasBlock,
190
+ // so we only need to draw text here.
166
191
  function drawText({
167
192
  model,
168
193
  offsetX,
169
- contrastScheme,
170
194
  ctx,
171
195
  visibleLeaves,
172
196
  xStart,
173
197
  xEnd,
198
+ referenceSeq,
174
199
  }: {
175
200
  offsetX: number
176
201
  model: MsaViewModel
177
- contrastScheme: Record<string, string>
178
- ctx: CanvasRenderingContext2D
202
+ ctx: RenderCtx
179
203
  visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
180
204
  xStart: number
181
205
  xEnd: number
206
+ referenceSeq: string | null | undefined
182
207
  }) {
183
- const {
184
- bgColor,
185
- actuallyShowDomains,
186
- showMsaLetters,
187
- colorScheme,
188
- columns,
189
- colWidth,
190
- rowHeight,
191
- relativeTo,
192
- } = model
208
+ const { showMsaLetters, columns, colWidth, rowHeight, relativeTo } = model
193
209
 
194
- // Get reference sequence if relativeTo is set
195
- const referenceSeq = relativeTo
196
- ? columns[relativeTo]?.slice(xStart, xEnd)
197
- : null
198
-
199
- if (showMsaLetters) {
200
- const offsetXAligned = offsetX - (offsetX % colWidth)
201
- const halfColWidth = colWidth / 2
202
- const quarterRowHeight = rowHeight / 4
203
-
204
- for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
205
- const node = visibleLeaves[i]!
206
- const {
207
- data: { name },
208
- } = node
209
- const y = node.x! - quarterRowHeight
210
- const str = columns[name]?.slice(xStart, xEnd)
211
- if (str) {
212
- for (let j = 0, l2 = str.length; j < l2; j++) {
213
- const letter = str[j]!
214
-
215
- // Check if this position matches the reference
216
- const isMatchingReference =
217
- referenceSeq && name !== relativeTo && letter === referenceSeq[j]
218
-
219
- // Show dot for matching positions, original letter for differences
220
- const displayLetter = isMatchingReference ? '.' : letter
221
-
222
- const color = colorScheme[letter.toUpperCase()]
223
- const contrast = contrastScheme[letter.toUpperCase()] || 'black'
210
+ if (!showMsaLetters) {
211
+ return
212
+ }
213
+ const offsetXAligned = offsetX - (offsetX % colWidth)
214
+ const halfColWidth = colWidth / 2
215
+ const quarterRowHeight = rowHeight / 4
216
+ // note: -rowHeight/4 matches +rowHeight/4 in tree
217
+ ctx.fillStyle = 'black'
224
218
 
225
- // note: -rowHeight/4 matches +rowHeight/4 in tree
226
- ctx.fillStyle = actuallyShowDomains
227
- ? 'black'
228
- : bgColor
229
- ? contrast
230
- : color || 'black'
231
- ctx.fillText(
232
- displayLetter,
233
- j * colWidth + offsetXAligned + halfColWidth,
234
- y,
235
- )
236
- }
237
- }
219
+ for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
220
+ const node = visibleLeaves[i]!
221
+ const { name } = node.data
222
+ const y = node.x! - quarterRowHeight
223
+ const str = columns.get(name)?.slice(xStart, xEnd)
224
+ if (!str) {
225
+ continue
226
+ }
227
+ for (let j = 0, l2 = str.length; j < l2; j++) {
228
+ const letter = str[j]!
229
+ const isMatchingReference =
230
+ referenceSeq && name !== relativeTo && letter === referenceSeq[j]
231
+ ctx.fillText(
232
+ isMatchingReference ? '.' : letter,
233
+ j * colWidth + offsetXAligned + halfColWidth,
234
+ y,
235
+ )
238
236
  }
239
237
  }
240
238
  }
@@ -247,7 +245,7 @@ function drawInsertionIndicators({
247
245
  xEnd,
248
246
  }: {
249
247
  model: MsaViewModel
250
- ctx: CanvasRenderingContext2D
248
+ ctx: RenderCtx
251
249
  visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
252
250
  xStart: number
253
251
  xEnd: number
@@ -273,7 +271,7 @@ function drawZigZag({
273
271
  offset,
274
272
  }: {
275
273
  model: MsaViewModel
276
- ctx: CanvasRenderingContext2D
274
+ ctx: RenderCtx
277
275
  visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
278
276
  xStart: number
279
277
  xEnd: number
@@ -25,17 +25,18 @@ export function renderMouseover({
25
25
  mouseClickRow,
26
26
  mouseClickCol,
27
27
  relativeTo,
28
- rowNamesSet,
29
28
  hoveredTreeNode,
30
29
  highlightedColumns,
30
+ highResScaleFactor,
31
31
  } = model
32
32
  const width = msaAreaWidth - verticalScrollbarWidth
33
33
  ctx.resetTransform()
34
- ctx.clearRect(0, 0, width, height)
34
+ ctx.clearRect(0, 0, width * highResScaleFactor, height * highResScaleFactor)
35
+ ctx.scale(highResScaleFactor, highResScaleFactor)
35
36
 
36
37
  // Highlight reference row (relativeTo) persistently
37
38
  if (relativeTo) {
38
- const referenceRowIndex = rowNamesSet.get(relativeTo)
39
+ const referenceRowIndex = model.rowNamesSet.get(relativeTo)
39
40
  if (referenceRowIndex !== undefined) {
40
41
  ctx.fillStyle = referenceColor
41
42
  ctx.fillRect(0, referenceRowIndex * rowHeight + scrollY, width, rowHeight)
@@ -46,7 +47,7 @@ export function renderMouseover({
46
47
  if (hoveredTreeNode) {
47
48
  ctx.fillStyle = multiRowHoverColor
48
49
  for (const descendantName of hoveredTreeNode.descendantNames) {
49
- const rowIndex = rowNamesSet.get(descendantName)
50
+ const rowIndex = model.rowNamesSet.get(descendantName)
50
51
  if (rowIndex !== undefined) {
51
52
  ctx.fillRect(0, rowIndex * rowHeight + scrollY, width, rowHeight)
52
53
  }
@@ -0,0 +1,17 @@
1
+ // The half-open range of alignment columns [xStart, xEnd) intersecting a block
2
+ // of width blockWidth at horizontal offset offsetX. X-axis counterpart to
3
+ // getVisibleLeaves.
4
+ export function visibleColRange({
5
+ offsetX,
6
+ blockWidth,
7
+ colWidth,
8
+ }: {
9
+ offsetX: number
10
+ blockWidth: number
11
+ colWidth: number
12
+ }) {
13
+ return {
14
+ xStart: Math.max(0, Math.floor(offsetX / colWidth)),
15
+ xEnd: Math.max(0, Math.ceil((offsetX + blockWidth) / colWidth)),
16
+ }
17
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The subset of the canvas 2D API that the block renderers use. Both a real
3
+ * `CanvasRenderingContext2D` and the `@jbrowse/svgcanvas` `Context` (used for
4
+ * SVG export) satisfy this, so renderers can be typed against it without an
5
+ * `as any` cast at the SVG-export boundary.
6
+ */
7
+ export type RenderCtx = Pick<
8
+ CanvasRenderingContext2D,
9
+ | 'arc'
10
+ | 'beginPath'
11
+ | 'clearRect'
12
+ | 'fill'
13
+ | 'fillRect'
14
+ | 'fillStyle'
15
+ | 'fillText'
16
+ | 'font'
17
+ | 'lineTo'
18
+ | 'lineWidth'
19
+ | 'measureText'
20
+ | 'moveTo'
21
+ | 'resetTransform'
22
+ | 'scale'
23
+ | 'setLineDash'
24
+ | 'stroke'
25
+ | 'strokeRect'
26
+ | 'strokeStyle'
27
+ | 'textAlign'
28
+ | 'translate'
29
+ >
@@ -1,5 +1,9 @@
1
+ import { setFontSize } from '../../setFontSize.ts'
2
+ import { visibleColRange } from '../msa/visibleColRange.ts'
3
+
1
4
  import type { MsaViewModel } from '../../model.ts'
2
5
  import type { BasicTrack } from '../../types.ts'
6
+ import type { RenderCtx } from '../renderCtx.ts'
3
7
 
4
8
  export function drawConservationBars({
5
9
  ctx,
@@ -9,15 +13,18 @@ export function drawConservationBars({
9
13
  offsetX,
10
14
  blockSize,
11
15
  }: {
12
- ctx: CanvasRenderingContext2D
16
+ ctx: RenderCtx
13
17
  conservation: number[]
14
18
  colWidth: number
15
19
  trackHeight: number
16
20
  offsetX: number
17
21
  blockSize: number
18
22
  }) {
19
- const xStart = Math.max(0, Math.floor(offsetX / colWidth))
20
- const xEnd = Math.max(0, Math.ceil((offsetX + blockSize) / colWidth))
23
+ const { xStart, xEnd } = visibleColRange({
24
+ offsetX,
25
+ blockWidth: blockSize,
26
+ colWidth,
27
+ })
21
28
 
22
29
  ctx.fillStyle = 'gray'
23
30
  for (let i = xStart; i < xEnd && i < conservation.length; i++) {
@@ -39,7 +46,7 @@ export function drawTextTrackContent({
39
46
  offsetX,
40
47
  blockSize,
41
48
  }: {
42
- ctx: CanvasRenderingContext2D
49
+ ctx: RenderCtx
43
50
  data: string | undefined
44
51
  colorScheme: Record<string, string>
45
52
  contrastScheme: Record<string, string>
@@ -49,8 +56,11 @@ export function drawTextTrackContent({
49
56
  offsetX: number
50
57
  blockSize: number
51
58
  }) {
52
- const xStart = Math.max(0, Math.floor(offsetX / colWidth))
53
- const xEnd = Math.max(0, Math.ceil((offsetX + blockSize) / colWidth))
59
+ const { xStart, xEnd } = visibleColRange({
60
+ offsetX,
61
+ blockWidth: blockSize,
62
+ colWidth,
63
+ })
54
64
  const str = data?.slice(xStart, xEnd)
55
65
 
56
66
  for (let i = 0; str && i < str.length; i++) {
@@ -78,7 +88,7 @@ export function renderConservationTrack({
78
88
  highResScaleFactorOverride,
79
89
  }: {
80
90
  model: MsaViewModel
81
- ctx: CanvasRenderingContext2D
91
+ ctx: RenderCtx
82
92
  offsetX: number
83
93
  offsetY: number
84
94
  trackHeight: number
@@ -116,7 +126,7 @@ export function renderTextTrack({
116
126
  highResScaleFactorOverride,
117
127
  }: {
118
128
  model: MsaViewModel
119
- ctx: CanvasRenderingContext2D
129
+ ctx: RenderCtx
120
130
  track: BasicTrack
121
131
  offsetX: number
122
132
  offsetY: number
@@ -143,7 +153,7 @@ export function renderTextTrack({
143
153
  ctx.scale(k, k)
144
154
  ctx.translate(-offsetX, offsetY)
145
155
  ctx.textAlign = 'center'
146
- ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
156
+ setFontSize(ctx, fontSize)
147
157
 
148
158
  drawTextTrackContent({
149
159
  ctx,
@@ -169,7 +179,7 @@ export function renderAllTracks({
169
179
  highResScaleFactorOverride,
170
180
  }: {
171
181
  model: MsaViewModel
172
- ctx: CanvasRenderingContext2D
182
+ ctx: RenderCtx
173
183
  offsetX: number
174
184
  contrastScheme: Record<string, string>
175
185
  blockSizeXOverride?: number
@@ -43,7 +43,9 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
43
43
  ctx.resetTransform()
44
44
  ctx.clearRect(0, 0, w, h)
45
45
 
46
- const leafByName = new Map(leaves.map(leaf => [leaf.data.name, leaf]))
46
+ const leafByName = new Map(
47
+ leaves.map(leaf => [leaf.data.name, leaf]),
48
+ )
47
49
  const fillRow = (name: string) => {
48
50
  const leaf = leafByName.get(name)
49
51
  if (leaf) {
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useRef, useState } from 'react'
1
+ import React, { useEffect, useRef, useState } from 'react'
2
2
 
3
3
  import { useTheme } from '@mui/material'
4
4
  import Flatbush from 'flatbush'
@@ -62,7 +62,6 @@ class ClickMapIndex {
62
62
  finish() {
63
63
  if (this.entries.length === 0) {
64
64
  this.flatbush = null
65
- return
66
65
  } else {
67
66
  this.flatbush = new Flatbush(this.entries.length)
68
67
  for (const entry of this.entries) {
@@ -114,15 +113,6 @@ const TreeCanvasBlock = observer(function ({
114
113
  const w2 = width * highResScaleFactor
115
114
  const h2 = height * highResScaleFactor
116
115
 
117
- // biome-ignore lint/correctness/useExhaustiveDependencies:
118
- const vref = useCallback(
119
- (arg: HTMLCanvasElement) => {
120
- model.incrementRef()
121
- ref.current = arg
122
- },
123
- // eslint-disable-next-line react-hooks/exhaustive-deps
124
- [model, height, width],
125
- )
126
116
  useEffect(() => {
127
117
  const ctx = ref.current?.getContext('2d')
128
118
  if (!ctx) {
@@ -289,7 +279,7 @@ const TreeCanvasBlock = observer(function ({
289
279
  model.setHoveredTreeNode(undefined)
290
280
  model.setMousePos(undefined, undefined)
291
281
  }}
292
- ref={vref}
282
+ ref={ref}
293
283
  />
294
284
  <canvas
295
285
  style={{