react-msaview 5.1.0 → 5.2.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 (100) hide show
  1. package/bundle/index.js +35 -135
  2. package/bundle/index.js.map +1 -1
  3. package/dist/calculateBlocks.d.ts +4 -10
  4. package/dist/calculateBlocks.js +12 -20
  5. package/dist/calculateBlocks.js.map +1 -1
  6. package/dist/components/ConservationTrack.js +18 -24
  7. package/dist/components/ConservationTrack.js.map +1 -1
  8. package/dist/components/TextTrack.js +24 -34
  9. package/dist/components/TextTrack.js.map +1 -1
  10. package/dist/components/dialogs/ExportSVGDialog.js +20 -20
  11. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  12. package/dist/components/dialogs/InterProScanDialog.js +6 -1
  13. package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
  14. package/dist/components/header/FileMenu.js +2 -1
  15. package/dist/components/header/FileMenu.js.map +1 -1
  16. package/dist/components/header/Header.js +1 -1
  17. package/dist/components/header/settingsMenuItems.js +24 -8
  18. package/dist/components/header/settingsMenuItems.js.map +1 -1
  19. package/dist/components/minimap/Minimap.js +1 -6
  20. package/dist/components/minimap/Minimap.js.map +1 -1
  21. package/dist/components/minimap/MinimapSVG.js +4 -8
  22. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  23. package/dist/components/msa/getVisibleLeaves.d.ts +6 -0
  24. package/dist/components/msa/getVisibleLeaves.js +9 -0
  25. package/dist/components/msa/getVisibleLeaves.js.map +1 -0
  26. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +2 -1
  27. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +5 -6
  28. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  29. package/dist/components/msa/renderMSABlock.d.ts +2 -1
  30. package/dist/components/msa/renderMSABlock.js +98 -84
  31. package/dist/components/msa/renderMSABlock.js.map +1 -1
  32. package/dist/components/msa/renderMSAMouseover.js +3 -3
  33. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  34. package/dist/components/msa/visibleColRange.d.ts +8 -0
  35. package/dist/components/msa/visibleColRange.js +10 -0
  36. package/dist/components/msa/visibleColRange.js.map +1 -0
  37. package/dist/components/renderCtx.d.ts +7 -0
  38. package/dist/components/renderCtx.js +2 -0
  39. package/dist/components/renderCtx.js.map +1 -0
  40. package/dist/components/tracks/renderTracksSvg.d.ts +6 -5
  41. package/dist/components/tracks/renderTracksSvg.js +11 -4
  42. package/dist/components/tracks/renderTracksSvg.js.map +1 -1
  43. package/dist/components/tree/TreeCanvas.js.map +1 -1
  44. package/dist/components/tree/TreeCanvasBlock.js +2 -10
  45. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  46. package/dist/components/tree/renderTreeCanvas.d.ts +5 -4
  47. package/dist/components/tree/renderTreeCanvas.js +16 -24
  48. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  49. package/dist/components/util.d.ts +0 -14
  50. package/dist/components/util.js +0 -58
  51. package/dist/components/util.js.map +1 -1
  52. package/dist/hierarchy.d.ts +2 -0
  53. package/dist/hierarchy.js +19 -0
  54. package/dist/hierarchy.js.map +1 -1
  55. package/dist/index.js +7 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/model.d.ts +26 -30
  58. package/dist/model.js +74 -82
  59. package/dist/model.js.map +1 -1
  60. package/dist/renderToSvg.d.ts +1 -1
  61. package/dist/renderToSvg.js +20 -26
  62. package/dist/renderToSvg.js.map +1 -1
  63. package/dist/rowCoordinateCalculations.d.ts +0 -9
  64. package/dist/rowCoordinateCalculations.js +0 -20
  65. package/dist/rowCoordinateCalculations.js.map +1 -1
  66. package/dist/util.d.ts +1 -0
  67. package/dist/util.js +23 -1
  68. package/dist/util.js.map +1 -1
  69. package/dist/version.d.ts +1 -1
  70. package/dist/version.js +1 -1
  71. package/package.json +5 -5
  72. package/src/calculateBlocks.ts +18 -51
  73. package/src/components/ConservationTrack.tsx +18 -27
  74. package/src/components/TextTrack.tsx +30 -38
  75. package/src/components/dialogs/ExportSVGDialog.tsx +27 -17
  76. package/src/components/dialogs/InterProScanDialog.tsx +6 -1
  77. package/src/components/header/FileMenu.tsx +2 -1
  78. package/src/components/header/Header.tsx +1 -1
  79. package/src/components/header/settingsMenuItems.ts +24 -8
  80. package/src/components/minimap/Minimap.tsx +1 -6
  81. package/src/components/minimap/MinimapSVG.tsx +4 -8
  82. package/src/components/msa/getVisibleLeaves.ts +21 -0
  83. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +9 -9
  84. package/src/components/msa/renderMSABlock.ts +119 -118
  85. package/src/components/msa/renderMSAMouseover.ts +2 -3
  86. package/src/components/msa/visibleColRange.ts +17 -0
  87. package/src/components/renderCtx.ts +29 -0
  88. package/src/components/tracks/renderTracksSvg.ts +18 -9
  89. package/src/components/tree/TreeCanvas.tsx +3 -1
  90. package/src/components/tree/TreeCanvasBlock.tsx +2 -12
  91. package/src/components/tree/renderTreeCanvas.ts +25 -28
  92. package/src/components/util.ts +0 -81
  93. package/src/hierarchy.ts +27 -0
  94. package/src/index.ts +7 -0
  95. package/src/model.ts +81 -98
  96. package/src/renderToSvg.tsx +23 -31
  97. package/src/rowCoordinateCalculations.test.ts +0 -51
  98. package/src/rowCoordinateCalculations.ts +0 -23
  99. package/src/util.ts +25 -1
  100. package/src/version.ts +1 -1
