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,57 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs'
|
|
2
|
-
|
|
3
|
-
import { expect, test } from 'vitest'
|
|
4
|
-
|
|
5
|
-
import colorSchemes from './colorSchemes.ts'
|
|
6
|
-
|
|
7
|
-
// Guard against doc drift: every color-scheme name listed in the JS embedding
|
|
8
|
-
// docs (USAGE.md) must be a real registered scheme, so a rename or removal in
|
|
9
|
-
// colorSchemes.ts can't silently leave a dead name in the docs that users copy.
|
|
10
|
-
// (Subset check only — a registered scheme may intentionally go undocumented.)
|
|
11
|
-
test('color schemes listed in USAGE.md all exist', () => {
|
|
12
|
-
const registered = new Set(Object.keys(colorSchemes))
|
|
13
|
-
const usage = readFileSync(
|
|
14
|
-
new URL('../../../USAGE.md', import.meta.url),
|
|
15
|
-
'utf8',
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
const section = /## Color schemes\n([\s\S]*?)\n## /.exec(usage)?.[1]
|
|
19
|
-
expect(section, 'could not find the "## Color schemes" section').toBeTruthy()
|
|
20
|
-
|
|
21
|
-
// drop the bold labels (**Protein:**, **Dynamic (per-column):**, …), leaving
|
|
22
|
-
// just the comma-separated scheme names
|
|
23
|
-
const names = section!.replace(/\*\*[^*]+\*\*/g, '').match(/[a-z][a-z0-9_]+/g)
|
|
24
|
-
expect(names?.length).toBeGreaterThan(0)
|
|
25
|
-
|
|
26
|
-
const unknown = names!.filter(name => !registered.has(name))
|
|
27
|
-
expect(
|
|
28
|
-
unknown,
|
|
29
|
-
`documented but not registered: ${unknown.join(', ')}`,
|
|
30
|
-
).toEqual([])
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
// Same guard for the R package docs, whose color_scheme names pass straight
|
|
34
|
-
// through as colorSchemeName with no remapping — a name that isn't registered
|
|
35
|
-
// silently renders with no coloring.
|
|
36
|
-
test('color schemes listed in the R README all exist', () => {
|
|
37
|
-
const registered = new Set(Object.keys(colorSchemes))
|
|
38
|
-
const readme = readFileSync(
|
|
39
|
-
new URL('../../r-msaview/README.md', import.meta.url),
|
|
40
|
-
'utf8',
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
const section = /## Color schemes\n([\s\S]*?)\n## /.exec(readme)?.[1]
|
|
44
|
-
expect(section, 'could not find the "## Color schemes" section').toBeTruthy()
|
|
45
|
-
|
|
46
|
-
// names are backtick-quoted, e.g. `jalview_taylor`
|
|
47
|
-
const names = [...section!.matchAll(/`([a-z][a-z0-9_]+)`/g)].flatMap(m =>
|
|
48
|
-
m[1] ? [m[1]] : [],
|
|
49
|
-
)
|
|
50
|
-
expect(names.length).toBeGreaterThan(0)
|
|
51
|
-
|
|
52
|
-
const unknown = names.filter(name => !registered.has(name))
|
|
53
|
-
expect(
|
|
54
|
-
unknown,
|
|
55
|
-
`documented but not registered: ${unknown.join(', ')}`,
|
|
56
|
-
).toEqual([])
|
|
57
|
-
})
|
package/src/columnCounts.test.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
columnCountsFromColumns,
|
|
5
|
-
columnCountsFromRows,
|
|
6
|
-
letterOfResidueSlot,
|
|
7
|
-
} from './columnCounts.ts'
|
|
8
|
-
|
|
9
|
-
describe('columnCounts', () => {
|
|
10
|
-
test('tallies residues and gaps per column', () => {
|
|
11
|
-
const counts = columnCountsFromRows(['AC-', 'AG.', 'AC-'])
|
|
12
|
-
expect(counts.numColumns).toBe(3)
|
|
13
|
-
expect(counts.count(0, 'A')).toBe(3)
|
|
14
|
-
expect(counts.count(1, 'C')).toBe(2)
|
|
15
|
-
expect(counts.count(1, 'G')).toBe(1)
|
|
16
|
-
expect(counts.total(2)).toBe(3)
|
|
17
|
-
expect(counts.gapCount(2)).toBe(3)
|
|
18
|
-
expect(counts.gapCount(0)).toBe(0)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('counts both gap characters, and neither as a residue', () => {
|
|
22
|
-
const counts = columnCountsFromColumns(['-.A'])
|
|
23
|
-
expect(counts.count(0, '-')).toBe(1)
|
|
24
|
-
expect(counts.count(0, '.')).toBe(1)
|
|
25
|
-
expect(counts.gapCount(0)).toBe(2)
|
|
26
|
-
expect(counts.residueEntries(0)).toEqual([['A', 1]])
|
|
27
|
-
expect(counts.lettersPresent).toEqual(new Set(['A']))
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
test('folds case, so lowercase (a3m insertions) counts as its residue', () => {
|
|
31
|
-
const counts = columnCountsFromColumns(['aA'])
|
|
32
|
-
expect(counts.count(0, 'A')).toBe(2)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test('digits and punctuation do not land on letter slots', () => {
|
|
36
|
-
// a plain `code & 31` would fold '0' onto 'P' and '!' onto 'A'
|
|
37
|
-
const counts = columnCountsFromColumns(['0!'])
|
|
38
|
-
expect(counts.count(0, 'P')).toBe(0)
|
|
39
|
-
expect(counts.count(0, 'A')).toBe(0)
|
|
40
|
-
expect(counts.residueEntries(0)).toEqual([['?', 2]])
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('ragged rows: the widest row sets the column count', () => {
|
|
44
|
-
const counts = columnCountsFromRows(['AAAA', 'AA'])
|
|
45
|
-
expect(counts.numColumns).toBe(4)
|
|
46
|
-
expect(counts.total(0)).toBe(2)
|
|
47
|
-
expect(counts.total(3)).toBe(1)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
test('entropy is taken over the non-gap residues only', () => {
|
|
51
|
-
const counts = columnCountsFromColumns(['AAAA', 'AACC', 'ACGT', 'AC--'])
|
|
52
|
-
// fully conserved: no choice left to make
|
|
53
|
-
expect(counts.entropy(0)).toBeCloseTo(0, 10)
|
|
54
|
-
// even two-way and four-way splits
|
|
55
|
-
expect(counts.entropy(1)).toBeCloseTo(1, 10)
|
|
56
|
-
expect(counts.entropy(2)).toBeCloseTo(2, 10)
|
|
57
|
-
// the two gaps do not turn a 1-bit choice into a 2-bit one
|
|
58
|
-
expect(counts.entropy(3)).toBeCloseTo(1, 10)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('an all-gap column has no entropy rather than NaN', () => {
|
|
62
|
-
expect(columnCountsFromColumns(['----']).entropy(0)).toBe(0)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test('residue slots round-trip to their letters', () => {
|
|
66
|
-
const counts = columnCountsFromColumns(['WY'])
|
|
67
|
-
const slots: number[] = []
|
|
68
|
-
counts.forEachResidue(0, slot => {
|
|
69
|
-
slots.push(slot)
|
|
70
|
-
})
|
|
71
|
-
expect(slots.map(letterOfResidueSlot)).toEqual(['W', 'Y'])
|
|
72
|
-
})
|
|
73
|
-
})
|
package/src/columnStats.test.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { beforeAll, describe, expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import MSAModelF from './model.ts'
|
|
5
|
-
|
|
6
|
-
beforeAll(() => {
|
|
7
|
-
HTMLCanvasElement.prototype.getContext = function () {
|
|
8
|
-
let font = '10px sans-serif'
|
|
9
|
-
return {
|
|
10
|
-
get font() {
|
|
11
|
-
return font
|
|
12
|
-
},
|
|
13
|
-
set font(v: string) {
|
|
14
|
-
font = v
|
|
15
|
-
},
|
|
16
|
-
measureText(t: string) {
|
|
17
|
-
const size = Number.parseFloat(font) || 10
|
|
18
|
-
return { width: t.length * size * 0.6 } as TextMetrics
|
|
19
|
-
},
|
|
20
|
-
} as unknown as CanvasRenderingContext2D
|
|
21
|
-
} as unknown as typeof HTMLCanvasElement.prototype.getContext
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
// col0 is invariant M; col3 varies V/V/A (all hydrophobic) so property
|
|
25
|
-
// conservation stays high while identity conservation drops
|
|
26
|
-
const protein = `>a
|
|
27
|
-
MKLVIL
|
|
28
|
-
>b
|
|
29
|
-
MRLVIL
|
|
30
|
-
>c
|
|
31
|
-
MKLAIL`
|
|
32
|
-
|
|
33
|
-
const dna = `>a
|
|
34
|
-
ACGTAC
|
|
35
|
-
>b
|
|
36
|
-
ACGTAG
|
|
37
|
-
>c
|
|
38
|
-
ACGAAC`
|
|
39
|
-
|
|
40
|
-
function make(msa: string, id: string) {
|
|
41
|
-
return MSAModelF().create({
|
|
42
|
-
id,
|
|
43
|
-
type: 'MsaView',
|
|
44
|
-
msaFormat: 'fasta',
|
|
45
|
-
data: { msa },
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe('property conservation track', () => {
|
|
50
|
-
test('present for protein alignments', () => {
|
|
51
|
-
const ids = make(protein, 'p').tracks.map(t => t.model.id)
|
|
52
|
-
expect(ids).toContain('conservation')
|
|
53
|
-
expect(ids).toContain('property-conservation')
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test('omitted for nucleotide alignments', () => {
|
|
57
|
-
const ids = make(dna, 'd').tracks.map(t => t.model.id)
|
|
58
|
-
expect(ids).toContain('conservation')
|
|
59
|
-
expect(ids).not.toContain('property-conservation')
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test('conservative-substitution column: property beats identity', () => {
|
|
63
|
-
const model = make(protein, 'pc')
|
|
64
|
-
// col3 = V/V/A: same property class, different identity
|
|
65
|
-
expect(model.propertyConservation[3]!).toBeGreaterThan(
|
|
66
|
-
model.conservation[3]!,
|
|
67
|
-
)
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
describe('mouseOverColumnStats', () => {
|
|
72
|
-
test('undefined when nothing hovered', () => {
|
|
73
|
-
expect(make(protein, 'm0').mouseOverColumnStats).toBeUndefined()
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
test('reports consensus, gaps and distribution for hovered column', () => {
|
|
77
|
-
const model = make(protein, 'm1')
|
|
78
|
-
model.setMousePos(0, 0)
|
|
79
|
-
const s = model.mouseOverColumnStats
|
|
80
|
-
expect(s?.consensusLetter).toBe('M')
|
|
81
|
-
expect(s?.consensusFraction).toBe(1)
|
|
82
|
-
expect(s?.gapFraction).toBe(0)
|
|
83
|
-
expect(s?.total).toBe(3)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
test('mixed column reports the majority residue as consensus', () => {
|
|
87
|
-
const model = make(protein, 'm2')
|
|
88
|
-
// col1 = K/R/K -> consensus K at 2/3
|
|
89
|
-
model.setMousePos(1, 0)
|
|
90
|
-
const s = model.mouseOverColumnStats
|
|
91
|
-
expect(s?.consensusLetter).toBe('K')
|
|
92
|
-
expect(s?.consensusCount).toBe(2)
|
|
93
|
-
})
|
|
94
|
-
})
|
package/src/columnTracks.test.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { getSnapshot } from '@jbrowse/mobx-state-tree'
|
|
2
|
-
import { expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import stateModelFactory from './model.ts'
|
|
5
|
-
|
|
6
|
-
import type { ColumnTrackSpec } from './types.ts'
|
|
7
|
-
|
|
8
|
-
const MsaView = stateModelFactory()
|
|
9
|
-
|
|
10
|
-
// s1 has a gap at column 1 and s2 a gap at column 3, and every row is gapped at
|
|
11
|
-
// column 5, so hiding gappy columns drops that one
|
|
12
|
-
const msa = '>s1\nA-CDE-F\n>s2\nAB-DE-F\n>s3\nABCDE-F\n'
|
|
13
|
-
|
|
14
|
-
function makeModel(columnTracks: ColumnTrackSpec[]) {
|
|
15
|
-
return MsaView.create({
|
|
16
|
-
type: 'MsaView',
|
|
17
|
-
msaFormat: 'fasta',
|
|
18
|
-
data: { msa },
|
|
19
|
-
columnTracks,
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
test('column-indexed values normalize by max and clamp to 0..1', () => {
|
|
24
|
-
const model = makeModel([
|
|
25
|
-
{ id: 't', name: 'T', kind: 'bar', values: [1, 2, 4, -1], max: 2 },
|
|
26
|
-
])
|
|
27
|
-
expect(model.columnTrackContent.get('t')?.values).toEqual([0.5, 1, 1, 0])
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
test('row-indexed values land on the residues of that row', () => {
|
|
31
|
-
const model = makeModel([
|
|
32
|
-
{ id: 't', name: 'T', kind: 'bar', values: [0.1, 0.2, 0.3], row: 's1' },
|
|
33
|
-
])
|
|
34
|
-
// residues 1..3 of s1 are A, C, D at global columns 0, 2, 3
|
|
35
|
-
expect(model.columnTrackContent.get('t')?.values).toEqual([
|
|
36
|
-
0.1, 0, 0.2, 0.3, 0, 0, 0,
|
|
37
|
-
])
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('a row-indexed text track spreads its letters the same way', () => {
|
|
41
|
-
const model = makeModel([
|
|
42
|
-
{ id: 'f', name: 'Frame', kind: 'text', data: '123', row: 's2' },
|
|
43
|
-
])
|
|
44
|
-
// residues of s2 sit at columns 0, 1, 3
|
|
45
|
-
expect(model.columnTrackContent.get('f')?.data).toBe('12 3 ')
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
test('hiding gappy columns drops them from the values and the text', () => {
|
|
49
|
-
const model = makeModel([
|
|
50
|
-
{ id: 't', name: 'T', kind: 'bar', values: [1, 2, 3, 4, 5, 6, 7], max: 7 },
|
|
51
|
-
{ id: 'f', name: 'F', kind: 'text', data: 'abcdefg' },
|
|
52
|
-
])
|
|
53
|
-
model.setHideGaps(true)
|
|
54
|
-
model.setAllowedGappyness(99)
|
|
55
|
-
expect(model.blanks).toEqual([5])
|
|
56
|
-
expect(model.columnTrackContent.get('t')?.values).toHaveLength(6)
|
|
57
|
-
expect(model.columnTrackContent.get('f')?.data).toBe('abcdeg')
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
test('data tracks join the track list with their own color and height', () => {
|
|
61
|
-
const model = makeModel([
|
|
62
|
-
{ id: 't', name: 'dN/dS', kind: 'bar', values: [1], color: 'red' },
|
|
63
|
-
{ id: 'f', name: 'Frame', kind: 'text', data: '1', colors: { 1: 'blue' } },
|
|
64
|
-
])
|
|
65
|
-
const ids = model.tracks.map(t => t.model.id)
|
|
66
|
-
expect(ids).toContain('t')
|
|
67
|
-
expect(ids).toContain('f')
|
|
68
|
-
const bar = model.tracks.find(t => t.model.id === 't')!.model
|
|
69
|
-
expect(bar.barColor).toBe('red')
|
|
70
|
-
expect(bar.height).toBe(model.conservationTrackHeight)
|
|
71
|
-
const text = model.tracks.find(t => t.model.id === 'f')!.model
|
|
72
|
-
expect(text.customColorScheme).toEqual({ 1: 'blue' })
|
|
73
|
-
expect(text.height).toBe(model.rowHeight)
|
|
74
|
-
expect(text.data).toBe('1')
|
|
75
|
-
|
|
76
|
-
expect(model.turnedOnTracks.map(t => t.model.id)).toContain('t')
|
|
77
|
-
model.toggleTrack('t')
|
|
78
|
-
expect(model.turnedOnTracks.map(t => t.model.id)).not.toContain('t')
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
test('setColumnTracks replaces the set and the snapshot carries it', () => {
|
|
82
|
-
const model = makeModel([])
|
|
83
|
-
expect(getSnapshot(model)).not.toHaveProperty('columnTracks')
|
|
84
|
-
model.setColumnTracks([{ id: 't', name: 'T', kind: 'bar', values: [0.5] }])
|
|
85
|
-
expect(getSnapshot(model).columnTracks).toEqual([
|
|
86
|
-
{ id: 't', name: 'T', kind: 'bar', values: [0.5] },
|
|
87
|
-
])
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('a track too large to share leaves the snapshot but keeps drawing', () => {
|
|
91
|
-
const model = makeModel([
|
|
92
|
-
{ id: 'big', name: 'Big', kind: 'bar', values: Array(20_000).fill(0.5) },
|
|
93
|
-
{ id: 'small', name: 'Small', kind: 'bar', values: [0.5] },
|
|
94
|
-
])
|
|
95
|
-
expect(model.columnTrackContent.get('big')?.values).toHaveLength(20_000)
|
|
96
|
-
expect(getSnapshot(model).columnTracks?.map(t => t.id)).toEqual(['small'])
|
|
97
|
-
})
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
|
|
3
|
-
import { Dialog } from '@jbrowse/core/ui'
|
|
4
|
-
import { getSession } from '@jbrowse/core/util'
|
|
5
|
-
import { Button, DialogActions, DialogContent, Typography } from '@mui/material'
|
|
6
|
-
import { observer } from 'mobx-react'
|
|
7
|
-
import { getUngappedSequence } from 'msa-parsers'
|
|
8
|
-
|
|
9
|
-
import { launchInterProScan } from '../../launchInterProScan.ts'
|
|
10
|
-
|
|
11
|
-
import type { MsaViewModel } from '../../model.ts'
|
|
12
|
-
|
|
13
|
-
const FAMILIES = 'Families, domains, sites & repeats'
|
|
14
|
-
const FEATURES = 'Other sequence features'
|
|
15
|
-
const STRUCTURAL = 'Structural domains'
|
|
16
|
-
const OTHER = 'Other category'
|
|
17
|
-
|
|
18
|
-
// The analyses the EBI iprscan5 endpoint accepts, all on by default. Static, so
|
|
19
|
-
// it lives out here rather than being rebuilt on every render of the dialog.
|
|
20
|
-
const interProScanPrograms = [
|
|
21
|
-
{
|
|
22
|
-
name: 'NCBIfam',
|
|
23
|
-
category: FAMILIES,
|
|
24
|
-
description: 'NCBI RefSeq FAMs including TIGRFAMs',
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: 'SFLD',
|
|
28
|
-
category: FAMILIES,
|
|
29
|
-
description: 'Structure function linkage database',
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
name: 'Phobius',
|
|
33
|
-
category: FEATURES,
|
|
34
|
-
description:
|
|
35
|
-
'A combined transmembrane topology and signal peptide predictor',
|
|
36
|
-
},
|
|
37
|
-
{ name: 'SignalP', category: FEATURES },
|
|
38
|
-
{ name: 'SignalP_EUK', category: OTHER },
|
|
39
|
-
{ name: 'SignalP_GRAM_POSITIVE', category: OTHER },
|
|
40
|
-
{ name: 'SignalP_GRAM_NEGATIVE', category: OTHER },
|
|
41
|
-
{ name: 'SuperFamily', category: STRUCTURAL },
|
|
42
|
-
{ name: 'Panther', category: FAMILIES },
|
|
43
|
-
{ name: 'Gene3d', category: STRUCTURAL },
|
|
44
|
-
{ name: 'HAMAP', category: FAMILIES },
|
|
45
|
-
{ name: 'PrositeProfiles', category: FAMILIES },
|
|
46
|
-
{ name: 'PrositePatterns', category: FAMILIES },
|
|
47
|
-
{ name: 'Coils', category: FEATURES },
|
|
48
|
-
{ name: 'SMART', category: FAMILIES },
|
|
49
|
-
{
|
|
50
|
-
name: 'CDD',
|
|
51
|
-
category: FAMILIES,
|
|
52
|
-
description: 'Conserved Domains Database',
|
|
53
|
-
},
|
|
54
|
-
{ name: 'PRINTS', category: FAMILIES },
|
|
55
|
-
{ name: 'PfamA', category: FAMILIES },
|
|
56
|
-
{ name: 'MobiDBLite', category: FEATURES },
|
|
57
|
-
{ name: 'PIRSF', category: OTHER },
|
|
58
|
-
{ name: 'TMHMM', category: FEATURES },
|
|
59
|
-
{ name: 'AntiFam', category: OTHER },
|
|
60
|
-
{ name: 'FunFam', category: OTHER },
|
|
61
|
-
{ name: 'PIRSR', category: FAMILIES },
|
|
62
|
-
].map(program => ({ ...program, checked: true }))
|
|
63
|
-
|
|
64
|
-
const InterProScanDialog = observer(function ({
|
|
65
|
-
handleClose,
|
|
66
|
-
model,
|
|
67
|
-
}: {
|
|
68
|
-
handleClose: () => void
|
|
69
|
-
model: MsaViewModel
|
|
70
|
-
}) {
|
|
71
|
-
const [vals, setVals] = useState(interProScanPrograms)
|
|
72
|
-
|
|
73
|
-
const programs = vals.filter(e => e.checked).map(e => e.name)
|
|
74
|
-
const [show, setShow] = useState(false)
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<Dialog
|
|
78
|
-
maxWidth="xl"
|
|
79
|
-
title="Query InterProScan API for domains"
|
|
80
|
-
onClose={() => {
|
|
81
|
-
handleClose()
|
|
82
|
-
}}
|
|
83
|
-
open
|
|
84
|
-
>
|
|
85
|
-
<DialogContent>
|
|
86
|
-
<Typography>
|
|
87
|
-
This will run InterProScan via the InterProScan API on all rows of the
|
|
88
|
-
current MSA
|
|
89
|
-
</Typography>
|
|
90
|
-
<Button
|
|
91
|
-
onClick={() => {
|
|
92
|
-
setShow(!show)
|
|
93
|
-
}}
|
|
94
|
-
>
|
|
95
|
-
{show ? 'Hide' : 'Show'} advanced options
|
|
96
|
-
</Button>
|
|
97
|
-
{show ? (
|
|
98
|
-
<div>
|
|
99
|
-
<Typography>Select algorithms for InterProScan to run</Typography>
|
|
100
|
-
<div>
|
|
101
|
-
<Button
|
|
102
|
-
variant="contained"
|
|
103
|
-
color="secondary"
|
|
104
|
-
onClick={() => {
|
|
105
|
-
setVals(vals.map(v => ({ ...v, checked: false })))
|
|
106
|
-
}}
|
|
107
|
-
>
|
|
108
|
-
Select none
|
|
109
|
-
</Button>
|
|
110
|
-
<Button
|
|
111
|
-
variant="contained"
|
|
112
|
-
color="primary"
|
|
113
|
-
onClick={() => {
|
|
114
|
-
setVals(vals.map(v => ({ ...v, checked: true })))
|
|
115
|
-
}}
|
|
116
|
-
>
|
|
117
|
-
Select all
|
|
118
|
-
</Button>
|
|
119
|
-
</div>
|
|
120
|
-
<table>
|
|
121
|
-
<tbody>
|
|
122
|
-
{vals
|
|
123
|
-
.toSorted((a, b) => a.category.localeCompare(b.category))
|
|
124
|
-
.map(({ name, checked, category, description }) => (
|
|
125
|
-
<tr key={name}>
|
|
126
|
-
<td>
|
|
127
|
-
<input
|
|
128
|
-
type="checkbox"
|
|
129
|
-
checked={checked}
|
|
130
|
-
onChange={() => {
|
|
131
|
-
setVals(
|
|
132
|
-
vals.map(e =>
|
|
133
|
-
e.name === name
|
|
134
|
-
? { ...e, checked: !e.checked }
|
|
135
|
-
: e,
|
|
136
|
-
),
|
|
137
|
-
)
|
|
138
|
-
}}
|
|
139
|
-
/>
|
|
140
|
-
</td>
|
|
141
|
-
<td title={description}>{name}</td>
|
|
142
|
-
<td>{category}</td>
|
|
143
|
-
</tr>
|
|
144
|
-
))}
|
|
145
|
-
</tbody>
|
|
146
|
-
</table>
|
|
147
|
-
</div>
|
|
148
|
-
) : null}
|
|
149
|
-
</DialogContent>
|
|
150
|
-
<DialogActions>
|
|
151
|
-
<Button
|
|
152
|
-
variant="contained"
|
|
153
|
-
color="secondary"
|
|
154
|
-
onClick={() => {
|
|
155
|
-
handleClose()
|
|
156
|
-
}}
|
|
157
|
-
>
|
|
158
|
-
Cancel
|
|
159
|
-
</Button>
|
|
160
|
-
<Button
|
|
161
|
-
variant="contained"
|
|
162
|
-
color="primary"
|
|
163
|
-
onClick={() => {
|
|
164
|
-
const controller = new AbortController()
|
|
165
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
166
|
-
;(async () => {
|
|
167
|
-
try {
|
|
168
|
-
const { rows } = model
|
|
169
|
-
if (rows.length > 140) {
|
|
170
|
-
throw new Error(
|
|
171
|
-
'Too many sequences, please run InterProScan offline',
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
await launchInterProScan({
|
|
175
|
-
algorithm: 'interproscan',
|
|
176
|
-
programs: programs,
|
|
177
|
-
seq: rows
|
|
178
|
-
.map(row => [row[0], getUngappedSequence(row[1])])
|
|
179
|
-
.filter(f => !!f[1])
|
|
180
|
-
.map(row => `>${row[0]}\n${row[1]}`)
|
|
181
|
-
.join('\n'),
|
|
182
|
-
onProgress: arg => {
|
|
183
|
-
model.setStatus(
|
|
184
|
-
arg
|
|
185
|
-
? {
|
|
186
|
-
...arg,
|
|
187
|
-
onCancel: () => {
|
|
188
|
-
controller.abort()
|
|
189
|
-
},
|
|
190
|
-
}
|
|
191
|
-
: undefined,
|
|
192
|
-
)
|
|
193
|
-
},
|
|
194
|
-
model,
|
|
195
|
-
signal: controller.signal,
|
|
196
|
-
})
|
|
197
|
-
} catch (e) {
|
|
198
|
-
console.error(e)
|
|
199
|
-
getSession(model).notifyError(`${e}`, e)
|
|
200
|
-
} finally {
|
|
201
|
-
model.setStatus()
|
|
202
|
-
}
|
|
203
|
-
})()
|
|
204
|
-
handleClose()
|
|
205
|
-
}}
|
|
206
|
-
>
|
|
207
|
-
Send sequences to InterProScan
|
|
208
|
-
</Button>
|
|
209
|
-
</DialogActions>
|
|
210
|
-
</Dialog>
|
|
211
|
-
)
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
export default InterProScanDialog
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
|
|
3
|
-
import { Dialog } from '@jbrowse/core/ui'
|
|
4
|
-
import { getSession } from '@jbrowse/core/util'
|
|
5
|
-
import {
|
|
6
|
-
Button,
|
|
7
|
-
DialogActions,
|
|
8
|
-
DialogContent,
|
|
9
|
-
FormControl,
|
|
10
|
-
FormControlLabel,
|
|
11
|
-
Radio,
|
|
12
|
-
RadioGroup,
|
|
13
|
-
TextField,
|
|
14
|
-
Typography,
|
|
15
|
-
} from '@mui/material'
|
|
16
|
-
import { observer } from 'mobx-react'
|
|
17
|
-
import { interProScanResponseToAnnotations } from 'msa-parsers'
|
|
18
|
-
|
|
19
|
-
import { jsonfetch } from '../../fetchUtils.ts'
|
|
20
|
-
|
|
21
|
-
import type { MsaViewModel } from '../../model.ts'
|
|
22
|
-
import type { InterProScanResponse } from 'msa-parsers'
|
|
23
|
-
|
|
24
|
-
const InterProScanFileDialog = observer(function ({
|
|
25
|
-
handleClose,
|
|
26
|
-
model,
|
|
27
|
-
}: {
|
|
28
|
-
handleClose: () => void
|
|
29
|
-
model: MsaViewModel
|
|
30
|
-
}) {
|
|
31
|
-
const [file, setFile] = useState<File>()
|
|
32
|
-
const [choice, setChoice] = useState('file')
|
|
33
|
-
const [interProURL, setInterProURL] = useState('')
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<Dialog
|
|
37
|
-
maxWidth="xl"
|
|
38
|
-
title="Open InterProScan results"
|
|
39
|
-
onClose={() => {
|
|
40
|
-
handleClose()
|
|
41
|
-
}}
|
|
42
|
-
open
|
|
43
|
-
>
|
|
44
|
-
<DialogContent>
|
|
45
|
-
<div>
|
|
46
|
-
<Typography>
|
|
47
|
-
Open a JSON file of InterProScan results that you run remotely on
|
|
48
|
-
EBI servers or locally
|
|
49
|
-
</Typography>
|
|
50
|
-
|
|
51
|
-
<div style={{ display: 'flex', margin: 30 }}>
|
|
52
|
-
<FormControl component="fieldset">
|
|
53
|
-
<RadioGroup
|
|
54
|
-
value={choice}
|
|
55
|
-
onChange={event => {
|
|
56
|
-
setChoice(event.target.value)
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
<FormControlLabel value="url" control={<Radio />} label="URL" />
|
|
60
|
-
<FormControlLabel
|
|
61
|
-
value="file"
|
|
62
|
-
control={<Radio />}
|
|
63
|
-
label="File"
|
|
64
|
-
/>
|
|
65
|
-
</RadioGroup>
|
|
66
|
-
</FormControl>
|
|
67
|
-
{choice === 'url' ? (
|
|
68
|
-
<div>
|
|
69
|
-
<Typography>
|
|
70
|
-
Open a InterProScan JSON file remote URL
|
|
71
|
-
</Typography>
|
|
72
|
-
<TextField
|
|
73
|
-
label="URL"
|
|
74
|
-
value={interProURL}
|
|
75
|
-
onChange={event => {
|
|
76
|
-
setInterProURL(event.target.value)
|
|
77
|
-
}}
|
|
78
|
-
/>
|
|
79
|
-
</div>
|
|
80
|
-
) : null}
|
|
81
|
-
{choice === 'file' ? (
|
|
82
|
-
<div style={{ paddingTop: 20 }}>
|
|
83
|
-
<Typography>
|
|
84
|
-
Open a InterProScan JSON file file from your local drive
|
|
85
|
-
</Typography>
|
|
86
|
-
<Button variant="outlined" component="label">
|
|
87
|
-
Choose File
|
|
88
|
-
<input
|
|
89
|
-
type="file"
|
|
90
|
-
hidden
|
|
91
|
-
onChange={({ target }) => {
|
|
92
|
-
const file = target.files?.[0]
|
|
93
|
-
if (file) {
|
|
94
|
-
setFile(file)
|
|
95
|
-
}
|
|
96
|
-
}}
|
|
97
|
-
/>
|
|
98
|
-
</Button>
|
|
99
|
-
</div>
|
|
100
|
-
) : null}
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
</DialogContent>
|
|
104
|
-
<DialogActions>
|
|
105
|
-
<Button
|
|
106
|
-
variant="contained"
|
|
107
|
-
color="primary"
|
|
108
|
-
onClick={() => {
|
|
109
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
110
|
-
;(async () => {
|
|
111
|
-
try {
|
|
112
|
-
const ret: InterProScanResponse = file
|
|
113
|
-
? JSON.parse(await file.text())
|
|
114
|
-
: await jsonfetch(interProURL)
|
|
115
|
-
|
|
116
|
-
model.setAnnotations(interProScanResponseToAnnotations(ret))
|
|
117
|
-
getSession(model).notify(
|
|
118
|
-
'Loaded interproscan results',
|
|
119
|
-
'success',
|
|
120
|
-
)
|
|
121
|
-
} catch (e) {
|
|
122
|
-
console.error(e)
|
|
123
|
-
getSession(model).notifyError(`${e}`, e)
|
|
124
|
-
} finally {
|
|
125
|
-
model.setStatus()
|
|
126
|
-
}
|
|
127
|
-
})()
|
|
128
|
-
handleClose()
|
|
129
|
-
}}
|
|
130
|
-
>
|
|
131
|
-
Open results
|
|
132
|
-
</Button>
|
|
133
|
-
</DialogActions>
|
|
134
|
-
</Dialog>
|
|
135
|
-
)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
export default InterProScanFileDialog
|