jbrowse-plugin-mafviewer 1.4.5 → 1.4.6
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/dist/BigMafAdapter/BigMafAdapter.js +4 -5
- package/dist/BigMafAdapter/BigMafAdapter.js.map +1 -1
- package/dist/BigMafAdapter/configSchema.d.ts +2 -2
- package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js +38 -108
- package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js.map +1 -1
- package/dist/LinearMafDisplay/components/MAFTooltip.d.ts +0 -3
- package/dist/LinearMafDisplay/components/MAFTooltip.js.map +1 -1
- package/dist/LinearMafDisplay/components/MsaHighlightOverlay.d.ts +9 -0
- package/dist/LinearMafDisplay/components/MsaHighlightOverlay.js +34 -0
- package/dist/LinearMafDisplay/components/MsaHighlightOverlay.js.map +1 -0
- package/dist/LinearMafDisplay/components/Sidebar/SvgWrapper.js +1 -1
- package/dist/LinearMafDisplay/components/Sidebar/SvgWrapper.js.map +1 -1
- package/dist/LinearMafDisplay/components/useDragSelection.d.ts +25 -0
- package/dist/LinearMafDisplay/components/useDragSelection.js +103 -0
- package/dist/LinearMafDisplay/components/useDragSelection.js.map +1 -0
- package/dist/LinearMafDisplay/configSchema.d.ts +3 -30
- package/dist/LinearMafDisplay/stateModel.d.ts +1043 -121
- package/dist/LinearMafDisplay/stateModel.js +85 -41
- package/dist/LinearMafDisplay/stateModel.js.map +1 -1
- package/dist/LinearMafDisplay/types.d.ts +2 -2
- package/dist/LinearMafDisplay/util.d.ts +5 -0
- package/dist/LinearMafDisplay/util.js +25 -4
- package/dist/LinearMafDisplay/util.js.map +1 -1
- package/dist/LinearMafRenderer/LinearMafRenderer.d.ts +41 -5
- package/dist/LinearMafRenderer/LinearMafRenderer.js +1 -1
- package/dist/LinearMafRenderer/LinearMafRenderer.js.map +1 -1
- package/dist/LinearMafRenderer/components/LinearMafRendering.d.ts +14 -5
- package/dist/LinearMafRenderer/components/LinearMafRendering.js +21 -19
- package/dist/LinearMafRenderer/components/LinearMafRendering.js.map +1 -1
- package/dist/LinearMafRenderer/configSchema.d.ts +1 -6
- package/dist/LinearMafRenderer/configSchema.js +1 -6
- package/dist/LinearMafRenderer/configSchema.js.map +1 -1
- package/dist/LinearMafRenderer/rendering/insertions.d.ts +1 -1
- package/dist/LinearMafRenderer/rendering/insertions.js +2 -2
- package/dist/LinearMafRenderer/rendering/mismatches.d.ts +1 -1
- package/dist/LinearMafRenderer/rendering/mismatches.js +3 -3
- package/dist/LinearMafRenderer/rendering/types.d.ts +1 -1
- package/dist/MafAddTrackWorkflow/AddTrackWorkflow.js +1 -1
- package/dist/MafAddTrackWorkflow/AddTrackWorkflow.js.map +1 -1
- package/dist/MafAddTrackWorkflow/index.js +1 -1
- package/dist/MafAddTrackWorkflow/index.js.map +1 -1
- package/dist/MafGetSequences/MafGetSequences.d.ts +1 -0
- package/dist/MafGetSequences/MafGetSequences.js +2 -1
- package/dist/MafGetSequences/MafGetSequences.js.map +1 -1
- package/dist/MafSequenceWidget/LabelsCanvas.d.ts +8 -0
- package/dist/MafSequenceWidget/LabelsCanvas.js +37 -0
- package/dist/MafSequenceWidget/LabelsCanvas.js.map +1 -0
- package/dist/MafSequenceWidget/MafSequenceHoverHighlight.d.ts +6 -0
- package/dist/MafSequenceWidget/MafSequenceHoverHighlight.js +52 -0
- package/dist/MafSequenceWidget/MafSequenceHoverHighlight.js.map +1 -0
- package/dist/MafSequenceWidget/MafSequenceHoverHighlightExtension.d.ts +2 -0
- package/dist/MafSequenceWidget/MafSequenceHoverHighlightExtension.js +12 -0
- package/dist/MafSequenceWidget/MafSequenceHoverHighlightExtension.js.map +1 -0
- package/dist/MafSequenceWidget/MafSequenceWidget.d.ts +6 -0
- package/dist/MafSequenceWidget/MafSequenceWidget.js +189 -0
- package/dist/MafSequenceWidget/MafSequenceWidget.js.map +1 -0
- package/dist/MafSequenceWidget/SequenceCanvas.d.ts +12 -0
- package/dist/MafSequenceWidget/SequenceCanvas.js +86 -0
- package/dist/MafSequenceWidget/SequenceCanvas.js.map +1 -0
- package/dist/MafSequenceWidget/SequenceDisplay.d.ts +12 -0
- package/dist/MafSequenceWidget/SequenceDisplay.js +117 -0
- package/dist/MafSequenceWidget/SequenceDisplay.js.map +1 -0
- package/dist/MafSequenceWidget/SequenceTooltip.d.ts +11 -0
- package/dist/MafSequenceWidget/SequenceTooltip.js +39 -0
- package/dist/MafSequenceWidget/SequenceTooltip.js.map +1 -0
- package/dist/MafSequenceWidget/baseColors.d.ts +3 -0
- package/dist/MafSequenceWidget/baseColors.js +64 -0
- package/dist/MafSequenceWidget/baseColors.js.map +1 -0
- package/dist/MafSequenceWidget/colToGenomePos.d.ts +13 -0
- package/dist/MafSequenceWidget/colToGenomePos.js +32 -0
- package/dist/MafSequenceWidget/colToGenomePos.js.map +1 -0
- package/dist/MafSequenceWidget/colToGenomePos.test.d.ts +1 -0
- package/dist/MafSequenceWidget/colToGenomePos.test.js +136 -0
- package/dist/MafSequenceWidget/colToGenomePos.test.js.map +1 -0
- package/dist/MafSequenceWidget/configSchema.d.ts +1 -0
- package/dist/MafSequenceWidget/configSchema.js +3 -0
- package/dist/MafSequenceWidget/configSchema.js.map +1 -0
- package/dist/MafSequenceWidget/constants.d.ts +4 -0
- package/dist/MafSequenceWidget/constants.js +5 -0
- package/dist/MafSequenceWidget/constants.js.map +1 -0
- package/dist/MafSequenceWidget/index.d.ts +2 -0
- package/dist/MafSequenceWidget/index.js +16 -0
- package/dist/MafSequenceWidget/index.js.map +1 -0
- package/dist/MafSequenceWidget/stateModelFactory.d.ts +67 -0
- package/dist/MafSequenceWidget/stateModelFactory.js +21 -0
- package/dist/MafSequenceWidget/stateModelFactory.js.map +1 -0
- package/dist/MafTabixAdapter/MafTabixAdapter.js +4 -35
- package/dist/MafTabixAdapter/MafTabixAdapter.js.map +1 -1
- package/dist/MafTabixAdapter/configSchema.d.ts +4 -4
- package/dist/MafTrack/configSchema.d.ts +16 -11
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +12 -24
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +4 -4
- package/dist/util/clipboard.d.ts +2 -0
- package/dist/util/clipboard.js +28 -0
- package/dist/util/clipboard.js.map +1 -0
- package/dist/util/fastaUtils.d.ts +2 -1
- package/dist/util/fastaUtils.js +72 -2
- package/dist/util/fastaUtils.js.map +1 -1
- package/dist/util/fastaUtils.test.js +190 -0
- package/dist/util/fastaUtils.test.js.map +1 -1
- package/dist/util/parseAssemblyName.d.ts +32 -0
- package/dist/util/parseAssemblyName.js +87 -0
- package/dist/util/parseAssemblyName.js.map +1 -0
- package/dist/util/parseAssemblyName.test.d.ts +1 -0
- package/dist/util/parseAssemblyName.test.js +269 -0
- package/dist/util/parseAssemblyName.test.js.map +1 -0
- package/package.json +7 -7
- package/src/BigMafAdapter/BigMafAdapter.ts +5 -5
- package/src/LinearMafDisplay/components/LinearMafDisplayComponent.tsx +62 -144
- package/src/LinearMafDisplay/components/MAFTooltip.tsx +0 -3
- package/src/LinearMafDisplay/components/MsaHighlightOverlay.tsx +62 -0
- package/src/LinearMafDisplay/components/Sidebar/SvgWrapper.tsx +1 -1
- package/src/LinearMafDisplay/components/useDragSelection.ts +159 -0
- package/src/LinearMafDisplay/stateModel.ts +135 -48
- package/src/LinearMafDisplay/types.ts +2 -2
- package/src/LinearMafDisplay/util.ts +31 -5
- package/src/LinearMafRenderer/LinearMafRenderer.ts +1 -1
- package/src/LinearMafRenderer/components/LinearMafRendering.tsx +38 -24
- package/src/LinearMafRenderer/configSchema.ts +1 -6
- package/src/LinearMafRenderer/rendering/insertions.ts +2 -2
- package/src/LinearMafRenderer/rendering/mismatches.ts +3 -3
- package/src/LinearMafRenderer/rendering/types.ts +1 -1
- package/src/MafAddTrackWorkflow/AddTrackWorkflow.tsx +1 -1
- package/src/MafAddTrackWorkflow/index.ts +1 -1
- package/src/MafGetSequences/MafGetSequences.ts +10 -2
- package/src/MafSequenceWidget/LabelsCanvas.tsx +58 -0
- package/src/MafSequenceWidget/MafSequenceHoverHighlight.tsx +83 -0
- package/src/MafSequenceWidget/MafSequenceHoverHighlightExtension.tsx +24 -0
- package/src/MafSequenceWidget/MafSequenceWidget.tsx +294 -0
- package/src/MafSequenceWidget/SequenceCanvas.tsx +136 -0
- package/src/MafSequenceWidget/SequenceDisplay.tsx +188 -0
- package/src/MafSequenceWidget/SequenceTooltip.tsx +70 -0
- package/src/MafSequenceWidget/baseColors.ts +76 -0
- package/src/MafSequenceWidget/colToGenomePos.test.ts +166 -0
- package/src/MafSequenceWidget/colToGenomePos.ts +40 -0
- package/src/MafSequenceWidget/configSchema.ts +3 -0
- package/src/MafSequenceWidget/constants.ts +4 -0
- package/src/MafSequenceWidget/index.ts +24 -0
- package/src/MafSequenceWidget/stateModelFactory.ts +43 -0
- package/src/MafTabixAdapter/MafTabixAdapter.ts +12 -51
- package/src/index.ts +2 -0
- package/src/util/__snapshots__/fastaUtils.test.ts.snap +35 -0
- package/src/util/clipboard.ts +35 -0
- package/src/util/fastaUtils.test.ts +199 -0
- package/src/util/fastaUtils.ts +94 -1
- package/src/util/parseAssemblyName.test.ts +350 -0
- package/src/util/parseAssemblyName.ts +106 -0
- package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.d.ts +0 -11
- package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.js +0 -97
- package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.js.map +0 -1
- package/dist/LinearMafDisplay/components/InsertionSequenceDialog/InsertionSequenceDialog.d.ts +0 -14
- package/dist/LinearMafDisplay/components/InsertionSequenceDialog/InsertionSequenceDialog.js +0 -69
- package/dist/LinearMafDisplay/components/InsertionSequenceDialog/InsertionSequenceDialog.js.map +0 -1
- package/dist/LinearMafDisplay/components/util.d.ts +0 -1
- package/dist/LinearMafDisplay/components/util.js +0 -8
- package/dist/LinearMafDisplay/components/util.js.map +0 -1
- package/dist/util/fetchSequences.d.ts +0 -18
- package/dist/util/fetchSequences.js +0 -39
- package/dist/util/fetchSequences.js.map +0 -1
- package/dist/util/useSequences.d.ts +0 -21
- package/dist/util/useSequences.js +0 -64
- package/dist/util/useSequences.js.map +0 -1
- package/src/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.tsx +0 -175
- package/src/LinearMafDisplay/components/InsertionSequenceDialog/InsertionSequenceDialog.tsx +0 -105
- package/src/LinearMafDisplay/components/util.ts +0 -7
- package/src/util/fetchSequences.ts +0 -57
- package/src/util/useSequences.ts +0 -90
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Theme } from '@mui/material'
|
|
2
|
+
|
|
3
|
+
interface BasePalette {
|
|
4
|
+
A: { main: string; contrastText: string }
|
|
5
|
+
C: { main: string; contrastText: string }
|
|
6
|
+
G: { main: string; contrastText: string }
|
|
7
|
+
T: { main: string; contrastText: string }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getBases(theme: Theme): BasePalette | undefined {
|
|
11
|
+
return (theme.palette as any).bases as BasePalette | undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getBaseColor(base: string, theme: Theme): string {
|
|
15
|
+
const bases = getBases(theme)
|
|
16
|
+
if (!bases) {
|
|
17
|
+
switch (base.toUpperCase()) {
|
|
18
|
+
case 'A':
|
|
19
|
+
return '#6dbf6d'
|
|
20
|
+
case 'C':
|
|
21
|
+
return '#6c6cff'
|
|
22
|
+
case 'G':
|
|
23
|
+
return '#ffb347'
|
|
24
|
+
case 'T':
|
|
25
|
+
case 'U':
|
|
26
|
+
return '#ff6b6b'
|
|
27
|
+
default:
|
|
28
|
+
return theme.palette.grey[500]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
switch (base.toUpperCase()) {
|
|
33
|
+
case 'A':
|
|
34
|
+
return bases.A.main
|
|
35
|
+
case 'C':
|
|
36
|
+
return bases.C.main
|
|
37
|
+
case 'G':
|
|
38
|
+
return bases.G.main
|
|
39
|
+
case 'T':
|
|
40
|
+
case 'U':
|
|
41
|
+
return bases.T.main
|
|
42
|
+
default:
|
|
43
|
+
return theme.palette.grey[500]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getContrastText(base: string, theme: Theme): string {
|
|
48
|
+
const bases = getBases(theme)
|
|
49
|
+
if (!bases) {
|
|
50
|
+
switch (base.toUpperCase()) {
|
|
51
|
+
case 'A':
|
|
52
|
+
case 'C':
|
|
53
|
+
case 'T':
|
|
54
|
+
case 'U':
|
|
55
|
+
return '#fff'
|
|
56
|
+
case 'G':
|
|
57
|
+
return '#000'
|
|
58
|
+
default:
|
|
59
|
+
return theme.palette.common.white
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
switch (base.toUpperCase()) {
|
|
64
|
+
case 'A':
|
|
65
|
+
return bases.A.contrastText
|
|
66
|
+
case 'C':
|
|
67
|
+
return bases.C.contrastText
|
|
68
|
+
case 'G':
|
|
69
|
+
return bases.G.contrastText
|
|
70
|
+
case 'T':
|
|
71
|
+
case 'U':
|
|
72
|
+
return bases.T.contrastText
|
|
73
|
+
default:
|
|
74
|
+
return theme.palette.common.white
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { buildColToGenomePos, findRefSampleIndex } from './colToGenomePos'
|
|
4
|
+
|
|
5
|
+
describe('findRefSampleIndex', () => {
|
|
6
|
+
const samples = [
|
|
7
|
+
{ id: 'mm10', label: 'Mouse' },
|
|
8
|
+
{ id: 'hg38', label: 'Human' },
|
|
9
|
+
{ id: 'panTro6', label: 'Chimp' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
test('returns index of sample matching assemblyName', () => {
|
|
13
|
+
expect(findRefSampleIndex(samples, 'hg38')).toBe(1)
|
|
14
|
+
expect(findRefSampleIndex(samples, 'mm10')).toBe(0)
|
|
15
|
+
expect(findRefSampleIndex(samples, 'panTro6')).toBe(2)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('returns 0 when no match found', () => {
|
|
19
|
+
expect(findRefSampleIndex(samples, 'galGal6')).toBe(0)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('returns 0 when samples is undefined', () => {
|
|
23
|
+
expect(findRefSampleIndex(undefined, 'hg38')).toBe(0)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('returns 0 when assemblyName is undefined', () => {
|
|
27
|
+
expect(findRefSampleIndex(samples, undefined)).toBe(0)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('returns 0 when both are undefined', () => {
|
|
31
|
+
expect(findRefSampleIndex(undefined, undefined)).toBe(0)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('returns 0 for empty samples array', () => {
|
|
35
|
+
expect(findRefSampleIndex([], 'hg38')).toBe(0)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('buildColToGenomePos', () => {
|
|
40
|
+
test('maps non-gap characters to sequential genomic positions', () => {
|
|
41
|
+
const result = buildColToGenomePos('ACGT', 100)
|
|
42
|
+
expect(result).toEqual([100, 101, 102, 103])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('maps gaps to undefined', () => {
|
|
46
|
+
const result = buildColToGenomePos('AC-GT', 100)
|
|
47
|
+
expect(result).toEqual([100, 101, undefined, 102, 103])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('handles multiple consecutive gaps', () => {
|
|
51
|
+
const result = buildColToGenomePos('A--G', 100)
|
|
52
|
+
expect(result).toEqual([100, undefined, undefined, 101])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('handles gaps at start', () => {
|
|
56
|
+
const result = buildColToGenomePos('--ACG', 100)
|
|
57
|
+
expect(result).toEqual([undefined, undefined, 100, 101, 102])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('handles gaps at end', () => {
|
|
61
|
+
const result = buildColToGenomePos('ACG--', 100)
|
|
62
|
+
expect(result).toEqual([100, 101, 102, undefined, undefined])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('handles all gaps', () => {
|
|
66
|
+
const result = buildColToGenomePos('----', 100)
|
|
67
|
+
expect(result).toEqual([undefined, undefined, undefined, undefined])
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('handles empty sequence', () => {
|
|
71
|
+
const result = buildColToGenomePos('', 100)
|
|
72
|
+
expect(result).toEqual([])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('works with different start positions', () => {
|
|
76
|
+
const result = buildColToGenomePos('AC-T', 50000)
|
|
77
|
+
expect(result).toEqual([50000, 50001, undefined, 50002])
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('integration: hover highlight with gaps', () => {
|
|
82
|
+
const samples = [
|
|
83
|
+
{ id: 'mm10', label: 'Mouse' },
|
|
84
|
+
{ id: 'hg38', label: 'Human' },
|
|
85
|
+
{ id: 'panTro6', label: 'Chimp' },
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
test('gap in non-reference sample does not affect mapping', () => {
|
|
89
|
+
// Reference is hg38 (index 1)
|
|
90
|
+
// mm10 (index 0) has a gap, but hg38 does not
|
|
91
|
+
const sequences = [
|
|
92
|
+
'A-GT', // mm10 - has gap at position 1
|
|
93
|
+
'ACGT', // hg38 - no gap (this is the reference)
|
|
94
|
+
'ACGT', // panTro6
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
const refIdx = findRefSampleIndex(samples, 'hg38')
|
|
98
|
+
expect(refIdx).toBe(1)
|
|
99
|
+
|
|
100
|
+
const mapping = buildColToGenomePos(sequences[refIdx]!, 100)
|
|
101
|
+
// All positions should map to genomic coordinates since reference has no gaps
|
|
102
|
+
expect(mapping).toEqual([100, 101, 102, 103])
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('gap in reference sample maps to undefined', () => {
|
|
106
|
+
// Reference is hg38 (index 1)
|
|
107
|
+
// hg38 has a gap (insertion in other species)
|
|
108
|
+
const sequences = [
|
|
109
|
+
'ACGT', // mm10
|
|
110
|
+
'A-GT', // hg38 - has gap (this is the reference)
|
|
111
|
+
'ATGT', // panTro6 - has T where hg38 has gap
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
const refIdx = findRefSampleIndex(samples, 'hg38')
|
|
115
|
+
const mapping = buildColToGenomePos(sequences[refIdx]!, 100)
|
|
116
|
+
// Position 1 should be undefined because reference has gap there
|
|
117
|
+
expect(mapping).toEqual([100, undefined, 101, 102])
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('correctly handles reference as first sample', () => {
|
|
121
|
+
// Reference is mm10 (index 0)
|
|
122
|
+
const sequences = [
|
|
123
|
+
'ACGT', // mm10 - reference
|
|
124
|
+
'A-GT', // hg38 - has gap
|
|
125
|
+
'ACGT', // panTro6
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
const refIdx = findRefSampleIndex(samples, 'mm10')
|
|
129
|
+
expect(refIdx).toBe(0)
|
|
130
|
+
|
|
131
|
+
const mapping = buildColToGenomePos(sequences[refIdx]!, 100)
|
|
132
|
+
// All positions map because reference (mm10) has no gaps
|
|
133
|
+
expect(mapping).toEqual([100, 101, 102, 103])
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('correctly handles reference as last sample', () => {
|
|
137
|
+
// Reference is panTro6 (index 2)
|
|
138
|
+
const sequences = [
|
|
139
|
+
'A-GT', // mm10 - has gap
|
|
140
|
+
'A-GT', // hg38 - has gap
|
|
141
|
+
'ACGT', // panTro6 - no gap (this is the reference)
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
const refIdx = findRefSampleIndex(samples, 'panTro6')
|
|
145
|
+
expect(refIdx).toBe(2)
|
|
146
|
+
|
|
147
|
+
const mapping = buildColToGenomePos(sequences[refIdx]!, 100)
|
|
148
|
+
// All positions map because reference (panTro6) has no gaps
|
|
149
|
+
expect(mapping).toEqual([100, 101, 102, 103])
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('falls back to first sample when assembly not found', () => {
|
|
153
|
+
// Reference is galGal6 which doesn't exist in samples
|
|
154
|
+
const sequences = [
|
|
155
|
+
'ACGT', // mm10
|
|
156
|
+
'A-GT', // hg38
|
|
157
|
+
'ACGT', // panTro6
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
const refIdx = findRefSampleIndex(samples, 'galGal6')
|
|
161
|
+
expect(refIdx).toBe(0) // Falls back to first sample
|
|
162
|
+
|
|
163
|
+
const mapping = buildColToGenomePos(sequences[refIdx]!, 100)
|
|
164
|
+
expect(mapping).toEqual([100, 101, 102, 103])
|
|
165
|
+
})
|
|
166
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Sample } from '../LinearMafDisplay/types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Find the index of the reference sample (matching the assembly name from the region)
|
|
5
|
+
* Falls back to 0 if no match is found
|
|
6
|
+
*/
|
|
7
|
+
export function findRefSampleIndex(
|
|
8
|
+
samples: Sample[] | undefined,
|
|
9
|
+
assemblyName: string | undefined,
|
|
10
|
+
): number {
|
|
11
|
+
if (!samples || !assemblyName) {
|
|
12
|
+
return 0
|
|
13
|
+
}
|
|
14
|
+
const idx = samples.findIndex(s => s.id === assemblyName)
|
|
15
|
+
return idx === -1 ? 0 : idx
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build a mapping from display column index to genomic position.
|
|
20
|
+
* Only the reference sequence determines genomic positions - gaps in the
|
|
21
|
+
* reference map to undefined (no genomic position), while gaps in other
|
|
22
|
+
* samples don't affect the mapping.
|
|
23
|
+
*/
|
|
24
|
+
export function buildColToGenomePos(
|
|
25
|
+
refSequence: string,
|
|
26
|
+
regionStart: number,
|
|
27
|
+
): (number | undefined)[] {
|
|
28
|
+
const mapping: (number | undefined)[] = []
|
|
29
|
+
let genomePos = regionStart
|
|
30
|
+
|
|
31
|
+
for (const char of refSequence) {
|
|
32
|
+
if (char === '-') {
|
|
33
|
+
mapping.push(undefined)
|
|
34
|
+
} else {
|
|
35
|
+
mapping.push(genomePos)
|
|
36
|
+
genomePos++
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return mapping
|
|
40
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { lazy } from 'react'
|
|
2
|
+
|
|
3
|
+
import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType'
|
|
4
|
+
|
|
5
|
+
import MafSequenceHoverHighlightExtensionF from './MafSequenceHoverHighlightExtension'
|
|
6
|
+
import { configSchema } from './configSchema'
|
|
7
|
+
import { stateModelFactory } from './stateModelFactory'
|
|
8
|
+
|
|
9
|
+
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
10
|
+
|
|
11
|
+
export default function MafSequenceWidgetF(pluginManager: PluginManager) {
|
|
12
|
+
pluginManager.addWidgetType(
|
|
13
|
+
() =>
|
|
14
|
+
new WidgetType({
|
|
15
|
+
name: 'MafSequenceWidget',
|
|
16
|
+
heading: 'MAF Sequence',
|
|
17
|
+
configSchema: configSchema,
|
|
18
|
+
stateModel: stateModelFactory(),
|
|
19
|
+
ReactComponent: lazy(() => import('./MafSequenceWidget')),
|
|
20
|
+
}),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
MafSequenceHoverHighlightExtensionF(pluginManager)
|
|
24
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { types } from '@jbrowse/mobx-state-tree'
|
|
2
|
+
|
|
3
|
+
import type { Sample } from '../LinearMafDisplay/types'
|
|
4
|
+
import type { AnyConfigurationModel } from '@jbrowse/core/configuration'
|
|
5
|
+
import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
6
|
+
|
|
7
|
+
export interface HoverHighlight {
|
|
8
|
+
refName: string
|
|
9
|
+
start: number
|
|
10
|
+
end: number
|
|
11
|
+
assemblyName: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function stateModelFactory() {
|
|
15
|
+
return types
|
|
16
|
+
.model('MafSequenceWidget', {
|
|
17
|
+
id: types.identifier,
|
|
18
|
+
type: types.literal('MafSequenceWidget'),
|
|
19
|
+
adapterConfig: types.frozen<AnyConfigurationModel | undefined>(undefined),
|
|
20
|
+
samples: types.frozen<Sample[] | undefined>(undefined),
|
|
21
|
+
regions: types.frozen<
|
|
22
|
+
| {
|
|
23
|
+
refName: string
|
|
24
|
+
start: number
|
|
25
|
+
end: number
|
|
26
|
+
assemblyName: string
|
|
27
|
+
}[]
|
|
28
|
+
| undefined
|
|
29
|
+
>(undefined),
|
|
30
|
+
connectedViewId: types.maybe(types.string),
|
|
31
|
+
})
|
|
32
|
+
.volatile(() => ({
|
|
33
|
+
hoverHighlight: undefined as HoverHighlight | undefined,
|
|
34
|
+
}))
|
|
35
|
+
.actions(self => ({
|
|
36
|
+
setHoverHighlight(highlight: HoverHighlight | undefined) {
|
|
37
|
+
self.hoverHighlight = highlight
|
|
38
|
+
},
|
|
39
|
+
}))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type MafSequenceWidgetStateModel = ReturnType<typeof stateModelFactory>
|
|
43
|
+
export type MafSequenceWidgetModel = Instance<MafSequenceWidgetStateModel>
|
|
@@ -10,11 +10,15 @@ import {
|
|
|
10
10
|
} from '@jbrowse/core/util'
|
|
11
11
|
import { openLocation } from '@jbrowse/core/util/io'
|
|
12
12
|
import { ObservableCreate } from '@jbrowse/core/util/rxjs'
|
|
13
|
-
import { getSnapshot } from 'mobx-state-tree'
|
|
13
|
+
import { getSnapshot } from '@jbrowse/mobx-state-tree'
|
|
14
14
|
import { firstValueFrom, toArray } from 'rxjs'
|
|
15
15
|
|
|
16
16
|
import parseNewick from '../parseNewick'
|
|
17
17
|
import { normalize } from '../util'
|
|
18
|
+
import {
|
|
19
|
+
parseAssemblyAndChr,
|
|
20
|
+
selectReferenceSequence,
|
|
21
|
+
} from '../util/parseAssemblyName'
|
|
18
22
|
|
|
19
23
|
interface OrganismRecord {
|
|
20
24
|
chr: string
|
|
@@ -107,53 +111,7 @@ export default class MafTabixAdapter extends BaseFeatureDataAdapter {
|
|
|
107
111
|
continue
|
|
108
112
|
}
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
let assemblyName: string
|
|
112
|
-
let chr: string
|
|
113
|
-
|
|
114
|
-
const firstDotIndex = assemblyAndChr.indexOf('.')
|
|
115
|
-
if (firstDotIndex === -1) {
|
|
116
|
-
// No dot found, entire string is assembly name
|
|
117
|
-
assemblyName = assemblyAndChr
|
|
118
|
-
chr = ''
|
|
119
|
-
} else {
|
|
120
|
-
const secondDotIndex = assemblyAndChr.indexOf(
|
|
121
|
-
'.',
|
|
122
|
-
firstDotIndex + 1,
|
|
123
|
-
)
|
|
124
|
-
if (secondDotIndex === -1) {
|
|
125
|
-
// Only one dot: assembly.chr
|
|
126
|
-
assemblyName = assemblyAndChr.slice(
|
|
127
|
-
0,
|
|
128
|
-
Math.max(0, firstDotIndex),
|
|
129
|
-
)
|
|
130
|
-
chr = assemblyAndChr.slice(Math.max(0, firstDotIndex + 1))
|
|
131
|
-
} else {
|
|
132
|
-
// Multiple dots: check if second part is numeric (version number)
|
|
133
|
-
const secondPart = assemblyAndChr.slice(
|
|
134
|
-
firstDotIndex + 1,
|
|
135
|
-
secondDotIndex,
|
|
136
|
-
)
|
|
137
|
-
const isNumeric =
|
|
138
|
-
secondPart.length > 0 && !Number.isNaN(+secondPart)
|
|
139
|
-
|
|
140
|
-
if (isNumeric) {
|
|
141
|
-
// assembly.version.chr format
|
|
142
|
-
assemblyName = assemblyAndChr.slice(
|
|
143
|
-
0,
|
|
144
|
-
Math.max(0, secondDotIndex),
|
|
145
|
-
)
|
|
146
|
-
chr = assemblyAndChr.slice(Math.max(0, secondDotIndex + 1))
|
|
147
|
-
} else {
|
|
148
|
-
// assembly.chr.more format
|
|
149
|
-
assemblyName = assemblyAndChr.slice(
|
|
150
|
-
0,
|
|
151
|
-
Math.max(0, firstDotIndex),
|
|
152
|
-
)
|
|
153
|
-
chr = assemblyAndChr.slice(Math.max(0, firstDotIndex + 1))
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
114
|
+
const { assemblyName, chr } = parseAssemblyAndChr(assemblyAndChr)
|
|
157
115
|
|
|
158
116
|
if (assemblyName) {
|
|
159
117
|
// Set first assembly name found (only once)
|
|
@@ -183,9 +141,12 @@ export default class MafTabixAdapter extends BaseFeatureDataAdapter {
|
|
|
183
141
|
name: feature.get('name'),
|
|
184
142
|
score: feature.get('score'),
|
|
185
143
|
alignments,
|
|
186
|
-
seq:
|
|
187
|
-
alignments
|
|
188
|
-
|
|
144
|
+
seq: selectReferenceSequence(
|
|
145
|
+
alignments,
|
|
146
|
+
refAssemblyName,
|
|
147
|
+
query.assemblyName,
|
|
148
|
+
firstAssemblyNameFound,
|
|
149
|
+
),
|
|
189
150
|
},
|
|
190
151
|
}),
|
|
191
152
|
)
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import LinearMafRendererF from './LinearMafRenderer'
|
|
|
8
8
|
import MafAddTrackWorkflowF from './MafAddTrackWorkflow'
|
|
9
9
|
import MafGetSamplesF from './MafGetSamples'
|
|
10
10
|
import MafGetSequencesF from './MafGetSequences'
|
|
11
|
+
import MafSequenceWidgetF from './MafSequenceWidget'
|
|
11
12
|
import MafTabixAdapterF from './MafTabixAdapter'
|
|
12
13
|
import MafTrackF from './MafTrack'
|
|
13
14
|
|
|
@@ -24,6 +25,7 @@ export default class MafViewerPlugin extends Plugin {
|
|
|
24
25
|
MafAddTrackWorkflowF(pluginManager)
|
|
25
26
|
MafGetSequencesF(pluginManager)
|
|
26
27
|
MafGetSamplesF(pluginManager)
|
|
28
|
+
MafSequenceWidgetF(pluginManager)
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
configure(_pluginManager: PluginManager) {}
|
|
@@ -7,6 +7,41 @@ exports[`gap in assembly1 1`] = `
|
|
|
7
7
|
]
|
|
8
8
|
`;
|
|
9
9
|
|
|
10
|
+
exports[`includeInsertions - insertions at multiple positions 1`] = `
|
|
11
|
+
[
|
|
12
|
+
"ATCGGTAC",
|
|
13
|
+
"A-CG-TAC",
|
|
14
|
+
]
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
exports[`includeInsertions - insertions in multiple samples with different lengths 1`] = `
|
|
18
|
+
[
|
|
19
|
+
"AC-T-GTAC",
|
|
20
|
+
"ACTTTGTAC",
|
|
21
|
+
]
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
exports[`includeInsertions - single insertion in one sample 1`] = `
|
|
25
|
+
[
|
|
26
|
+
"AC--GTAC",
|
|
27
|
+
"ACTTGTAC",
|
|
28
|
+
]
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
exports[`includeInsertions with no insertions present 1`] = `
|
|
32
|
+
[
|
|
33
|
+
"ACGTA",
|
|
34
|
+
"AC-TT",
|
|
35
|
+
]
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
exports[`includeInsertions=false ignores insertions 1`] = `
|
|
39
|
+
[
|
|
40
|
+
"ACGTAC",
|
|
41
|
+
"ACGTAC",
|
|
42
|
+
]
|
|
43
|
+
`;
|
|
44
|
+
|
|
10
45
|
exports[`no showAllLetters 1`] = `
|
|
11
46
|
[
|
|
12
47
|
".....",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export async function copyToClipboard(
|
|
2
|
+
text: string,
|
|
3
|
+
onSuccess?: () => void,
|
|
4
|
+
onError?: (e: unknown) => void,
|
|
5
|
+
) {
|
|
6
|
+
try {
|
|
7
|
+
await navigator.clipboard.writeText(text)
|
|
8
|
+
onSuccess?.()
|
|
9
|
+
} catch (e) {
|
|
10
|
+
console.error(e)
|
|
11
|
+
onError?.(e)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function downloadAsFile(
|
|
16
|
+
content: string,
|
|
17
|
+
filename: string,
|
|
18
|
+
onSuccess?: () => void,
|
|
19
|
+
onError?: (e: unknown) => void,
|
|
20
|
+
) {
|
|
21
|
+
try {
|
|
22
|
+
const url = URL.createObjectURL(new Blob([content], { type: 'text/plain' }))
|
|
23
|
+
const a = document.createElement('a')
|
|
24
|
+
a.href = url
|
|
25
|
+
a.download = filename
|
|
26
|
+
document.body.append(a)
|
|
27
|
+
a.click()
|
|
28
|
+
a.remove()
|
|
29
|
+
URL.revokeObjectURL(url)
|
|
30
|
+
onSuccess?.()
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error(e)
|
|
33
|
+
onError?.(e)
|
|
34
|
+
}
|
|
35
|
+
}
|