react-msaview 5.4.0 → 5.4.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 (37) hide show
  1. package/bundle/index.js +22 -22
  2. package/bundle/index.js.LICENSE.txt +1 -1
  3. package/bundle/index.js.map +1 -1
  4. package/dist/components/Checkbox2.js +3 -1
  5. package/dist/components/Checkbox2.js.map +1 -1
  6. package/dist/components/MSAViewer.js +1 -3
  7. package/dist/components/MSAViewer.js.map +1 -1
  8. package/dist/components/dialogs/InterProScanDialog.js +2 -1
  9. package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
  10. package/dist/components/msa/renderMSABlock.js +4 -6
  11. package/dist/components/msa/renderMSABlock.js.map +1 -1
  12. package/dist/components/tracks/renderTracksSvg.js +1 -1
  13. package/dist/components/tracks/renderTracksSvg.js.map +1 -1
  14. package/dist/components/tree/renderTreeCanvas.js +3 -0
  15. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  16. package/dist/hierarchy.js +4 -1
  17. package/dist/hierarchy.js.map +1 -1
  18. package/dist/model/msaModel.js.map +1 -1
  19. package/dist/model.d.ts +4 -2
  20. package/dist/model.js +30 -16
  21. package/dist/model.js.map +1 -1
  22. package/dist/neighborJoining.js +44 -30
  23. package/dist/neighborJoining.js.map +1 -1
  24. package/dist/version.d.ts +1 -1
  25. package/dist/version.js +1 -1
  26. package/package.json +5 -5
  27. package/src/components/Checkbox2.tsx +7 -1
  28. package/src/components/MSAViewer.tsx +1 -3
  29. package/src/components/dialogs/InterProScanDialog.tsx +2 -1
  30. package/src/components/msa/renderMSABlock.ts +2 -8
  31. package/src/components/tracks/renderTracksSvg.ts +1 -1
  32. package/src/components/tree/renderTreeCanvas.ts +5 -1
  33. package/src/hierarchy.ts +4 -1
  34. package/src/model/msaModel.ts +3 -1
  35. package/src/model.ts +32 -34
  36. package/src/neighborJoining.ts +44 -31
  37. package/src/version.ts +1 -1
package/src/model.ts CHANGED
@@ -249,6 +249,15 @@ function stateModelFactory() {
249
249
  * #property
250
250
  */
251
251
  relativeTo: types.maybe(types.string),
252
+ /**
253
+ * #property
254
+ * declarative seed for the highlighted-columns overlay (visible column
255
+ * indices). Unlike the volatile `highlightedColumns` (driven by
256
+ * transient genome-hover sync), this persists in the snapshot/URL so a
257
+ * shared link can open with specific columns highlighted. Applied once
258
+ * in afterCreate.
259
+ */
260
+ highlightColumns: types.frozen<number[] | undefined>(),
252
261
  }),
253
262
  )
