react-msaview 4.5.0 → 4.6.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.
@@ -65,7 +65,6 @@ export function renderMSABlock({
65
65
  ctx,
66
66
  offsetX,
67
67
  contrastScheme,
68
- theme,
69
68
  xStart,
70
69
  xEnd,
71
70
  visibleLeaves,
@@ -112,6 +111,10 @@ function drawTiles({
112
111
  ? columns[relativeTo]?.slice(xStart, xEnd)
113
112
  : null
114
113
 
114
+ const isClustalX = colorSchemeName === 'clustalx_protein_dynamic'
115
+ const isPercentIdentity = colorSchemeName === 'percent_identity_dynamic'
116
+ const offsetXAligned = offsetX - (offsetX % colWidth)
117
+
115
118
  for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
116
119
  const node = visibleLeaves[i]!
117
120
  const {
@@ -120,31 +123,29 @@ function drawTiles({
120
123
  const y = node.x!
121
124
  const str = columns[name]?.slice(xStart, xEnd)
122
125
  if (str) {
123
- for (let i = 0, l2 = str.length; i < l2; i++) {
124
- const letter = str[i]!
126
+ for (let j = 0, l2 = str.length; j < l2; j++) {
127
+ const letter = str[j]!
125
128
 
126
129
  // Use a muted background for positions that match reference
127
130
  const isMatchingReference =
128
- referenceSeq && name !== relativeTo && letter === referenceSeq[i]
131
+ referenceSeq && name !== relativeTo && letter === referenceSeq[j]
129
132
 
130
- const r1 = colorSchemeName === 'clustalx_protein_dynamic'
131
- const r2 = colorSchemeName === 'percent_identity_dynamic'
132
- const color = r1
133
- ? model.colClustalX[xStart + i]![letter]
134
- : r2
133
+ const color = isClustalX
134
+ ? model.colClustalX[xStart + j]![letter]
135
+ : isPercentIdentity
135
136
  ? (() => {
136
- const consensus = model.colConsensus[xStart + i]!
137
+ const consensus = model.colConsensus[xStart + j]!
137
138
  return letter === consensus.letter ? consensus.color : undefined
138
139
  })()
139
140
  : colorScheme[letter.toUpperCase()]
140
- if (bgColor || r1 || r2) {
141
+ if (bgColor || isClustalX || isPercentIdentity) {
141
142
  // Use a very light background for matching positions in relative mode
142
143
  const finalColor = isMatchingReference
143
144
  ? theme.palette.action.hover
144
145
  : color || theme.palette.background.default
145
146
  ctx.fillStyle = finalColor
146
147
  ctx.fillRect(
147
- i * colWidth + offsetX - (offsetX % colWidth),
148
+ j * colWidth + offsetXAligned,
148
149
  y - rowHeight,
149
150
  colWidth,
150
151
  rowHeight,
@@ -167,7 +168,6 @@ function drawText({
167
168
  offsetX: number
168
169
  model: MsaViewModel
169
170
  contrastScheme: Record<string, string>
170
- theme: Theme
171
171
  ctx: CanvasRenderingContext2D
172
172
  visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
173
173
  xStart: number
@@ -191,20 +191,24 @@ function drawText({
191
191
  : null
192
192
 
193
193
  if (showMsaLetters) {
194
+ const offsetXAligned = offsetX - (offsetX % colWidth)
195
+ const halfColWidth = colWidth / 2
196
+ const quarterRowHeight = rowHeight / 4
197
+
194
198
  for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
195
199
  const node = visibleLeaves[i]!
196
200
  const {
197
201
  data: { name },
198
202
  } = node
199
- const y = node.x!
203
+ const y = node.x! - quarterRowHeight
200
204
  const str = columns[name]?.slice(xStart, xEnd)
201
205
  if (str) {
202
- for (let i = 0, l2 = str.length; i < l2; i++) {
203
- const letter = str[i]!
206
+ for (let j = 0, l2 = str.length; j < l2; j++) {
207
+ const letter = str[j]!
204
208
 
205
209
  // Check if this position matches the reference
206
210
  const isMatchingReference =
207
- referenceSeq && name !== relativeTo && letter === referenceSeq[i]
211
+ referenceSeq && name !== relativeTo && letter === referenceSeq[j]
208
212
 
209
213
  // Show dot for matching positions, original letter for differences
210
214
  const displayLetter = isMatchingReference ? '.' : letter
@@ -213,7 +217,6 @@ function drawText({
213
217
  const contrast = contrastLettering
214
218
  ? contrastScheme[letter.toUpperCase()] || 'black'
215
219
  : 'black'
216
- const x = i * colWidth + offsetX - (offsetX % colWidth)
217
220
 
218
221
  // note: -rowHeight/4 matches +rowHeight/4 in tree
219
222
  ctx.fillStyle = actuallyShowDomains
@@ -221,7 +224,7 @@ function drawText({
221
224
  : bgColor
222
225
  ? contrast
223
226
  : color || 'black'
224
- ctx.fillText(displayLetter, x + colWidth / 2, y - rowHeight / 4)
227
+ ctx.fillText(displayLetter, j * colWidth + offsetXAligned + halfColWidth, y)
225
228
  }
226
229
  }
227
230
  }
@@ -28,6 +28,7 @@ export function renderMouseover({
28
28
  relativeTo,
29
29
  rowNamesSet,
30
30
  hoveredTreeNode,
31
+ highlightedColumns,
31
32
  } = model
32
33
  ctx.resetTransform()
33
34
  ctx.clearRect(0, 0, width, height)
@@ -52,6 +53,14 @@ export function renderMouseover({
52
53
  }
53
54
  }
54
55
 
56
+ // Highlight multiple columns
57
+ if (highlightedColumns?.length) {
58
+ ctx.fillStyle = highlightColor
59
+ for (const col of highlightedColumns) {
60
+ ctx.fillRect(col * colWidth + scrollX, 0, colWidth, height)
61
+ }
62
+ }
63
+
55
64
  if (mouseCol !== undefined) {
56
65
  ctx.fillStyle = hoverColor
57
66
  ctx.fillRect(mouseCol * colWidth + scrollX, 0, colWidth, height)
@@ -42,15 +42,9 @@ export function renderTree({
42
42
  theme: Theme
43
43
  blockSizeYOverride?: number
44
44
  }) {
45
- const {
46
- hierarchy,
47
- allBranchesLength0,
48
- showBranchLen: showBranchLenPre,
49
- blockSize,
50
- } = model
45
+ const { hierarchy, showBranchLenEffective: showBranchLen, blockSize } = model
51
46
  const by = blockSizeYOverride || blockSize
52
47
  ctx.strokeStyle = theme.palette.text.primary
53
- const showBranchLen = allBranchesLength0 ? false : showBranchLenPre
54
48
  for (const link of hierarchy.links()) {
55
49
  const { source, target } = link
56
50
  if (target.height === 0 && !showBranchLen) {
@@ -89,19 +83,16 @@ export function renderNodeBubbles({
89
83
  clickMap?: ClickMapIndex
90
84
  offsetY: number
91
85
  model: MsaViewModel
92
- theme: Theme
93
86
  blockSizeYOverride?: number
94
87
  }) {
95
88
  const {
96
89
  hierarchy,
97
- showBranchLen: showBranchLenPre,
98
- allBranchesLength0,
90
+ showBranchLenEffective: showBranchLen,
99
91
  collapsed,
100
92
  blockSize,
101
93
  marginLeft: ml,
102
94
  } = model
103
95
  const by = blockSizeYOverride || blockSize
104
- const showBranchLen = allBranchesLength0 ? false : showBranchLenPre
105
96
  for (const node of hierarchy.descendants()) {
106
97
  const val = showBranchLen ? 'len' : 'y'
107
98
  // @ts-expect-error
@@ -150,8 +141,7 @@ export function renderTreeLabels({
150
141
  }) {
151
142
  const {
152
143
  fontSize,
153
- showBranchLen: showBranchLenPre,
154
- allBranchesLength0,
144
+ showBranchLenEffective: showBranchLen,
155
145
  treeMetadata,
156
146
  hierarchy,
157
147
  collapsed,
@@ -167,13 +157,13 @@ export function renderTreeLabels({
167
157
  noTree,
168
158
  } = model
169
159
  const by = blockSizeYOverride || blockSize
160
+ const emHeight = ctx.measureText('M').width
170
161
  if (labelsAlignRight) {
171
162
  ctx.textAlign = 'right'
172
163
  ctx.setLineDash([1, 3])
173
164
  } else {
174
165
  ctx.textAlign = 'start'
175
166
  }
176
- const showBranchLen = allBranchesLength0 ? false : showBranchLenPre
177
167
  for (const node of leaves) {
178
168
  const {
179
169
  data: { name, id },
@@ -187,28 +177,29 @@ export function renderTreeLabels({
187
177
  if (y > offsetY - extendBounds && y < offsetY + by + extendBounds) {
188
178
  // note: +rowHeight/4 matches with -rowHeight/4 in msa
189
179
  const yp = y + fontSize / 4
190
- let xp = (showBranchLen ? len : x) || 0
191
- if (
192
- !showBranchLen &&
193
- !collapsed.includes(id) &&
194
- !collapsedLeaves.includes(id)
195
- ) {
196
- // this subtraction is a hack to compensate for the leafnode rendering
197
- // glitch (issue #71). the context is that an extra leaf node is added
198
- // so that 'collapsing/hiding leaf nodes is possible' but this causes
199
- // weird workarounds
200
- xp -= treeWidth / hierarchy.height
180
+ let xp = 0
181
+ if (!noTree) {
182
+ xp = (showBranchLen ? len : x) || 0
183
+ if (
184
+ !showBranchLen &&
185
+ !collapsed.includes(id) &&
186
+ !collapsedLeaves.includes(id)
187
+ ) {
188
+ // this subtraction is a hack to compensate for the leafnode rendering
189
+ // glitch (issue #71). the context is that an extra leaf node is added
190
+ // so that 'collapsing/hiding leaf nodes is possible' but this causes
191
+ // weird workarounds
192
+ xp -= treeWidth / hierarchy.height
193
+ }
201
194
  }
202
195
 
203
196
  const { width } = ctx.measureText(displayName)
204
- const height = ctx.measureText('M').width // use an 'em' for height
205
197
 
206
198
  ctx.fillStyle = theme.palette.text.primary
207
199
  if (labelsAlignRight) {
208
200
  const smallPadding = 2
209
201
  const offset = treeAreaWidthMinusMargin - smallPadding
210
202
  if (drawTree && !noTree) {
211
- const { width } = ctx.measureText(displayName)
212
203
  ctx.moveTo(xp + radius + 2, y)
213
204
  ctx.lineTo(offset - smallPadding - width, y)
214
205
  ctx.stroke()
@@ -217,17 +208,18 @@ export function renderTreeLabels({
217
208
  clickMap?.insert({
218
209
  minX: treeAreaWidth - width,
219
210
  maxX: treeAreaWidth,
220
- minY: yp - height,
211
+ minY: yp - emHeight,
221
212
  maxY: yp,
222
213
  name,
223
214
  id,
224
215
  })
225
216
  } else {
226
- ctx.fillText(displayName, xp + d, yp)
217
+ const labelX = noTree ? 2 : xp + d
218
+ ctx.fillText(displayName, labelX, yp)
227
219
  clickMap?.insert({
228
- minX: xp + d + marginLeft,
229
- maxX: xp + d + width + marginLeft,
230
- minY: yp - height,
220
+ minX: labelX + marginLeft,
221
+ maxX: labelX + width + marginLeft,
222
+ minY: yp - emHeight,
231
223
  maxY: yp,
232
224
  name,
233
225
  id,
@@ -303,7 +295,6 @@ export function renderTreeCanvas({
303
295
  offsetY,
304
296
  clickMap,
305
297
  model,
306
- theme,
307
298
  blockSizeYOverride,
308
299
  })
309
300
  }
package/src/model.ts CHANGED
@@ -321,6 +321,12 @@ function stateModelFactory() {
321
321
  | { nodeId: string; descendantNames: string[] }
322
322
  | undefined,
323
323
 
324
+ /**
325
+ * #volatile
326
+ * array of column indices to highlight
327
+ */
328
+ highlightedColumns: undefined as number[] | undefined,
329
+
324
330
  /**
325
331
  * #volatile
326
332
  * a dummy variable that is incremented when ref changes so autorun for
@@ -453,6 +459,13 @@ function stateModelFactory() {
453
459
 
454
460
  self.hoveredTreeNode = { nodeId, descendantNames }
455
461
  },
462
+ /**
463
+ * #action
464
+ * set highlighted columns
465
+ */
466
+ setHighlightedColumns(columns?: number[]) {
467
+ self.highlightedColumns = columns
468
+ },
456
469
  /**
457
470
  * #action
458
471
  */
@@ -1217,6 +1230,14 @@ function stateModelFactory() {
1217
1230
  get allBranchesLength0() {
1218
1231
  return this.hierarchy.links().every(s => !s.source.data.length)
1219
1232
  },
1233
+
1234
+ /**
1235
+ * #getter
1236
+ * effective showBranchLen accounting for allBranchesLength0
1237
+ */
1238
+ get showBranchLenEffective() {
1239
+ return this.allBranchesLength0 ? false : self.showBranchLen
1240
+ },
1220
1241
  }))
1221
1242
  .views(self => ({
1222
1243
  /**
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '4.4.7'
1
+ export const version = '4.5.0'