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.
- package/bundle/index.js +35 -135
- package/bundle/index.js.map +1 -1
- package/dist/calculateBlocks.d.ts +4 -10
- package/dist/calculateBlocks.js +12 -20
- package/dist/calculateBlocks.js.map +1 -1
- package/dist/components/ConservationTrack.js +18 -24
- package/dist/components/ConservationTrack.js.map +1 -1
- package/dist/components/TextTrack.js +24 -34
- package/dist/components/TextTrack.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +20 -20
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/InterProScanDialog.js +6 -1
- package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
- package/dist/components/header/FileMenu.js +2 -1
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/Header.js +1 -1
- package/dist/components/header/settingsMenuItems.js +24 -8
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/minimap/Minimap.js +1 -6
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.js +4 -8
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/msa/getVisibleLeaves.d.ts +6 -0
- package/dist/components/msa/getVisibleLeaves.js +9 -0
- package/dist/components/msa/getVisibleLeaves.js.map +1 -0
- package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +2 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +5 -6
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.d.ts +2 -1
- package/dist/components/msa/renderMSABlock.js +98 -84
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +3 -3
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/msa/visibleColRange.d.ts +8 -0
- package/dist/components/msa/visibleColRange.js +10 -0
- package/dist/components/msa/visibleColRange.js.map +1 -0
- package/dist/components/renderCtx.d.ts +7 -0
- package/dist/components/renderCtx.js +2 -0
- package/dist/components/renderCtx.js.map +1 -0
- package/dist/components/tracks/renderTracksSvg.d.ts +6 -5
- package/dist/components/tracks/renderTracksSvg.js +11 -4
- package/dist/components/tracks/renderTracksSvg.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +2 -10
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +5 -4
- package/dist/components/tree/renderTreeCanvas.js +16 -24
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/util.d.ts +0 -14
- package/dist/components/util.js +0 -58
- package/dist/components/util.js.map +1 -1
- package/dist/hierarchy.d.ts +2 -0
- package/dist/hierarchy.js +19 -0
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +26 -30
- package/dist/model.js +74 -82
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.d.ts +1 -1
- package/dist/renderToSvg.js +20 -26
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +0 -9
- package/dist/rowCoordinateCalculations.js +0 -20
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/util.d.ts +1 -0
- package/dist/util.js +23 -1
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -5
- package/src/calculateBlocks.ts +18 -51
- package/src/components/ConservationTrack.tsx +18 -27
- package/src/components/TextTrack.tsx +30 -38
- package/src/components/dialogs/ExportSVGDialog.tsx +27 -17
- package/src/components/dialogs/InterProScanDialog.tsx +6 -1
- package/src/components/header/FileMenu.tsx +2 -1
- package/src/components/header/Header.tsx +1 -1
- package/src/components/header/settingsMenuItems.ts +24 -8
- package/src/components/minimap/Minimap.tsx +1 -6
- package/src/components/minimap/MinimapSVG.tsx +4 -8
- package/src/components/msa/getVisibleLeaves.ts +21 -0
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +9 -9
- package/src/components/msa/renderMSABlock.ts +119 -118
- package/src/components/msa/renderMSAMouseover.ts +2 -3
- package/src/components/msa/visibleColRange.ts +17 -0
- package/src/components/renderCtx.ts +29 -0
- package/src/components/tracks/renderTracksSvg.ts +18 -9
- package/src/components/tree/TreeCanvas.tsx +3 -1
- package/src/components/tree/TreeCanvasBlock.tsx +2 -12
- package/src/components/tree/renderTreeCanvas.ts +25 -28
- package/src/components/util.ts +0 -81
- package/src/hierarchy.ts +27 -0
- package/src/index.ts +7 -0
- package/src/model.ts +81 -98
- package/src/renderToSvg.tsx +23 -31
- package/src/rowCoordinateCalculations.test.ts +0 -51
- package/src/rowCoordinateCalculations.ts +0 -23
- package/src/util.ts +25 -1
- package/src/version.ts +1 -1
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
calcDepthToLeaf,
|
|
3
|
+
forEachDescendant,
|
|
4
|
+
forEachLink,
|
|
5
|
+
} from '../../hierarchy.ts'
|
|
2
6
|
|
|
3
7
|
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
4
8
|
import type { MsaViewModel } from '../../model.ts'
|
|
9
|
+
import type { RenderCtx } from '../renderCtx.ts'
|
|
5
10
|
import type { Theme } from '@mui/material'
|
|
6
11
|
|
|
7
12
|
export const padding = 600
|
|
@@ -59,7 +64,7 @@ export function renderTree({
|
|
|
59
64
|
blockSizeYOverride,
|
|
60
65
|
}: {
|
|
61
66
|
offsetY: number
|
|
62
|
-
ctx:
|
|
67
|
+
ctx: RenderCtx
|
|
63
68
|
model: MsaViewModel
|
|
64
69
|
theme: Theme
|
|
65
70
|
maxBranchLen: number
|
|
@@ -67,16 +72,15 @@ export function renderTree({
|
|
|
67
72
|
blockSizeYOverride?: number
|
|
68
73
|
}) {
|
|
69
74
|
const { hierarchy, showBranchLenEffective: showBranchLen, blockSize } = model
|
|
70
|
-
const by = blockSizeYOverride
|
|
75
|
+
const by = blockSizeYOverride ?? blockSize
|
|
71
76
|
ctx.strokeStyle = theme.palette.text.primary
|
|
72
|
-
|
|
73
|
-
const { source, target } = link
|
|
77
|
+
forEachLink(hierarchy, (source, target) => {
|
|
74
78
|
const sy = source.x!
|
|
75
79
|
const ty = target.x!
|
|
76
80
|
const tx = getNodeX(target, showBranchLen, maxBranchLen, maxDepthToLeaf)
|
|
77
81
|
const sx = getNodeX(source, showBranchLen, maxBranchLen, maxDepthToLeaf)
|
|
78
82
|
if (tx === undefined || sx === undefined) {
|
|
79
|
-
|
|
83
|
+
return
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
const y1 = Math.min(sy, ty)
|
|
@@ -91,7 +95,7 @@ export function renderTree({
|
|
|
91
95
|
ctx.lineTo(tx, ty)
|
|
92
96
|
ctx.stroke()
|
|
93
97
|
}
|
|
94
|
-
}
|
|
98
|
+
})
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
export function renderNodeBubbles({
|
|
@@ -103,7 +107,7 @@ export function renderNodeBubbles({
|
|
|
103
107
|
maxDepthToLeaf,
|
|
104
108
|
blockSizeYOverride,
|
|
105
109
|
}: {
|
|
106
|
-
ctx:
|
|
110
|
+
ctx: RenderCtx
|
|
107
111
|
clickMap?: ClickMapIndex
|
|
108
112
|
offsetY: number
|
|
109
113
|
model: MsaViewModel
|
|
@@ -118,11 +122,11 @@ export function renderNodeBubbles({
|
|
|
118
122
|
blockSize,
|
|
119
123
|
marginLeft: ml,
|
|
120
124
|
} = model
|
|
121
|
-
const by = blockSizeYOverride
|
|
122
|
-
|
|
125
|
+
const by = blockSizeYOverride ?? blockSize
|
|
126
|
+
forEachDescendant(hierarchy, node => {
|
|
123
127
|
const x = getNodeX(node, showBranchLen, maxBranchLen, maxDepthToLeaf)
|
|
124
128
|
if (x === undefined) {
|
|
125
|
-
|
|
129
|
+
return
|
|
126
130
|
}
|
|
127
131
|
const { data } = node
|
|
128
132
|
const y = node.x!
|
|
@@ -149,7 +153,7 @@ export function renderNodeBubbles({
|
|
|
149
153
|
name,
|
|
150
154
|
})
|
|
151
155
|
}
|
|
152
|
-
}
|
|
156
|
+
})
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
export function renderTreeLabels({
|
|
@@ -164,7 +168,7 @@ export function renderTreeLabels({
|
|
|
164
168
|
}: {
|
|
165
169
|
model: MsaViewModel
|
|
166
170
|
offsetY: number
|
|
167
|
-
ctx:
|
|
171
|
+
ctx: RenderCtx
|
|
168
172
|
clickMap?: ClickMapIndex
|
|
169
173
|
theme: Theme
|
|
170
174
|
maxBranchLen: number
|
|
@@ -183,8 +187,9 @@ export function renderTreeLabels({
|
|
|
183
187
|
marginLeft,
|
|
184
188
|
leaves,
|
|
185
189
|
noTree,
|
|
190
|
+
labelWidthMap,
|
|
186
191
|
} = model
|
|
187
|
-
const by = blockSizeYOverride
|
|
192
|
+
const by = blockSizeYOverride ?? blockSize
|
|
188
193
|
const emHeight = ctx.measureText('M').width
|
|
189
194
|
if (labelsAlignRight) {
|
|
190
195
|
ctx.textAlign = 'right'
|
|
@@ -204,10 +209,11 @@ export function renderTreeLabels({
|
|
|
204
209
|
const yp = y + fontSize / 4
|
|
205
210
|
let xp = 0
|
|
206
211
|
if (!noTree) {
|
|
207
|
-
xp = getNodeX(node, showBranchLen, maxBranchLen, maxDepthToLeaf)
|
|
212
|
+
xp = getNodeX(node, showBranchLen, maxBranchLen, maxDepthToLeaf) ?? 0
|
|
208
213
|
}
|
|
209
214
|
|
|
210
|
-
const
|
|
215
|
+
const width =
|
|
216
|
+
labelWidthMap.get(name) ?? ctx.measureText(displayName).width
|
|
211
217
|
|
|
212
218
|
ctx.fillStyle = theme.palette.text.primary
|
|
213
219
|
if (labelsAlignRight) {
|
|
@@ -255,7 +261,7 @@ export function renderTreeCanvas({
|
|
|
255
261
|
}: {
|
|
256
262
|
model: MsaViewModel
|
|
257
263
|
offsetY: number
|
|
258
|
-
ctx:
|
|
264
|
+
ctx: RenderCtx
|
|
259
265
|
clickMap?: ClickMapIndex
|
|
260
266
|
theme: Theme
|
|
261
267
|
highResScaleFactorOverride?: number
|
|
@@ -272,22 +278,13 @@ export function renderTreeCanvas({
|
|
|
272
278
|
fontSize,
|
|
273
279
|
showTreeText,
|
|
274
280
|
marginLeft,
|
|
275
|
-
nref,
|
|
276
281
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
277
|
-
rowHeight: _rowHeight, //
|
|
282
|
+
rowHeight: _rowHeight, // read so the draw autorun re-fires after a zoom change
|
|
278
283
|
} = model
|
|
279
284
|
|
|
280
285
|
ctx.resetTransform()
|
|
281
286
|
|
|
282
|
-
|
|
283
|
-
// it in this statement because otherwise it would be an unused variable,
|
|
284
|
-
// we just need to use nref to indicate a redraw in an autorun when canvas
|
|
285
|
-
// ref is updated and in order to convince bundlers like not to delete
|
|
286
|
-
// unused usage with propertyReadSideEffects
|
|
287
|
-
const k =
|
|
288
|
-
nref < 0
|
|
289
|
-
? Number.NEGATIVE_INFINITY
|
|
290
|
-
: highResScaleFactorOverride || highResScaleFactor
|
|
287
|
+
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
291
288
|
ctx.scale(k, k)
|
|
292
289
|
ctx.translate(marginLeft, -offsetY)
|
|
293
290
|
|
package/src/components/util.ts
CHANGED
|
@@ -1,84 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Given a scale ( bp/px ) and minimum distances (px) between major and minor
|
|
3
|
-
* gridlines, return an object like `{ majorPitch: bp, minorPitch: bp }` giving
|
|
4
|
-
* the gridline pitches to use.
|
|
5
|
-
*/
|
|
6
|
-
export function chooseGridPitch(
|
|
7
|
-
scale: number,
|
|
8
|
-
minMajorPitchPx: number,
|
|
9
|
-
minMinorPitchPx: number,
|
|
10
|
-
) {
|
|
11
|
-
scale = Math.abs(scale)
|
|
12
|
-
const minMajorPitchBp = minMajorPitchPx * scale
|
|
13
|
-
const majorMagnitude = Number.parseInt(
|
|
14
|
-
minMajorPitchBp.toExponential().split(/e/i)[1]!,
|
|
15
|
-
10,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
let majorPitch = 10 ** majorMagnitude
|
|
19
|
-
while (majorPitch < minMajorPitchBp) {
|
|
20
|
-
majorPitch *= 2
|
|
21
|
-
if (majorPitch >= minMajorPitchBp) {
|
|
22
|
-
break
|
|
23
|
-
}
|
|
24
|
-
majorPitch *= 2.5
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
majorPitch = Math.max(majorPitch, 5)
|
|
28
|
-
|
|
29
|
-
const majorPitchPx = majorPitch / scale
|
|
30
|
-
|
|
31
|
-
let minorPitch = 0
|
|
32
|
-
if (!(majorPitch % 10) && majorPitchPx / 10 >= minMinorPitchPx) {
|
|
33
|
-
minorPitch = majorPitch / 10
|
|
34
|
-
} else if (!(majorPitch % 5) && majorPitchPx / 5 >= minMinorPitchPx) {
|
|
35
|
-
minorPitch = majorPitch / 5
|
|
36
|
-
} else if (!(majorPitch % 2) && majorPitchPx / 2 >= minMinorPitchPx) {
|
|
37
|
-
minorPitch = majorPitch / 2
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return { majorPitch, minorPitch }
|
|
41
|
-
}
|
|
42
|
-
export function makeTicks(
|
|
43
|
-
start: number,
|
|
44
|
-
end: number,
|
|
45
|
-
bpPerPx: number,
|
|
46
|
-
emitMajor = true,
|
|
47
|
-
emitMinor = true,
|
|
48
|
-
) {
|
|
49
|
-
const gridPitch = chooseGridPitch(bpPerPx, 60, 15)
|
|
50
|
-
|
|
51
|
-
let minBase = start
|
|
52
|
-
let maxBase = end
|
|
53
|
-
|
|
54
|
-
if (bpPerPx < 0) {
|
|
55
|
-
;[minBase, maxBase] = [maxBase, minBase]
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// add 20px additional on the right and left to allow us to draw the ends of
|
|
59
|
-
// labels that lie a little outside our region
|
|
60
|
-
minBase -= Math.abs(20 * bpPerPx)
|
|
61
|
-
maxBase += Math.abs(20 * bpPerPx) + 1
|
|
62
|
-
|
|
63
|
-
const iterPitch = gridPitch.minorPitch || gridPitch.majorPitch
|
|
64
|
-
let index = 0
|
|
65
|
-
const ticks = []
|
|
66
|
-
for (
|
|
67
|
-
let base = Math.ceil(minBase / iterPitch) * iterPitch;
|
|
68
|
-
base < maxBase;
|
|
69
|
-
base += iterPitch
|
|
70
|
-
) {
|
|
71
|
-
if (emitMinor && base % (gridPitch.majorPitch * 2)) {
|
|
72
|
-
ticks.push({ type: 'minor', base: base - 1, index })
|
|
73
|
-
index += 1
|
|
74
|
-
} else if (emitMajor && !(base % (gridPitch.majorPitch * 2))) {
|
|
75
|
-
ticks.push({ type: 'major', base: base - 1, index })
|
|
76
|
-
index += 1
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return ticks
|
|
80
|
-
}
|
|
81
|
-
|
|
82
1
|
export function mathPower(num: number): string {
|
|
83
2
|
if (num < 999) {
|
|
84
3
|
return String(num)
|
package/src/hierarchy.ts
CHANGED
|
@@ -157,6 +157,33 @@ export function links<T>(node: HierarchyNode<T>): HierarchyLink<T>[] {
|
|
|
157
157
|
return result
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
export function forEachLink<T>(
|
|
161
|
+
node: HierarchyNode<T>,
|
|
162
|
+
cb: (source: HierarchyNode<T>, target: HierarchyNode<T>) => void,
|
|
163
|
+
) {
|
|
164
|
+
function visit(n: HierarchyNode<T>) {
|
|
165
|
+
if (n.children) {
|
|
166
|
+
for (const child of n.children) {
|
|
167
|
+
cb(n, child)
|
|
168
|
+
visit(child)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
visit(node)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function forEachDescendant<T>(
|
|
176
|
+
node: HierarchyNode<T>,
|
|
177
|
+
cb: (n: HierarchyNode<T>) => void,
|
|
178
|
+
) {
|
|
179
|
+
cb(node)
|
|
180
|
+
if (node.children) {
|
|
181
|
+
for (const child of node.children) {
|
|
182
|
+
forEachDescendant(child, cb)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
160
187
|
export function clusterLayout<T>(
|
|
161
188
|
root: HierarchyNode<T>,
|
|
162
189
|
sizeX: number,
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// PUBLIC API — consumed by external JBrowse 2 plugins. Changing or removing any
|
|
2
|
+
// export here is a breaking change for downstream repos:
|
|
3
|
+
// - jbrowse-plugin-msaview (wraps MSAModelF + MSAView into a JBrowse view)
|
|
4
|
+
// - jbrowse-plugin-protein3d (drives MSA<->structure hover/highlight sync)
|
|
5
|
+
// Those plugins also reach into the MsaViewModel instance at runtime; the model
|
|
6
|
+
// members they rely on (e.g. mouseCol/setMousePos, setHighlightedColumns,
|
|
7
|
+
// seqPosToVisibleCol/visibleColToSeqPos) are flagged inline in model.ts.
|
|
1
8
|
export { default as MSAView } from './components/Loading.tsx'
|
|
2
9
|
export { default as MSAViewer } from './components/MSAViewer.tsx'
|
|
3
10
|
export { type MsaViewModel, default as MSAModelF } from './model.ts'
|
package/src/model.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
parseNewick,
|
|
20
20
|
} from 'msa-parsers'
|
|
21
21
|
|
|
22
|
-
import {
|
|
22
|
+
import { calculateBlocks } from './calculateBlocks.ts'
|
|
23
23
|
import { clustalXColumnColors } from './clustalX.ts'
|
|
24
24
|
import colorSchemes from './colorSchemes.ts'
|
|
25
25
|
import ConservationTrack from './components/ConservationTrack.tsx'
|
|
@@ -317,13 +317,6 @@ function stateModelFactory() {
|
|
|
317
317
|
*/
|
|
318
318
|
highlightedColumns: undefined as number[] | undefined,
|
|
319
319
|
|
|
320
|
-
/**
|
|
321
|
-
* #volatile
|
|
322
|
-
* a dummy variable that is incremented when ref changes so autorun for
|
|
323
|
-
* drawing canvas commands will run
|
|
324
|
-
*/
|
|
325
|
-
nref: 0,
|
|
326
|
-
|
|
327
320
|
/**
|
|
328
321
|
* #volatile
|
|
329
322
|
*/
|
|
@@ -408,6 +401,10 @@ function stateModelFactory() {
|
|
|
408
401
|
/**
|
|
409
402
|
* #action
|
|
410
403
|
* set mouse position (row, column) in the MSA
|
|
404
|
+
*
|
|
405
|
+
* CROSS-REPO CONTRACT: jbrowse-plugin-protein3d calls this (and reads the
|
|
406
|
+
* `mouseCol` volatile) to sync MSA<->3D-structure hover. Keep the name and
|
|
407
|
+
* signature stable; see that repo's ProteinToMsaHoverSync.tsx.
|
|
411
408
|
*/
|
|
412
409
|
setMousePos(col?: number, row?: number) {
|
|
413
410
|
self.mouseCol = col
|
|
@@ -440,6 +437,10 @@ function stateModelFactory() {
|
|
|
440
437
|
/**
|
|
441
438
|
* #action
|
|
442
439
|
* set highlighted columns
|
|
440
|
+
*
|
|
441
|
+
* CROSS-REPO CONTRACT: called by jbrowse-plugin-msaview
|
|
442
|
+
* (afterCreateAutoruns.ts) to highlight alignment columns. It has no
|
|
443
|
+
* in-repo caller, so do not flag it as dead code — it is public API.
|
|
443
444
|
*/
|
|
444
445
|
setHighlightedColumns(columns?: number[]) {
|
|
445
446
|
self.highlightedColumns = columns
|
|
@@ -625,14 +626,14 @@ function stateModelFactory() {
|
|
|
625
626
|
* #getter
|
|
626
627
|
*/
|
|
627
628
|
get header() {
|
|
628
|
-
return (this.MSA?.getHeader()
|
|
629
|
+
return (this.MSA?.getHeader() ?? {}) as Record<string, unknown>
|
|
629
630
|
},
|
|
630
631
|
|
|
631
632
|
/**
|
|
632
633
|
* #getter
|
|
633
634
|
*/
|
|
634
635
|
get alignmentNames() {
|
|
635
|
-
return this.MSA?.alignmentNames
|
|
636
|
+
return this.MSA?.alignmentNames ?? []
|
|
636
637
|
},
|
|
637
638
|
/**
|
|
638
639
|
* #getter
|
|
@@ -665,7 +666,7 @@ function stateModelFactory() {
|
|
|
665
666
|
* #getter
|
|
666
667
|
*/
|
|
667
668
|
get numColumns() {
|
|
668
|
-
return (this.MSA?.getWidth()
|
|
669
|
+
return (this.MSA?.getWidth() ?? 0) - this.blanks.length
|
|
669
670
|
},
|
|
670
671
|
|
|
671
672
|
/**
|
|
@@ -693,15 +694,32 @@ function stateModelFactory() {
|
|
|
693
694
|
/**
|
|
694
695
|
* #getter
|
|
695
696
|
*/
|
|
697
|
+
/**
|
|
698
|
+
* #getter
|
|
699
|
+
* Returns the list of row (sequence) names in display order.
|
|
700
|
+
* Part of the public API used by downstream consumers (e.g. jbrowse plugins).
|
|
701
|
+
*/
|
|
696
702
|
get rowNames(): string[] {
|
|
697
703
|
return this.leaves.map(n => n.data.name)
|
|
698
704
|
},
|
|
705
|
+
/**
|
|
706
|
+
* #getter
|
|
707
|
+
*/
|
|
708
|
+
get rowNamesSet() {
|
|
709
|
+
const map = new Map<string, number>()
|
|
710
|
+
this.leaves.forEach((leaf, index) => {
|
|
711
|
+
map.set(leaf.data.name, index)
|
|
712
|
+
})
|
|
713
|
+
return map
|
|
714
|
+
},
|
|
699
715
|
/**
|
|
700
716
|
* #getter
|
|
701
717
|
*/
|
|
702
718
|
get mouseOverRowName() {
|
|
703
719
|
const { mouseRow } = self
|
|
704
|
-
return mouseRow === undefined
|
|
720
|
+
return mouseRow === undefined
|
|
721
|
+
? undefined
|
|
722
|
+
: this.leaves[mouseRow]?.data.name
|
|
705
723
|
},
|
|
706
724
|
/**
|
|
707
725
|
* #getter
|
|
@@ -712,7 +730,7 @@ function stateModelFactory() {
|
|
|
712
730
|
if (mouseCol === undefined || mouseRow === undefined) {
|
|
713
731
|
return undefined
|
|
714
732
|
}
|
|
715
|
-
const rowName = this.
|
|
733
|
+
const rowName = this.leaves[mouseRow]?.data.name
|
|
716
734
|
if (!rowName) {
|
|
717
735
|
return undefined
|
|
718
736
|
}
|
|
@@ -898,7 +916,7 @@ function stateModelFactory() {
|
|
|
898
916
|
* #getter
|
|
899
917
|
*/
|
|
900
918
|
get columns() {
|
|
901
|
-
return
|
|
919
|
+
return new Map(
|
|
902
920
|
this.rows.map(
|
|
903
921
|
(row, index) => [row[0], this.columns2d[index]!] as const,
|
|
904
922
|
),
|
|
@@ -911,7 +929,12 @@ function stateModelFactory() {
|
|
|
911
929
|
const { hideGapsEffective } = self
|
|
912
930
|
return this.rows
|
|
913
931
|
.map(r => r[1])
|
|
914
|
-
.map(str =>
|
|
932
|
+
.map(str =>
|
|
933
|
+
(hideGapsEffective
|
|
934
|
+
? skipBlanks(this.blanks, str)
|
|
935
|
+
: str
|
|
936
|
+
).toUpperCase(),
|
|
937
|
+
)
|
|
915
938
|
},
|
|
916
939
|
/**
|
|
917
940
|
* #getter
|
|
@@ -939,7 +962,13 @@ function stateModelFactory() {
|
|
|
939
962
|
* #getter
|
|
940
963
|
*/
|
|
941
964
|
get colStatsSums() {
|
|
942
|
-
return this.colStats.map(
|
|
965
|
+
return this.colStats.map(col => {
|
|
966
|
+
let s = 0
|
|
967
|
+
for (const k in col) {
|
|
968
|
+
s += col[k]!
|
|
969
|
+
}
|
|
970
|
+
return s
|
|
971
|
+
})
|
|
943
972
|
},
|
|
944
973
|
|
|
945
974
|
/**
|
|
@@ -951,9 +980,8 @@ function stateModelFactory() {
|
|
|
951
980
|
const letters = new Set<string>()
|
|
952
981
|
for (const stats of this.colStats) {
|
|
953
982
|
for (const letter of Object.keys(stats)) {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
letters.add(upper)
|
|
983
|
+
if (letter !== '-' && letter !== '.') {
|
|
984
|
+
letters.add(letter)
|
|
957
985
|
}
|
|
958
986
|
}
|
|
959
987
|
}
|
|
@@ -1029,24 +1057,18 @@ function stateModelFactory() {
|
|
|
1029
1057
|
return 0
|
|
1030
1058
|
}
|
|
1031
1059
|
|
|
1032
|
-
const gapCount = (stats['-']
|
|
1060
|
+
const gapCount = (stats['-'] ?? 0) + (stats['.'] ?? 0)
|
|
1033
1061
|
const nonGapTotal = total - gapCount
|
|
1034
1062
|
if (nonGapTotal === 0) {
|
|
1035
1063
|
return 0
|
|
1036
1064
|
}
|
|
1037
1065
|
|
|
1038
|
-
|
|
1066
|
+
let entropy = 0
|
|
1039
1067
|
for (const letter of Object.keys(stats)) {
|
|
1040
1068
|
if (letter === '-' || letter === '.') {
|
|
1041
1069
|
continue
|
|
1042
1070
|
}
|
|
1043
|
-
const
|
|
1044
|
-
merged[upper] = (merged[upper] || 0) + stats[letter]!
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
let entropy = 0
|
|
1048
|
-
for (const key of Object.keys(merged)) {
|
|
1049
|
-
const freq = merged[key]! / nonGapTotal
|
|
1071
|
+
const freq = stats[letter]! / nonGapTotal
|
|
1050
1072
|
if (freq > 0) {
|
|
1051
1073
|
entropy -= freq * Math.log2(freq)
|
|
1052
1074
|
}
|
|
@@ -1134,22 +1156,22 @@ function stateModelFactory() {
|
|
|
1134
1156
|
* #getter
|
|
1135
1157
|
*/
|
|
1136
1158
|
get blocksX() {
|
|
1137
|
-
return
|
|
1138
|
-
|
|
1139
|
-
|
|
1159
|
+
return calculateBlocks({
|
|
1160
|
+
viewportSize: self.msaAreaWidth,
|
|
1161
|
+
viewportPos: -self.scrollX,
|
|
1140
1162
|
blockSize: self.blockSize,
|
|
1141
|
-
|
|
1163
|
+
mapSize: self.totalWidth,
|
|
1142
1164
|
})
|
|
1143
1165
|
},
|
|
1144
1166
|
/**
|
|
1145
1167
|
* #getter
|
|
1146
1168
|
*/
|
|
1147
1169
|
get blocksY() {
|
|
1148
|
-
return
|
|
1149
|
-
|
|
1150
|
-
|
|
1170
|
+
return calculateBlocks({
|
|
1171
|
+
viewportSize: self.height,
|
|
1172
|
+
viewportPos: -self.scrollY,
|
|
1151
1173
|
blockSize: self.blockSize,
|
|
1152
|
-
|
|
1174
|
+
mapSize: self.totalHeight,
|
|
1153
1175
|
})
|
|
1154
1176
|
},
|
|
1155
1177
|
}))
|
|
@@ -1272,7 +1294,7 @@ function stateModelFactory() {
|
|
|
1272
1294
|
self.scrollY = clamp(self.scrollY + deltaY, -self.totalHeight + 10, 0)
|
|
1273
1295
|
},
|
|
1274
1296
|
|
|
1275
|
-
setInterProAnnotations(data
|
|
1297
|
+
setInterProAnnotations(data?: Record<string, InterProScanResults>) {
|
|
1276
1298
|
self.interProAnnotations = data
|
|
1277
1299
|
},
|
|
1278
1300
|
|
|
@@ -1318,24 +1340,25 @@ function stateModelFactory() {
|
|
|
1318
1340
|
/**
|
|
1319
1341
|
* #getter
|
|
1320
1342
|
*/
|
|
1321
|
-
get
|
|
1343
|
+
get labelWidthMap() {
|
|
1322
1344
|
const { rowHeight, leaves, treeMetadata, fontSize } = self
|
|
1323
1345
|
if (rowHeight <= 5) {
|
|
1324
|
-
return
|
|
1346
|
+
return new Map<string, number>()
|
|
1325
1347
|
}
|
|
1326
|
-
return
|
|
1327
|
-
(
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
fontSize,
|
|
1333
|
-
),
|
|
1334
|
-
),
|
|
1335
|
-
0,
|
|
1348
|
+
return new Map(
|
|
1349
|
+
leaves.map(node => {
|
|
1350
|
+
const { name } = node.data
|
|
1351
|
+
const displayName = treeMetadata[name]?.genome || name
|
|
1352
|
+
return [name, measureTextCanvas(displayName, fontSize)] as const
|
|
1353
|
+
}),
|
|
1336
1354
|
)
|
|
1337
1355
|
},
|
|
1338
1356
|
|
|
1357
|
+
get labelsWidth() {
|
|
1358
|
+
const widths = this.labelWidthMap
|
|
1359
|
+
return widths.size === 0 ? 0 : Math.max(...widths.values())
|
|
1360
|
+
},
|
|
1361
|
+
|
|
1339
1362
|
/**
|
|
1340
1363
|
* #getter
|
|
1341
1364
|
*/
|
|
@@ -1402,13 +1425,6 @@ function stateModelFactory() {
|
|
|
1402
1425
|
return self.msaAreaWidth < self.totalWidth
|
|
1403
1426
|
},
|
|
1404
1427
|
|
|
1405
|
-
/**
|
|
1406
|
-
* #getter
|
|
1407
|
-
*/
|
|
1408
|
-
get rowNamesSet() {
|
|
1409
|
-
return new Map(self.rowNames.map((r, idx) => [r, idx]))
|
|
1410
|
-
},
|
|
1411
|
-
|
|
1412
1428
|
/**
|
|
1413
1429
|
* #method
|
|
1414
1430
|
* Return a row-specific letter at a visible column, or undefined if gap.
|
|
@@ -1427,6 +1443,11 @@ function stateModelFactory() {
|
|
|
1427
1443
|
* Convert a visible column to a row-specific sequence position (0-based).
|
|
1428
1444
|
* Returns undefined if the position is a gap in the sequence.
|
|
1429
1445
|
*
|
|
1446
|
+
* CROSS-REPO CONTRACT: this and the sibling coordinate converters
|
|
1447
|
+
* (seqPosToVisibleCol, globalColToVisibleCol, seqPosToGlobalCol) are used
|
|
1448
|
+
* by jbrowse-plugin-protein3d to translate between alignment columns and
|
|
1449
|
+
* structure/sequence residue positions across gaps. Keep them stable.
|
|
1450
|
+
*
|
|
1430
1451
|
* @param rowName - The name of the row
|
|
1431
1452
|
* @param visibleCol - The visible column index
|
|
1432
1453
|
* @returns The sequence position (0-based), or undefined if it's a gap
|
|
@@ -1480,14 +1501,8 @@ function stateModelFactory() {
|
|
|
1480
1501
|
* @returns The global column index in the full MSA
|
|
1481
1502
|
*/
|
|
1482
1503
|
seqPosToGlobalCol(rowName: string, seqPos: number) {
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1485
|
-
return index !== undefined && rows[index]
|
|
1486
|
-
? seqPosToGlobalCol({
|
|
1487
|
-
row: rows[index][1],
|
|
1488
|
-
seqPos,
|
|
1489
|
-
})
|
|
1490
|
-
: 0
|
|
1504
|
+
const seq = self.rowMap.get(rowName)
|
|
1505
|
+
return seq ? seqPosToGlobalCol({ row: seq, seqPos }) : 0
|
|
1491
1506
|
},
|
|
1492
1507
|
|
|
1493
1508
|
/**
|
|
@@ -1506,30 +1521,6 @@ function stateModelFactory() {
|
|
|
1506
1521
|
}))
|
|
1507
1522
|
|
|
1508
1523
|
.views(self => ({
|
|
1509
|
-
/**
|
|
1510
|
-
* #getter
|
|
1511
|
-
* Returns information about the currently hovered cell
|
|
1512
|
-
*/
|
|
1513
|
-
get hoveredCell() {
|
|
1514
|
-
const { mouseCol, mouseRow } = self
|
|
1515
|
-
if (mouseCol === undefined || mouseRow === undefined) {
|
|
1516
|
-
return undefined
|
|
1517
|
-
}
|
|
1518
|
-
const rowName = self.rowNames[mouseRow]
|
|
1519
|
-
if (!rowName) {
|
|
1520
|
-
return undefined
|
|
1521
|
-
}
|
|
1522
|
-
const seq = self.columns[rowName]
|
|
1523
|
-
const base = seq?.[mouseCol]
|
|
1524
|
-
const seqPos = self.visibleColToSeqPosOneBased(rowName, mouseCol)
|
|
1525
|
-
return {
|
|
1526
|
-
rowName,
|
|
1527
|
-
col: mouseCol,
|
|
1528
|
-
base,
|
|
1529
|
-
seqPos,
|
|
1530
|
-
}
|
|
1531
|
-
},
|
|
1532
|
-
|
|
1533
1524
|
/**
|
|
1534
1525
|
* #getter
|
|
1535
1526
|
* widget width minus the tree area gives the space for the MSA
|
|
@@ -1650,7 +1641,7 @@ function stateModelFactory() {
|
|
|
1650
1641
|
self.setTreeFilehandle(undefined)
|
|
1651
1642
|
self.setMSAFilehandle(undefined)
|
|
1652
1643
|
self.setGFFFilehandle(undefined)
|
|
1653
|
-
self.setInterProAnnotations(
|
|
1644
|
+
self.setInterProAnnotations(undefined)
|
|
1654
1645
|
self.setShowDomains(false)
|
|
1655
1646
|
},
|
|
1656
1647
|
/**
|
|
@@ -1660,21 +1651,13 @@ function stateModelFactory() {
|
|
|
1660
1651
|
theme: Theme
|
|
1661
1652
|
includeMinimap?: boolean
|
|
1662
1653
|
includeTracks?: boolean
|
|
1663
|
-
exportType:
|
|
1654
|
+
exportType: 'entire' | 'viewport'
|
|
1664
1655
|
}) {
|
|
1665
1656
|
const { renderToSvg } = await import('./renderToSvg.tsx')
|
|
1666
1657
|
const html = await renderToSvg(self as MsaViewModel, opts)
|
|
1667
1658
|
const blob = new Blob([html], { type: 'image/svg+xml' })
|
|
1668
1659
|
saveAs(blob, 'image.svg')
|
|
1669
1660
|
},
|
|
1670
|
-
/**
|
|
1671
|
-
* #action
|
|
1672
|
-
* internal, used for drawing to canvas
|
|
1673
|
-
*/
|
|
1674
|
-
incrementRef() {
|
|
1675
|
-
self.nref++
|
|
1676
|
-
},
|
|
1677
|
-
|
|
1678
1661
|
initFilter(arg: string) {
|
|
1679
1662
|
if (!self.featureFilters.has(arg)) {
|
|
1680
1663
|
self.featureFilters.set(arg, true)
|