react-msaview 6.4.2 → 7.0.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/README.md +6 -5
- package/bundle/index.js +100 -101
- package/bundle/index.js.map +4 -4
- package/dist/components/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +10 -2
- package/dist/components/MSAViewer.js +90 -2
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.d.ts +3 -3
- package/dist/components/ResizeHandles.js +28 -21
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/dialogs/{InterProScanDialog.d.ts → AnnotationFileDialog.d.ts} +2 -2
- package/dist/components/dialogs/AnnotationFileDialog.js +92 -0
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -0
- package/dist/components/dialogs/FeatureDialog.js +13 -9
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/header/Header.js +4 -0
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderStatusArea.js +0 -2
- package/dist/components/header/HeaderStatusArea.js.map +1 -1
- package/dist/components/header/LoadWarnings.d.ts +6 -0
- package/dist/components/header/LoadWarnings.js +36 -0
- package/dist/components/header/LoadWarnings.js.map +1 -0
- package/dist/components/header/MultiAlignmentSelector.js +0 -0
- package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.js +9 -1
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/UnshareableDataWarning.d.ts +6 -0
- package/dist/components/header/UnshareableDataWarning.js +42 -0
- package/dist/components/header/UnshareableDataWarning.js.map +1 -0
- package/dist/components/header/getAnnotationsMenu.js +14 -14
- package/dist/components/header/getAnnotationsMenu.js.map +1 -1
- package/dist/components/import/ImportForm.js +1 -1
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +2 -6
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/import/util.js +3 -0
- package/dist/components/import/util.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +4 -5
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +13 -3
- package/dist/components/msa/msaRaster.js +173 -61
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +39 -1
- package/dist/components/msa/renderHighlights.js +71 -6
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSABlock.d.ts +1 -2
- package/dist/components/msa/renderMSABlock.js +24 -28
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +9 -30
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/tracks/TrackBlocks.js +10 -11
- package/dist/components/tracks/TrackBlocks.js.map +1 -1
- package/dist/components/tracks/drawTracks.d.ts +26 -8
- package/dist/components/tracks/drawTracks.js +123 -13
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +2 -3
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +4 -2
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeRuler.js +13 -3
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
- package/dist/components/tree/renderTreeCanvas.js +70 -30
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/scaleBar.d.ts +10 -0
- package/dist/components/tree/scaleBar.js +24 -0
- package/dist/components/tree/scaleBar.js.map +1 -0
- package/dist/constants.d.ts +3 -1
- package/dist/constants.js +18 -1
- package/dist/constants.js.map +1 -1
- package/dist/fetchUtils.d.ts +7 -7
- package/dist/fetchUtils.js +4 -32
- package/dist/fetchUtils.js.map +1 -1
- package/dist/hierarchy.d.ts +2 -1
- package/dist/hierarchy.js +16 -10
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/model/DataModel.js +5 -2
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model.d.ts +454 -177
- package/dist/model.js +1180 -511
- package/dist/model.js.map +1 -1
- package/dist/renderToStaticMarkup.d.ts +11 -0
- package/dist/renderToStaticMarkup.js +30 -7
- package/dist/renderToStaticMarkup.js.map +1 -1
- package/dist/renderToSvg.js +25 -19
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +25 -25
- package/dist/rowCoordinateCalculations.js +12 -36
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/seqPosToGlobalCol.js +3 -1
- package/dist/seqPosToGlobalCol.js.map +1 -1
- package/dist/svgTestUtil.d.ts +144 -82
- package/dist/types.d.ts +97 -3
- package/dist/util.d.ts +11 -4
- package/dist/util.js +30 -10
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wuss.d.ts +14 -0
- package/dist/wuss.js +55 -0
- package/dist/wuss.js.map +1 -0
- package/package.json +10 -8
- package/src/components/Loading.tsx +12 -2
- package/src/components/MSAViewer.tsx +106 -2
- package/src/components/ResizeHandles.tsx +31 -31
- package/src/components/dialogs/AnnotationFileDialog.tsx +162 -0
- package/src/components/dialogs/FeatureDialog.tsx +13 -9
- package/src/components/header/Header.tsx +4 -0
- package/src/components/header/HeaderStatusArea.tsx +0 -5
- package/src/components/header/LoadWarnings.tsx +49 -0
- package/src/components/header/MultiAlignmentSelector.tsx +0 -0
- package/src/components/header/TreeSettingsMenu.tsx +9 -1
- package/src/components/header/UnshareableDataWarning.tsx +58 -0
- package/src/components/header/getAnnotationsMenu.ts +16 -17
- package/src/components/import/ImportForm.tsx +2 -2
- package/src/components/import/ImportFormExamples.tsx +2 -6
- package/src/components/import/util.ts +3 -0
- package/src/components/msa/MSACanvasBlock.tsx +4 -5
- package/src/components/msa/msaRaster.ts +216 -59
- package/src/components/msa/renderHighlights.ts +103 -5
- package/src/components/msa/renderMSABlock.ts +25 -36
- package/src/components/msa/renderMSAMouseover.ts +9 -39
- package/src/components/renderCtx.ts +1 -0
- package/src/components/tracks/TrackBlocks.tsx +10 -13
- package/src/components/tracks/drawTracks.ts +165 -17
- package/src/components/tree/TreeCanvas.tsx +2 -3
- package/src/components/tree/TreeCanvasBlock.tsx +4 -2
- package/src/components/tree/TreeRuler.tsx +38 -3
- package/src/components/tree/renderTreeCanvas.ts +79 -35
- package/src/components/tree/scaleBar.ts +23 -0
- package/src/constants.ts +20 -1
- package/src/fetchUtils.ts +7 -40
- package/src/hierarchy.ts +20 -11
- package/src/index.ts +18 -2
- package/src/model/DataModel.ts +6 -2
- package/src/model.ts +1352 -616
- package/src/rawImports.d.ts +9 -0
- package/src/renderToStaticMarkup.ts +34 -7
- package/src/renderToSvg.tsx +33 -27
- package/src/rowCoordinateCalculations.ts +15 -59
- package/src/seqPosToGlobalCol.ts +3 -1
- package/src/types.ts +131 -4
- package/src/util.ts +31 -14
- package/src/version.ts +1 -1
- package/src/wuss.ts +62 -0
- package/dist/components/dialogs/InterProScanDialog.js +0 -137
- package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
- package/dist/components/dialogs/InterProScanFileDialog.d.ts +0 -7
- package/dist/components/dialogs/InterProScanFileDialog.js +0 -64
- package/dist/components/dialogs/InterProScanFileDialog.js.map +0 -1
- package/dist/launchInterProScan.d.ts +0 -13
- package/dist/launchInterProScan.js +0 -110
- package/dist/launchInterProScan.js.map +0 -1
- package/dist/useColorContrast.d.ts +0 -10
- package/dist/useColorContrast.js +0 -13
- package/dist/useColorContrast.js.map +0 -1
- package/src/clustalX.test.ts +0 -74
- package/src/colSpace.test.ts +0 -67
- package/src/collapseLogic.test.ts +0 -115
- package/src/colorSchemes.docs.test.ts +0 -57
- package/src/columnCounts.test.ts +0 -73
- package/src/columnStats.test.ts +0 -94
- package/src/columnTracks.test.ts +0 -97
- package/src/components/dialogs/InterProScanDialog.tsx +0 -214
- package/src/components/dialogs/InterProScanFileDialog.tsx +0 -138
- package/src/components/minimap/minimapLayout.test.ts +0 -71
- package/src/components/msa/msaRaster.test.ts +0 -145
- package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +0 -51
- package/src/components/tree/renderTreeCanvas.test.ts +0 -296
- package/src/dataInitialized.test.ts +0 -42
- package/src/domainBands.test.ts +0 -122
- package/src/domainLetterColor.test.ts +0 -80
- package/src/exportFileName.test.ts +0 -40
- package/src/fetchUtils.test.ts +0 -83
- package/src/flatToTree.test.ts +0 -51
- package/src/hierarchy.test.ts +0 -228
- package/src/highlights.test.ts +0 -70
- package/src/impgRender.test.tsx +0 -68
- package/src/largeTree.test.ts +0 -67
- package/src/launchInterProScan.ts +0 -167
- package/src/loadErrorDisplay.test.tsx +0 -102
- package/src/modelFilehandleLoaders.test.ts +0 -137
- package/src/modelReset.test.ts +0 -120
- package/src/neighborJoining.test.ts +0 -164
- package/src/parseAsn1.test.ts +0 -36
- package/src/propertyConservation.test.ts +0 -36
- package/src/renderColumnTracksSvg.test.tsx +0 -108
- package/src/renderDomainsSvg.test.tsx +0 -118
- package/src/renderHighlightsSvg.test.tsx +0 -85
- package/src/renderMinimapSvg.test.tsx +0 -62
- package/src/renderRasterSvg.test.tsx +0 -144
- package/src/renderSequenceLogoSvg.test.tsx +0 -165
- package/src/renderToSvg.test.tsx +0 -65
- package/src/reset.test.ts +0 -61
- package/src/rowCoordinateCalculations.test.ts +0 -211
- package/src/rowSpace.test.ts +0 -46
- package/src/segmentOverlay.test.ts +0 -70
- package/src/seqPosToGlobalCol.test.ts +0 -59
- package/src/sequenceLogo.test.ts +0 -88
- package/src/setFontSize.test.ts +0 -33
- package/src/stripDefault.test.ts +0 -48
- package/src/stripDefaultSnapshot.test.ts +0 -87
- package/src/treeMetadata.test.ts +0 -39
- package/src/useCanvasAutorun.test.tsx +0 -98
- package/src/useColorContrast.ts +0 -18
- package/src/viewChrome.test.tsx +0 -134
- package/src/wheelZoom.test.ts +0 -46
|
@@ -1,31 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
calcDepthToLeaf,
|
|
3
|
-
forEachDescendant,
|
|
4
|
-
forEachLink,
|
|
5
|
-
} from '../../hierarchy.ts'
|
|
1
|
+
import { calcDepthToLeaf } from '../../hierarchy.ts'
|
|
6
2
|
import { setFontSize } from '../../setFontSize.ts'
|
|
7
3
|
import { getVisibleLeaves } from '../getVisibleLeaves.ts'
|
|
8
4
|
import {
|
|
9
5
|
drawHighlightLabel,
|
|
6
|
+
highlightLabelHeight,
|
|
10
7
|
highlightRowFill,
|
|
11
8
|
} from '../msa/renderHighlights.ts'
|
|
12
9
|
|
|
13
10
|
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
14
11
|
import type { MsaViewModel } from '../../model.ts'
|
|
12
|
+
import type { LabelBox } from '../msa/renderHighlights.ts'
|
|
15
13
|
import type { RenderCtx } from '../renderCtx.ts'
|
|
16
14
|
import type { ClickMapIndex } from './clickMap.ts'
|
|
17
15
|
import type { Theme } from '@mui/material'
|
|
18
16
|
|
|
19
|
-
export const padding = 600
|
|
20
|
-
|
|
21
|
-
const extendBounds = 5
|
|
22
17
|
const radius = 2.5
|
|
23
18
|
const d = radius * 2
|
|
24
19
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
// How far outside a block a node's row-center can sit and still draw into it: a
|
|
21
|
+
// collapsed triangle reaches 0.42 rows each way, a tip label an em, and a
|
|
22
|
+
// highlight label its whole box. Five pixels clipped all three at block edges.
|
|
23
|
+
function blockPad(model: MsaViewModel) {
|
|
24
|
+
return Math.max(model.rowHeight, highlightLabelHeight)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// whether a node's row-center y falls within the block being drawn, padded so
|
|
28
|
+
// bubbles/labels/triangles straddling the edge still render
|
|
29
|
+
function inYBlock(y: number, offsetY: number, by: number, pad: number) {
|
|
30
|
+
return y > offsetY - pad && y < offsetY + by + pad
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Every node whose subtree reaches into the block, skipping the subtrees that
|
|
35
|
+
* do not. A subtree's leaves are one contiguous run of rows -- clusterLayout
|
|
36
|
+
* records the run as xMin/xMax -- so a block of a 200k-tip tree visits a few
|
|
37
|
+
* dozen nodes instead of every one of them, three times over.
|
|
38
|
+
*
|
|
39
|
+
* A link out of a visited node is still drawn even when the child it reaches is
|
|
40
|
+
* pruned, which is what keeps a branch that crosses the block from vanishing.
|
|
41
|
+
*/
|
|
42
|
+
function forEachNodeInBlock(
|
|
43
|
+
root: HierarchyNode,
|
|
44
|
+
offsetY: number,
|
|
45
|
+
by: number,
|
|
46
|
+
pad: number,
|
|
47
|
+
cb: (node: HierarchyNode) => void,
|
|
48
|
+
) {
|
|
49
|
+
const top = offsetY - pad
|
|
50
|
+
const bottom = offsetY + by + pad
|
|
51
|
+
const stack = [root]
|
|
52
|
+
while (stack.length > 0) {
|
|
53
|
+
const node = stack.pop()!
|
|
54
|
+
if (node.xMax! < top || node.xMin! > bottom) {
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
cb(node)
|
|
58
|
+
if (node.children) {
|
|
59
|
+
for (const child of node.children) {
|
|
60
|
+
stack.push(child)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
29
64
|
}
|
|
30
65
|
|
|
31
66
|
// Calculate node x-coordinate for both phylogram (with branch lengths) and
|
|
@@ -69,26 +104,30 @@ function renderTree({
|
|
|
69
104
|
const { hierarchy, showBranchLenEffective: showBranchLen, blockSize } = model
|
|
70
105
|
const by = blockSizeYOverride ?? blockSize
|
|
71
106
|
ctx.strokeStyle = theme.palette.text.primary
|
|
72
|
-
|
|
107
|
+
forEachNodeInBlock(hierarchy, offsetY, by, blockPad(model), source => {
|
|
73
108
|
const sy = source.x!
|
|
74
|
-
const ty = target.x!
|
|
75
|
-
const tx = getNodeX(target, showBranchLen, tipX, maxDepthToLeaf)
|
|
76
109
|
const sx = getNodeX(source, showBranchLen, tipX, maxDepthToLeaf)
|
|
77
|
-
if (
|
|
110
|
+
if (sx === undefined || !source.children) {
|
|
78
111
|
return
|
|
79
112
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
113
|
+
for (const target of source.children) {
|
|
114
|
+
const ty = target.x!
|
|
115
|
+
const tx = getNodeX(target, showBranchLen, tipX, maxDepthToLeaf)
|
|
116
|
+
if (tx === undefined) {
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
const y1 = Math.min(sy, ty)
|
|
120
|
+
const y2 = Math.max(sy, ty)
|
|
121
|
+
// 1d line intersection to check if line crosses block at all, this is an
|
|
122
|
+
// optimization that allows us to skip drawing most tree links outside the
|
|
123
|
+
// block
|
|
124
|
+
if (offsetY + by >= y1 && y2 >= offsetY) {
|
|
125
|
+
ctx.beginPath()
|
|
126
|
+
ctx.moveTo(sx, sy)
|
|
127
|
+
ctx.lineTo(sx, ty)
|
|
128
|
+
ctx.lineTo(tx, ty)
|
|
129
|
+
ctx.stroke()
|
|
130
|
+
}
|
|
92
131
|
}
|
|
93
132
|
})
|
|
94
133
|
}
|
|
@@ -129,12 +168,12 @@ function renderCollapsedTriangles({
|
|
|
129
168
|
}
|
|
130
169
|
const by = blockSizeYOverride ?? blockSize
|
|
131
170
|
const halfHeight = Math.max(2, rowHeight * 0.42)
|
|
132
|
-
|
|
171
|
+
forEachNodeInBlock(hierarchy, offsetY, by, blockPad(model), node => {
|
|
133
172
|
const { id, name } = node.data
|
|
134
173
|
if (collapsedSet.has(id)) {
|
|
135
174
|
const apexX = getNodeX(node, showBranchLen, tipX, maxDepthToLeaf)
|
|
136
175
|
const y = node.x!
|
|
137
|
-
const inBlock = inYBlock(y, offsetY, by)
|
|
176
|
+
const inBlock = inYBlock(y, offsetY, by, blockPad(model))
|
|
138
177
|
// in cladogram mode the hidden tips align at the right edge, otherwise use
|
|
139
178
|
// the branch-length extent recorded in the model's hierarchy getter
|
|
140
179
|
const baseX = showBranchLen ? (node.collapsedTipXFar ?? tipX) : tipX
|
|
@@ -205,7 +244,7 @@ function renderNodeBubbles({
|
|
|
205
244
|
marginLeft: ml,
|
|
206
245
|
} = model
|
|
207
246
|
const by = blockSizeYOverride ?? blockSize
|
|
208
|
-
|
|
247
|
+
forEachNodeInBlock(hierarchy, offsetY, by, blockPad(model), node => {
|
|
209
248
|
const x = getNodeX(node, showBranchLen, tipX, maxDepthToLeaf)
|
|
210
249
|
if (x === undefined) {
|
|
211
250
|
return
|
|
@@ -213,7 +252,7 @@ function renderNodeBubbles({
|
|
|
213
252
|
const { data } = node
|
|
214
253
|
const y = node.x!
|
|
215
254
|
const { id, name } = data
|
|
216
|
-
if (node.height >= 1 && inYBlock(y, offsetY, by)) {
|
|
255
|
+
if (node.height >= 1 && inYBlock(y, offsetY, by, blockPad(model))) {
|
|
217
256
|
const isCollapsed = collapsedSet.has(id)
|
|
218
257
|
if (draw) {
|
|
219
258
|
ctx.strokeStyle = 'black'
|
|
@@ -279,6 +318,7 @@ function renderTreeLabels({
|
|
|
279
318
|
// labels only exist for leaves, which are laid out top to bottom, so take the
|
|
280
319
|
// same slice the MSA renderer uses instead of walking every tip per block
|
|
281
320
|
const visibleLeaves = getVisibleLeaves({ model, offsetY, blockSizeY: by })
|
|
321
|
+
const pad = blockPad(model)
|
|
282
322
|
const emHeight = ctx.measureText('M').width
|
|
283
323
|
if (labelsAlignRight) {
|
|
284
324
|
ctx.textAlign = 'right'
|
|
@@ -297,7 +337,7 @@ function renderTreeLabels({
|
|
|
297
337
|
// a collapsed clade is drawn as a triangle + tip count; suppress its leaf
|
|
298
338
|
// label when the "name" is just the auto-generated internal-node id
|
|
299
339
|
const isAnonymousCollapsed = collapsedSet.has(id) && name === id
|
|
300
|
-
if (!isAnonymousCollapsed && inYBlock(y, offsetY, by)) {
|
|
340
|
+
if (!isAnonymousCollapsed && inYBlock(y, offsetY, by, pad)) {
|
|
301
341
|
// note: +rowHeight/4 matches with -rowHeight/4 in msa
|
|
302
342
|
const yp = y + fontSize / 4
|
|
303
343
|
let xp = 0
|
|
@@ -376,11 +416,13 @@ function renderRowHighlights({
|
|
|
376
416
|
blockSize,
|
|
377
417
|
} = model
|
|
378
418
|
const by = blockSizeYOverride ?? blockSize
|
|
419
|
+
const pad = blockPad(model)
|
|
420
|
+
const placed: LabelBox[] = []
|
|
379
421
|
for (const { rowIndices, label, color } of resolvedHighlights) {
|
|
380
422
|
ctx.fillStyle = color ?? highlightRowFill
|
|
381
423
|
for (const index of rowIndices) {
|
|
382
424
|
const y = index * rowHeight
|
|
383
|
-
if (inYBlock(y + rowHeight / 2, offsetY, by)) {
|
|
425
|
+
if (inYBlock(y + rowHeight / 2, offsetY, by, pad)) {
|
|
384
426
|
ctx.fillRect(-marginLeft, y, treeAreaWidth, rowHeight)
|
|
385
427
|
}
|
|
386
428
|
}
|
|
@@ -388,7 +430,7 @@ function renderRowHighlights({
|
|
|
388
430
|
if (
|
|
389
431
|
label &&
|
|
390
432
|
rowIndices.length &&
|
|
391
|
-
inYBlock(first * rowHeight, offsetY, by)
|
|
433
|
+
inYBlock(first * rowHeight, offsetY, by, pad)
|
|
392
434
|
) {
|
|
393
435
|
drawHighlightLabel({
|
|
394
436
|
ctx,
|
|
@@ -397,6 +439,8 @@ function renderRowHighlights({
|
|
|
397
439
|
x: -marginLeft + 2,
|
|
398
440
|
y: first * rowHeight,
|
|
399
441
|
spanWidth: treeAreaWidth,
|
|
442
|
+
bounds: { min: -marginLeft, max: treeAreaWidth - marginLeft },
|
|
443
|
+
placed,
|
|
400
444
|
})
|
|
401
445
|
}
|
|
402
446
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A round branch-length step and its width in pixels: the largest 1/2/5 x 10^n
|
|
3
|
+
* substitutions-per-site that fits in `maxPx`, aiming at about half of it.
|
|
4
|
+
* Returns undefined in cladogram mode, where the x-positions carry no length.
|
|
5
|
+
*/
|
|
6
|
+
export function scaleBarLength(pxPerUnit: number, maxPx: number) {
|
|
7
|
+
if (pxPerUnit <= 0 || maxPx < 20) {
|
|
8
|
+
return undefined
|
|
9
|
+
}
|
|
10
|
+
const target = maxPx / 2 / pxPerUnit
|
|
11
|
+
const magnitude = 10 ** Math.floor(Math.log10(target))
|
|
12
|
+
const mantissa = target / magnitude
|
|
13
|
+
const step = (mantissa >= 5 ? 5 : mantissa >= 2 ? 2 : 1) * magnitude
|
|
14
|
+
const px = step * pxPerUnit
|
|
15
|
+
return px > maxPx
|
|
16
|
+
? undefined
|
|
17
|
+
: {
|
|
18
|
+
step,
|
|
19
|
+
px,
|
|
20
|
+
// a length of 0.02 reads as 0.02, not 0.020000000000000004
|
|
21
|
+
label: `${Number(step.toPrecision(2))}`,
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/constants.ts
CHANGED
|
@@ -5,7 +5,11 @@ export const defaultHeight = 550
|
|
|
5
5
|
export const defaultScrollX = 0
|
|
6
6
|
export const defaultScrollY = 0
|
|
7
7
|
export const defaultCurrentAlignment = 0
|
|
8
|
-
|
|
8
|
+
// Annotations are loaded on purpose -- a gff filehandle, a file picked in the
|
|
9
|
+
// dialog -- so they are drawn unless the reader has said otherwise. Since only
|
|
10
|
+
// the "no" is stored, a shared link with the overlay hidden reopens hidden, and
|
|
11
|
+
// one that never mentions it opens showing whatever annotations it carries.
|
|
12
|
+
export const defaultShowDomains = true
|
|
9
13
|
export const defaultShowDomainLegend = true
|
|
10
14
|
export const defaultHideGaps = true
|
|
11
15
|
export const defaultAllowedGappyness = 100
|
|
@@ -61,3 +65,18 @@ export const defaultTreeWidth = 300
|
|
|
61
65
|
export const defaultShowBranchLen = true
|
|
62
66
|
export const defaultDrawTree = true
|
|
63
67
|
export const defaultDrawNodeBubbles = true
|
|
68
|
+
|
|
69
|
+
// Neighbor joining refuses above this many rows. The distance matrix is O(n^2*L)
|
|
70
|
+
// and the join loop O(n^3), both on the main thread with no progress and no
|
|
71
|
+
// cancel: 400 rows measured 1.5s, 800 rows 10s, and 1600 would be about seventy.
|
|
72
|
+
// See agent-docs/ideas/neighbor-joining-scaling.md for why the RapidNJ fix is a
|
|
73
|
+
// real algorithm change rather than a port, and why capping is the honest answer
|
|
74
|
+
// -- NJ on thousands of sequences is the wrong tool however fast it runs.
|
|
75
|
+
export const maxNeighborJoiningRows = 500
|
|
76
|
+
|
|
77
|
+
// The largest inline document the snapshot carries. Past this a pasted or
|
|
78
|
+
// locally-opened file stays in the live model and leaves the snapshot, since a
|
|
79
|
+
// session or a shared URL holding megabytes of sequence is not a link anyone
|
|
80
|
+
// can send. `unshareableData` reads the same number, so what the viewer warns
|
|
81
|
+
// about and what the snapshot drops cannot drift apart.
|
|
82
|
+
export const maxInlineSnapshotBytes = 50_000
|
package/src/fetchUtils.ts
CHANGED
|
@@ -19,7 +19,6 @@ function statusMessageText(status: RpcStatus | undefined) {
|
|
|
19
19
|
|
|
20
20
|
export interface FetchStatus {
|
|
21
21
|
msg: string
|
|
22
|
-
url?: string
|
|
23
22
|
onCancel?: () => void
|
|
24
23
|
}
|
|
25
24
|
|
|
@@ -34,14 +33,18 @@ type ProgressFetcher = (
|
|
|
34
33
|
* Fetch text from a filehandle while reporting download/unzip progress through
|
|
35
34
|
* setStatus, and wiring a Cancel handler that aborts the underlying request.
|
|
36
35
|
* The status is always cleared once the fetch settles. The fetcher is injectable
|
|
37
|
-
* for testing
|
|
36
|
+
* for testing, and so is the AbortController: a caller that can supersede its
|
|
37
|
+
* own request -- the model's loaders, when the filehandle changes under them --
|
|
38
|
+
* holds one and aborts the download it no longer wants.
|
|
38
39
|
*/
|
|
39
40
|
export async function fetchTextWithProgress(
|
|
40
41
|
loc: Filehandle,
|
|
41
42
|
setStatus: (status?: FetchStatus) => void,
|
|
42
|
-
|
|
43
|
+
{
|
|
44
|
+
fetcher = fetchAndMaybeUnzipText,
|
|
45
|
+
controller = new AbortController(),
|
|
46
|
+
}: { fetcher?: ProgressFetcher; controller?: AbortController } = {},
|
|
43
47
|
) {
|
|
44
|
-
const controller = new AbortController()
|
|
45
48
|
try {
|
|
46
49
|
return await fetcher(loc, {
|
|
47
50
|
signal: controller.signal,
|
|
@@ -64,42 +67,6 @@ export async function fetchTextWithProgress(
|
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
export async function myfetch(url: string, args?: RequestInit) {
|
|
68
|
-
const response = await fetch(url, args)
|
|
69
|
-
|
|
70
|
-
if (!response.ok) {
|
|
71
|
-
throw new Error(
|
|
72
|
-
`HTTP ${response.status} fetching ${url} ${await response.text()}`,
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return response
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function textfetch(url: string, args?: RequestInit) {
|
|
80
|
-
const response = await myfetch(url, args)
|
|
81
|
-
return response.text()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export async function jsonfetch<T>(url: string, args?: RequestInit) {
|
|
85
|
-
const response = await myfetch(url, args)
|
|
86
|
-
return response.json() as T
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function timeout(time: number, signal?: AbortSignal) {
|
|
90
|
-
return new Promise<void>((resolve, reject) => {
|
|
91
|
-
if (signal?.aborted) {
|
|
92
|
-
reject(new DOMException('Aborted', 'AbortError'))
|
|
93
|
-
} else {
|
|
94
|
-
const id = setTimeout(resolve, time)
|
|
95
|
-
signal?.addEventListener('abort', () => {
|
|
96
|
-
clearTimeout(id)
|
|
97
|
-
reject(new DOMException('Aborted', 'AbortError'))
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
|
|
103
70
|
export function isAbortError(e: unknown) {
|
|
104
71
|
return e instanceof DOMException && e.name === 'AbortError'
|
|
105
72
|
}
|
package/src/hierarchy.ts
CHANGED
|
@@ -25,6 +25,11 @@ export interface HierarchyNode<T = NodeWithIds> extends CoreHierarchyNode<T> {
|
|
|
25
25
|
parent: HierarchyNode<T> | null
|
|
26
26
|
x?: number
|
|
27
27
|
y?: number
|
|
28
|
+
// the span of `x` over this node's subtree, set by clusterLayout. Leaves are
|
|
29
|
+
// laid out in order, so a subtree occupies one contiguous run of rows and a
|
|
30
|
+
// renderer can skip the whole thing when the run misses its block.
|
|
31
|
+
xMin?: number
|
|
32
|
+
xMax?: number
|
|
28
33
|
len?: number
|
|
29
34
|
depthToLeaf?: number
|
|
30
35
|
_children?: HierarchyNode<T>[] | null
|
|
@@ -75,16 +80,26 @@ export function clusterLayout<T>(
|
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
// x of an internal node is the mean of its children's x, so process in
|
|
78
|
-
// post-order (children before parents)
|
|
83
|
+
// post-order (children before parents). The subtree span comes from the same
|
|
84
|
+
// pass, for the block culls in the tree renderer.
|
|
79
85
|
const nodes = descendants(root)
|
|
80
86
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
81
87
|
const node = nodes[i]!
|
|
82
88
|
if (node.children) {
|
|
83
89
|
let sum = 0
|
|
90
|
+
let min = Infinity
|
|
91
|
+
let max = -Infinity
|
|
84
92
|
for (const child of node.children) {
|
|
85
93
|
sum += child.x!
|
|
94
|
+
min = Math.min(min, child.xMin!)
|
|
95
|
+
max = Math.max(max, child.xMax!)
|
|
86
96
|
}
|
|
87
97
|
node.x = sum / node.children.length
|
|
98
|
+
node.xMin = min
|
|
99
|
+
node.xMax = max
|
|
100
|
+
} else {
|
|
101
|
+
node.xMin = node.x
|
|
102
|
+
node.xMax = node.x
|
|
88
103
|
}
|
|
89
104
|
}
|
|
90
105
|
|
|
@@ -172,15 +187,9 @@ export function calcDepthToLeaf<T>(node: HierarchyNode<T>): number {
|
|
|
172
187
|
return node.depthToLeaf!
|
|
173
188
|
}
|
|
174
189
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
maxLen = Math.max(maxLen, n.len || 0)
|
|
179
|
-
}
|
|
180
|
-
return maxLen
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Max root-to-leaf sum of branch lengths within the subtree at d
|
|
190
|
+
// Max root-to-leaf sum of branch lengths within the subtree at d. Negative
|
|
191
|
+
// lengths are floored at zero, the same as setBrLength does when it lays the
|
|
192
|
+
// tree out, so the extent this reports is the one that gets drawn
|
|
184
193
|
export function maxLength(d: HierarchyNode): number {
|
|
185
194
|
const nodes = descendants(d)
|
|
186
195
|
const pathLen = new Map<HierarchyNode, number>()
|
|
@@ -192,7 +201,7 @@ export function maxLength(d: HierarchyNode): number {
|
|
|
192
201
|
childMax = Math.max(childMax, pathLen.get(child)!)
|
|
193
202
|
}
|
|
194
203
|
}
|
|
195
|
-
pathLen.set(n, (n.data.length || 0) + childMax)
|
|
204
|
+
pathLen.set(n, Math.max(n.data.length || 0, 0) + childMax)
|
|
196
205
|
}
|
|
197
206
|
return pathLen.get(d)!
|
|
198
207
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// PUBLIC API — consumed by external JBrowse 2 plugins. Changing or removing any
|
|
2
2
|
// export here is a breaking change for downstream repos:
|
|
3
|
-
// - jbrowse-plugin-msaview
|
|
4
|
-
// - jbrowse-plugin-
|
|
3
|
+
// - jbrowse-plugin-msaview (wraps MSAModelF + MSAView into a JBrowse view)
|
|
4
|
+
// - jbrowse-plugin-tview (the same, for a tree-first view)
|
|
5
5
|
// Those plugins also reach into the MsaViewModel instance at runtime; the model
|
|
6
6
|
// members they rely on (e.g. mouseCol/setMousePos, setHighlightedColumns,
|
|
7
7
|
// seqPosToVisibleCol/visibleColToSeqPos) are flagged inline in model.ts.
|
|
8
|
+
// jbrowse-plugin-protein3d no longer depends on this package at all: it reads
|
|
9
|
+
// the msaview plugin's model through a structural type of its own.
|
|
8
10
|
export { renderToSvg } from './renderToSvg.tsx'
|
|
9
11
|
export type { ExportSvgOptions } from './renderToSvg.tsx'
|
|
10
12
|
// renderToSvg outside a browser needs DOM bits jsdom omits; react-msaview-cli
|
|
@@ -13,6 +15,10 @@ export {
|
|
|
13
15
|
CHAR_WIDTH_RATIO,
|
|
14
16
|
installHeadlessRenderEnv,
|
|
15
17
|
} from './headlessRenderEnv.ts'
|
|
18
|
+
// the row count past which calculateNeighborJoiningTreeFromMSA refuses, so a
|
|
19
|
+
// host can gate its own menu item on the same number instead of offering a
|
|
20
|
+
// button that throws
|
|
21
|
+
export { maxNeighborJoiningRows } from './constants.ts'
|
|
16
22
|
export { default as MSAView } from './components/Loading.tsx'
|
|
17
23
|
export { default as MSAViewer } from './components/MSAViewer.tsx'
|
|
18
24
|
export { type MsaViewModel, default as MSAModelF } from './model.ts'
|
|
@@ -29,15 +35,25 @@ export type { ColumnCounts } from './columnCounts.ts'
|
|
|
29
35
|
export type {
|
|
30
36
|
Accession,
|
|
31
37
|
Annotation,
|
|
38
|
+
Arc,
|
|
39
|
+
ArcSpec,
|
|
40
|
+
ArcTrackModel,
|
|
32
41
|
BasicTrack,
|
|
33
42
|
BasicTrackModel,
|
|
34
43
|
ColumnTrackSpec,
|
|
35
44
|
DomainBand,
|
|
36
45
|
Highlight,
|
|
37
46
|
Node,
|
|
47
|
+
MappedStructure,
|
|
38
48
|
NodeWithIds,
|
|
39
49
|
NodeWithIdsAndLength,
|
|
50
|
+
ResidueMapping,
|
|
51
|
+
ResidueMappingProblem,
|
|
52
|
+
ResidueSegment,
|
|
40
53
|
ResolvedHighlight,
|
|
54
|
+
RowResidue,
|
|
55
|
+
StructureResidue,
|
|
41
56
|
TextTrackModel,
|
|
42
57
|
TidyDomainAnnotation,
|
|
58
|
+
UnshareableData,
|
|
43
59
|
} from './types.ts'
|
package/src/model/DataModel.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { types } from '@jbrowse/mobx-state-tree'
|
|
2
2
|
|
|
3
|
+
import { maxInlineSnapshotBytes } from '../constants.ts'
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* #stateModel DataModel
|
|
5
7
|
* the data stored for the model. this is sometimes temporary in the case that
|
|
@@ -56,11 +58,13 @@ export function DataModelF() {
|
|
|
56
58
|
.postProcessSnapshot(snap =>
|
|
57
59
|
// a large document is dropped from the snapshot rather than inlined
|
|
58
60
|
// into a session or a shared URL, and every field here holds one, so
|
|
59
|
-
// the rule is the same for all of them
|
|
61
|
+
// the rule is the same for all of them. The parent model's
|
|
62
|
+
// `unshareableData` reports what this drops, since dropping it silently
|
|
63
|
+
// is how a copied link comes to open an empty viewer
|
|
60
64
|
Object.fromEntries(
|
|
61
65
|
Object.entries(snap).map(([key, text]) => [
|
|
62
66
|
key,
|
|
63
|
-
text && text.length >
|
|
67
|
+
text && text.length > maxInlineSnapshotBytes ? undefined : text,
|
|
64
68
|
]),
|
|
65
69
|
),
|
|
66
70
|
)
|