254
263
  .volatile(() => ({
@@ -944,7 +953,10 @@ function stateModelFactory() {
944
953
  get colConsensus() {
945
954
  const { colStats, colStatsSums } = this
946
955
  return colStats.map((stats, i) => {
947
- const total = colStatsSums[i]!
956
+ const total = colStatsSums[i]
957
+ if (!total) {
958
+ return { letter: '', color: undefined }
959
+ }
948
960
  let maxCount = 0
949
961
  let letter = ''
950
962
  for (const [key, val] of Object.entries(stats)) {
@@ -1259,12 +1271,8 @@ function stateModelFactory() {
1259
1271
  */
1260
1272
  zoomIn() {
1261
1273
  transaction(() => {
1262
- self.colWidth = Math.min(maxCellSize, Math.ceil(self.colWidth * 1.5))
1263
- self.rowHeight = Math.min(
1264
- maxCellSize,
1265
- Math.ceil(self.rowHeight * 1.5),
1266
- )
1267
- self.scrollX = clamp(self.scrollX, self.maxScrollX, 0)
1274
+ this.zoomInHorizontal()
1275
+ this.zoomInVertical()
1268
1276
  })
1269
1277
  },
1270
1278
  /**
@@ -1272,15 +1280,8 @@ function stateModelFactory() {
1272
1280
  */
1273
1281
  zoomOut() {
1274
1282
  transaction(() => {
1275
- self.colWidth = Math.max(
1276
- minColWidth,
1277
- Math.floor(self.colWidth * 0.75),
1278
- )
1279
- self.rowHeight = Math.max(
1280
- minRowHeight,
1281
- Math.floor(self.rowHeight * 0.75),
1282
- )
1283
- self.scrollX = clamp(self.scrollX, self.maxScrollX, 0)
1283
+ this.zoomOutHorizontal()
1284
+ this.zoomOutVertical()
1284
1285
  })
1285
1286
  },
1286
1287
  /**
@@ -1314,9 +1315,13 @@ function stateModelFactory() {
1314
1315
  )
1315
1316
 
1316
1317
  const anchoredScrollY = offsetY - rowInView * self.rowHeight
1317
- const overflow = Math.max(0, self.totalHeight - self.height)
1318
+ // maxScrollY is -(totalHeight - visibleMsaHeight) when the alignment
1319
+ // overflows, so -maxScrollY is exactly that overflow past the
1320
+ // scrollable MSA viewport (0 when it fits)
1321
+ const overflow = Math.max(0, -self.maxScrollY)
1322
+ const visibleHeight = self.totalHeight - overflow
1318
1323
  const topBias =
1319
- self.height > 0 ? clamp(1 - overflow / self.height, 0, 1) : 0
1324
+ visibleHeight > 0 ? clamp(1 - overflow / visibleHeight, 0, 1) : 1
1320
1325
  self.scrollY = clamp(
1321
1326
  anchoredScrollY * (1 - topBias),
1322
1327
  self.maxScrollY,
@@ -1703,22 +1708,8 @@ function stateModelFactory() {
1703
1708
  * #action
1704
1709
  */
1705
1710
  fit() {
1706
- if (self.numRows > 0) {
1707
- self.rowHeight = clamp(
1708
- self.msaAreaHeight / self.numRows,
1709
- minRowHeight,
1710
- maxCellSize,
1711
- )
1712
- }
1713
- if (self.numColumns > 0) {
1714
- self.colWidth = clamp(
1715
- self.msaAreaWidth / self.numColumns,
1716
- minColWidth,
1717
- maxCellSize,
1718
- )
1719
- }
1720
- self.scrollX = 0
1721
- self.scrollY = 0
1711
+ this.fitVertically()
1712
+ this.fitHorizontally()
1722
1713
  },
1723
1714
  /**
1724
1715
  * #action
@@ -1748,6 +1739,13 @@ function stateModelFactory() {
1748
1739
  },
1749
1740
 
1750
1741
  afterCreate() {
1742
+ // seed the highlighted-columns overlay from the declarative property so
1743
+ // a shared snapshot/URL opens with those columns highlighted (the
1744
+ // volatile highlightedColumns can later be driven by genome-hover sync)
1745
+ if (self.highlightColumns?.length) {
1746
+ self.setHighlightedColumns(self.highlightColumns)
1747
+ }
1748
+
1751
1749
  addDisposer(
1752
1750
  self,
1753
1751
  autorun(() => {
@@ -194,16 +194,10 @@ function neighborJoining(distances: number[][], names: string[]): NJNode {
194
194
  const ri = r.get(minI)!
195
195
  const rj = r.get(minJ)!
196
196
 
197
- let limbI: number
198
- let limbJ: number
199
-
200
- if (remaining > 2) {
201
- limbI = dij / 2 + (ri - rj) / (2 * (remaining - 2))
202
- limbJ = dij - limbI
203
- } else {
204
- limbI = dij / 2
205
- limbJ = dij / 2
206
- }
197
+ // remaining is always > 2 inside this loop; the final two nodes are
198
+ // connected after it
199
+ let limbI = dij / 2 + (ri - rj) / (2 * (remaining - 2))
200
+ let limbJ = dij - limbI
207
201
 
208
202
  // Ensure non-negative branch lengths
209
203
  limbI = Math.max(0, limbI)
@@ -217,19 +211,18 @@ function neighborJoining(distances: number[][], names: string[]): NJNode {
217
211
  rightLength: limbJ,
218
212
  }
219
213
 
220
- // Update distance matrix
221
- const newIdx = minI
214
+ // Update distance matrix, reusing minI's slot for the merged node
222
215
  for (const k of active) {
223
216
  if (k !== minI && k !== minJ) {
224
217
  const newDist = (D[minI]![k]! + D[minJ]![k]! - dij) / 2
225
- D[newIdx]![k] = Math.max(0, newDist)
226
- D[k]![newIdx] = Math.max(0, newDist)
218
+ D[minI]![k] = Math.max(0, newDist)
219
+ D[k]![minI] = Math.max(0, newDist)
227
220
  }
228
221
  }
229
222
 
230
223
  // Mark minJ as removed
231
224
  nodes[minJ] = undefined
232
- nodes[newIdx] = newNode
225
+ nodes[minI] = newNode
233
226
 
234
227
  remaining--
235
228
  }
@@ -257,25 +250,45 @@ function neighborJoining(distances: number[][], names: string[]): NJNode {
257
250
  return nodes[finalActive[0]!]!
258
251
  }
259
252
 
260
- function nodeToNewick(node: NJNode, branchLength?: number): string {
261
- let result: string
262
-
263
- if (node.name !== undefined && !node.left && !node.right) {
264
- const quotedName = node.name.replaceAll("'", "''")
265
- result = `'${quotedName}'`
266
- } else {
267
- const leftNewick = node.left ? nodeToNewick(node.left, node.leftLength) : ''
268
- const rightNewick = node.right
269
- ? nodeToNewick(node.right, node.rightLength)
270
- : ''
271
- result = `(${leftNewick},${rightNewick})`
272
- }
253
+ function withBranchLength(newick: string, branchLength?: number) {
254
+ return branchLength === undefined
255
+ ? newick
256
+ : `${newick}:${branchLength.toFixed(6)}`
257
+ }
273
258
 
274
- if (branchLength !== undefined) {
275
- result += `:${branchLength.toFixed(6)}`
259
+ // Emit Newick iteratively rather than recursively: an NJ tree of N leaves can
260
+ // be a caterpillar of depth ~N, so recursion risks a stack overflow on large
261
+ // alignments (matching the iterative-traversal convention in hierarchy.ts).
262
+ function nodeToNewick(root: NJNode): string {
263
+ const postOrder: NJNode[] = []
264
+ const stack: NJNode[] = [root]
265
+ while (stack.length > 0) {
266
+ const node = stack.pop()!
267
+ postOrder.push(node)
268
+ if (node.left) {
269
+ stack.push(node.left)
270
+ }
271
+ if (node.right) {
272
+ stack.push(node.right)
273
+ }
276
274
  }
277
275
 
278
- return result
276
+ const newickByNode = new Map<NJNode, string>()
277
+ for (let i = postOrder.length - 1; i >= 0; i--) {
278
+ const node = postOrder[i]!
279
+ if (node.name !== undefined && !node.left && !node.right) {
280
+ newickByNode.set(node, `'${node.name.replaceAll("'", "''")}'`)
281
+ } else {
282
+ const left = node.left
283
+ ? withBranchLength(newickByNode.get(node.left)!, node.leftLength)
284
+ : ''
285
+ const right = node.right
286
+ ? withBranchLength(newickByNode.get(node.right)!, node.rightLength)
287
+ : ''
288
+ newickByNode.set(node, `(${left},${right})`)
289
+ }
290
+ }
291
+ return newickByNode.get(root)!
279
292
  }
280
293
 
281
294
  export function calculateNeighborJoiningTree(
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '5.4.0'
1
+ export const version = '5.4.1'