react-msaview 5.3.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.
- package/bundle/index.js +22 -22
- package/bundle/index.js.LICENSE.txt +1 -1
- package/bundle/index.js.map +1 -1
- package/dist/components/Checkbox2.js +3 -1
- package/dist/components/Checkbox2.js.map +1 -1
- package/dist/components/Loading.js +6 -4
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +3 -1
- package/dist/components/MSAViewer.js +3 -4
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/dialogs/InterProScanDialog.js +2 -1
- package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +4 -6
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/tracks/renderTracksSvg.js +1 -1
- package/dist/components/tracks/renderTracksSvg.js.map +1 -1
- package/dist/components/tree/TreeNodeMenu.js +1 -1
- package/dist/components/tree/TreeNodeMenu.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +10 -0
- package/dist/components/tree/renderTreeCanvas.js +84 -12
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/fetchUtils.d.ts +19 -0
- package/dist/fetchUtils.js +28 -0
- package/dist/fetchUtils.js.map +1 -1
- package/dist/hierarchy.d.ts +9 -1
- package/dist/hierarchy.js +37 -0
- package/dist/hierarchy.js.map +1 -1
- package/dist/model/msaModel.d.ts +7 -0
- package/dist/model/msaModel.js +14 -0
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model.d.ts +9 -4
- package/dist/model.js +95 -32
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +44 -30
- package/dist/neighborJoining.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -5
- package/src/components/Checkbox2.tsx +7 -1
- package/src/components/Loading.tsx +15 -5
- package/src/components/MSAViewer.tsx +5 -3
- package/src/components/dialogs/InterProScanDialog.tsx +2 -1
- package/src/components/msa/renderMSABlock.ts +2 -8
- package/src/components/renderCtx.ts +1 -0
- package/src/components/tracks/renderTracksSvg.ts +1 -1
- package/src/components/tree/TreeNodeMenu.tsx +1 -1
- package/src/components/tree/renderTreeCanvas.ts +117 -11
- package/src/fetchUtils.test.ts +66 -0
- package/src/fetchUtils.ts +48 -0
- package/src/hierarchy.test.ts +56 -0
- package/src/hierarchy.ts +44 -1
- package/src/impgRender.test.tsx +125 -0
- package/src/model/msaModel.ts +21 -0
- package/src/model.ts +99 -55
- package/src/neighborJoining.ts +44 -31
- package/src/version.ts +1 -1
package/src/model.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
clamp,
|
|
3
|
-
fetchAndMaybeUnzipText,
|
|
4
|
-
groupBy,
|
|
5
|
-
notEmpty,
|
|
6
|
-
sum,
|
|
7
|
-
} from '@jbrowse/core/util'
|
|
1
|
+
import { clamp, groupBy, notEmpty, sum } from '@jbrowse/core/util'
|
|
8
2
|
import { openLocation } from '@jbrowse/core/util/io'
|
|
9
3
|
import { ElementId, FileLocation } from '@jbrowse/core/util/types/mst'
|
|
10
4
|
import { addDisposer, cast, types } from '@jbrowse/mobx-state-tree'
|
|
@@ -51,13 +45,16 @@ import {
|
|
|
51
45
|
minRowHeight,
|
|
52
46
|
} from './constants.ts'
|
|
53
47
|
import { createPaletteMap } from './createPaletteMap.ts'
|
|
48
|
+
import { fetchTextWithProgress, isAbortError } from './fetchUtils.ts'
|
|
54
49
|
import { flatToTree } from './flatToTree.ts'
|
|
55
50
|
import {
|
|
56
51
|
calcDepthToLeaf,
|
|
57
52
|
clusterLayout,
|
|
58
53
|
collapse,
|
|
54
|
+
collapsedSubtreeLengthExtent,
|
|
59
55
|
find,
|
|
60
56
|
findMaxBranchLen,
|
|
57
|
+
forEachDescendant,
|
|
61
58
|
hierarchy,
|
|
62
59
|
leaves,
|
|
63
60
|
links,
|
|
@@ -252,6 +249,15 @@ function stateModelFactory() {
|
|
|
252
249
|
* #property
|
|
253
250
|
*/
|
|
254
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>(),
|
|
255
261
|
}),
|
|
256
262
|
)
|
|
257
263
|
.volatile(() => ({
|
|
@@ -669,7 +675,9 @@ function stateModelFactory() {
|
|
|
669
675
|
const text = self.data.msa
|
|
670
676
|
// uses parseMSA so the named MSAParserType return type is portable
|
|
671
677
|
// to downstream consumers (avoids TS2883 with default exports)
|
|
672
|
-
return text
|
|
678
|
+
return text
|
|
679
|
+
? parseMSA(text, self.currentAlignment, self.msaFormat)
|
|
680
|
+
: null
|
|
673
681
|
},
|
|
674
682
|
/**
|
|
675
683
|
* #getter
|
|
@@ -945,7 +953,10 @@ function stateModelFactory() {
|
|
|
945
953
|
get colConsensus() {
|
|
946
954
|
const { colStats, colStatsSums } = this
|
|
947
955
|
return colStats.map((stats, i) => {
|
|
948
|
-
const total = colStatsSums[i]
|
|
956
|
+
const total = colStatsSums[i]
|
|
957
|
+
if (!total) {
|
|
958
|
+
return { letter: '', color: undefined }
|
|
959
|
+
}
|
|
949
960
|
let maxCount = 0
|
|
950
961
|
let letter = ''
|
|
951
962
|
for (const [key, val] of Object.entries(stats)) {
|
|
@@ -1023,7 +1034,18 @@ function stateModelFactory() {
|
|
|
1023
1034
|
clusterLayout(r, this.totalHeight, self.treeWidth)
|
|
1024
1035
|
r.data.length = 0
|
|
1025
1036
|
const max = maxLength(r)
|
|
1026
|
-
|
|
1037
|
+
const k = max ? self.treeWidth / max : 0
|
|
1038
|
+
setBrLength(r, 0, k)
|
|
1039
|
+
// for each collapsed clade, record the pixel x-positions of its
|
|
1040
|
+
// nearest/farthest tips so the renderer can draw a triangle that spans
|
|
1041
|
+
// the branch-length extent of the hidden subtree
|
|
1042
|
+
forEachDescendant(r, node => {
|
|
1043
|
+
if (node._children) {
|
|
1044
|
+
const { min, max: mx } = collapsedSubtreeLengthExtent(node)
|
|
1045
|
+
node.collapsedTipXNear = (node.len ?? 0) + min * k
|
|
1046
|
+
node.collapsedTipXFar = (node.len ?? 0) + mx * k
|
|
1047
|
+
}
|
|
1048
|
+
})
|
|
1027
1049
|
return r as HierarchyNode<NodeWithIdsAndLength>
|
|
1028
1050
|
},
|
|
1029
1051
|
|
|
@@ -1249,12 +1271,8 @@ function stateModelFactory() {
|
|
|
1249
1271
|
*/
|
|
1250
1272
|
zoomIn() {
|
|
1251
1273
|
transaction(() => {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
maxCellSize,
|
|
1255
|
-
Math.ceil(self.rowHeight * 1.5),
|
|
1256
|
-
)
|
|
1257
|
-
self.scrollX = clamp(self.scrollX, self.maxScrollX, 0)
|
|
1274
|
+
this.zoomInHorizontal()
|
|
1275
|
+
this.zoomInVertical()
|
|
1258
1276
|
})
|
|
1259
1277
|
},
|
|
1260
1278
|
/**
|
|
@@ -1262,15 +1280,8 @@ function stateModelFactory() {
|
|
|
1262
1280
|
*/
|
|
1263
1281
|
zoomOut() {
|
|
1264
1282
|
transaction(() => {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
Math.floor(self.colWidth * 0.75),
|
|
1268
|
-
)
|
|
1269
|
-
self.rowHeight = Math.max(
|
|
1270
|
-
minRowHeight,
|
|
1271
|
-
Math.floor(self.rowHeight * 0.75),
|
|
1272
|
-
)
|
|
1273
|
-
self.scrollX = clamp(self.scrollX, self.maxScrollX, 0)
|
|
1283
|
+
this.zoomOutHorizontal()
|
|
1284
|
+
this.zoomOutVertical()
|
|
1274
1285
|
})
|
|
1275
1286
|
},
|
|
1276
1287
|
/**
|
|
@@ -1304,9 +1315,13 @@ function stateModelFactory() {
|
|
|
1304
1315
|
)
|
|
1305
1316
|
|
|
1306
1317
|
const anchoredScrollY = offsetY - rowInView * self.rowHeight
|
|
1307
|
-
|
|
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
|
|
1308
1323
|
const topBias =
|
|
1309
|
-
|
|
1324
|
+
visibleHeight > 0 ? clamp(1 - overflow / visibleHeight, 0, 1) : 1
|
|
1310
1325
|
self.scrollY = clamp(
|
|
1311
1326
|
anchoredScrollY * (1 - topBias),
|
|
1312
1327
|
self.maxScrollY,
|
|
@@ -1693,22 +1708,8 @@ function stateModelFactory() {
|
|
|
1693
1708
|
* #action
|
|
1694
1709
|
*/
|
|
1695
1710
|
fit() {
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
self.msaAreaHeight / self.numRows,
|
|
1699
|
-
minRowHeight,
|
|
1700
|
-
maxCellSize,
|
|
1701
|
-
)
|
|
1702
|
-
}
|
|
1703
|
-
if (self.numColumns > 0) {
|
|
1704
|
-
self.colWidth = clamp(
|
|
1705
|
-
self.msaAreaWidth / self.numColumns,
|
|
1706
|
-
minColWidth,
|
|
1707
|
-
maxCellSize,
|
|
1708
|
-
)
|
|
1709
|
-
}
|
|
1710
|
-
self.scrollX = 0
|
|
1711
|
-
self.scrollY = 0
|
|
1711
|
+
this.fitVertically()
|
|
1712
|
+
this.fitHorizontally()
|
|
1712
1713
|
},
|
|
1713
1714
|
/**
|
|
1714
1715
|
* #action
|
|
@@ -1738,6 +1739,13 @@ function stateModelFactory() {
|
|
|
1738
1739
|
},
|
|
1739
1740
|
|
|
1740
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
|
+
|
|
1741
1749
|
addDisposer(
|
|
1742
1750
|
self,
|
|
1743
1751
|
autorun(() => {
|
|
@@ -1759,8 +1767,13 @@ function stateModelFactory() {
|
|
|
1759
1767
|
const generation = ++treeGeneration
|
|
1760
1768
|
try {
|
|
1761
1769
|
self.setLoadingTree(true)
|
|
1762
|
-
const text = await
|
|
1770
|
+
const text = await fetchTextWithProgress(
|
|
1763
1771
|
openLocation(treeFilehandle),
|
|
1772
|
+
status => {
|
|
1773
|
+
if (generation === treeGeneration) {
|
|
1774
|
+
self.setStatus(status)
|
|
1775
|
+
}
|
|
1776
|
+
},
|
|
1764
1777
|
)
|
|
1765
1778
|
if (generation === treeGeneration) {
|
|
1766
1779
|
transaction(() => {
|
|
@@ -1773,8 +1786,14 @@ function stateModelFactory() {
|
|
|
1773
1786
|
}
|
|
1774
1787
|
} catch (e) {
|
|
1775
1788
|
if (generation === treeGeneration) {
|
|
1776
|
-
|
|
1777
|
-
|
|
1789
|
+
if (isAbortError(e)) {
|
|
1790
|
+
// cancelled by the user: drop the filehandle so the view
|
|
1791
|
+
// returns to the import form instead of a stuck spinner
|
|
1792
|
+
self.setTreeFilehandle(undefined)
|
|
1793
|
+
} else {
|
|
1794
|
+
console.error(e)
|
|
1795
|
+
self.setError(e)
|
|
1796
|
+
}
|
|
1778
1797
|
}
|
|
1779
1798
|
} finally {
|
|
1780
1799
|
if (generation === treeGeneration) {
|
|
@@ -1792,13 +1811,19 @@ function stateModelFactory() {
|
|
|
1792
1811
|
if (treeMetadataFilehandle) {
|
|
1793
1812
|
try {
|
|
1794
1813
|
self.setTreeMetadata(
|
|
1795
|
-
await
|
|
1814
|
+
await fetchTextWithProgress(
|
|
1796
1815
|
openLocation(treeMetadataFilehandle),
|
|
1816
|
+
status => {
|
|
1817
|
+
self.setStatus(status)
|
|
1818
|
+
},
|
|
1797
1819
|
),
|
|
1798
1820
|
)
|
|
1799
1821
|
} catch (e) {
|
|
1800
|
-
|
|
1801
|
-
|
|
1822
|
+
// ignore user cancel (isAbortError); treeMetadata is optional
|
|
1823
|
+
if (!isAbortError(e)) {
|
|
1824
|
+
console.error(e)
|
|
1825
|
+
self.setError(e)
|
|
1826
|
+
}
|
|
1802
1827
|
}
|
|
1803
1828
|
}
|
|
1804
1829
|
}),
|
|
@@ -1827,16 +1852,24 @@ function stateModelFactory() {
|
|
|
1827
1852
|
const { gffFilehandle } = self
|
|
1828
1853
|
if (gffFilehandle) {
|
|
1829
1854
|
try {
|
|
1830
|
-
const gffText = await
|
|
1855
|
+
const gffText = await fetchTextWithProgress(
|
|
1831
1856
|
openLocation(gffFilehandle),
|
|
1857
|
+
status => {
|
|
1858
|
+
self.setStatus(status)
|
|
1859
|
+
},
|
|
1832
1860
|
)
|
|
1833
1861
|
self.applyGFFText(gffText)
|
|
1834
1862
|
if (gffFilehandle.locationType === 'BlobLocation') {
|
|
1835
1863
|
self.setGFFFilehandle(undefined)
|
|
1836
1864
|
}
|
|
1837
1865
|
} catch (e) {
|
|
1838
|
-
|
|
1839
|
-
|
|
1866
|
+
// on user cancel (isAbortError) drop the filehandle
|
|
1867
|
+
if (isAbortError(e)) {
|
|
1868
|
+
self.setGFFFilehandle(undefined)
|
|
1869
|
+
} else {
|
|
1870
|
+
console.error(e)
|
|
1871
|
+
self.setError(e)
|
|
1872
|
+
}
|
|
1840
1873
|
}
|
|
1841
1874
|
}
|
|
1842
1875
|
}),
|
|
@@ -1853,8 +1886,13 @@ function stateModelFactory() {
|
|
|
1853
1886
|
try {
|
|
1854
1887
|
self.setLoadingMSA(true)
|
|
1855
1888
|
self.setError(undefined)
|
|
1856
|
-
const txt = await
|
|
1889
|
+
const txt = await fetchTextWithProgress(
|
|
1857
1890
|
openLocation(msaFilehandle),
|
|
1891
|
+
status => {
|
|
1892
|
+
if (generation === msaGeneration) {
|
|
1893
|
+
self.setStatus(status)
|
|
1894
|
+
}
|
|
1895
|
+
},
|
|
1858
1896
|
)
|
|
1859
1897
|
if (generation === msaGeneration) {
|
|
1860
1898
|
transaction(() => {
|
|
@@ -1867,8 +1905,14 @@ function stateModelFactory() {
|
|
|
1867
1905
|
}
|
|
1868
1906
|
} catch (e) {
|
|
1869
1907
|
if (generation === msaGeneration) {
|
|
1870
|
-
|
|
1871
|
-
|
|
1908
|
+
if (isAbortError(e)) {
|
|
1909
|
+
// cancelled by the user: drop the filehandle so the view
|
|
1910
|
+
// returns to the import form instead of a stuck spinner
|
|
1911
|
+
self.setMSAFilehandle(undefined)
|
|
1912
|
+
} else {
|
|
1913
|
+
console.error(e)
|
|
1914
|
+
self.setError(e)
|
|
1915
|
+
}
|
|
1872
1916
|
}
|
|
1873
1917
|
} finally {
|
|
1874
1918
|
if (generation === msaGeneration) {
|
package/src/neighborJoining.ts
CHANGED
|
@@ -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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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[
|
|
226
|
-
D[k]![
|
|
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[
|
|
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
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
275
|
-
|
|
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
|
-
|
|
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.
|
|
1
|
+
export const version = '5.4.1'
|