react-msaview 5.1.1 → 5.2.1

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 (190) hide show
  1. package/README.md +41 -8
  2. package/bundle/index.js +17 -17
  3. package/bundle/index.js.LICENSE.txt +1 -1
  4. package/bundle/index.js.map +1 -1
  5. package/dist/calculateBlocks.d.ts +4 -10
  6. package/dist/calculateBlocks.js +12 -20
  7. package/dist/calculateBlocks.js.map +1 -1
  8. package/dist/components/ConservationTrack.js +20 -37
  9. package/dist/components/ConservationTrack.js.map +1 -1
  10. package/dist/components/MSAView.js +8 -1
  11. package/dist/components/MSAView.js.map +1 -1
  12. package/dist/components/ResizeHandles.d.ts +3 -0
  13. package/dist/components/ResizeHandles.js +60 -105
  14. package/dist/components/ResizeHandles.js.map +1 -1
  15. package/dist/components/TextTrack.js +25 -34
  16. package/dist/components/TextTrack.js.map +1 -1
  17. package/dist/components/Track.js +1 -1
  18. package/dist/components/Track.js.map +1 -1
  19. package/dist/components/VerticalScrollbar.js +2 -2
  20. package/dist/components/VerticalScrollbar.js.map +1 -1
  21. package/dist/components/dialogs/ExportSVGDialog.js +20 -20
  22. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  23. package/dist/components/dialogs/InterProScanDialog.js +6 -1
  24. package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
  25. package/dist/components/dialogs/UserProvidedDomainsDialog.js +1 -2
  26. package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +1 -1
  27. package/dist/components/header/ColorSchemeMenu.js +1 -1
  28. package/dist/components/header/ColorSchemeMenu.js.map +1 -1
  29. package/dist/components/header/FileMenu.js +3 -2
  30. package/dist/components/header/FileMenu.js.map +1 -1
  31. package/dist/components/header/Header.js +1 -1
  32. package/dist/components/header/HeaderInfoArea.js +28 -8
  33. package/dist/components/header/HeaderInfoArea.js.map +1 -1
  34. package/dist/components/header/MSASettingsMenu.js +1 -1
  35. package/dist/components/header/MSASettingsMenu.js.map +1 -1
  36. package/dist/components/header/TreeSettingsMenu.js +1 -1
  37. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  38. package/dist/components/header/ZoomControls.js +7 -2
  39. package/dist/components/header/ZoomControls.js.map +1 -1
  40. package/dist/components/header/ZoomMenu.js +9 -0
  41. package/dist/components/header/ZoomMenu.js.map +1 -1
  42. package/dist/components/header/settingsMenuItems.js +24 -8
  43. package/dist/components/header/settingsMenuItems.js.map +1 -1
  44. package/dist/components/minimap/Minimap.js +6 -17
  45. package/dist/components/minimap/Minimap.js.map +1 -1
  46. package/dist/components/minimap/MinimapSVG.js +7 -17
  47. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  48. package/dist/components/minimap/minimapLayout.d.ts +9 -0
  49. package/dist/components/minimap/minimapLayout.js +15 -0
  50. package/dist/components/minimap/minimapLayout.js.map +1 -0
  51. package/dist/components/msa/MSACanvas.js +6 -1
  52. package/dist/components/msa/MSACanvas.js.map +1 -1
  53. package/dist/components/msa/MSACanvasBlock.js +5 -1
  54. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  55. package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
  56. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  57. package/dist/components/msa/getVisibleLeaves.d.ts +6 -0
  58. package/dist/components/msa/getVisibleLeaves.js +9 -0
  59. package/dist/components/msa/getVisibleLeaves.js.map +1 -0
  60. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +2 -1
  61. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +5 -6
  62. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  63. package/dist/components/msa/renderMSABlock.d.ts +2 -1
  64. package/dist/components/msa/renderMSABlock.js +100 -88
  65. package/dist/components/msa/renderMSABlock.js.map +1 -1
  66. package/dist/components/msa/renderMSAMouseover.js +5 -4
  67. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  68. package/dist/components/msa/visibleColRange.d.ts +8 -0
  69. package/dist/components/msa/visibleColRange.js +10 -0
  70. package/dist/components/msa/visibleColRange.js.map +1 -0
  71. package/dist/components/renderCtx.d.ts +7 -0
  72. package/dist/components/renderCtx.js +2 -0
  73. package/dist/components/renderCtx.js.map +1 -0
  74. package/dist/components/tracks/renderTracksSvg.d.ts +6 -5
  75. package/dist/components/tracks/renderTracksSvg.js +13 -5
  76. package/dist/components/tracks/renderTracksSvg.js.map +1 -1
  77. package/dist/components/tree/TreeCanvas.js.map +1 -1
  78. package/dist/components/tree/TreeCanvasBlock.js +2 -10
  79. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  80. package/dist/components/tree/renderTreeCanvas.d.ts +5 -4
  81. package/dist/components/tree/renderTreeCanvas.js +18 -26
  82. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  83. package/dist/components/util.d.ts +0 -14
  84. package/dist/components/util.js +0 -58
  85. package/dist/components/util.js.map +1 -1
  86. package/dist/constants.d.ts +4 -0
  87. package/dist/constants.js +7 -0
  88. package/dist/constants.js.map +1 -1
  89. package/dist/flatToTree.js +6 -0
  90. package/dist/flatToTree.js.map +1 -1
  91. package/dist/hierarchy.d.ts +3 -1
  92. package/dist/hierarchy.js +142 -89
  93. package/dist/hierarchy.js.map +1 -1
  94. package/dist/index.d.ts +1 -0
  95. package/dist/index.js +8 -0
  96. package/dist/index.js.map +1 -1
  97. package/dist/launchInterProScan.js +22 -9
  98. package/dist/launchInterProScan.js.map +1 -1
  99. package/dist/measureTextCanvas.js +2 -1
  100. package/dist/measureTextCanvas.js.map +1 -1
  101. package/dist/model.d.ts +234 -176
  102. package/dist/model.js +285 -259
  103. package/dist/model.js.map +1 -1
  104. package/dist/parseAsn1.js +8 -2
  105. package/dist/parseAsn1.js.map +1 -1
  106. package/dist/renderToSvg.d.ts +1 -1
  107. package/dist/renderToSvg.js +43 -46
  108. package/dist/renderToSvg.js.map +1 -1
  109. package/dist/rowCoordinateCalculations.d.ts +0 -9
  110. package/dist/rowCoordinateCalculations.js +0 -20
  111. package/dist/rowCoordinateCalculations.js.map +1 -1
  112. package/dist/setFontSize.d.ts +3 -0
  113. package/dist/setFontSize.js +14 -0
  114. package/dist/setFontSize.js.map +1 -0
  115. package/dist/useDevicePixelRatio.d.ts +1 -0
  116. package/dist/useDevicePixelRatio.js +27 -0
  117. package/dist/useDevicePixelRatio.js.map +1 -0
  118. package/dist/useWheelScroll.d.ts +3 -1
  119. package/dist/useWheelScroll.js +64 -22
  120. package/dist/useWheelScroll.js.map +1 -1
  121. package/dist/util.d.ts +5 -0
  122. package/dist/util.js +59 -1
  123. package/dist/util.js.map +1 -1
  124. package/dist/version.d.ts +1 -1
  125. package/dist/version.js +1 -1
  126. package/dist/webpack.js +0 -1
  127. package/dist/webpack.js.map +1 -1
  128. package/dist/wheelZoom.d.ts +4 -0
  129. package/dist/wheelZoom.js +43 -0
  130. package/dist/wheelZoom.js.map +1 -0
  131. package/package.json +5 -5
  132. package/src/calculateBlocks.ts +18 -51
  133. package/src/components/ConservationTrack.tsx +20 -45
  134. package/src/components/MSAView.tsx +10 -1
  135. package/src/components/ResizeHandles.tsx +101 -113
  136. package/src/components/TextTrack.tsx +31 -38
  137. package/src/components/Track.tsx +1 -1
  138. package/src/components/VerticalScrollbar.tsx +2 -2
  139. package/src/components/dialogs/ExportSVGDialog.tsx +27 -17
  140. package/src/components/dialogs/InterProScanDialog.tsx +6 -1
  141. package/src/components/dialogs/UserProvidedDomainsDialog.tsx +1 -2
  142. package/src/components/header/ColorSchemeMenu.tsx +1 -0
  143. package/src/components/header/FileMenu.tsx +3 -1
  144. package/src/components/header/Header.tsx +1 -1
  145. package/src/components/header/HeaderInfoArea.tsx +32 -7
  146. package/src/components/header/MSASettingsMenu.tsx +1 -0
  147. package/src/components/header/TreeSettingsMenu.tsx +1 -0
  148. package/src/components/header/ZoomControls.tsx +14 -1
  149. package/src/components/header/ZoomMenu.tsx +10 -0
  150. package/src/components/header/settingsMenuItems.ts +24 -8
  151. package/src/components/minimap/Minimap.tsx +6 -20
  152. package/src/components/minimap/MinimapSVG.tsx +10 -27
  153. package/src/components/minimap/minimapLayout.ts +17 -0
  154. package/src/components/msa/MSACanvas.tsx +9 -0
  155. package/src/components/msa/MSACanvasBlock.tsx +7 -1
  156. package/src/components/msa/MSAMouseoverCanvas.tsx +4 -3
  157. package/src/components/msa/getVisibleLeaves.ts +21 -0
  158. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +9 -9
  159. package/src/components/msa/renderMSABlock.ts +123 -125
  160. package/src/components/msa/renderMSAMouseover.ts +5 -4
  161. package/src/components/msa/visibleColRange.ts +17 -0
  162. package/src/components/renderCtx.ts +29 -0
  163. package/src/components/tracks/renderTracksSvg.ts +20 -10
  164. package/src/components/tree/TreeCanvas.tsx +3 -1
  165. package/src/components/tree/TreeCanvasBlock.tsx +2 -12
  166. package/src/components/tree/renderTreeCanvas.ts +27 -30
  167. package/src/components/util.ts +0 -81
  168. package/src/constants.ts +8 -0
  169. package/src/flatToTree.test.ts +30 -0
  170. package/src/flatToTree.ts +10 -1
  171. package/src/hierarchy.test.ts +53 -1
  172. package/src/hierarchy.ts +153 -100
  173. package/src/index.ts +8 -0
  174. package/src/launchInterProScan.ts +33 -11
  175. package/src/measureTextCanvas.ts +3 -1
  176. package/src/model.ts +359 -306
  177. package/src/parseAsn1.test.ts +6 -0
  178. package/src/parseAsn1.ts +11 -3
  179. package/src/renderToSvg.tsx +99 -95
  180. package/src/rowCoordinateCalculations.test.ts +0 -51
  181. package/src/rowCoordinateCalculations.ts +0 -23
  182. package/src/setFontSize.test.ts +33 -0
  183. package/src/setFontSize.ts +20 -0
  184. package/src/useDevicePixelRatio.ts +29 -0
  185. package/src/useWheelScroll.ts +79 -21
  186. package/src/util.ts +60 -1
  187. package/src/version.ts +1 -1
  188. package/src/webpack.ts +0 -1
  189. package/src/wheelZoom.test.ts +46 -0
  190. package/src/wheelZoom.ts +47 -0
