react-msaview 5.2.0 → 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 (141) 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/components/ConservationTrack.js +2 -13
  6. package/dist/components/ConservationTrack.js.map +1 -1
  7. package/dist/components/MSAView.js +8 -1
  8. package/dist/components/MSAView.js.map +1 -1
  9. package/dist/components/ResizeHandles.d.ts +3 -0
  10. package/dist/components/ResizeHandles.js +60 -105
  11. package/dist/components/ResizeHandles.js.map +1 -1
  12. package/dist/components/TextTrack.js +2 -1
  13. package/dist/components/TextTrack.js.map +1 -1
  14. package/dist/components/Track.js +1 -1
  15. package/dist/components/Track.js.map +1 -1
  16. package/dist/components/VerticalScrollbar.js +2 -2
  17. package/dist/components/VerticalScrollbar.js.map +1 -1
  18. package/dist/components/dialogs/UserProvidedDomainsDialog.js +1 -2
  19. package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +1 -1
  20. package/dist/components/header/ColorSchemeMenu.js +1 -1
  21. package/dist/components/header/ColorSchemeMenu.js.map +1 -1
  22. package/dist/components/header/FileMenu.js +1 -1
  23. package/dist/components/header/FileMenu.js.map +1 -1
  24. package/dist/components/header/HeaderInfoArea.js +28 -8
  25. package/dist/components/header/HeaderInfoArea.js.map +1 -1
  26. package/dist/components/header/MSASettingsMenu.js +1 -1
  27. package/dist/components/header/MSASettingsMenu.js.map +1 -1
  28. package/dist/components/header/TreeSettingsMenu.js +1 -1
  29. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  30. package/dist/components/header/ZoomControls.js +7 -2
  31. package/dist/components/header/ZoomControls.js.map +1 -1
  32. package/dist/components/header/ZoomMenu.js +9 -0
  33. package/dist/components/header/ZoomMenu.js.map +1 -1
  34. package/dist/components/minimap/Minimap.js +6 -12
  35. package/dist/components/minimap/Minimap.js.map +1 -1
  36. package/dist/components/minimap/MinimapSVG.js +7 -13
  37. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  38. package/dist/components/minimap/minimapLayout.d.ts +9 -0
  39. package/dist/components/minimap/minimapLayout.js +15 -0
  40. package/dist/components/minimap/minimapLayout.js.map +1 -0
  41. package/dist/components/msa/MSACanvas.js +6 -1
  42. package/dist/components/msa/MSACanvas.js.map +1 -1
  43. package/dist/components/msa/MSACanvasBlock.js +5 -1
  44. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  45. package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
  46. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  47. package/dist/components/msa/renderMSABlock.js +5 -7
  48. package/dist/components/msa/renderMSABlock.js.map +1 -1
  49. package/dist/components/msa/renderMSAMouseover.js +3 -2
  50. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  51. package/dist/components/tracks/renderTracksSvg.js +2 -1
  52. package/dist/components/tracks/renderTracksSvg.js.map +1 -1
  53. package/dist/components/tree/renderTreeCanvas.js +2 -2
  54. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  55. package/dist/constants.d.ts +4 -0
  56. package/dist/constants.js +7 -0
  57. package/dist/constants.js.map +1 -1
  58. package/dist/flatToTree.js +6 -0
  59. package/dist/flatToTree.js.map +1 -1
  60. package/dist/hierarchy.d.ts +1 -1
  61. package/dist/hierarchy.js +136 -102
  62. package/dist/hierarchy.js.map +1 -1
  63. package/dist/index.d.ts +1 -0
  64. package/dist/index.js +1 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/launchInterProScan.js +22 -9
  67. package/dist/launchInterProScan.js.map +1 -1
  68. package/dist/measureTextCanvas.js +2 -1
  69. package/dist/measureTextCanvas.js.map +1 -1
  70. package/dist/model.d.ts +212 -150
  71. package/dist/model.js +239 -205
  72. package/dist/model.js.map +1 -1
  73. package/dist/parseAsn1.js +8 -2
  74. package/dist/parseAsn1.js.map +1 -1
  75. package/dist/renderToSvg.js +27 -24
  76. package/dist/renderToSvg.js.map +1 -1
  77. package/dist/setFontSize.d.ts +3 -0
  78. package/dist/setFontSize.js +14 -0
  79. package/dist/setFontSize.js.map +1 -0
  80. package/dist/useDevicePixelRatio.d.ts +1 -0
  81. package/dist/useDevicePixelRatio.js +27 -0
  82. package/dist/useDevicePixelRatio.js.map +1 -0
  83. package/dist/useWheelScroll.d.ts +3 -1
  84. package/dist/useWheelScroll.js +64 -22
  85. package/dist/useWheelScroll.js.map +1 -1
  86. package/dist/util.d.ts +4 -0
  87. package/dist/util.js +36 -0
  88. package/dist/util.js.map +1 -1
  89. package/dist/version.d.ts +1 -1
  90. package/dist/version.js +1 -1
  91. package/dist/webpack.js +0 -1
  92. package/dist/webpack.js.map +1 -1
  93. package/dist/wheelZoom.d.ts +4 -0
  94. package/dist/wheelZoom.js +43 -0
  95. package/dist/wheelZoom.js.map +1 -0
  96. package/package.json +5 -5
  97. package/src/components/ConservationTrack.tsx +2 -18
  98. package/src/components/MSAView.tsx +10 -1
  99. package/src/components/ResizeHandles.tsx +101 -113
  100. package/src/components/TextTrack.tsx +2 -1
  101. package/src/components/Track.tsx +1 -1
  102. package/src/components/VerticalScrollbar.tsx +2 -2
  103. package/src/components/dialogs/UserProvidedDomainsDialog.tsx +1 -2
  104. package/src/components/header/ColorSchemeMenu.tsx +1 -0
  105. package/src/components/header/FileMenu.tsx +1 -0
  106. package/src/components/header/HeaderInfoArea.tsx +32 -7
  107. package/src/components/header/MSASettingsMenu.tsx +1 -0
  108. package/src/components/header/TreeSettingsMenu.tsx +1 -0
  109. package/src/components/header/ZoomControls.tsx +14 -1
  110. package/src/components/header/ZoomMenu.tsx +10 -0
  111. package/src/components/minimap/Minimap.tsx +6 -15
  112. package/src/components/minimap/MinimapSVG.tsx +9 -22
  113. package/src/components/minimap/minimapLayout.ts +17 -0
  114. package/src/components/msa/MSACanvas.tsx +9 -0
  115. package/src/components/msa/MSACanvasBlock.tsx +7 -1
  116. package/src/components/msa/MSAMouseoverCanvas.tsx +4 -3
  117. package/src/components/msa/renderMSABlock.ts +6 -9
  118. package/src/components/msa/renderMSAMouseover.ts +3 -1
  119. package/src/components/tracks/renderTracksSvg.ts +2 -1
  120. package/src/components/tree/renderTreeCanvas.ts +2 -2
  121. package/src/constants.ts +8 -0
  122. package/src/flatToTree.test.ts +30 -0
  123. package/src/flatToTree.ts +10 -1
  124. package/src/hierarchy.test.ts +53 -1
  125. package/src/hierarchy.ts +139 -113
  126. package/src/index.ts +1 -0
  127. package/src/launchInterProScan.ts +33 -11
  128. package/src/measureTextCanvas.ts +3 -1
  129. package/src/model.ts +312 -242
  130. package/src/parseAsn1.test.ts +6 -0
  131. package/src/parseAsn1.ts +11 -3
  132. package/src/renderToSvg.tsx +79 -67
  133. package/src/setFontSize.test.ts +33 -0
  134. package/src/setFontSize.ts +20 -0
  135. package/src/useDevicePixelRatio.ts +29 -0
  136. package/src/useWheelScroll.ts +79 -21
  137. package/src/util.ts +35 -0
  138. package/src/version.ts +1 -1
  139. package/src/webpack.ts +0 -1
  140. package/src/wheelZoom.test.ts +46 -0
  141. 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]!)
