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.
Files changed (212) hide show
  1. package/README.md +6 -5
  2. package/bundle/index.js +100 -101
  3. package/bundle/index.js.map +4 -4
  4. package/dist/components/Loading.js +2 -2
  5. package/dist/components/Loading.js.map +1 -1
  6. package/dist/components/MSAViewer.d.ts +10 -2
  7. package/dist/components/MSAViewer.js +90 -2
  8. package/dist/components/MSAViewer.js.map +1 -1
  9. package/dist/components/ResizeHandles.d.ts +3 -3
  10. package/dist/components/ResizeHandles.js +28 -21
  11. package/dist/components/ResizeHandles.js.map +1 -1
  12. package/dist/components/dialogs/{InterProScanDialog.d.ts → AnnotationFileDialog.d.ts} +2 -2
  13. package/dist/components/dialogs/AnnotationFileDialog.js +92 -0
  14. package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -0
  15. package/dist/components/dialogs/FeatureDialog.js +13 -9
  16. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  17. package/dist/components/header/Header.js +4 -0
  18. package/dist/components/header/Header.js.map +1 -1
  19. package/dist/components/header/HeaderStatusArea.js +0 -2
  20. package/dist/components/header/HeaderStatusArea.js.map +1 -1
  21. package/dist/components/header/LoadWarnings.d.ts +6 -0
  22. package/dist/components/header/LoadWarnings.js +36 -0
  23. package/dist/components/header/LoadWarnings.js.map +1 -0
  24. package/dist/components/header/MultiAlignmentSelector.js +0 -0
  25. package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
  26. package/dist/components/header/TreeSettingsMenu.js +9 -1
  27. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  28. package/dist/components/header/UnshareableDataWarning.d.ts +6 -0
  29. package/dist/components/header/UnshareableDataWarning.js +42 -0
  30. package/dist/components/header/UnshareableDataWarning.js.map +1 -0
  31. package/dist/components/header/getAnnotationsMenu.js +14 -14
  32. package/dist/components/header/getAnnotationsMenu.js.map +1 -1
  33. package/dist/components/import/ImportForm.js +1 -1
  34. package/dist/components/import/ImportForm.js.map +1 -1
  35. package/dist/components/import/ImportFormExamples.js +2 -6
  36. package/dist/components/import/ImportFormExamples.js.map +1 -1
  37. package/dist/components/import/util.js +3 -0
  38. package/dist/components/import/util.js.map +1 -1
  39. package/dist/components/msa/MSACanvasBlock.js +4 -5
  40. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  41. package/dist/components/msa/msaRaster.d.ts +13 -3
  42. package/dist/components/msa/msaRaster.js +173 -61
  43. package/dist/components/msa/msaRaster.js.map +1 -1
  44. package/dist/components/msa/renderHighlights.d.ts +39 -1
  45. package/dist/components/msa/renderHighlights.js +71 -6
  46. package/dist/components/msa/renderHighlights.js.map +1 -1
  47. package/dist/components/msa/renderMSABlock.d.ts +1 -2
  48. package/dist/components/msa/renderMSABlock.js +24 -28
  49. package/dist/components/msa/renderMSABlock.js.map +1 -1
  50. package/dist/components/msa/renderMSAMouseover.js +9 -30
  51. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  52. package/dist/components/renderCtx.d.ts +1 -1
  53. package/dist/components/tracks/TrackBlocks.js +10 -11
  54. package/dist/components/tracks/TrackBlocks.js.map +1 -1
  55. package/dist/components/tracks/drawTracks.d.ts +26 -8
  56. package/dist/components/tracks/drawTracks.js +123 -13
  57. package/dist/components/tracks/drawTracks.js.map +1 -1
  58. package/dist/components/tree/TreeCanvas.js +2 -3
  59. package/dist/components/tree/TreeCanvas.js.map +1 -1
  60. package/dist/components/tree/TreeCanvasBlock.js +4 -2
  61. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  62. package/dist/components/tree/TreeRuler.js +13 -3
  63. package/dist/components/tree/TreeRuler.js.map +1 -1
  64. package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
  65. package/dist/components/tree/renderTreeCanvas.js +70 -30
  66. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  67. package/dist/components/tree/scaleBar.d.ts +10 -0
  68. package/dist/components/tree/scaleBar.js +24 -0
  69. package/dist/components/tree/scaleBar.js.map +1 -0
  70. package/dist/constants.d.ts +3 -1
  71. package/dist/constants.js +18 -1
  72. package/dist/constants.js.map +1 -1
  73. package/dist/fetchUtils.d.ts +7 -7
  74. package/dist/fetchUtils.js +4 -32
  75. package/dist/fetchUtils.js.map +1 -1
  76. package/dist/hierarchy.d.ts +2 -1
  77. package/dist/hierarchy.js +16 -10
  78. package/dist/hierarchy.js.map +1 -1
  79. package/dist/index.d.ts +2 -1
  80. package/dist/index.js +8 -2
  81. package/dist/index.js.map +1 -1
  82. package/dist/model/DataModel.js +5 -2
  83. package/dist/model/DataModel.js.map +1 -1
  84. package/dist/model.d.ts +454 -177
  85. package/dist/model.js +1180 -511
  86. package/dist/model.js.map +1 -1
  87. package/dist/renderToStaticMarkup.d.ts +11 -0
  88. package/dist/renderToStaticMarkup.js +30 -7
  89. package/dist/renderToStaticMarkup.js.map +1 -1
  90. package/dist/renderToSvg.js +25 -19
  91. package/dist/renderToSvg.js.map +1 -1
  92. package/dist/rowCoordinateCalculations.d.ts +25 -25
  93. package/dist/rowCoordinateCalculations.js +12 -36
  94. package/dist/rowCoordinateCalculations.js.map +1 -1
  95. package/dist/seqPosToGlobalCol.js +3 -1
  96. package/dist/seqPosToGlobalCol.js.map +1 -1
  97. package/dist/svgTestUtil.d.ts +144 -82
  98. package/dist/types.d.ts +97 -3
  99. package/dist/util.d.ts +11 -4
  100. package/dist/util.js +30 -10
  101. package/dist/util.js.map +1 -1
  102. package/dist/version.d.ts +1 -1
  103. package/dist/version.js +1 -1
  104. package/dist/wuss.d.ts +14 -0
  105. package/dist/wuss.js +55 -0
  106. package/dist/wuss.js.map +1 -0
  107. package/package.json +10 -8
  108. package/src/components/Loading.tsx +12 -2
  109. package/src/components/MSAViewer.tsx +106 -2
  110. package/src/components/ResizeHandles.tsx +31 -31
  111. package/src/components/dialogs/AnnotationFileDialog.tsx +162 -0
  112. package/src/components/dialogs/FeatureDialog.tsx +13 -9
  113. package/src/components/header/Header.tsx +4 -0
  114. package/src/components/header/HeaderStatusArea.tsx +0 -5
  115. package/src/components/header/LoadWarnings.tsx +49 -0
  116. package/src/components/header/MultiAlignmentSelector.tsx +0 -0
  117. package/src/components/header/TreeSettingsMenu.tsx +9 -1
  118. package/src/components/header/UnshareableDataWarning.tsx +58 -0
  119. package/src/components/header/getAnnotationsMenu.ts +16 -17
  120. package/src/components/import/ImportForm.tsx +2 -2
  121. package/src/components/import/ImportFormExamples.tsx +2 -6
  122. package/src/components/import/util.ts +3 -0
  123. package/src/components/msa/MSACanvasBlock.tsx +4 -5
  124. package/src/components/msa/msaRaster.ts +216 -59
  125. package/src/components/msa/renderHighlights.ts +103 -5
  126. package/src/components/msa/renderMSABlock.ts +25 -36
  127. package/src/components/msa/renderMSAMouseover.ts +9 -39
  128. package/src/components/renderCtx.ts +1 -0
  129. package/src/components/tracks/TrackBlocks.tsx +10 -13
  130. package/src/components/tracks/drawTracks.ts +165 -17
  131. package/src/components/tree/TreeCanvas.tsx +2 -3
  132. package/src/components/tree/TreeCanvasBlock.tsx +4 -2
  133. package/src/components/tree/TreeRuler.tsx +38 -3
  134. package/src/components/tree/renderTreeCanvas.ts +79 -35
  135. package/src/components/tree/scaleBar.ts +23 -0
  136. package/src/constants.ts +20 -1
  137. package/src/fetchUtils.ts +7 -40
  138. package/src/hierarchy.ts +20 -11
  139. package/src/index.ts +18 -2
  140. package/src/model/DataModel.ts +6 -2
  141. package/src/model.ts +1352 -616
  142. package/src/rawImports.d.ts +9 -0
  143. package/src/renderToStaticMarkup.ts +34 -7
  144. package/src/renderToSvg.tsx +33 -27
  145. package/src/rowCoordinateCalculations.ts +15 -59
  146. package/src/seqPosToGlobalCol.ts +3 -1
  147. package/src/types.ts +131 -4
  148. package/src/util.ts +31 -14
  149. package/src/version.ts +1 -1
  150. package/src/wuss.ts +62 -0
  151. package/dist/components/dialogs/InterProScanDialog.js +0 -137
  152. package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
  153. package/dist/components/dialogs/InterProScanFileDialog.d.ts +0 -7
  154. package/dist/components/dialogs/InterProScanFileDialog.js +0 -64
  155. package/dist/components/dialogs/InterProScanFileDialog.js.map +0 -1
  156. package/dist/launchInterProScan.d.ts +0 -13
  157. package/dist/launchInterProScan.js +0 -110
  158. package/dist/launchInterProScan.js.map +0 -1
  159. package/dist/useColorContrast.d.ts +0 -10
  160. package/dist/useColorContrast.js +0 -13
  161. package/dist/useColorContrast.js.map +0 -1
  162. package/src/clustalX.test.ts +0 -74
  163. package/src/colSpace.test.ts +0 -67
  164. package/src/collapseLogic.test.ts +0 -115
  165. package/src/colorSchemes.docs.test.ts +0 -57
  166. package/src/columnCounts.test.ts +0 -73
  167. package/src/columnStats.test.ts +0 -94
  168. package/src/columnTracks.test.ts +0 -97
  169. package/src/components/dialogs/InterProScanDialog.tsx +0 -214
  170. package/src/components/dialogs/InterProScanFileDialog.tsx +0 -138
  171. package/src/components/minimap/minimapLayout.test.ts +0 -71
  172. package/src/components/msa/msaRaster.test.ts +0 -145
  173. package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +0 -51
  174. package/src/components/tree/renderTreeCanvas.test.ts +0 -296
  175. package/src/dataInitialized.test.ts +0 -42
  176. package/src/domainBands.test.ts +0 -122
  177. package/src/domainLetterColor.test.ts +0 -80
  178. package/src/exportFileName.test.ts +0 -40
  179. package/src/fetchUtils.test.ts +0 -83
  180. package/src/flatToTree.test.ts +0 -51
  181. package/src/hierarchy.test.ts +0 -228
  182. package/src/highlights.test.ts +0 -70
  183. package/src/impgRender.test.tsx +0 -68
  184. package/src/largeTree.test.ts +0 -67
  185. package/src/launchInterProScan.ts +0 -167
  186. package/src/loadErrorDisplay.test.tsx +0 -102
  187. package/src/modelFilehandleLoaders.test.ts +0 -137
  188. package/src/modelReset.test.ts +0 -120
  189. package/src/neighborJoining.test.ts +0 -164
  190. package/src/parseAsn1.test.ts +0 -36
  191. package/src/propertyConservation.test.ts +0 -36
  192. package/src/renderColumnTracksSvg.test.tsx +0 -108
  193. package/src/renderDomainsSvg.test.tsx +0 -118
  194. package/src/renderHighlightsSvg.test.tsx +0 -85
  195. package/src/renderMinimapSvg.test.tsx +0 -62
  196. package/src/renderRasterSvg.test.tsx +0 -144
  197. package/src/renderSequenceLogoSvg.test.tsx +0 -165
  198. package/src/renderToSvg.test.tsx +0 -65
  199. package/src/reset.test.ts +0 -61
  200. package/src/rowCoordinateCalculations.test.ts +0 -211
  201. package/src/rowSpace.test.ts +0 -46
  202. package/src/segmentOverlay.test.ts +0 -70
  203. package/src/seqPosToGlobalCol.test.ts +0 -59
  204. package/src/sequenceLogo.test.ts +0 -88
  205. package/src/setFontSize.test.ts +0 -33
  206. package/src/stripDefault.test.ts +0 -48
  207. package/src/stripDefaultSnapshot.test.ts +0 -87
  208. package/src/treeMetadata.test.ts +0 -39
  209. package/src/useCanvasAutorun.test.tsx +0 -98
  210. package/src/useColorContrast.ts +0 -18
  211. package/src/viewChrome.test.tsx +0 -134
  212. 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
