react-msaview 6.4.2 → 7.0.0
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/README.md +6 -5
- package/bundle/index.js +100 -101
- package/bundle/index.js.map +4 -4
- package/dist/components/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +10 -2
- package/dist/components/MSAViewer.js +90 -2
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.d.ts +3 -3
- package/dist/components/ResizeHandles.js +28 -21
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/dialogs/{InterProScanDialog.d.ts → AnnotationFileDialog.d.ts} +2 -2
- package/dist/components/dialogs/AnnotationFileDialog.js +92 -0
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -0
- package/dist/components/dialogs/FeatureDialog.js +13 -9
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/header/Header.js +4 -0
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderStatusArea.js +0 -2
- package/dist/components/header/HeaderStatusArea.js.map +1 -1
- package/dist/components/header/LoadWarnings.d.ts +6 -0
- package/dist/components/header/LoadWarnings.js +36 -0
- package/dist/components/header/LoadWarnings.js.map +1 -0
- package/dist/components/header/MultiAlignmentSelector.js +0 -0
- package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.js +9 -1
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/UnshareableDataWarning.d.ts +6 -0
- package/dist/components/header/UnshareableDataWarning.js +42 -0
- package/dist/components/header/UnshareableDataWarning.js.map +1 -0
- package/dist/components/header/getAnnotationsMenu.js +14 -14
- package/dist/components/header/getAnnotationsMenu.js.map +1 -1
- package/dist/components/import/ImportForm.js +1 -1
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +2 -6
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/import/util.js +3 -0
- package/dist/components/import/util.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +4 -5
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +13 -3
- package/dist/components/msa/msaRaster.js +173 -61
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +39 -1
- package/dist/components/msa/renderHighlights.js +71 -6
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSABlock.d.ts +1 -2
- package/dist/components/msa/renderMSABlock.js +24 -28
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +9 -30
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/tracks/TrackBlocks.js +10 -11
- package/dist/components/tracks/TrackBlocks.js.map +1 -1
- package/dist/components/tracks/drawTracks.d.ts +26 -8
- package/dist/components/tracks/drawTracks.js +123 -13
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +2 -3
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +4 -2
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeRuler.js +13 -3
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
- package/dist/components/tree/renderTreeCanvas.js +70 -30
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/scaleBar.d.ts +10 -0
- package/dist/components/tree/scaleBar.js +24 -0
- package/dist/components/tree/scaleBar.js.map +1 -0
- package/dist/constants.d.ts +3 -1
- package/dist/constants.js +18 -1
- package/dist/constants.js.map +1 -1
- package/dist/fetchUtils.d.ts +7 -7
- package/dist/fetchUtils.js +4 -32
- package/dist/fetchUtils.js.map +1 -1
- package/dist/hierarchy.d.ts +2 -1
- package/dist/hierarchy.js +16 -10
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/model/DataModel.js +5 -2
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model.d.ts +454 -177
- package/dist/model.js +1180 -511
- package/dist/model.js.map +1 -1
- package/dist/renderToStaticMarkup.d.ts +11 -0
- package/dist/renderToStaticMarkup.js +30 -7
- package/dist/renderToStaticMarkup.js.map +1 -1
- package/dist/renderToSvg.js +25 -19
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +25 -25
- package/dist/rowCoordinateCalculations.js +12 -36
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/seqPosToGlobalCol.js +3 -1
- package/dist/seqPosToGlobalCol.js.map +1 -1
- package/dist/svgTestUtil.d.ts +144 -82
- package/dist/types.d.ts +97 -3
- package/dist/util.d.ts +11 -4
- package/dist/util.js +30 -10
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wuss.d.ts +14 -0
- package/dist/wuss.js +55 -0
- package/dist/wuss.js.map +1 -0
- package/package.json +10 -8
- package/src/components/Loading.tsx +12 -2
- package/src/components/MSAViewer.tsx +106 -2
- package/src/components/ResizeHandles.tsx +31 -31
- package/src/components/dialogs/AnnotationFileDialog.tsx +162 -0
- package/src/components/dialogs/FeatureDialog.tsx +13 -9
- package/src/components/header/Header.tsx +4 -0
- package/src/components/header/HeaderStatusArea.tsx +0 -5
- package/src/components/header/LoadWarnings.tsx +49 -0
- package/src/components/header/MultiAlignmentSelector.tsx +0 -0
- package/src/components/header/TreeSettingsMenu.tsx +9 -1
- package/src/components/header/UnshareableDataWarning.tsx +58 -0
- package/src/components/header/getAnnotationsMenu.ts +16 -17
- package/src/components/import/ImportForm.tsx +2 -2
- package/src/components/import/ImportFormExamples.tsx +2 -6
- package/src/components/import/util.ts +3 -0
- package/src/components/msa/MSACanvasBlock.tsx +4 -5
- package/src/components/msa/msaRaster.ts +216 -59
- package/src/components/msa/renderHighlights.ts +103 -5
- package/src/components/msa/renderMSABlock.ts +25 -36
- package/src/components/msa/renderMSAMouseover.ts +9 -39
- package/src/components/renderCtx.ts +1 -0
- package/src/components/tracks/TrackBlocks.tsx +10 -13
- package/src/components/tracks/drawTracks.ts +165 -17
- package/src/components/tree/TreeCanvas.tsx +2 -3
- package/src/components/tree/TreeCanvasBlock.tsx +4 -2
- package/src/components/tree/TreeRuler.tsx +38 -3
- package/src/components/tree/renderTreeCanvas.ts +79 -35
- package/src/components/tree/scaleBar.ts +23 -0
- package/src/constants.ts +20 -1
- package/src/fetchUtils.ts +7 -40
- package/src/hierarchy.ts +20 -11
- package/src/index.ts +18 -2
- package/src/model/DataModel.ts +6 -2
- package/src/model.ts +1352 -616
- package/src/rawImports.d.ts +9 -0
- package/src/renderToStaticMarkup.ts +34 -7
- package/src/renderToSvg.tsx +33 -27
- package/src/rowCoordinateCalculations.ts +15 -59
- package/src/seqPosToGlobalCol.ts +3 -1
- package/src/types.ts +131 -4
- package/src/util.ts +31 -14
- package/src/version.ts +1 -1
- package/src/wuss.ts +62 -0
- package/dist/components/dialogs/InterProScanDialog.js +0 -137
- package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
- package/dist/components/dialogs/InterProScanFileDialog.d.ts +0 -7
- package/dist/components/dialogs/InterProScanFileDialog.js +0 -64
- package/dist/components/dialogs/InterProScanFileDialog.js.map +0 -1
- package/dist/launchInterProScan.d.ts +0 -13
- package/dist/launchInterProScan.js +0 -110
- package/dist/launchInterProScan.js.map +0 -1
- package/dist/useColorContrast.d.ts +0 -10
- package/dist/useColorContrast.js +0 -13
- package/dist/useColorContrast.js.map +0 -1
- package/src/clustalX.test.ts +0 -74
- package/src/colSpace.test.ts +0 -67
- package/src/collapseLogic.test.ts +0 -115
- package/src/colorSchemes.docs.test.ts +0 -57
- package/src/columnCounts.test.ts +0 -73
- package/src/columnStats.test.ts +0 -94
- package/src/columnTracks.test.ts +0 -97
- package/src/components/dialogs/InterProScanDialog.tsx +0 -214
- package/src/components/dialogs/InterProScanFileDialog.tsx +0 -138
- package/src/components/minimap/minimapLayout.test.ts +0 -71
- package/src/components/msa/msaRaster.test.ts +0 -145
- package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +0 -51
- package/src/components/tree/renderTreeCanvas.test.ts +0 -296
- package/src/dataInitialized.test.ts +0 -42
- package/src/domainBands.test.ts +0 -122
- package/src/domainLetterColor.test.ts +0 -80
- package/src/exportFileName.test.ts +0 -40
- package/src/fetchUtils.test.ts +0 -83
- package/src/flatToTree.test.ts +0 -51
- package/src/hierarchy.test.ts +0 -228
- package/src/highlights.test.ts +0 -70
- package/src/impgRender.test.tsx +0 -68
- package/src/largeTree.test.ts +0 -67
- package/src/launchInterProScan.ts +0 -167
- package/src/loadErrorDisplay.test.tsx +0 -102
- package/src/modelFilehandleLoaders.test.ts +0 -137
- package/src/modelReset.test.ts +0 -120
- package/src/neighborJoining.test.ts +0 -164
- package/src/parseAsn1.test.ts +0 -36
- package/src/propertyConservation.test.ts +0 -36
- package/src/renderColumnTracksSvg.test.tsx +0 -108
- package/src/renderDomainsSvg.test.tsx +0 -118
- package/src/renderHighlightsSvg.test.tsx +0 -85
- package/src/renderMinimapSvg.test.tsx +0 -62
- package/src/renderRasterSvg.test.tsx +0 -144
- package/src/renderSequenceLogoSvg.test.tsx +0 -165
- package/src/renderToSvg.test.tsx +0 -65
- package/src/reset.test.ts +0 -61
- package/src/rowCoordinateCalculations.test.ts +0 -211
- package/src/rowSpace.test.ts +0 -46
- package/src/segmentOverlay.test.ts +0 -70
- package/src/seqPosToGlobalCol.test.ts +0 -59
- package/src/sequenceLogo.test.ts +0 -88
- package/src/setFontSize.test.ts +0 -33
- package/src/stripDefault.test.ts +0 -48
- package/src/stripDefaultSnapshot.test.ts +0 -87
- package/src/treeMetadata.test.ts +0 -39
- package/src/useCanvasAutorun.test.tsx +0 -98
- package/src/useColorContrast.ts +0 -18
- package/src/viewChrome.test.tsx +0 -134
- package/src/wheelZoom.test.ts +0 -46
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { parseNewick } from 'msa-parsers'
|
|
2
|
-
import { describe, expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import { calculateNeighborJoiningTree } from './neighborJoining.ts'
|
|
5
|
-
|
|
6
|
-
import type { NewickNode } from '@gmod/newick'
|
|
7
|
-
|
|
8
|
-
describe('calculateNeighborJoiningTree', () => {
|
|
9
|
-
test('generates valid Newick tree for 2 sequences', () => {
|
|
10
|
-
const rows: [string, string][] = [
|
|
11
|
-
['seq1', 'MKAA'],
|
|
12
|
-
['seq2', 'MKAA'],
|
|
13
|
-
]
|
|
14
|
-
const tree = calculateNeighborJoiningTree(rows)
|
|
15
|
-
|
|
16
|
-
expect(tree).toMatch(/;$/)
|
|
17
|
-
expect(tree).toContain('seq1')
|
|
18
|
-
expect(tree).toContain('seq2')
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('generates valid Newick tree for 3 sequences', () => {
|
|
22
|
-
const rows: [string, string][] = [
|
|
23
|
-
['human', 'MKAAYLSMFG'],
|
|
24
|
-
['mouse', 'MKAAYLSMFG'],
|
|
25
|
-
['chicken', 'MKAAFLSMFG'],
|
|
26
|
-
]
|
|
27
|
-
const tree = calculateNeighborJoiningTree(rows)
|
|
28
|
-
|
|
29
|
-
expect(tree).toMatch(/;$/)
|
|
30
|
-
expect(tree).toContain('human')
|
|
31
|
-
expect(tree).toContain('mouse')
|
|
32
|
-
expect(tree).toContain('chicken')
|
|
33
|
-
expect(tree).toContain('(')
|
|
34
|
-
expect(tree).toContain(')')
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('generates valid Newick tree for 4 sequences', () => {
|
|
38
|
-
const rows: [string, string][] = [
|
|
39
|
-
['A', 'MKAAYLSMFGKED'],
|
|
40
|
-
['B', 'MKAAYLSMFGKED'],
|
|
41
|
-
['C', 'MKAAFLSMFGKEE'],
|
|
42
|
-
['D', 'MKAAFLSMFGKEE'],
|
|
43
|
-
]
|
|
44
|
-
const tree = calculateNeighborJoiningTree(rows)
|
|
45
|
-
|
|
46
|
-
expect(tree).toMatch(/;$/)
|
|
47
|
-
expect(tree).toContain('A')
|
|
48
|
-
expect(tree).toContain('B')
|
|
49
|
-
expect(tree).toContain('C')
|
|
50
|
-
expect(tree).toContain('D')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('includes branch lengths in tree', () => {
|
|
54
|
-
const rows: [string, string][] = [
|
|
55
|
-
['seq1', 'MKAAYLSMFG'],
|
|
56
|
-
['seq2', 'MKAAFLSMFG'],
|
|
57
|
-
['seq3', 'MKBBFLSMFG'],
|
|
58
|
-
]
|
|
59
|
-
const tree = calculateNeighborJoiningTree(rows)
|
|
60
|
-
|
|
61
|
-
// Branch lengths are formatted as :0.123456
|
|
62
|
-
expect(tree).toMatch(/:\d+\.\d+/)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test('throws error for less than 2 sequences', () => {
|
|
66
|
-
const rows: [string, string][] = [['seq1', 'MKAA']]
|
|
67
|
-
expect(() => calculateNeighborJoiningTree(rows)).toThrow(
|
|
68
|
-
'Need at least 2 sequences',
|
|
69
|
-
)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test('handles sequences with gaps', () => {
|
|
73
|
-
const rows: [string, string][] = [
|
|
74
|
-
['seq1', 'MK-AYLSMFG'],
|
|
75
|
-
['seq2', 'MKAAYLSMFG'],
|
|
76
|
-
['seq3', 'MKA-YLSMFG'],
|
|
77
|
-
]
|
|
78
|
-
const tree = calculateNeighborJoiningTree(rows)
|
|
79
|
-
|
|
80
|
-
expect(tree).toMatch(/;$/)
|
|
81
|
-
expect(tree).toContain('seq1')
|
|
82
|
-
expect(tree).toContain('seq2')
|
|
83
|
-
expect(tree).toContain('seq3')
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
test('treats a dot gap the same as a dash gap', () => {
|
|
87
|
-
// stockholm and a3m pad with '.', so the two spellings have to score alike
|
|
88
|
-
const withDashes: [string, string][] = [
|
|
89
|
-
['seq1', 'MK-AYLSMFG'],
|
|
90
|
-
['seq2', 'MKAAYLSMFG'],
|
|
91
|
-
['seq3', 'MKA-YLSMFG'],
|
|
92
|
-
]
|
|
93
|
-
const withDots = withDashes.map(
|
|
94
|
-
([name, seq]) => [name, seq.replaceAll('-', '.')] as [string, string],
|
|
95
|
-
)
|
|
96
|
-
expect(calculateNeighborJoiningTree(withDots)).toBe(
|
|
97
|
-
calculateNeighborJoiningTree(withDashes),
|
|
98
|
-
)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
test('names with special characters round-trip through Newick parsing', () => {
|
|
102
|
-
const rows: [string, string][] = [
|
|
103
|
-
['EU105457.1|chr09:67680268..67675529_LTR/Copia', 'MKAA'],
|
|
104
|
-
['seq(2)', 'MKAA'],
|
|
105
|
-
]
|
|
106
|
-
const newick = calculateNeighborJoiningTree(rows)
|
|
107
|
-
const tree = parseNewick(newick)
|
|
108
|
-
|
|
109
|
-
function getLeafNames(node: NewickNode): (string | undefined)[] {
|
|
110
|
-
return node.children?.length
|
|
111
|
-
? node.children.flatMap(c => getLeafNames(c))
|
|
112
|
-
: [node.name]
|
|
113
|
-
}
|
|
114
|
-
const names = getLeafNames(tree)
|
|
115
|
-
expect(names).toContain('EU105457.1|chr09:67680268..67675529_LTR/Copia')
|
|
116
|
-
expect(names).toContain('seq(2)')
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
test('identical sequences have zero distance', () => {
|
|
120
|
-
const rows: [string, string][] = [
|
|
121
|
-
['seq1', 'MKAAYLSMFG'],
|
|
122
|
-
['seq2', 'MKAAYLSMFG'],
|
|
123
|
-
]
|
|
124
|
-
const tree = calculateNeighborJoiningTree(rows)
|
|
125
|
-
|
|
126
|
-
// Identical sequences should have equal branch lengths
|
|
127
|
-
const match = tree.match(/:(\d+\.\d+)/g)
|
|
128
|
-
expect(match).not.toBeNull()
|
|
129
|
-
if (match) {
|
|
130
|
-
const lengths = match.map(m => parseFloat(m.slice(1)))
|
|
131
|
-
// Branch lengths should be small or zero for identical sequences
|
|
132
|
-
for (const len of lengths) {
|
|
133
|
-
expect(len).toBeGreaterThanOrEqual(0)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test('more divergent sequences have larger distances', () => {
|
|
139
|
-
const rows: [string, string][] = [
|
|
140
|
-
['similar1', 'MKAAYLSMFGKED'],
|
|
141
|
-
['similar2', 'MKAAYLSMFGKED'],
|
|
142
|
-
['different', 'WWWWWWWWWWWWW'],
|
|
143
|
-
]
|
|
144
|
-
const tree = calculateNeighborJoiningTree(rows)
|
|
145
|
-
|
|
146
|
-
expect(tree).toMatch(/;$/)
|
|
147
|
-
// The tree should be valid even with very different sequences
|
|
148
|
-
expect(tree).toContain('similar1')
|
|
149
|
-
expect(tree).toContain('similar2')
|
|
150
|
-
expect(tree).toContain('different')
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
test('handles ragged rows, treating a short row as gapped past its end', () => {
|
|
154
|
-
const rows: [string, string][] = [
|
|
155
|
-
['full', 'MKAAYLSMFGKED'],
|
|
156
|
-
['short', 'MKAAYL'],
|
|
157
|
-
['other', 'WWWWWWWWWWWWW'],
|
|
158
|
-
]
|
|
159
|
-
const tree = calculateNeighborJoiningTree(rows)
|
|
160
|
-
|
|
161
|
-
expect(tree).toMatch(/;$/)
|
|
162
|
-
expect(tree).toContain('short')
|
|
163
|
-
})
|
|
164
|
-
})
|
package/src/parseAsn1.test.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs'
|
|
2
|
-
|
|
3
|
-
import { expect, test } from 'vitest'
|
|
4
|
-
|
|
5
|
-
import { flatToTree } from './flatToTree.ts'
|
|
6
|
-
import MSAModelF from './model.ts'
|
|
7
|
-
import { parseAsn1 } from './parseAsn1.ts'
|
|
8
|
-
|
|
9
|
-
const r = readFileSync(
|
|
10
|
-
new URL('../test/data/tree.asn', import.meta.url),
|
|
11
|
-
'utf8',
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
test('real data file', () => {
|
|
15
|
-
expect(parseAsn1(r)).toMatchSnapshot()
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
test('throws a clear error on input missing required sections', () => {
|
|
19
|
-
expect(() => parseAsn1('BioTreeContainer ::= { something {} }')).toThrow(
|
|
20
|
-
/missing/,
|
|
21
|
-
)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
test('the tree keeps the labels and branch lengths the file carries', () => {
|
|
25
|
-
// the parsed features have to survive into the tree the viewer draws, or a
|
|
26
|
-
// BLAST tree renders as a lengthless list of node ids
|
|
27
|
-
const model = MSAModelF().create({ type: 'MsaView', data: { tree: r } })
|
|
28
|
-
model.setWidth(1000)
|
|
29
|
-
|
|
30
|
-
expect(model.numRows).toBe(101)
|
|
31
|
-
expect(model.rowNames[0]).toBe(
|
|
32
|
-
'sodium/glucose cotransporter 4 [Gouania willdenowi]',
|
|
33
|
-
)
|
|
34
|
-
expect(model.allBranchesLength0).toBe(false)
|
|
35
|
-
expect(flatToTree(parseAsn1(r)).length).toBeUndefined()
|
|
36
|
-
})
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { columnCountsFromColumns } from './columnCounts.ts'
|
|
4
|
-
import { calculatePropertyConservation } from './propertyConservation.ts'
|
|
5
|
-
|
|
6
|
-
function propertyConservation(columns: string[]) {
|
|
7
|
-
return calculatePropertyConservation(columnCountsFromColumns(columns))
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
describe('property conservation', () => {
|
|
11
|
-
test('an invariant column scores 1', () => {
|
|
12
|
-
expect(propertyConservation(['LLLL'])[0]).toBe(1)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
test('a column varying only within one property class stays fully conserved', () => {
|
|
16
|
-
// L/I/V/M are all hydrophobic: identity varies but the property does not
|
|
17
|
-
expect(propertyConservation(['LIVM'])[0]).toBe(1)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
test('mixing opposite-charge classes lowers the score', () => {
|
|
21
|
-
// K/R (positive) vs D/E (negative): two equally-sized classes
|
|
22
|
-
const score = propertyConservation(['KRDE'])[0]!
|
|
23
|
-
expect(score).toBeGreaterThan(0)
|
|
24
|
-
expect(score).toBeLessThan(1)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('gaps reduce the score proportionally', () => {
|
|
28
|
-
const full = propertyConservation(['LLLL'])[0]!
|
|
29
|
-
const halfGapped = propertyConservation(['LL--'])[0]!
|
|
30
|
-
expect(halfGapped).toBeCloseTo(full * 0.5)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('an all-gap column scores 0', () => {
|
|
34
|
-
expect(propertyConservation(['----'])[0]).toBe(0)
|
|
35
|
-
})
|
|
36
|
-
})
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
|
|
3
|
-
import { enableStaticRendering } from 'mobx-react'
|
|
4
|
-
import { beforeAll, expect, test } from 'vitest'
|
|
5
|
-
|
|
6
|
-
import { installHeadlessRenderEnv } from './headlessRenderEnv.ts'
|
|
7
|
-
import MSAModelF from './model.ts'
|
|
8
|
-
import { renderToSvg } from './renderToSvg.tsx'
|
|
9
|
-
|
|
10
|
-
import type { ColumnTrackSpec } from './types.ts'
|
|
11
|
-
|
|
12
|
-
beforeAll(() => {
|
|
13
|
-
enableStaticRendering(true)
|
|
14
|
-
installHeadlessRenderEnv()
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const msa = '>s1\nACGT\n>s2\nACGT\n'
|
|
18
|
-
const colWidth = 20
|
|
19
|
-
const barColor = '#123456'
|
|
20
|
-
|
|
21
|
-
function makeModel(columnTracks: ColumnTrackSpec[]) {
|
|
22
|
-
const model = MSAModelF().create({
|
|
23
|
-
type: 'MsaView',
|
|
24
|
-
msaFormat: 'fasta',
|
|
25
|
-
height: 400,
|
|
26
|
-
colWidth,
|
|
27
|
-
data: { msa },
|
|
28
|
-
columnTracks,
|
|
29
|
-
})
|
|
30
|
-
model.setWidth(1000)
|
|
31
|
-
model.toggleTrack('conservation')
|
|
32
|
-
return model
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function exportSvg(model: ReturnType<typeof makeModel>) {
|
|
36
|
-
return renderToSvg(model, {
|
|
37
|
-
theme: createJBrowseTheme(),
|
|
38
|
-
exportType: 'entire',
|
|
39
|
-
includeTracks: true,
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function rectsFilled(svg: string, fill: string) {
|
|
44
|
-
return [...svg.matchAll(/<rect ([^>]*)\/?>/g)]
|
|
45
|
-
.map(m => m[1]!)
|
|
46
|
-
.filter(attrs => attrs.includes(`fill="${fill}"`))
|
|
47
|
-
.map(attrs => ({
|
|
48
|
-
x: Number(/x="([\d.-]+)"/.exec(attrs)?.[1]),
|
|
49
|
-
width: Number(/width="([\d.-]+)"/.exec(attrs)?.[1]),
|
|
50
|
-
height: Number(/height="([\d.-]+)"/.exec(attrs)?.[1]),
|
|
51
|
-
}))
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
test('a bar track from values exports one bar per column in its color', async () => {
|
|
55
|
-
const model = makeModel([
|
|
56
|
-
{
|
|
57
|
-
id: 'dnds',
|
|
58
|
-
name: 'dN/dS',
|
|
59
|
-
kind: 'bar',
|
|
60
|
-
values: [1, 0.5, 0, 0.25],
|
|
61
|
-
color: barColor,
|
|
62
|
-
},
|
|
63
|
-
])
|
|
64
|
-
const bars = rectsFilled(await exportSvg(model), barColor).sort(
|
|
65
|
-
(a, b) => a.x - b.x,
|
|
66
|
-
)
|
|
67
|
-
const trackHeight = model.conservationTrackHeight
|
|
68
|
-
expect(bars.map(b => b.x)).toEqual([0, colWidth, colWidth * 2, colWidth * 3])
|
|
69
|
-
expect(bars.map(b => b.height)).toEqual([
|
|
70
|
-
trackHeight,
|
|
71
|
-
trackHeight / 2,
|
|
72
|
-
0,
|
|
73
|
-
trackHeight / 4,
|
|
74
|
-
])
|
|
75
|
-
expect(svgNamesTrack(await exportSvg(model))).toContain('dN/dS')
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test('a text track from data exports its letters and its own colors', async () => {
|
|
79
|
-
const model = makeModel([
|
|
80
|
-
{
|
|
81
|
-
id: 'frame',
|
|
82
|
-
name: 'Codon frame',
|
|
83
|
-
kind: 'text',
|
|
84
|
-
data: '1231',
|
|
85
|
-
colors: { 1: '#aaaaaa', 2: '#bbbbbb', 3: '#cccccc' },
|
|
86
|
-
},
|
|
87
|
-
])
|
|
88
|
-
const svg = await exportSvg(model)
|
|
89
|
-
expect(
|
|
90
|
-
rectsFilled(svg, '#aaaaaa')
|
|
91
|
-
.map(b => b.x)
|
|
92
|
-
.sort((a, b) => a - b),
|
|
93
|
-
).toEqual([0, colWidth * 3])
|
|
94
|
-
expect(rectsFilled(svg, '#bbbbbb').map(b => b.x)).toEqual([colWidth])
|
|
95
|
-
expect(svgNamesTrack(svg)).toContain('Codon frame')
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
test('a data track that is toggled off stays out of the export', async () => {
|
|
99
|
-
const model = makeModel([
|
|
100
|
-
{ id: 'dnds', name: 'dN/dS', kind: 'bar', values: [1], color: barColor },
|
|
101
|
-
])
|
|
102
|
-
model.toggleTrack('dnds')
|
|
103
|
-
expect(rectsFilled(await exportSvg(model), barColor)).toEqual([])
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
function svgNamesTrack(svg: string) {
|
|
107
|
-
return [...svg.matchAll(/<text[^>]*>([^<]*)<\/text>/g)].map(m => m[1])
|
|
108
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
//
|
|
3
|
-
// Verifies the SVG export draws the domain overlay: the boxes themselves across
|
|
4
|
-
// the full alignment width, and the color-key legend as a reserved column on the
|
|
5
|
-
// right. Polyfills mirror impgRender.test.tsx (svgcanvas needs
|
|
6
|
-
// DOMMatrix/DOMPoint + a measureText).
|
|
7
|
-
import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
|
|
8
|
-
import { beforeAll, expect, test } from 'vitest'
|
|
9
|
-
|
|
10
|
-
import { renderToSvg } from './renderToSvg.tsx'
|
|
11
|
-
import { createTestModel, installSvgTestEnv } from './svgTestUtil.ts'
|
|
12
|
-
|
|
13
|
-
beforeAll(() => {
|
|
14
|
-
installSvgTestEnv()
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const msa = `>seq1
|
|
18
|
-
ACDEFGHIKL
|
|
19
|
-
>seq2
|
|
20
|
-
ACDE-GHIKL`
|
|
21
|
-
|
|
22
|
-
function makeModel({
|
|
23
|
-
data = msa,
|
|
24
|
-
start = 1,
|
|
25
|
-
end = 5,
|
|
26
|
-
}: { data?: string; start?: number; end?: number } = {}) {
|
|
27
|
-
const model = createTestModel({
|
|
28
|
-
id: 'domain-svg-test',
|
|
29
|
-
data: { msa: data },
|
|
30
|
-
})
|
|
31
|
-
model.setDomains({
|
|
32
|
-
seq1: {
|
|
33
|
-
xref: [{ id: 'seq1' }],
|
|
34
|
-
matches: [
|
|
35
|
-
{
|
|
36
|
-
signature: {
|
|
37
|
-
entry: {
|
|
38
|
-
name: 'Kinase',
|
|
39
|
-
accession: 'PF00069',
|
|
40
|
-
description: 'Protein kinase domain',
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
locations: [{ start, end }],
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
},
|
|
47
|
-
})
|
|
48
|
-
return model
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function exportEntire(model: ReturnType<typeof makeModel>) {
|
|
52
|
-
return renderToSvg(model, {
|
|
53
|
-
theme: createJBrowseTheme(),
|
|
54
|
-
exportType: 'entire',
|
|
55
|
-
includeMinimap: false,
|
|
56
|
-
includeTracks: false,
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
test('domain legend is drawn in the svg export when domains are shown', async () => {
|
|
61
|
-
const model = makeModel()
|
|
62
|
-
expect(model.actuallyShowDomains).toBe(true)
|
|
63
|
-
expect(model.visibleDomainTypes.map(d => d.accession)).toEqual(['PF00069'])
|
|
64
|
-
|
|
65
|
-
expect(await exportEntire(model)).toContain('Kinase')
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test('no legend column when domains are hidden', async () => {
|
|
69
|
-
const model = makeModel()
|
|
70
|
-
model.setShowDomains(false)
|
|
71
|
-
expect(model.actuallyShowDomains).toBe(false)
|
|
72
|
-
|
|
73
|
-
expect(await exportEntire(model)).not.toContain('Kinase')
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
test('the page grows to hold a key taller than the alignment', async () => {
|
|
77
|
-
const model = makeModel()
|
|
78
|
-
model.setDomains({
|
|
79
|
-
seq1: {
|
|
80
|
-
xref: [{ id: 'seq1' }],
|
|
81
|
-
matches: Array.from({ length: 12 }, (_, i) => ({
|
|
82
|
-
signature: {
|
|
83
|
-
entry: { name: `Dom${i}`, accession: `PF${i}`, description: '' },
|
|
84
|
-
},
|
|
85
|
-
locations: [{ start: 1, end: 5 }],
|
|
86
|
-
})),
|
|
87
|
-
},
|
|
88
|
-
})
|
|
89
|
-
expect(model.visibleDomainTypes).toHaveLength(12)
|
|
90
|
-
const keyHeight = 16 + 12 * 16
|
|
91
|
-
expect(model.totalHeight).toBeLessThan(keyHeight)
|
|
92
|
-
|
|
93
|
-
const svg = await exportEntire(model)
|
|
94
|
-
expect(svg).toContain(`height="${keyHeight}"`)
|
|
95
|
-
expect(/<svg[^>]*height="(\d+)"/.exec(svg)?.[1]).toBe(String(keyHeight))
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
test('a domain box past the on-screen block size still draws', async () => {
|
|
99
|
-
// the export renders the alignment in one pass, so the overlay has to be told
|
|
100
|
-
// how wide that pass is; falling back to the 500px on-screen block size culled
|
|
101
|
-
// every band to the left edge of a wide alignment
|
|
102
|
-
const row = 'ACDEFGHIKL'.repeat(20)
|
|
103
|
-
const model = makeModel({
|
|
104
|
-
data: `>seq1\n${row}\n>seq2\n${row}`,
|
|
105
|
-
start: 151,
|
|
106
|
-
end: 190,
|
|
107
|
-
})
|
|
108
|
-
const band = model.domainBands.get('seq1')![0]!
|
|
109
|
-
const x = band.startCol * model.colWidth
|
|
110
|
-
expect(x).toBeGreaterThan(model.blockSize)
|
|
111
|
-
|
|
112
|
-
const svg = await exportEntire(model)
|
|
113
|
-
const fill = model.fillPalette.PF00069!
|
|
114
|
-
const rects = [...svg.matchAll(/<rect[^>]*>/g)].map(m => m[0])
|
|
115
|
-
expect(
|
|
116
|
-
rects.some(r => r.includes(`fill="${fill}"`) && r.includes(`x="${x}"`)),
|
|
117
|
-
).toBe(true)
|
|
118
|
-
})
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
//
|
|
3
|
-
// The SVG export draws the `highlights` layer: a bordered band with its label
|
|
4
|
-
// over the alignment, a residue span at the column the row's gaps put it in,
|
|
5
|
-
// and a row set tinted across both the tree and the alignment.
|
|
6
|
-
import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
|
|
7
|
-
import { enableStaticRendering } from 'mobx-react'
|
|
8
|
-
import { beforeAll, expect, test } from 'vitest'
|
|
9
|
-
|
|
10
|
-
import { installHeadlessRenderEnv } from './headlessRenderEnv.ts'
|
|
11
|
-
import MSAModelF from './model.ts'
|
|
12
|
-
import { renderToSvg } from './renderToSvg.tsx'
|
|
13
|
-
|
|
14
|
-
import type { Highlight } from './types.ts'
|
|
15
|
-
|
|
16
|
-
beforeAll(() => {
|
|
17
|
-
enableStaticRendering(true)
|
|
18
|
-
installHeadlessRenderEnv()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
const msa = `>seq1
|
|
22
|
-
ACDEFGHIKL
|
|
23
|
-
>seq2
|
|
24
|
-
AC--FGHIKL
|
|
25
|
-
>seq3
|
|
26
|
-
ACDEFGHIKL`
|
|
27
|
-
|
|
28
|
-
async function exportWith(highlights: Highlight[]) {
|
|
29
|
-
const model = MSAModelF().create({
|
|
30
|
-
id: 'highlight-svg-test',
|
|
31
|
-
type: 'MsaView',
|
|
32
|
-
height: 400,
|
|
33
|
-
msaFormat: 'fasta',
|
|
34
|
-
data: { msa },
|
|
35
|
-
highlights,
|
|
36
|
-
})
|
|
37
|
-
model.setWidth(800)
|
|
38
|
-
const svg = await renderToSvg(model, {
|
|
39
|
-
theme: createJBrowseTheme(),
|
|
40
|
-
exportType: 'entire',
|
|
41
|
-
})
|
|
42
|
-
return { model, svg }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const rects = (svg: string) => svg.match(/<rect[^>]*>/g) ?? []
|
|
46
|
-
|
|
47
|
-
test('a labeled column span draws a band and its label', async () => {
|
|
48
|
-
const { model, svg } = await exportWith([
|
|
49
|
-
{ start: 3, end: 5, label: 'motif', color: 'rgb(1, 2, 3)' },
|
|
50
|
-
])
|
|
51
|
-
const { colWidth, totalHeight } = model
|
|
52
|
-
const band = rects(svg).find(
|
|
53
|
-
r => r.includes(`x="${2 * colWidth}"`) && r.includes('rgb(1, 2, 3)'),
|
|
54
|
-
)
|
|
55
|
-
expect(band).toBeDefined()
|
|
56
|
-
expect(band).toContain(`width="${3 * colWidth}"`)
|
|
57
|
-
expect(band).toContain(`height="${totalHeight}"`)
|
|
58
|
-
expect(svg).toContain('>motif<')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('a residue span lands where the row has its residues', async () => {
|
|
62
|
-
const { model, svg } = await exportWith([
|
|
63
|
-
{ row: 'seq2', start: 3, end: 3, color: 'rgb(9, 9, 9)' },
|
|
64
|
-
])
|
|
65
|
-
expect(model.resolvedHighlights[0]).toMatchObject({
|
|
66
|
-
startCol: 4,
|
|
67
|
-
endCol: 4,
|
|
68
|
-
})
|
|
69
|
-
const band = rects(svg).find(r => r.includes('rgb(9, 9, 9)'))
|
|
70
|
-
expect(band).toContain(`x="${4 * model.colWidth}"`)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test('a row set tints the row in the tree and the alignment', async () => {
|
|
74
|
-
const { model, svg } = await exportWith([
|
|
75
|
-
{ rows: ['seq3'], label: 'odd one out', color: 'rgb(7, 7, 7)' },
|
|
76
|
-
])
|
|
77
|
-
const { rowHeight, treeAreaWidth, totalWidth } = model
|
|
78
|
-
const tints = rects(svg).filter(
|
|
79
|
-
r => r.includes('rgb(7, 7, 7)') && r.includes(`y="${2 * rowHeight}"`),
|
|
80
|
-
)
|
|
81
|
-
expect(tints.map(r => /width="([^"]+)"/.exec(r)?.[1])).toEqual(
|
|
82
|
-
expect.arrayContaining([`${treeAreaWidth}`, `${totalWidth}`]),
|
|
83
|
-
)
|
|
84
|
-
expect(svg).toContain('>odd one out<')
|
|
85
|
-
})
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
//
|
|
3
|
-
// The viewport export draws a minimap by default, and nothing covered it. It
|
|
4
|
-
// spans the same alignment canvas the live minimap does, so its trapezoid has
|
|
5
|
-
// to reach the right edge of the exported alignment column -- and it is drawn
|
|
6
|
-
// only when the live view shows one at all.
|
|
7
|
-
import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
|
|
8
|
-
import { beforeAll, expect, test } from 'vitest'
|
|
9
|
-
|
|
10
|
-
import { renderToSvg } from './renderToSvg.tsx'
|
|
11
|
-
import { createTestModel, installSvgTestEnv } from './svgTestUtil.ts'
|
|
12
|
-
|
|
13
|
-
beforeAll(() => {
|
|
14
|
-
installSvgTestEnv()
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const width = 1000
|
|
18
|
-
|
|
19
|
-
function makeModel(cols = 400) {
|
|
20
|
-
const row = 'ACDEFGHIKL'.repeat(cols / 10)
|
|
21
|
-
return createTestModel(
|
|
22
|
-
{ height: 300, data: { msa: `>seq1\n${row}\n>seq2\n${row}` } },
|
|
23
|
-
width,
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function exportViewport(model: ReturnType<typeof makeModel>) {
|
|
28
|
-
return renderToSvg(model, {
|
|
29
|
-
theme: createJBrowseTheme(),
|
|
30
|
-
exportType: 'viewport',
|
|
31
|
-
includeMinimap: true,
|
|
32
|
-
includeTracks: false,
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
test('the exported minimap spans the exported alignment column', async () => {
|
|
37
|
-
const model = makeModel()
|
|
38
|
-
expect(model.showHorizontalScrollbar).toBe(true)
|
|
39
|
-
|
|
40
|
-
const svg = await exportViewport(model)
|
|
41
|
-
|
|
42
|
-
// the minimap column is the alignment canvas, which is what the export lays
|
|
43
|
-
// out beside the tree
|
|
44
|
-
const minimapWidth = model.msaCanvasWidth
|
|
45
|
-
const polygon = /<polygon[^>]*points="([^"]*)"/.exec(svg)?.[1]
|
|
46
|
-
expect(polygon).toBeDefined()
|
|
47
|
-
// bottom edge of the trapezoid reaches the full width it was given
|
|
48
|
-
expect(polygon!.endsWith(`${minimapWidth},${model.minimapHeight - 12}`)).toBe(
|
|
49
|
-
true,
|
|
50
|
-
)
|
|
51
|
-
// and the bar outline is drawn to the same width
|
|
52
|
-
expect(svg).toContain(`width="${minimapWidth}"`)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test('no minimap when the alignment already fits across', async () => {
|
|
56
|
-
const model = makeModel(10)
|
|
57
|
-
expect(model.showHorizontalScrollbar).toBe(false)
|
|
58
|
-
|
|
59
|
-
// a minimap here would mark a viewport wider than the bar it sits in, and the
|
|
60
|
-
// live view draws none either
|
|
61
|
-
expect(await exportViewport(model)).not.toContain('<polygon')
|
|
62
|
-
})
|