38
+ }
39
+ }
40
+ }
41
+ return result
42
+ }
43
+
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
+ }
29
54
  }
30
55
  }
56
+ n.height = h
31
57
  }
32
- node.height = h
33
- return h
34
58
  }
35
59
 
36
- function wrap<T>(
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,60 +128,42 @@ 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
- return result
130
- }
131
-
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)
143
159
  return result
144
160
  }
145
161
 
146
162
  export function links<T>(node: HierarchyNode<T>): HierarchyLink<T>[] {
147
163
  const result: HierarchyLink<T>[] = []
148
- function visit(n: HierarchyNode<T>) {
149
- if (n.children) {
150
- for (const child of n.children) {
151
- result.push({ source: n, target: child })
152
- visit(child)
153
- }
154
- }
155
- }
156
- visit(node)
164
+ forEachLink(node, (source, target) => {
165
+ result.push({ source, target })
166
+ })
157
167
  return result
158
168
  }
159
169
 
@@ -161,26 +171,25 @@ export function forEachLink<T>(
161
171
  node: HierarchyNode<T>,
162
172
  cb: (source: HierarchyNode<T>, target: HierarchyNode<T>) => void,
163
173
  ) {
164
- function visit(n: HierarchyNode<T>) {
174
+ const stack = [node]
175
+ while (stack.length > 0) {
176
+ const n = stack.pop()!
165
177
  if (n.children) {
166
- for (const child of n.children) {
178
+ for (let i = n.children.length - 1; i >= 0; i--) {
179
+ const child = n.children[i]!
167
180
  cb(n, child)
168
- visit(child)
181
+ stack.push(child)
169
182
  }
170
183
  }
171
184
  }
172
- visit(node)
173
185
  }
174
186
 
175
187
  export function forEachDescendant<T>(
176
188
  node: HierarchyNode<T>,
177
189
  cb: (n: HierarchyNode<T>) => void,
178
190
  ) {
179
- cb(node)
180
- if (node.children) {
181
- for (const child of node.children) {
182
- forEachDescendant(child, cb)
183
- }
191
+ for (const n of descendants(node)) {
192
+ cb(n)
184
193
  }
185
194
  }
186
195
 
@@ -197,31 +206,31 @@ export function clusterLayout<T>(
197
206
  leafNodes[i]!.x = (i + 0.5) * step
198
207
  }
199
208
 
200
- function assignX(node: HierarchyNode<T>) {
201
- if (!node.children) {
202
- return
203
- }
204
- for (const child of node.children) {
205
- assignX(child)
206
- }
207
- let sum = 0
208
- for (const child of node.children) {
209
- 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
210
220
  }
211
- node.x = sum / node.children.length
212
221
  }
213
- assignX(root)
214
222
 
215
223
  const rootHeight = root.height
216
- 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()!
217
227
  node.y = rootHeight === 0 ? sizeY : (depth / rootHeight) * sizeY
218
228
  if (node.children) {
219
229
  for (const child of node.children) {
220
- assignY(child, depth + 1)
230
+ stack.push({ node: child, depth: depth + 1 })
221
231
  }
222
232
  }
223
233
  }
224
- assignY(root, 0)
225
234
  }
226
235
 
227
236
  export function collapse<T>(node: HierarchyNode<T>) {
@@ -236,40 +245,57 @@ export function collapse<T>(node: HierarchyNode<T>) {
236
245
  // x. Memoizes onto node.depthToLeaf since the layout walks the tree repeatedly.
237
246
  // See https://github.com/emmanuelparadis/ape/blob/master/R/plot.phylo.R
238
247
  export function calcDepthToLeaf(node: HierarchyNode): number {
239
- if (node.depthToLeaf === undefined) {
240
- let maxDepth = 0
241
- if (node.children) {
242
- for (const child of node.children) {
243
- 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
+ }
244
257
  }
258
+ n.depthToLeaf = maxDepth
245
259
  }
246
- node.depthToLeaf = maxDepth
247
260
  }
248
- return node.depthToLeaf
261
+ return node.depthToLeaf!
249
262
  }
250
263
 
251
264
  export function findMaxBranchLen(node: HierarchyNode): number {
252
- let maxLen = node.len || 0
253
- if (node.children) {
254
- for (const child of node.children) {
255
- maxLen = Math.max(maxLen, findMaxBranchLen(child))
256
- }
265
+ let maxLen = 0
266
+ for (const n of descendants(node)) {
267
+ maxLen = Math.max(maxLen, n.len || 0)
257
268
  }
258
269
  return maxLen
259
270
  }
260
271
 
272
+ // Max root-to-leaf sum of branch lengths within the subtree at d
261
273
  export function maxLength(d: HierarchyNode): number {
262
- return (
263
- (d.data.length || 0) +
264
- (d.children ? d.children.reduce((m, c) => Math.max(m, maxLength(c)), 0) : 0)
265
- )
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)!
266
287
  }
267
288
 
268
289
  export function setBrLength(d: HierarchyNode, y0: number, k: number) {
269
- d.len = (y0 += Math.max(d.data.length || 0, 0)) * k
270
- if (d.children) {
271
- for (const child of d.children) {
272
- 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
+ }
273
299
  }
274
300
  }
275
301
  }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@
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
+ export { renderToSvg } from './renderToSvg.tsx'
8
9
  export { default as MSAView } from './components/Loading.tsx'
9
10
  export { default as MSAViewer } from './components/MSAViewer.tsx'
10
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
  }