- // whether a node's row-center y falls within the block being drawn, padded by
26
- // extendBounds so bubbles/labels/triangles straddling the edge still render
27
- function inYBlock(y: number, offsetY: number, by: number) {
28
- return y > offsetY - extendBounds && y < offsetY + by + extendBounds
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
- forEachLink(hierarchy, (source, target) => {
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 (tx === undefined || sx === undefined) {
110
+ if (sx === undefined || !source.children) {
78
111
  return
79
112
  }
80
-
81
- const y1 = Math.min(sy, ty)
82
- const y2 = Math.max(sy, ty)
83
- // 1d line intersection to check if line crosses block at all, this is an
84
- // optimization that allows us to skip drawing most tree links outside the
85
- // block
86
- if (offsetY + by >= y1 && y2 >= offsetY) {
87
- ctx.beginPath()
88
- ctx.moveTo(sx, sy)
89
- ctx.lineTo(sx, ty)
90
- ctx.lineTo(tx, ty)
91
- ctx.stroke()
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
- forEachDescendant(hierarchy, node => {
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
- forEachDescendant(hierarchy, node => {
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
- export const defaultShowDomains = false
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
- fetcher: ProgressFetcher = fetchAndMaybeUnzipText,
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
- export function findMaxBranchLen(node: HierarchyNode): number {
176
- let maxLen = 0
177
- for (const n of descendants(node)) {
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 (wraps MSAModelF + MSAView into a JBrowse view)
4
- // - jbrowse-plugin-protein3d (drives MSA<->structure hover/highlight sync)
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'
@@ -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 > 50_000 ? undefined : text,
67
+ text && text.length > maxInlineSnapshotBytes ? undefined : text,
64
68
  ]),
65
69
  ),
66
70
  )