@@ -1,6 +1,9 @@
1
+ import { getVisibleLeaves } from './getVisibleLeaves.ts'
2
+
1
3
  import type { HierarchyNode } from '../../hierarchy.ts'
2
4
  import type { MsaViewModel } from '../../model.ts'
3
5
  import type { NodeWithIdsAndLength } from '../../types.ts'
6
+ import type { RenderCtx } from '../renderCtx.ts'
4
7
 
5
8
  export function renderBoxFeatureCanvasBlock({
6
9
  model,
@@ -13,22 +16,19 @@ export function renderBoxFeatureCanvasBlock({
13
16
  offsetX: number
14
17
  offsetY: number
15
18
  model: MsaViewModel
16
- ctx: CanvasRenderingContext2D
19
+ ctx: RenderCtx
17
20
  highResScaleFactorOverride?: number
18
21
  blockSizeYOverride?: number
19
22
  }) {
20
- const { leaves, blockSize, rowHeight, highResScaleFactor, showDomains } =
21
- model
23
+ const { blockSize, rowHeight, highResScaleFactor, showDomains } = model
22
24
  if (showDomains) {
23
- const k = highResScaleFactorOverride || highResScaleFactor
24
- const by = blockSizeYOverride || blockSize
25
+ const k = highResScaleFactorOverride ?? highResScaleFactor
26
+ const by = blockSizeYOverride ?? blockSize
25
27
  ctx.resetTransform()
26
28
  ctx.scale(k, k)
27
29
  ctx.translate(-offsetX, rowHeight / 2 - offsetY)
28
30
 
29
- const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
30
- const yEnd = Math.max(0, Math.ceil((offsetY + by + rowHeight) / rowHeight))
31
- const visibleLeaves = leaves.slice(yStart, yEnd)
31
+ const visibleLeaves = getVisibleLeaves({ model, offsetY, blockSizeY: by })
32
32
 
33
33
  drawTiles({
34
34
  model,
@@ -44,7 +44,7 @@ function drawTiles({
44
44
  visibleLeaves,
45
45
  }: {
46
46
  model: MsaViewModel
47
- ctx: CanvasRenderingContext2D
47
+ ctx: RenderCtx
48
48
  visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
49
49
  }) {
50
50
  const {
@@ -1,6 +1,11 @@
1
+ import { getVisibleLeaves } from './getVisibleLeaves.ts'
2
+ import { visibleColRange } from './visibleColRange.ts'
3
+ import { adjustColorForContrast } from '../../util.ts'
4
+
1
5
  import type { HierarchyNode } from '../../hierarchy.ts'
2
6
  import type { MsaViewModel } from '../../model.ts'
3
7
  import type { NodeWithIdsAndLength } from '../../types.ts'
8
+ import type { RenderCtx } from '../renderCtx.ts'
4
9
  import type { Theme } from '@mui/material'
5
10
 
6
11
  export function renderMSABlock({
@@ -19,7 +24,7 @@ export function renderMSABlock({
19
24
  theme: Theme
20
25
  model: MsaViewModel
21
26
  contrastScheme: Record<string, string>
22
- ctx: CanvasRenderingContext2D
27
+ ctx: RenderCtx
23
28
  highResScaleFactorOverride?: number
24
29
  blockSizeXOverride?: number
25
30
  blockSizeYOverride?: number
@@ -31,12 +36,11 @@ export function renderMSABlock({
31
36
  fontSize,
32
37
  highResScaleFactor,
33
38
  actuallyShowDomains,
34
- leaves,
35
39
  bgColor,
36
40
  } = model
37
- const k = highResScaleFactorOverride || highResScaleFactor
38
- const bx = blockSizeXOverride || blockSize
39
- const by = blockSizeYOverride || blockSize
41
+ const k = highResScaleFactorOverride ?? highResScaleFactor
42
+ const bx = blockSizeXOverride ?? blockSize
43
+ const by = blockSizeYOverride ?? blockSize
40
44
  ctx.resetTransform()
41
45
  ctx.scale(k, k)
42
46
  ctx.translate(-offsetX, rowHeight / 2 - offsetY)
@@ -49,32 +53,40 @@ export function renderMSABlock({
49
53
  `${bgColor ? '' : 'bold '}${fontSize}px`,
50
54
  )
51
55
 
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)
56
+ const { xStart, xEnd } = visibleColRange({
57
+ offsetX,
58
+ blockWidth: bx,
59
+ colWidth,
60
+ })
61
+ const visibleLeaves = getVisibleLeaves({ model, offsetY, blockSizeY: by })
62
+ const { relativeTo, columns } = model
63
+ const referenceSeq = relativeTo
64
+ ? columns.get(relativeTo)?.slice(xStart, xEnd)
65
+ : null
57
66
 
58
67
  if (!actuallyShowDomains) {
59
- drawTiles({
68
+ drawTilesAndText({
60
69
  model,
61
70
  ctx,
62
71
  theme,
72
+ contrastScheme,
63
73
  offsetX,
64
74
  xStart,
65
75
  xEnd,
66
76
  visibleLeaves,
77
+ referenceSeq,
78
+ })
79
+ } else {
80
+ drawText({
81
+ model,
82
+ ctx,
83
+ offsetX,
84
+ xStart,
85
+ xEnd,
86
+ visibleLeaves,
87
+ referenceSeq,
67
88
  })
68
89
  }
69
- drawText({
70
- model,
71
- ctx,
72
- offsetX,
73
- contrastScheme,
74
- xStart,
75
- xEnd,
76
- visibleLeaves,
77
- })
78
90
  drawInsertionIndicators({
79
91
  model,
80
92
  ctx,
@@ -85,22 +97,26 @@ export function renderMSABlock({
85
97
  ctx.resetTransform()
86
98
  }
87
99
 
88
- function drawTiles({
100
+ function drawTilesAndText({
89
101
  model,
90
102
  offsetX,
91
103
  ctx,
92
- visibleLeaves,
93
104
  theme,
105
+ contrastScheme,
106
+ visibleLeaves,
94
107
  xStart,
95
108
  xEnd,
109
+ referenceSeq,
96
110
  }: {
97
111
  model: MsaViewModel
98
112
  offsetX: number
99
113
  theme: Theme
100
- ctx: CanvasRenderingContext2D
114
+ ctx: RenderCtx
115
+ contrastScheme: Record<string, string>
101
116
  visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
102
117
  xStart: number
103
118
  xEnd: number
119
+ referenceSeq: string | null | undefined
104
120
  }) {
105
121
  const {
106
122
  bgColor,
@@ -110,131 +126,116 @@ function drawTiles({
110
126
  colWidth,
111
127
  rowHeight,
112
128
  relativeTo,
129
+ showMsaLetters,
113
130
  } = model
114
131
 
115
- // Get reference sequence if relativeTo is set
116
- const referenceSeq = relativeTo
117
- ? columns[relativeTo]?.slice(xStart, xEnd)
118
- : null
119
-
120
132
  const isClustalX = colorSchemeName === 'clustalx_protein_dynamic'
121
133
  const isPercentIdentity = colorSchemeName === 'percent_identity_dynamic'
134
+ const drawBgTiles = bgColor || isClustalX || isPercentIdentity
135
+ if (!drawBgTiles && !showMsaLetters) {
136
+ return
137
+ }
122
138
  const offsetXAligned = offsetX - (offsetX % colWidth)
139
+ const halfColWidth = colWidth / 2
140
+ const quarterRowHeight = rowHeight / 4
123
141
 
124
142
  for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
125
143
  const node = visibleLeaves[i]!
126
- const {
127
- data: { name },
128
- } = node
144
+ const { name } = node.data
129
145
  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]!
146
+ const str = columns.get(name)?.slice(xStart, xEnd)
147
+ if (!str) {
148
+ continue
149
+ }
150
+ const tileY = y - rowHeight
151
+ const textY = y - quarterRowHeight
134
152
 
135
- // Use a muted background for positions that match reference
136
- const isMatchingReference =
137
- referenceSeq && name !== relativeTo && letter === referenceSeq[j]
153
+ for (let j = 0, l2 = str.length; j < l2; j++) {
154
+ const letter = str[j]!
155
+ const x = j * colWidth + offsetXAligned
156
+ const isMatchingReference =
157
+ referenceSeq && name !== relativeTo && letter === referenceSeq[j]
138
158
 
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
- }
159
+ let color: string | undefined
160
+ if (isClustalX) {
161
+ color = model.colClustalX[xStart + j]![letter]
162
+ } else if (isPercentIdentity) {
163
+ const consensus = model.colConsensus[xStart + j]!
164
+ color = letter === consensus.letter ? consensus.color : undefined
165
+ } else {
166
+ color = colorScheme[letter]
167
+ }
168
+
169
+ if (drawBgTiles) {
170
+ ctx.fillStyle = isMatchingReference
171
+ ? theme.palette.action.hover
172
+ : color || theme.palette.background.default
173
+ ctx.fillRect(x, tileY, colWidth, rowHeight)
174
+ }
175
+
176
+ if (showMsaLetters) {
177
+ ctx.fillStyle = bgColor
178
+ ? (contrastScheme[letter] ?? 'black')
179
+ : color
180
+ ? adjustColorForContrast(color, theme.palette.background.default)
181
+ : theme.palette.text.primary
182
+ ctx.fillText(
183
+ isMatchingReference ? '.' : letter,
184
+ x + halfColWidth,
185
+ textY,
186
+ )
161
187
  }
162
188
  }
163
189
  }
164
190
  }
165
191
 
192
+ // When domains are shown the background tiles come from renderBoxFeatureCanvasBlock,
193
+ // so we only need to draw text here.
166
194
  function drawText({
167
195
  model,
168
196
  offsetX,
169
- contrastScheme,
170
197
  ctx,
171
198
  visibleLeaves,
172
199
  xStart,
173
200
  xEnd,
201
+ referenceSeq,
174
202
  }: {
175
203
  offsetX: number
176
204
  model: MsaViewModel
177
- contrastScheme: Record<string, string>
178
- ctx: CanvasRenderingContext2D
205
+ ctx: RenderCtx
179
206
  visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
180
207
  xStart: number
181
208
  xEnd: number
209
+ referenceSeq: string | null | undefined
182
210
  }) {
183
- const {
184
- bgColor,
185
- actuallyShowDomains,
186
- showMsaLetters,
187
- colorScheme,
188
- columns,
189
- colWidth,
190
- rowHeight,
191
- relativeTo,
192
- } = model
211
+ const { showMsaLetters, columns, colWidth, rowHeight, relativeTo } = model
193
212
 
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'
213
+ if (!showMsaLetters) {
214
+ return
215
+ }
216
+ const offsetXAligned = offsetX - (offsetX % colWidth)
217
+ const halfColWidth = colWidth / 2
218
+ const quarterRowHeight = rowHeight / 4
219
+ // note: -rowHeight/4 matches +rowHeight/4 in tree
220
+ ctx.fillStyle = 'black'
224
221
 
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
- }
222
+ for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
223
+ const node = visibleLeaves[i]!
224
+ const { name } = node.data
225
+ const y = node.x! - quarterRowHeight
226
+ const str = columns.get(name)?.slice(xStart, xEnd)
227
+ if (!str) {
228
+ continue
229
+ }
230
+ for (let j = 0, l2 = str.length; j < l2; j++) {
231
+ const letter = str[j]!
232
+ const isMatchingReference =
233
+ referenceSeq && name !== relativeTo && letter === referenceSeq[j]
234
+ ctx.fillText(
235
+ isMatchingReference ? '.' : letter,
236
+ j * colWidth + offsetXAligned + halfColWidth,
237
+ y,
238
+ )
238
239
  }
239
240
  }
240
241
  }
@@ -247,7 +248,7 @@ function drawInsertionIndicators({
247
248
  xEnd,
248
249
  }: {
249
250
  model: MsaViewModel
250
- ctx: CanvasRenderingContext2D
251
+ ctx: RenderCtx
251
252
  visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
252
253
  xStart: number
253
254
  xEnd: number
@@ -273,7 +274,7 @@ function drawZigZag({
273
274
  offset,
274
275
  }: {
275
276
  model: MsaViewModel
276
- ctx: CanvasRenderingContext2D
277
+ ctx: RenderCtx
277
278
  visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
278
279
  xStart: number
279
280
  xEnd: number
@@ -25,7 +25,6 @@ export function renderMouseover({
25
25
  mouseClickRow,
26
26
  mouseClickCol,
27
27
  relativeTo,
28
- rowNamesSet,
29
28
  hoveredTreeNode,
30
29
  highlightedColumns,
31
30
  } = model
@@ -35,7 +34,7 @@ export function renderMouseover({
35
34
 
36
35
  // Highlight reference row (relativeTo) persistently
37
36
  if (relativeTo) {
38
- const referenceRowIndex = rowNamesSet.get(relativeTo)
37
+ const referenceRowIndex = model.rowNamesSet.get(relativeTo)
39
38
  if (referenceRowIndex !== undefined) {
40
39
  ctx.fillStyle = referenceColor
41
40
  ctx.fillRect(0, referenceRowIndex * rowHeight + scrollY, width, rowHeight)
@@ -46,7 +45,7 @@ export function renderMouseover({
46
45
  if (hoveredTreeNode) {
47
46
  ctx.fillStyle = multiRowHoverColor
48
47
  for (const descendantName of hoveredTreeNode.descendantNames) {
49
- const rowIndex = rowNamesSet.get(descendantName)
48
+ const rowIndex = model.rowNamesSet.get(descendantName)
50
49
  if (rowIndex !== undefined) {
51
50
  ctx.fillRect(0, rowIndex * rowHeight + scrollY, width, rowHeight)
52
51
  }
@@ -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,8 @@
1
+ import { visibleColRange } from '../msa/visibleColRange.ts'
2
+
1
3
  import type { MsaViewModel } from '../../model.ts'
2
4
  import type { BasicTrack } from '../../types.ts'
5
+ import type { RenderCtx } from '../renderCtx.ts'
3
6
 
4
7
  export function drawConservationBars({
5
8
  ctx,
@@ -9,15 +12,18 @@ export function drawConservationBars({
9
12
  offsetX,
10
13
  blockSize,
11
14
  }: {
12
- ctx: CanvasRenderingContext2D
15
+ ctx: RenderCtx
13
16
  conservation: number[]
14
17
  colWidth: number
15
18
  trackHeight: number
16
19
  offsetX: number
17
20
  blockSize: number
18
21
  }) {
19
- const xStart = Math.max(0, Math.floor(offsetX / colWidth))
20
- const xEnd = Math.max(0, Math.ceil((offsetX + blockSize) / colWidth))
22
+ const { xStart, xEnd } = visibleColRange({
23
+ offsetX,
24
+ blockWidth: blockSize,
25
+ colWidth,
26
+ })
21
27
 
22
28
  ctx.fillStyle = 'gray'
23
29
  for (let i = xStart; i < xEnd && i < conservation.length; i++) {
@@ -39,7 +45,7 @@ export function drawTextTrackContent({
39
45
  offsetX,
40
46
  blockSize,
41
47
  }: {
42
- ctx: CanvasRenderingContext2D
48
+ ctx: RenderCtx
43
49
  data: string | undefined
44
50
  colorScheme: Record<string, string>
45
51
  contrastScheme: Record<string, string>
@@ -49,8 +55,11 @@ export function drawTextTrackContent({
49
55
  offsetX: number
50
56
  blockSize: number
51
57
  }) {
52
- const xStart = Math.max(0, Math.floor(offsetX / colWidth))
53
- const xEnd = Math.max(0, Math.ceil((offsetX + blockSize) / colWidth))
58
+ const { xStart, xEnd } = visibleColRange({
59
+ offsetX,
60
+ blockWidth: blockSize,
61
+ colWidth,
62
+ })
54
63
  const str = data?.slice(xStart, xEnd)
55
64
 
56
65
  for (let i = 0; str && i < str.length; i++) {
@@ -78,7 +87,7 @@ export function renderConservationTrack({
78
87
  highResScaleFactorOverride,
79
88
  }: {
80
89
  model: MsaViewModel
81
- ctx: CanvasRenderingContext2D
90
+ ctx: RenderCtx
82
91
  offsetX: number
83
92
  offsetY: number
84
93
  trackHeight: number
@@ -116,7 +125,7 @@ export function renderTextTrack({
116
125
  highResScaleFactorOverride,
117
126
  }: {
118
127
  model: MsaViewModel
119
- ctx: CanvasRenderingContext2D
128
+ ctx: RenderCtx
120
129
  track: BasicTrack
121
130
  offsetX: number
122
131
  offsetY: number
@@ -169,7 +178,7 @@ export function renderAllTracks({
169
178
  highResScaleFactorOverride,
170
179
  }: {
171
180
  model: MsaViewModel
172
- ctx: CanvasRenderingContext2D
181
+ ctx: RenderCtx
173
182
  offsetX: number
174
183
  contrastScheme: Record<string, string>
175
184
  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={{