react-msaview 5.0.6 → 5.0.7
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 +106 -106
- package/bundle/index.js.LICENSE.txt +1 -1
- package/bundle/index.js.map +1 -1
- package/dist/components/Track.d.ts +0 -4
- package/dist/components/Track.js +2 -3
- package/dist/components/Track.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +22 -27
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +32 -38
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +33 -1
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/model.d.ts +10 -0
- package/dist/model.js +23 -0
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +2 -4
- package/dist/neighborJoining.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +11 -8
- package/src/components/Track.tsx +2 -5
- package/src/components/msa/MSACanvas.tsx +24 -30
- package/src/components/tree/TreeCanvas.tsx +42 -40
- package/src/components/tree/TreeCanvasBlock.tsx +44 -0
- package/src/model.ts +24 -0
- package/src/neighborJoining.test.ts +15 -7
- package/src/neighborJoining.ts +2 -4
- package/src/parseAsn1.test.ts +2 -2
- package/src/version.ts +1 -1
- package/dist/createPaletteMap.test.d.ts +0 -1
- package/dist/createPaletteMap.test.js +0 -49
- package/dist/createPaletteMap.test.js.map +0 -1
- package/dist/neighborJoining.test.d.ts +0 -1
- package/dist/neighborJoining.test.js +0 -110
- package/dist/neighborJoining.test.js.map +0 -1
- package/dist/parseAsn1.test.d.ts +0 -1
- package/dist/parseAsn1.test.js +0 -8
- package/dist/parseAsn1.test.js.map +0 -1
- package/dist/rowCoordinateCalculations.test.d.ts +0 -1
- package/dist/rowCoordinateCalculations.test.js +0 -224
- package/dist/rowCoordinateCalculations.test.js.map +0 -1
- package/dist/seqPosToGlobalCol.test.d.ts +0 -1
- package/dist/seqPosToGlobalCol.test.js +0 -60
- package/dist/seqPosToGlobalCol.test.js.map +0 -1
|
@@ -14,8 +14,8 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
14
14
|
const mouseoverRef = useRef<HTMLCanvasElement>(null)
|
|
15
15
|
const scheduled = useRef(false)
|
|
16
16
|
const deltaY = useRef(0)
|
|
17
|
-
const prevY = useRef
|
|
18
|
-
const { treeWidth, height, blocksY, treeAreaWidth
|
|
17
|
+
const prevY = useRef(0)
|
|
18
|
+
const { treeWidth, height, blocksY, treeAreaWidth } = model
|
|
19
19
|
const [mouseDragging, setMouseDragging] = useState(false)
|
|
20
20
|
|
|
21
21
|
useEffect(() => {
|
|
@@ -37,71 +37,75 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
37
37
|
event.preventDefault()
|
|
38
38
|
event.stopPropagation()
|
|
39
39
|
}
|
|
40
|
-
curr.addEventListener('wheel', onWheel)
|
|
40
|
+
curr.addEventListener('wheel', onWheel, { passive: false })
|
|
41
41
|
return () => {
|
|
42
42
|
curr.removeEventListener('wheel', onWheel)
|
|
43
43
|
}
|
|
44
44
|
}, [model])
|
|
45
45
|
|
|
46
46
|
useEffect(() => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
scheduled.current = false
|
|
62
|
-
prevY.current = event.clientY
|
|
63
|
-
})
|
|
47
|
+
if (mouseDragging) {
|
|
48
|
+
function globalMouseMove(event: MouseEvent) {
|
|
49
|
+
event.preventDefault()
|
|
50
|
+
const currY = event.clientY
|
|
51
|
+
const distanceY = currY - prevY.current
|
|
52
|
+
if (distanceY) {
|
|
53
|
+
if (!scheduled.current) {
|
|
54
|
+
scheduled.current = true
|
|
55
|
+
window.requestAnimationFrame(() => {
|
|
56
|
+
model.doScrollY(distanceY)
|
|
57
|
+
scheduled.current = false
|
|
58
|
+
prevY.current = event.clientY
|
|
59
|
+
})
|
|
60
|
+
}
|
|
64
61
|
}
|
|
65
62
|
}
|
|
66
|
-
}
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (mouseDragging) {
|
|
64
|
+
function globalMouseUp() {
|
|
65
|
+
prevY.current = 0
|
|
71
66
|
setMouseDragging(false)
|
|
72
67
|
}
|
|
73
|
-
}
|
|
74
68
|
|
|
75
|
-
if (mouseDragging) {
|
|
76
69
|
window.addEventListener('mousemove', globalMouseMove, true)
|
|
77
70
|
window.addEventListener('mouseup', globalMouseUp, true)
|
|
78
|
-
|
|
71
|
+
return () => {
|
|
79
72
|
window.removeEventListener('mousemove', globalMouseMove, true)
|
|
80
73
|
window.removeEventListener('mouseup', globalMouseUp, true)
|
|
81
74
|
}
|
|
82
75
|
}
|
|
83
|
-
return
|
|
76
|
+
return undefined
|
|
84
77
|
}, [model, mouseDragging])
|
|
85
78
|
|
|
86
|
-
// Global tree mouseover effect
|
|
79
|
+
// Global tree mouseover effect. Only [model] is needed in the dependency
|
|
80
|
+
// array because autorun internally tracks all accessed observables
|
|
81
|
+
// (treeAreaWidth, height, scrollY, etc.) and re-runs when they change
|
|
87
82
|
useEffect(() => {
|
|
88
83
|
const ctx = mouseoverRef.current?.getContext('2d')
|
|
89
84
|
return ctx
|
|
90
85
|
? autorun(() => {
|
|
91
86
|
if (isAlive(model)) {
|
|
87
|
+
const {
|
|
88
|
+
relativeTo,
|
|
89
|
+
leaves,
|
|
90
|
+
rowHeight,
|
|
91
|
+
hoveredTreeNode,
|
|
92
|
+
treeAreaWidth: w,
|
|
93
|
+
height: h,
|
|
94
|
+
scrollY: sy,
|
|
95
|
+
mouseOverRowName,
|
|
96
|
+
} = model
|
|
92
97
|
ctx.resetTransform()
|
|
93
|
-
ctx.clearRect(0, 0,
|
|
98
|
+
ctx.clearRect(0, 0, w, h)
|
|
94
99
|
|
|
95
100
|
// Highlight reference row (relativeTo) persistently
|
|
96
|
-
const { relativeTo, leaves, rowHeight, hoveredTreeNode } = model
|
|
97
101
|
if (relativeTo) {
|
|
98
102
|
const referenceLeaf = leaves.find(
|
|
99
103
|
leaf => leaf.data.name === relativeTo,
|
|
100
104
|
)
|
|
101
105
|
if (referenceLeaf) {
|
|
102
|
-
const y = referenceLeaf.x! +
|
|
106
|
+
const y = referenceLeaf.x! + sy
|
|
103
107
|
ctx.fillStyle = 'rgba(0,128,255,0.3)' // Blue highlight for reference row
|
|
104
|
-
ctx.fillRect(0, y - rowHeight / 2,
|
|
108
|
+
ctx.fillRect(0, y - rowHeight / 2, w, rowHeight)
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
|
|
@@ -113,33 +117,31 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
113
117
|
leaf => leaf.data.name === descendantName,
|
|
114
118
|
)
|
|
115
119
|
if (matchingLeaf) {
|
|
116
|
-
const y = matchingLeaf.x! +
|
|
117
|
-
ctx.fillRect(0, y - rowHeight / 2,
|
|
120
|
+
const y = matchingLeaf.x! + sy
|
|
121
|
+
ctx.fillRect(0, y - rowHeight / 2, w, rowHeight)
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
// Highlight single tree row corresponding to MSA mouseover (if not part of multi-row hover)
|
|
123
|
-
const { mouseOverRowName } = model
|
|
124
127
|
if (
|
|
125
128
|
mouseOverRowName &&
|
|
126
129
|
mouseOverRowName !== relativeTo &&
|
|
127
130
|
!hoveredTreeNode?.descendantNames.includes(mouseOverRowName)
|
|
128
131
|
) {
|
|
129
|
-
// Find the leaf node that matches the hovered row
|
|
130
132
|
const matchingLeaf = leaves.find(
|
|
131
133
|
leaf => leaf.data.name === mouseOverRowName,
|
|
132
134
|
)
|
|
133
135
|
if (matchingLeaf) {
|
|
134
|
-
const y = matchingLeaf.x! +
|
|
136
|
+
const y = matchingLeaf.x! + sy
|
|
135
137
|
ctx.fillStyle = 'rgba(255,165,0,0.2)' // Orange highlight for MSA sync
|
|
136
|
-
ctx.fillRect(0, y - rowHeight / 2,
|
|
138
|
+
ctx.fillRect(0, y - rowHeight / 2, w, rowHeight)
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
})
|
|
141
143
|
: undefined
|
|
142
|
-
}, [model
|
|
144
|
+
}, [model])
|
|
143
145
|
|
|
144
146
|
function mouseDown(event: React.MouseEvent) {
|
|
145
147
|
// check if clicking a draggable element or a resize handle
|
|
@@ -4,6 +4,7 @@ import { useTheme } from '@mui/material'
|
|
|
4
4
|
import Flatbush from 'flatbush'
|
|
5
5
|
import { autorun } from 'mobx'
|
|
6
6
|
import { observer } from 'mobx-react'
|
|
7
|
+
import { makeStyles } from 'tss-react/mui'
|
|
7
8
|
|
|
8
9
|
import TreeBranchMenu from './TreeBranchMenu.tsx'
|
|
9
10
|
import TreeNodeMenu from './TreeNodeMenu.tsx'
|
|
@@ -11,6 +12,23 @@ import { padding, renderTreeCanvas } from './renderTreeCanvas.ts'
|
|
|
11
12
|
|
|
12
13
|
import type { MsaViewModel } from '../../model.ts'
|
|
13
14
|
|
|
15
|
+
const useStyles = makeStyles()(theme => ({
|
|
16
|
+
tooltip: {
|
|
17
|
+
position: 'fixed',
|
|
18
|
+
pointerEvents: 'none',
|
|
19
|
+
zIndex: 10000,
|
|
20
|
+
backgroundColor: theme.palette.grey[700],
|
|
21
|
+
color: theme.palette.common.white,
|
|
22
|
+
padding: '4px 8px',
|
|
23
|
+
borderRadius: 4,
|
|
24
|
+
fontSize: 12,
|
|
25
|
+
whiteSpace: 'nowrap',
|
|
26
|
+
maxWidth: 300,
|
|
27
|
+
overflow: 'hidden',
|
|
28
|
+
textOverflow: 'ellipsis',
|
|
29
|
+
},
|
|
30
|
+
}))
|
|
31
|
+
|
|
14
32
|
interface TooltipData {
|
|
15
33
|
name: string
|
|
16
34
|
id: string
|
|
@@ -75,6 +93,7 @@ const TreeCanvasBlock = observer(function ({
|
|
|
75
93
|
model: MsaViewModel
|
|
76
94
|
offsetY: number
|
|
77
95
|
}) {
|
|
96
|
+
const { classes } = useStyles()
|
|
78
97
|
const theme = useTheme()
|
|
79
98
|
const ref = useRef<HTMLCanvasElement>(null)
|
|
80
99
|
const clickMap = useRef(new ClickMapIndex())
|
|
@@ -82,6 +101,11 @@ const TreeCanvasBlock = observer(function ({
|
|
|
82
101
|
const [branchMenu, setBranchMenu] = useState<TooltipData>()
|
|
83
102
|
const [toggleNodeMenu, setToggleNodeMenu] = useState<TooltipData>()
|
|
84
103
|
const [hoverElt, setHoverElt] = useState<ClickEntry>()
|
|
104
|
+
const [tooltipInfo, setTooltipInfo] = useState<{
|
|
105
|
+
name: string
|
|
106
|
+
x: number
|
|
107
|
+
y: number
|
|
108
|
+
}>()
|
|
85
109
|
|
|
86
110
|
const { scrollY, treeAreaWidth, blockSize, highResScaleFactor } = model
|
|
87
111
|
|
|
@@ -217,6 +241,17 @@ const TreeCanvasBlock = observer(function ({
|
|
|
217
241
|
ref.current.style.cursor = hoveredAny ? 'pointer' : 'default'
|
|
218
242
|
setHoverElt(hoveredLeaf) // Only show direct hover highlight for leaf nodes
|
|
219
243
|
|
|
244
|
+
// Set tooltip info
|
|
245
|
+
if (hoveredAny) {
|
|
246
|
+
setTooltipInfo({
|
|
247
|
+
name: hoveredAny.name,
|
|
248
|
+
x: event.clientX,
|
|
249
|
+
y: event.clientY,
|
|
250
|
+
})
|
|
251
|
+
} else {
|
|
252
|
+
setTooltipInfo(undefined)
|
|
253
|
+
}
|
|
254
|
+
|
|
220
255
|
// Handle tree node hover for multi-row highlighting
|
|
221
256
|
if (hoveredAny) {
|
|
222
257
|
model.setHoveredTreeNode(hoveredAny.id)
|
|
@@ -249,6 +284,7 @@ const TreeCanvasBlock = observer(function ({
|
|
|
249
284
|
}}
|
|
250
285
|
onMouseLeave={() => {
|
|
251
286
|
setHoverElt(undefined)
|
|
287
|
+
setTooltipInfo(undefined)
|
|
252
288
|
// Clear all highlighting when leaving tree area
|
|
253
289
|
model.setHoveredTreeNode(undefined)
|
|
254
290
|
model.setMousePos(undefined, undefined)
|
|
@@ -265,6 +301,14 @@ const TreeCanvasBlock = observer(function ({
|
|
|
265
301
|
height={height}
|
|
266
302
|
ref={mouseoverRef}
|
|
267
303
|
/>
|
|
304
|
+
{tooltipInfo ? (
|
|
305
|
+
<div
|
|
306
|
+
className={classes.tooltip}
|
|
307
|
+
style={{ left: tooltipInfo.x + 12, top: tooltipInfo.y + 12 }}
|
|
308
|
+
>
|
|
309
|
+
{tooltipInfo.name}
|
|
310
|
+
</div>
|
|
311
|
+
) : null}
|
|
268
312
|
</>
|
|
269
313
|
)
|
|
270
314
|
})
|
package/src/model.ts
CHANGED
|
@@ -1676,6 +1676,30 @@ function stateModelFactory() {
|
|
|
1676
1676
|
}))
|
|
1677
1677
|
|
|
1678
1678
|
.views(self => ({
|
|
1679
|
+
/**
|
|
1680
|
+
* #getter
|
|
1681
|
+
* Returns information about the currently hovered cell
|
|
1682
|
+
*/
|
|
1683
|
+
get hoveredCell() {
|
|
1684
|
+
const { mouseCol, mouseRow } = self
|
|
1685
|
+
if (mouseCol === undefined || mouseRow === undefined) {
|
|
1686
|
+
return undefined
|
|
1687
|
+
}
|
|
1688
|
+
const rowName = self.rowNames[mouseRow]
|
|
1689
|
+
if (!rowName) {
|
|
1690
|
+
return undefined
|
|
1691
|
+
}
|
|
1692
|
+
const seq = self.columns[rowName]
|
|
1693
|
+
const base = seq?.[mouseCol]
|
|
1694
|
+
const seqPos = self.visibleColToSeqPosOneBased(rowName, mouseCol)
|
|
1695
|
+
return {
|
|
1696
|
+
rowName,
|
|
1697
|
+
col: mouseCol,
|
|
1698
|
+
base,
|
|
1699
|
+
seqPos,
|
|
1700
|
+
}
|
|
1701
|
+
},
|
|
1702
|
+
|
|
1679
1703
|
/**
|
|
1680
1704
|
* #getter
|
|
1681
1705
|
* widget width minus the tree area gives the space for the MSA
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseNewick } from 'msa-parsers'
|
|
1
2
|
import { describe, expect, test } from 'vitest'
|
|
2
3
|
|
|
3
4
|
import { calculateNeighborJoiningTree } from './neighborJoining.ts'
|
|
@@ -80,17 +81,24 @@ describe('calculateNeighborJoiningTree', () => {
|
|
|
80
81
|
expect(tree).toContain('seq3')
|
|
81
82
|
})
|
|
82
83
|
|
|
83
|
-
test('
|
|
84
|
+
test('names with special characters round-trip through Newick parsing', () => {
|
|
84
85
|
const rows: [string, string][] = [
|
|
85
|
-
['
|
|
86
|
+
['EU105457.1|chr09:67680268..67675529_LTR/Copia', 'MKAA'],
|
|
86
87
|
['seq(2)', 'MKAA'],
|
|
87
88
|
]
|
|
88
|
-
const
|
|
89
|
+
const newick = calculateNeighborJoiningTree(rows)
|
|
90
|
+
const tree = parseNewick(newick)
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
function getLeafNames(node: Record<string, unknown>): string[] {
|
|
93
|
+
const children = node.children as Record<string, unknown>[] | undefined
|
|
94
|
+
if (!children?.length) {
|
|
95
|
+
return [node.name as string]
|
|
96
|
+
}
|
|
97
|
+
return children.flatMap(c => getLeafNames(c))
|
|
98
|
+
}
|
|
99
|
+
const names = getLeafNames(tree)
|
|
100
|
+
expect(names).toContain('EU105457.1|chr09:67680268..67675529_LTR/Copia')
|
|
101
|
+
expect(names).toContain('seq(2)')
|
|
94
102
|
})
|
|
95
103
|
|
|
96
104
|
test('identical sequences have zero distance', () => {
|
package/src/neighborJoining.ts
CHANGED
|
@@ -851,11 +851,9 @@ function nodeToNewick(node: NJNode, branchLength?: number): string {
|
|
|
851
851
|
let result: string
|
|
852
852
|
|
|
853
853
|
if (node.name !== undefined && !node.left && !node.right) {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
result = escapedName
|
|
854
|
+
const quotedName = node.name.replaceAll("'", "''")
|
|
855
|
+
result = `'${quotedName}'`
|
|
857
856
|
} else {
|
|
858
|
-
// Internal node
|
|
859
857
|
const leftNewick = node.left ? nodeToNewick(node.left, node.leftLength) : ''
|
|
860
858
|
const rightNewick = node.right
|
|
861
859
|
? nodeToNewick(node.right, node.rightLength)
|
package/src/parseAsn1.test.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
2
|
|
|
3
3
|
import { expect, test } from 'vitest'
|
|
4
4
|
|
|
5
5
|
import { parseAsn1 } from './parseAsn1.ts'
|
|
6
6
|
|
|
7
|
-
const r =
|
|
7
|
+
const r = readFileSync(new URL('../test/data/tree.asn', import.meta.url), 'utf8')
|
|
8
8
|
|
|
9
9
|
test('real data file', () => {
|
|
10
10
|
expect(parseAsn1(r)).toMatchSnapshot()
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '5.0.
|
|
1
|
+
export const version = '5.0.7'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { expect, test } from 'vitest';
|
|
2
|
-
import { createPaletteMap } from "./createPaletteMap.js";
|
|
3
|
-
import palettes from "./ggplotPalettes.js";
|
|
4
|
-
// Original implementation for comparison
|
|
5
|
-
function originalFillPalette(keys) {
|
|
6
|
-
let i = 0;
|
|
7
|
-
const map = {};
|
|
8
|
-
for (const key of keys) {
|
|
9
|
-
const k = Math.min(keys.length - 1, palettes.length - 1);
|
|
10
|
-
map[key] = palettes[k][i];
|
|
11
|
-
i++;
|
|
12
|
-
}
|
|
13
|
-
return map;
|
|
14
|
-
}
|
|
15
|
-
test('createPaletteMap matches original implementation with 1 key', () => {
|
|
16
|
-
const keys = ['IPR001234'];
|
|
17
|
-
expect(createPaletteMap(keys)).toEqual(originalFillPalette(keys));
|
|
18
|
-
});
|
|
19
|
-
test('createPaletteMap matches original implementation with 3 keys', () => {
|
|
20
|
-
const keys = ['IPR001234', 'IPR005678', 'IPR009012'];
|
|
21
|
-
expect(createPaletteMap(keys)).toEqual(originalFillPalette(keys));
|
|
22
|
-
});
|
|
23
|
-
test('createPaletteMap matches original implementation with 8 keys (max palette size)', () => {
|
|
24
|
-
const keys = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
|
25
|
-
expect(createPaletteMap(keys)).toEqual(originalFillPalette(keys));
|
|
26
|
-
});
|
|
27
|
-
test('createPaletteMap matches original implementation with more keys than palette colors', () => {
|
|
28
|
-
const keys = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'];
|
|
29
|
-
expect(createPaletteMap(keys)).toEqual(originalFillPalette(keys));
|
|
30
|
-
});
|
|
31
|
-
test('createPaletteMap returns correct colors for 3 keys', () => {
|
|
32
|
-
const keys = ['X', 'Y', 'Z'];
|
|
33
|
-
const result = createPaletteMap(keys);
|
|
34
|
-
// With 3 keys, uses palette index 2 (0-indexed)
|
|
35
|
-
expect(result).toEqual({
|
|
36
|
-
X: '#F8766D',
|
|
37
|
-
Y: '#00BA38',
|
|
38
|
-
Z: '#619CFF',
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
test('createPaletteMap returns empty object for empty keys', () => {
|
|
42
|
-
expect(createPaletteMap([])).toEqual({});
|
|
43
|
-
});
|
|
44
|
-
test('createPaletteMap preserves key order', () => {
|
|
45
|
-
const keys = ['Z', 'A', 'M'];
|
|
46
|
-
const result = createPaletteMap(keys);
|
|
47
|
-
expect(Object.keys(result)).toEqual(['Z', 'A', 'M']);
|
|
48
|
-
});
|
|
49
|
-
//# sourceMappingURL=createPaletteMap.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createPaletteMap.test.js","sourceRoot":"","sources":["../src/createPaletteMap.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest';
|
|
2
|
-
import { calculateNeighborJoiningTree } from "./neighborJoining.js";
|
|
3
|
-
describe('calculateNeighborJoiningTree', () => {
|
|
4
|
-
test('generates valid Newick tree for 2 sequences', () => {
|
|
5
|
-
const rows = [
|
|
6
|
-
['seq1', 'MKAA'],
|
|
7
|
-
['seq2', 'MKAA'],
|
|
8
|
-
];
|
|
9
|
-
const tree = calculateNeighborJoiningTree(rows);
|
|
10
|
-
expect(tree).toMatch(/;$/);
|
|
11
|
-
expect(tree).toContain('seq1');
|
|
12
|
-
expect(tree).toContain('seq2');
|
|
13
|
-
});
|
|
14
|
-
test('generates valid Newick tree for 3 sequences', () => {
|
|
15
|
-
const rows = [
|
|
16
|
-
['human', 'MKAAYLSMFG'],
|
|
17
|
-
['mouse', 'MKAAYLSMFG'],
|
|
18
|
-
['chicken', 'MKAAFLSMFG'],
|
|
19
|
-
];
|
|
20
|
-
const tree = calculateNeighborJoiningTree(rows);
|
|
21
|
-
expect(tree).toMatch(/;$/);
|
|
22
|
-
expect(tree).toContain('human');
|
|
23
|
-
expect(tree).toContain('mouse');
|
|
24
|
-
expect(tree).toContain('chicken');
|
|
25
|
-
expect(tree).toContain('(');
|
|
26
|
-
expect(tree).toContain(')');
|
|
27
|
-
});
|
|
28
|
-
test('generates valid Newick tree for 4 sequences', () => {
|
|
29
|
-
const rows = [
|
|
30
|
-
['A', 'MKAAYLSMFGKED'],
|
|
31
|
-
['B', 'MKAAYLSMFGKED'],
|
|
32
|
-
['C', 'MKAAFLSMFGKEE'],
|
|
33
|
-
['D', 'MKAAFLSMFGKEE'],
|
|
34
|
-
];
|
|
35
|
-
const tree = calculateNeighborJoiningTree(rows);
|
|
36
|
-
expect(tree).toMatch(/;$/);
|
|
37
|
-
expect(tree).toContain('A');
|
|
38
|
-
expect(tree).toContain('B');
|
|
39
|
-
expect(tree).toContain('C');
|
|
40
|
-
expect(tree).toContain('D');
|
|
41
|
-
});
|
|
42
|
-
test('includes branch lengths in tree', () => {
|
|
43
|
-
const rows = [
|
|
44
|
-
['seq1', 'MKAAYLSMFG'],
|
|
45
|
-
['seq2', 'MKAAFLSMFG'],
|
|
46
|
-
['seq3', 'MKBBFLSMFG'],
|
|
47
|
-
];
|
|
48
|
-
const tree = calculateNeighborJoiningTree(rows);
|
|
49
|
-
// Branch lengths are formatted as :0.123456
|
|
50
|
-
expect(tree).toMatch(/:\d+\.\d+/);
|
|
51
|
-
});
|
|
52
|
-
test('throws error for less than 2 sequences', () => {
|
|
53
|
-
const rows = [['seq1', 'MKAA']];
|
|
54
|
-
expect(() => calculateNeighborJoiningTree(rows)).toThrow('Need at least 2 sequences');
|
|
55
|
-
});
|
|
56
|
-
test('handles sequences with gaps', () => {
|
|
57
|
-
const rows = [
|
|
58
|
-
['seq1', 'MK-AYLSMFG'],
|
|
59
|
-
['seq2', 'MKAAYLSMFG'],
|
|
60
|
-
['seq3', 'MKA-YLSMFG'],
|
|
61
|
-
];
|
|
62
|
-
const tree = calculateNeighborJoiningTree(rows);
|
|
63
|
-
expect(tree).toMatch(/;$/);
|
|
64
|
-
expect(tree).toContain('seq1');
|
|
65
|
-
expect(tree).toContain('seq2');
|
|
66
|
-
expect(tree).toContain('seq3');
|
|
67
|
-
});
|
|
68
|
-
test('handles special characters in sequence names', () => {
|
|
69
|
-
const rows = [
|
|
70
|
-
['seq:1', 'MKAA'],
|
|
71
|
-
['seq(2)', 'MKAA'],
|
|
72
|
-
];
|
|
73
|
-
const tree = calculateNeighborJoiningTree(rows);
|
|
74
|
-
expect(tree).toMatch(/;$/);
|
|
75
|
-
// Special characters should be escaped
|
|
76
|
-
expect(tree).not.toMatch(/seq:1/);
|
|
77
|
-
expect(tree).not.toMatch(/seq\(2\)/);
|
|
78
|
-
});
|
|
79
|
-
test('identical sequences have zero distance', () => {
|
|
80
|
-
const rows = [
|
|
81
|
-
['seq1', 'MKAAYLSMFG'],
|
|
82
|
-
['seq2', 'MKAAYLSMFG'],
|
|
83
|
-
];
|
|
84
|
-
const tree = calculateNeighborJoiningTree(rows);
|
|
85
|
-
// Identical sequences should have equal branch lengths
|
|
86
|
-
const match = tree.match(/:(\d+\.\d+)/g);
|
|
87
|
-
expect(match).not.toBeNull();
|
|
88
|
-
if (match) {
|
|
89
|
-
const lengths = match.map(m => parseFloat(m.slice(1)));
|
|
90
|
-
// Branch lengths should be small or zero for identical sequences
|
|
91
|
-
for (const len of lengths) {
|
|
92
|
-
expect(len).toBeGreaterThanOrEqual(0);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
test('more divergent sequences have larger distances', () => {
|
|
97
|
-
const rows = [
|
|
98
|
-
['similar1', 'MKAAYLSMFGKED'],
|
|
99
|
-
['similar2', 'MKAAYLSMFGKED'],
|
|
100
|
-
['different', 'WWWWWWWWWWWWW'],
|
|
101
|
-
];
|
|
102
|
-
const tree = calculateNeighborJoiningTree(rows);
|
|
103
|
-
expect(tree).toMatch(/;$/);
|
|
104
|
-
// The tree should be valid even with very different sequences
|
|
105
|
-
expect(tree).toContain('similar1');
|
|
106
|
-
expect(tree).toContain('similar2');
|
|
107
|
-
expect(tree).toContain('different');
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
//# sourceMappingURL=neighborJoining.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"neighborJoining.test.js","sourceRoot":"","sources":["../src/neighborJoining.test.ts"],"names":[],"mappings":"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"}
|
package/dist/parseAsn1.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/parseAsn1.test.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import { expect, test } from 'vitest';
|
|
3
|
-
import { parseAsn1 } from "./parseAsn1.js";
|
|
4
|
-
const r = fs.readFileSync(require.resolve('../test/data/tree.asn'), 'utf8');
|
|
5
|
-
test('real data file', () => {
|
|
6
|
-
expect(parseAsn1(r)).toMatchSnapshot();
|
|
7
|
-
});
|
|
8
|
-
//# sourceMappingURL=parseAsn1.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parseAsn1.test.js","sourceRoot":"","sources":["../src/parseAsn1.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AAEnB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAErC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC,CAAA;AAE3E,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC1B,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,EAAE,CAAA;AACxC,CAAC,CAAC,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|