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.
- package/bundle/index.js +2 -2
- package/bundle/index.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +20 -18
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +8 -1
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
- package/dist/components/tree/renderTreeCanvas.js +22 -23
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/model.d.ts +15 -0
- package/dist/model.js +19 -0
- package/dist/model.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/components/msa/renderMSABlock.ts +22 -19
- package/src/components/msa/renderMSAMouseover.ts +9 -0
- package/src/components/tree/renderTreeCanvas.ts +24 -33
- package/src/model.ts +21 -0
- package/src/version.ts +1 -1
|
@@ -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
|
|
124
|
-
const letter = str[
|
|
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[
|
|
131
|
+
referenceSeq && name !== relativeTo && letter === referenceSeq[j]
|
|
129
132
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
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 +
|
|
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 ||
|
|
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
|
-
|
|
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
|
|
203
|
-
const letter = str[
|
|
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[
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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 -
|
|
211
|
+
minY: yp - emHeight,
|
|
221
212
|
maxY: yp,
|
|
222
213
|
name,
|
|
223
214
|
id,
|
|
224
215
|
})
|
|
225
216
|
} else {
|
|
226
|
-
|
|
217
|
+
const labelX = noTree ? 2 : xp + d
|
|
218
|
+
ctx.fillText(displayName, labelX, yp)
|
|
227
219
|
clickMap?.insert({
|
|
228
|
-
minX:
|
|
229
|
-
maxX:
|
|
230
|
-
minY: yp -
|
|
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.
|
|
1
|
+
export const version = '4.5.0'
|