package/src/hierarchy.ts CHANGED
@@ -19,45 +19,72 @@ export interface HierarchyLink<T = NodeWithIds> {
19
19
  target: HierarchyNode<T>
20
20
  }
21
21
 
22
- function computeHeight<T>(node: HierarchyNode<T>): number {
23
- let h = 0
24
- if (node.children) {
25
- for (const child of node.children) {
26
- const ch = computeHeight(child) + 1
27
- if (ch > h) {
28
- h = ch
22
+ // All traversals below are iterative (explicit stack / reversed-preorder) rather
23
+ // than recursive: phylogenetic trees can be deeply unbalanced (a caterpillar tree
24
+ // has depth ~= leaf count), which overflows the JS call stack on recursion.
25
+
26
+ // Pre-order: every parent precedes all of its descendants. Iterating the result
27
+ // in reverse therefore yields a valid post-order (children before parents), which
28
+ // the accumulation helpers rely on.
29
+ export function descendants<T>(node: HierarchyNode<T>): HierarchyNode<T>[] {
30
+ const result: HierarchyNode<T>[] = []
31
+ const stack = [node]
32
+ while (stack.length > 0) {
33
+ const n = stack.pop()!
34
+ result.push(n)
35
+ if (n.children) {
36
+ for (let i = n.children.length - 1; i >= 0; i--) {
37
+ stack.push(n.children[i]!)
29
38
  }
30
39
  }
31
40
  }
32
- node.height = h
33
- return h
41
+ return result
34
42
  }
35
43
 
36
- function wrap<T>(
44
+ function computeHeight<T>(node: HierarchyNode<T>) {
45
+ const nodes = descendants(node)
46
+ for (let i = nodes.length - 1; i >= 0; i--) {
47
+ const n = nodes[i]!
48
+ let h = 0
49
+ if (n.children) {
50
+ for (const child of n.children) {
51
+ if (child.height + 1 > h) {
52
+ h = child.height + 1
53
+ }
54
+ }
55
+ }
56
+ n.height = h
57
+ }
58
+ }
59
+
60
+ export function hierarchy<T>(
37
61
  data: T,
38
62
  childrenAccessor: (d: T) => T[] | undefined,
39
- parent: HierarchyNode<T> | null,
40
- depth: number,
41
63
  ): HierarchyNode<T> {
42
- const kids = childrenAccessor(data)
43
- const node: HierarchyNode<T> = {
64
+ const root: HierarchyNode<T> = {
44
65
  data,
45
66
  children: null,
46
- parent,
47
- depth,
67
+ parent: null,
68
+ depth: 0,
48
69
  height: 0,
49
70
  }
50
- if (kids?.length) {
51
- node.children = kids.map(d => wrap(d, childrenAccessor, node, depth + 1))
71
+ const stack = [root]
72
+ while (stack.length > 0) {
73
+ const node = stack.pop()!
74
+ const kids = childrenAccessor(node.data)
75
+ if (kids?.length) {
76
+ node.children = kids.map(d => ({
77
+ data: d,
78
+ children: null,
79
+ parent: node,
80
+ depth: node.depth + 1,
81
+ height: 0,
82
+ }))
83
+ for (const child of node.children) {
84
+ stack.push(child)
85
+ }
86
+ }
52
87
  }
53
- return node
54
- }
55
-
56
- export function hierarchy<T>(
57
- data: T,
58
- childrenAccessor: (d: T) => T[] | undefined,
59
- ): HierarchyNode<T> {
60
- const root = wrap(data, childrenAccessor, null, 0)
61
88
  computeHeight(root)
62
89
  return root
63
90
  }
@@ -66,17 +93,17 @@ export function sum<T>(
66
93
  node: HierarchyNode<T>,
67
94
  valueFn: (d: T) => number,
68
95
  ): HierarchyNode<T> {
69
- function visit(n: HierarchyNode<T>): number {
96
+ const nodes = descendants(node)
97
+ for (let i = nodes.length - 1; i >= 0; i--) {
98
+ const n = nodes[i]!
70
99
  let s = valueFn(n.data)
71
100
  if (n.children) {
72
101
  for (const child of n.children) {
73
- s += visit(child)
102
+ s += child.value!
74
103
  }
75
104
  }
76
105
  n.value = s
77
- return s
78
106
  }
79
- visit(node)
80
107
  return node
81
108
  }
82
109
 
@@ -84,15 +111,16 @@ export function sort<T>(
84
111
  node: HierarchyNode<T>,
85
112
  compareFn: (a: HierarchyNode<T>, b: HierarchyNode<T>) => number,
86
113
  ): HierarchyNode<T> {
87
- function visit(n: HierarchyNode<T>) {
114
+ const stack = [node]
115
+ while (stack.length > 0) {
116
+ const n = stack.pop()!
88
117
  if (n.children) {
89
118
  n.children.sort(compareFn)
90
119
  for (const child of n.children) {
91
- visit(child)
120
+ stack.push(child)
92
121
  }
93
122
  }
94
123
  }
95
- visit(node)
96
124
  return node
97
125
  }
98
126
 
@@ -100,61 +128,69 @@ export function find<T>(
100
128
  node: HierarchyNode<T>,
101
129
  predicate: (n: HierarchyNode<T>) => boolean,
102
130
  ): HierarchyNode<T> | undefined {
103
- if (predicate(node)) {
104
- return node
105
- }
106
- if (node.children) {
107
- for (const child of node.children) {
108
- const result = find(child, predicate)
109
- if (result) {
110
- return result
131
+ const stack = [node]
132
+ let found: HierarchyNode<T> | undefined
133
+ while (stack.length > 0 && found === undefined) {
134
+ const n = stack.pop()!
135
+ if (predicate(n)) {
136
+ found = n
137
+ } else if (n.children) {
138
+ for (let i = n.children.length - 1; i >= 0; i--) {
139
+ stack.push(n.children[i]!)
111
140
  }
112
141
  }
113
142
  }
114
- return undefined
143
+ return found
115
144
  }
116
145
 
117
146
  export function leaves<T>(node: HierarchyNode<T>): HierarchyNode<T>[] {
118
147
  const result: HierarchyNode<T>[] = []
119
- function visit(n: HierarchyNode<T>) {
148
+ const stack = [node]
149
+ while (stack.length > 0) {
150
+ const n = stack.pop()!
120
151
  if (n.children) {
121
- for (const child of n.children) {
122
- visit(child)
152
+ for (let i = n.children.length - 1; i >= 0; i--) {
153
+ stack.push(n.children[i]!)
123
154
  }
124
155
  } else {
125
156
  result.push(n)
126
157
  }
127
158
  }
128
- visit(node)
129
159
  return result
130
160
  }
131
161
 
132
- export function descendants<T>(node: HierarchyNode<T>): HierarchyNode<T>[] {
133
- const result: HierarchyNode<T>[] = []
134
- function visit(n: HierarchyNode<T>) {
135
- result.push(n)
136
- if (n.children) {
137
- for (const child of n.children) {
138
- visit(child)
139
- }
140
- }
141
- }
142
- visit(node)
162
+ export function links<T>(node: HierarchyNode<T>): HierarchyLink<T>[] {
163
+ const result: HierarchyLink<T>[] = []
164
+ forEachLink(node, (source, target) => {
165
+ result.push({ source, target })
166
+ })
143
167
  return result
144
168
  }
145
169
 
146
- export function links<T>(node: HierarchyNode<T>): HierarchyLink<T>[] {
147
- const result: HierarchyLink<T>[] = []
148
- function visit(n: HierarchyNode<T>) {
170
+ export function forEachLink<T>(
171
+ node: HierarchyNode<T>,
172
+ cb: (source: HierarchyNode<T>, target: HierarchyNode<T>) => void,
173
+ ) {
174
+ const stack = [node]
175
+ while (stack.length > 0) {
176
+ const n = stack.pop()!
149
177
  if (n.children) {
150
- for (const child of n.children) {
151
- result.push({ source: n, target: child })
152
- visit(child)
178
+ for (let i = n.children.length - 1; i >= 0; i--) {
179
+ const child = n.children[i]!
180
+ cb(n, child)
181
+ stack.push(child)
153
182
  }
154
183
  }
155
184
  }
156
- visit(node)
157
- return result
185
+ }
186
+
187
+ export function forEachDescendant<T>(
188
+ node: HierarchyNode<T>,
189
+ cb: (n: HierarchyNode<T>) => void,
190
+ ) {
191
+ for (const n of descendants(node)) {
192
+ cb(n)
193
+ }
158
194
  }
159
195
 
160
196
  export function clusterLayout<T>(
@@ -170,31 +206,31 @@ export function clusterLayout<T>(
170
206
  leafNodes[i]!.x = (i + 0.5) * step
171
207
  }
172
208
 
173
- function assignX(node: HierarchyNode<T>) {
174
- if (!node.children) {
175
- return
176
- }
177
- for (const child of node.children) {
178
- assignX(child)
179
- }
180
- let sum = 0
181
- for (const child of node.children) {
182
- sum += child.x!
209
+ // x of an internal node is the mean of its children's x, so process in
210
+ // post-order (children before parents)
211
+ const nodes = descendants(root)
212
+ for (let i = nodes.length - 1; i >= 0; i--) {
213
+ const node = nodes[i]!
214
+ if (node.children) {
215
+ let sum = 0
216
+ for (const child of node.children) {
217
+ sum += child.x!
218
+ }
219
+ node.x = sum / node.children.length
183
220
  }
184
- node.x = sum / node.children.length
185
221
  }
186
- assignX(root)
187
222
 
188
223
  const rootHeight = root.height
189
- function assignY(node: HierarchyNode<T>, depth: number) {
224
+ const stack = [{ node: root, depth: 0 }]
225
+ while (stack.length > 0) {
226
+ const { node, depth } = stack.pop()!
190
227
  node.y = rootHeight === 0 ? sizeY : (depth / rootHeight) * sizeY
191
228
  if (node.children) {
192
229
  for (const child of node.children) {
193
- assignY(child, depth + 1)
230
+ stack.push({ node: child, depth: depth + 1 })
194
231
  }
195
232
  }
196
233
  }
197
- assignY(root, 0)
198
234
  }
199
235
 
200
236
  export function collapse<T>(node: HierarchyNode<T>) {
@@ -209,40 +245,57 @@ export function collapse<T>(node: HierarchyNode<T>) {
209
245
  // x. Memoizes onto node.depthToLeaf since the layout walks the tree repeatedly.
210
246
  // See https://github.com/emmanuelparadis/ape/blob/master/R/plot.phylo.R
211
247
  export function calcDepthToLeaf(node: HierarchyNode): number {
212
- if (node.depthToLeaf === undefined) {
213
- let maxDepth = 0
214
- if (node.children) {
215
- for (const child of node.children) {
216
- maxDepth = Math.max(maxDepth, 1 + calcDepthToLeaf(child))
248
+ const nodes = descendants(node)
249
+ for (let i = nodes.length - 1; i >= 0; i--) {
250
+ const n = nodes[i]!
251
+ if (n.depthToLeaf === undefined) {
252
+ let maxDepth = 0
253
+ if (n.children) {
254
+ for (const child of n.children) {
255
+ maxDepth = Math.max(maxDepth, 1 + child.depthToLeaf!)
256
+ }
217
257
  }
258
+ n.depthToLeaf = maxDepth
218
259
  }
219
- node.depthToLeaf = maxDepth
220
260
  }
221
- return node.depthToLeaf
261
+ return node.depthToLeaf!
222
262
  }
223
263
 
224
264
  export function findMaxBranchLen(node: HierarchyNode): number {
225
- let maxLen = node.len || 0
226
- if (node.children) {
227
- for (const child of node.children) {
228
- maxLen = Math.max(maxLen, findMaxBranchLen(child))
229
- }
265
+ let maxLen = 0
266
+ for (const n of descendants(node)) {
267
+ maxLen = Math.max(maxLen, n.len || 0)
230
268
  }
231
269
  return maxLen
232
270
  }
233
271
 
272
+ // Max root-to-leaf sum of branch lengths within the subtree at d
234
273
  export function maxLength(d: HierarchyNode): number {
235
- return (
236
- (d.data.length || 0) +
237
- (d.children ? d.children.reduce((m, c) => Math.max(m, maxLength(c)), 0) : 0)
238
- )
274
+ const nodes = descendants(d)
275
+ const pathLen = new Map<HierarchyNode, number>()
276
+ for (let i = nodes.length - 1; i >= 0; i--) {
277
+ const n = nodes[i]!
278
+ let childMax = 0
279
+ if (n.children) {
280
+ for (const child of n.children) {
281
+ childMax = Math.max(childMax, pathLen.get(child)!)
282
+ }
283
+ }
284
+ pathLen.set(n, (n.data.length || 0) + childMax)
285
+ }
286
+ return pathLen.get(d)!
239
287
  }
240
288
 
241
289
  export function setBrLength(d: HierarchyNode, y0: number, k: number) {
242
- d.len = (y0 += Math.max(d.data.length || 0, 0)) * k
243
- if (d.children) {
244
- for (const child of d.children) {
245
- setBrLength(child, y0, k)
290
+ const stack = [{ node: d, y0 }]
291
+ while (stack.length > 0) {
292
+ const { node, y0: acc } = stack.pop()!
293
+ const next = acc + Math.max(node.data.length || 0, 0)
294
+ node.len = next * k
295
+ if (node.children) {
296
+ for (const child of node.children) {
297
+ stack.push({ node: child, y0: next })
298
+ }
246
299
  }
247
300
  }
248
301
  }
package/src/index.ts CHANGED
@@ -1,3 +1,11 @@
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.
8
+ export { renderToSvg } from './renderToSvg.tsx'
1
9
  export { default as MSAView } from './components/Loading.tsx'
2
10
  export { default as MSAViewer } from './components/MSAViewer.tsx'
3
11
  export { type MsaViewModel, default as MSAModelF } from './model.ts'
@@ -73,21 +73,36 @@ async function wait({
73
73
  signal?: AbortSignal
74
74
  }) {
75
75
  const url = `${base}/iprscan5/status/${jobId}`
76
+ // EBI jobs run for minutes; cap polling so an unexpected/stuck status can't
77
+ // spin forever (the abort signal is the normal early exit). ~10s per check.
78
+ const maxChecks = 360
76
79
  try {
77
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
78
- while (true) {
80
+ let finished = false
81
+ for (let check = 0; check < maxChecks && !finished; check++) {
79
82
  for (let i = 0; i < 10; i++) {
80
83
  await timeout(1000, signal)
81
84
  onProgress({ msg: `Checking status ${10 - i}`, url })
82
85
  }
83
- const result = await textfetch(url, { signal })
84
- if (result.includes('FINISHED')) {
85
- break
86
- }
87
- if (result.includes('FAILURE')) {
88
- throw new Error(`Failed to run: jobId ${jobId}`)
86
+ // status endpoint returns a bare status token; match exactly rather than
87
+ // substring so an error page mentioning "FINISHED" can't be a false hit
88
+ const status = (await textfetch(url, { signal })).trim()
89
+ if (status === 'FINISHED') {
90
+ finished = true
91
+ } else if (
92
+ status === 'FAILURE' ||
93
+ status === 'ERROR' ||
94
+ status === 'NOT_FOUND'
95
+ ) {
96
+ throw new Error(
97
+ `InterProScan job ${jobId} ended with status: ${status}`,
98
+ )
89
99
  }
90
100
  }
101
+ if (!finished) {
102
+ throw new Error(
103
+ `InterProScan job ${jobId} did not finish after ${maxChecks} status checks`,
104
+ )
105
+ }
91
106
  } finally {
92
107
  onProgress()
93
108
  }
@@ -152,10 +167,17 @@ async function loadInterProScanResultsWithStatus({
152
167
  url: `https://www.ebi.ac.uk/Tools/services/rest/iprscan5/result/${jobId}/json`,
153
168
  })
154
169
  const ret = await loadInterProScanResults(jobId, signal)
155
- model.setInterProAnnotations(
156
- Object.fromEntries(ret.results.map(r => [r.xref[0]!.id, r])),
170
+ // key each result by its xref id; skip any result lacking an xref entry
171
+ // rather than crashing the whole load on one malformed result
172
+ model.setDomains(
173
+ Object.fromEntries(
174
+ ret.results
175
+ .map(r => [r.xref[0]?.id, r] as const)
176
+ .filter(
177
+ (e): e is [string, InterProScanResults] => e[0] !== undefined,
178
+ ),
179
+ ),
157
180
  )
158
- model.setShowDomains(true)
159
181
  getSession(model).notify(`Loaded interproscan ${jobId} results`, 'success')
160
182
  } catch (e) {
161
183
  if (isAbortError(e)) {
@@ -1,3 +1,5 @@
1
+ import { setFontSize } from './setFontSize.ts'
2
+
1
3
  let canvasHandle: HTMLCanvasElement | undefined
2
4
 
3
5
  export function measureTextCanvas(text: string, fontSize: number) {
@@ -9,6 +11,6 @@ export function measureTextCanvas(text: string, fontSize: number) {
9
11
  if (!ctx) {
10
12
  throw new Error('no canvas context')
11
13
  }
12
- ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
14
+ setFontSize(ctx, fontSize)
13
15
  return ctx.measureText(text).width
14
16
  }