jbrowse-plugin-mafviewer 1.2.3 → 1.3.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/dist/BgzipTaffyAdapter/BgzipTaffyAdapter.d.ts +1 -1
- package/dist/BigMafAdapter/BigMafAdapter.d.ts +1 -1
- package/dist/BigMafAdapter/BigMafAdapter.js +50 -49
- package/dist/BigMafAdapter/BigMafAdapter.js.map +1 -1
- package/dist/LinearMafDisplay/components/Crosshairs.d.ts +10 -0
- package/dist/LinearMafDisplay/components/Crosshairs.js +18 -0
- package/dist/LinearMafDisplay/components/Crosshairs.js.map +1 -0
- package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.d.ts +11 -0
- package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.js +97 -0
- package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.js.map +1 -0
- package/dist/LinearMafDisplay/components/{ReactComponent.d.ts → LinearMafDisplayComponent.d.ts} +1 -1
- package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js +168 -0
- package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js.map +1 -0
- package/dist/LinearMafDisplay/components/MAFTooltip.d.ts +12 -0
- package/dist/LinearMafDisplay/components/MAFTooltip.js +29 -0
- package/dist/LinearMafDisplay/components/MAFTooltip.js.map +1 -0
- package/dist/LinearMafDisplay/components/SetRowHeightDialog/SetRowHeightDialog.js +38 -0
- package/dist/LinearMafDisplay/components/SetRowHeightDialog/SetRowHeightDialog.js.map +1 -0
- package/dist/LinearMafDisplay/components/Sidebar/ColorLegend.d.ts +6 -0
- package/dist/LinearMafDisplay/components/{ColorLegend.js → Sidebar/ColorLegend.js} +2 -3
- package/dist/LinearMafDisplay/components/Sidebar/ColorLegend.js.map +1 -0
- package/dist/LinearMafDisplay/components/Sidebar/RectBg.js.map +1 -0
- package/dist/LinearMafDisplay/components/{SvgWrapper.d.ts → Sidebar/SvgWrapper.d.ts} +1 -1
- package/dist/LinearMafDisplay/components/{SvgWrapper.js → Sidebar/SvgWrapper.js} +3 -1
- package/dist/LinearMafDisplay/components/Sidebar/SvgWrapper.js.map +1 -0
- package/dist/LinearMafDisplay/components/{Tree.d.ts → Sidebar/Tree.d.ts} +2 -1
- package/dist/LinearMafDisplay/components/{Tree.js → Sidebar/Tree.js} +2 -0
- package/dist/LinearMafDisplay/components/Sidebar/Tree.js.map +1 -0
- package/dist/LinearMafDisplay/components/{YScaleBars.d.ts → Sidebar/YScaleBars.d.ts} +1 -1
- package/dist/LinearMafDisplay/components/Sidebar/YScaleBars.js +11 -0
- package/dist/LinearMafDisplay/components/Sidebar/YScaleBars.js.map +1 -0
- package/dist/LinearMafDisplay/index.js +1 -1
- package/dist/LinearMafDisplay/index.js.map +1 -1
- package/dist/LinearMafDisplay/renderSvg.js +1 -1
- package/dist/LinearMafDisplay/renderSvg.js.map +1 -1
- package/dist/LinearMafDisplay/stateModel.d.ts +23 -20
- package/dist/LinearMafDisplay/stateModel.js +62 -8
- package/dist/LinearMafDisplay/stateModel.js.map +1 -1
- package/dist/LinearMafDisplay/types.d.ts +5 -3
- package/dist/LinearMafDisplay/types.js +1 -15
- package/dist/LinearMafDisplay/types.js.map +1 -1
- package/dist/LinearMafDisplay/util.d.ts +4 -0
- package/dist/LinearMafDisplay/util.js +16 -0
- package/dist/LinearMafDisplay/util.js.map +1 -0
- package/dist/LinearMafRenderer/LinearMafRenderer.d.ts +5 -4
- package/dist/LinearMafRenderer/LinearMafRenderer.js +8 -10
- package/dist/LinearMafRenderer/LinearMafRenderer.js.map +1 -1
- package/dist/LinearMafRenderer/makeImageData.d.ts +1 -0
- package/dist/LinearMafRenderer/makeImageData.js +25 -20
- package/dist/LinearMafRenderer/makeImageData.js.map +1 -1
- package/dist/MafAddTrackWorkflow/index.js +0 -1
- package/dist/MafAddTrackWorkflow/index.js.map +1 -1
- package/dist/{MafRPC/index.d.ts → MafGetSamples/MafGetSamples.d.ts} +1 -3
- package/dist/{MafRPC/index.js → MafGetSamples/MafGetSamples.js} +2 -7
- package/dist/MafGetSamples/MafGetSamples.js.map +1 -0
- package/dist/MafGetSamples/index.d.ts +2 -0
- package/dist/MafGetSamples/index.js +7 -0
- package/dist/MafGetSamples/index.js.map +1 -0
- package/dist/MafGetSequences/MafGetSequences.d.ts +16 -0
- package/dist/MafGetSequences/MafGetSequences.js +20 -0
- package/dist/MafGetSequences/MafGetSequences.js.map +1 -0
- package/dist/MafGetSequences/index.d.ts +2 -0
- package/dist/MafGetSequences/index.js +7 -0
- package/dist/MafGetSequences/index.js.map +1 -0
- package/dist/MafTabixAdapter/MafTabixAdapter.d.ts +1 -1
- package/dist/MafTabixAdapter/MafTabixAdapter.js +9 -11
- package/dist/MafTabixAdapter/MafTabixAdapter.js.map +1 -1
- package/dist/MafTabixAdapter/configSchema.js +29 -1
- package/dist/MafTabixAdapter/configSchema.js.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +9 -20
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +4 -4
- package/dist/util/extractSubsequence.d.ts +12 -0
- package/dist/util/extractSubsequence.js +60 -0
- package/dist/util/extractSubsequence.js.map +1 -0
- package/dist/util/extractSubsequence.test.d.ts +1 -0
- package/dist/util/extractSubsequence.test.js +42 -0
- package/dist/util/extractSubsequence.test.js.map +1 -0
- package/dist/util/fastaUtils.d.ts +16 -0
- package/dist/util/fastaUtils.js +84 -0
- package/dist/util/fastaUtils.js.map +1 -0
- package/dist/util/fastaUtils.test.d.ts +1 -0
- package/dist/util/fastaUtils.test.js +95 -0
- package/dist/util/fastaUtils.test.js.map +1 -0
- package/dist/util/fetchSequences.d.ts +18 -0
- package/dist/util/fetchSequences.js +39 -0
- package/dist/util/fetchSequences.js.map +1 -0
- package/dist/util/useSequences.d.ts +21 -0
- package/dist/util/useSequences.js +64 -0
- package/dist/util/useSequences.js.map +1 -0
- package/dist/util.d.ts +2 -2
- package/dist/util.js +5 -1
- package/dist/util.js.map +1 -1
- package/package.json +13 -13
- package/src/BigMafAdapter/BigMafAdapter.ts +52 -49
- package/src/LinearMafDisplay/components/Crosshairs.tsx +50 -0
- package/src/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.tsx +175 -0
- package/src/LinearMafDisplay/components/LinearMafDisplayComponent.tsx +257 -0
- package/src/LinearMafDisplay/components/MAFTooltip.tsx +59 -0
- package/src/LinearMafDisplay/components/SetRowHeightDialog/SetRowHeightDialog.tsx +83 -0
- package/src/LinearMafDisplay/components/{ColorLegend.tsx → Sidebar/ColorLegend.tsx} +11 -7
- package/src/LinearMafDisplay/components/{SvgWrapper.tsx → Sidebar/SvgWrapper.tsx} +5 -3
- package/src/LinearMafDisplay/components/{Tree.tsx → Sidebar/Tree.tsx} +5 -1
- package/src/LinearMafDisplay/components/Sidebar/YScaleBars.tsx +23 -0
- package/src/LinearMafDisplay/index.ts +1 -1
- package/src/LinearMafDisplay/renderSvg.tsx +1 -1
- package/src/LinearMafDisplay/stateModel.ts +71 -18
- package/src/LinearMafDisplay/types.ts +4 -24
- package/src/LinearMafDisplay/util.ts +27 -0
- package/src/LinearMafRenderer/LinearMafRenderer.ts +10 -14
- package/src/LinearMafRenderer/makeImageData.ts +33 -22
- package/src/MafAddTrackWorkflow/index.ts +0 -1
- package/src/{MafRPC/index.ts → MafGetSamples/MafGetSamples.ts} +1 -8
- package/src/MafGetSamples/index.ts +9 -0
- package/src/MafGetSequences/MafGetSequences.ts +47 -0
- package/src/MafGetSequences/index.ts +9 -0
- package/src/MafTabixAdapter/MafTabixAdapter.ts +13 -12
- package/src/MafTabixAdapter/configSchema.ts +29 -1
- package/src/index.ts +4 -2
- package/src/util/__snapshots__/fastaUtils.test.ts.snap +22 -0
- package/src/util/extractSubsequence.test.ts +54 -0
- package/src/util/extractSubsequence.ts +74 -0
- package/src/util/fastaUtils.test.ts +99 -0
- package/src/util/fastaUtils.ts +102 -0
- package/src/util/fetchSequences.ts +57 -0
- package/src/util/useSequences.ts +90 -0
- package/src/util.ts +6 -2
- package/dist/LinearMafDisplay/components/ColorLegend.d.ts +0 -8
- package/dist/LinearMafDisplay/components/ColorLegend.js.map +0 -1
- package/dist/LinearMafDisplay/components/ReactComponent.js +0 -50
- package/dist/LinearMafDisplay/components/ReactComponent.js.map +0 -1
- package/dist/LinearMafDisplay/components/RectBg.js.map +0 -1
- package/dist/LinearMafDisplay/components/SetRowHeight.js +0 -36
- package/dist/LinearMafDisplay/components/SetRowHeight.js.map +0 -1
- package/dist/LinearMafDisplay/components/SvgWrapper.js.map +0 -1
- package/dist/LinearMafDisplay/components/Tree.js.map +0 -1
- package/dist/LinearMafDisplay/components/YScaleBars.js +0 -20
- package/dist/LinearMafDisplay/components/YScaleBars.js.map +0 -1
- package/dist/MafRPC/index.js.map +0 -1
- package/src/LinearMafDisplay/components/ReactComponent.tsx +0 -91
- package/src/LinearMafDisplay/components/SetRowHeight.tsx +0 -83
- package/src/LinearMafDisplay/components/YScaleBars.tsx +0 -41
- /package/dist/LinearMafDisplay/components/{SetRowHeight.d.ts → SetRowHeightDialog/SetRowHeightDialog.d.ts} +0 -0
- /package/dist/LinearMafDisplay/components/{RectBg.d.ts → Sidebar/RectBg.d.ts} +0 -0
- /package/dist/LinearMafDisplay/components/{RectBg.js → Sidebar/RectBg.js} +0 -0
- /package/src/LinearMafDisplay/components/{RectBg.tsx → Sidebar/RectBg.tsx} +0 -0
|
@@ -26,12 +26,13 @@ export default class BigMafAdapter extends BaseFeatureDataAdapter {
|
|
|
26
26
|
if (!this.getSubAdapter) {
|
|
27
27
|
throw new Error('no getSubAdapter available')
|
|
28
28
|
}
|
|
29
|
-
const adapter = await this.getSubAdapter({
|
|
30
|
-
...getSnapshot(this.config),
|
|
31
|
-
type: 'BigBedAdapter',
|
|
32
|
-
})
|
|
33
29
|
return {
|
|
34
|
-
adapter:
|
|
30
|
+
adapter: (
|
|
31
|
+
await this.getSubAdapter({
|
|
32
|
+
...getSnapshot(this.config),
|
|
33
|
+
type: 'BigBedAdapter',
|
|
34
|
+
})
|
|
35
|
+
).dataAdapter as BaseFeatureDataAdapter,
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
async setupPre() {
|
|
@@ -59,59 +60,61 @@ export default class BigMafAdapter extends BaseFeatureDataAdapter {
|
|
|
59
60
|
return ObservableCreate<Feature>(async observer => {
|
|
60
61
|
const { adapter } = await this.setup()
|
|
61
62
|
const features = await updateStatus(
|
|
62
|
-
'Downloading
|
|
63
|
+
'Downloading alignments',
|
|
63
64
|
statusCallback,
|
|
64
65
|
() => firstValueFrom(adapter.getFeatures(query).pipe(toArray())),
|
|
65
66
|
)
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
67
|
+
await updateStatus('Processing alignments', statusCallback, () => {
|
|
68
|
+
for (const feature of features) {
|
|
69
|
+
const maf = feature.get('mafBlock') as string
|
|
70
|
+
const blocks = maf.split(';')
|
|
71
|
+
let aln: string | undefined
|
|
72
|
+
const alns = [] as string[]
|
|
73
|
+
const alignments = {} as Record<string, OrganismRecord>
|
|
74
|
+
const blocks2 = [] as string[]
|
|
75
|
+
for (const block of blocks) {
|
|
76
|
+
if (block.startsWith('s')) {
|
|
77
|
+
if (aln) {
|
|
78
|
+
alns.push(block.split(/ +/)[6]!)
|
|
79
|
+
blocks2.push(block)
|
|
80
|
+
} else {
|
|
81
|
+
aln = block.split(/ +/)[6]
|
|
82
|
+
alns.push(aln!)
|
|
83
|
+
blocks2.push(block)
|
|
84
|
+
}
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
|
-
}
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
for (let i = 0; i < blocks2.length; i++) {
|
|
89
|
+
const elt = blocks2[i]!
|
|
90
|
+
const ad = elt.split(/ +/)
|
|
91
|
+
const y = ad[1]!.split('.')
|
|
92
|
+
const org = y[0]!
|
|
93
|
+
const chr = y[1]!
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
alignments[org] = {
|
|
96
|
+
chr: chr,
|
|
97
|
+
start: +ad[1]!,
|
|
98
|
+
srcSize: +ad[2]!,
|
|
99
|
+
strand: ad[3] === '+' ? 1 : -1,
|
|
100
|
+
unknown: +ad[4]!,
|
|
101
|
+
data: alns[i]!,
|
|
102
|
+
}
|
|
100
103
|
}
|
|
104
|
+
observer.next(
|
|
105
|
+
new SimpleFeature({
|
|
106
|
+
id: feature.id(),
|
|
107
|
+
data: {
|
|
108
|
+
start: feature.get('start'),
|
|
109
|
+
end: feature.get('end'),
|
|
110
|
+
refName: feature.get('refName'),
|
|
111
|
+
seq: alns[0],
|
|
112
|
+
alignments: alignments,
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
)
|
|
101
116
|
}
|
|
102
|
-
|
|
103
|
-
new SimpleFeature({
|
|
104
|
-
id: feature.id(),
|
|
105
|
-
data: {
|
|
106
|
-
start: feature.get('start'),
|
|
107
|
-
end: feature.get('end'),
|
|
108
|
-
refName: feature.get('refName'),
|
|
109
|
-
seq: alns[0],
|
|
110
|
-
alignments: alignments,
|
|
111
|
-
},
|
|
112
|
-
}),
|
|
113
|
-
)
|
|
114
|
-
}
|
|
117
|
+
})
|
|
115
118
|
observer.complete()
|
|
116
119
|
})
|
|
117
120
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { makeStyles } from 'tss-react/mui'
|
|
4
|
+
|
|
5
|
+
const useStyles = makeStyles()({
|
|
6
|
+
cursor: {
|
|
7
|
+
pointerEvents: 'none',
|
|
8
|
+
},
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
interface CrosshairsProps {
|
|
12
|
+
width: number
|
|
13
|
+
height: number
|
|
14
|
+
scrollTop: number
|
|
15
|
+
mouseX?: number
|
|
16
|
+
mouseY: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Crosshairs = ({
|
|
20
|
+
width,
|
|
21
|
+
height,
|
|
22
|
+
scrollTop,
|
|
23
|
+
mouseX,
|
|
24
|
+
mouseY,
|
|
25
|
+
}: CrosshairsProps) => {
|
|
26
|
+
const { classes } = useStyles()
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<svg
|
|
30
|
+
className={classes.cursor}
|
|
31
|
+
width={width}
|
|
32
|
+
height={height}
|
|
33
|
+
style={{
|
|
34
|
+
position: 'absolute',
|
|
35
|
+
top: scrollTop,
|
|
36
|
+
}}
|
|
37
|
+
>
|
|
38
|
+
<line
|
|
39
|
+
x1={0}
|
|
40
|
+
x2={width}
|
|
41
|
+
y1={mouseY - scrollTop}
|
|
42
|
+
y2={mouseY - scrollTop}
|
|
43
|
+
stroke="black"
|
|
44
|
+
/>
|
|
45
|
+
<line x1={mouseX} x2={mouseX} y1={0} y2={height} stroke="black" />
|
|
46
|
+
</svg>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default Crosshairs
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Dialog, ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui'
|
|
4
|
+
import { getSession } from '@jbrowse/core/util'
|
|
5
|
+
import {
|
|
6
|
+
Button,
|
|
7
|
+
DialogActions,
|
|
8
|
+
DialogContent,
|
|
9
|
+
TextField,
|
|
10
|
+
ToggleButton,
|
|
11
|
+
ToggleButtonGroup,
|
|
12
|
+
} from '@mui/material'
|
|
13
|
+
import { observer } from 'mobx-react'
|
|
14
|
+
import { makeStyles } from 'tss-react/mui'
|
|
15
|
+
|
|
16
|
+
import { useSequences } from '../../../util/useSequences'
|
|
17
|
+
|
|
18
|
+
import type { LinearMafDisplayModel } from '../../stateModel'
|
|
19
|
+
|
|
20
|
+
const useStyles = makeStyles()({
|
|
21
|
+
dialogContent: {
|
|
22
|
+
width: '80em',
|
|
23
|
+
},
|
|
24
|
+
textAreaInput: {
|
|
25
|
+
fontFamily: 'monospace',
|
|
26
|
+
whiteSpace: 'pre',
|
|
27
|
+
overflowX: 'auto',
|
|
28
|
+
},
|
|
29
|
+
ml: {
|
|
30
|
+
marginLeft: 10,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const GetSequenceDialog = observer(function ({
|
|
35
|
+
onClose,
|
|
36
|
+
model,
|
|
37
|
+
selectionCoords,
|
|
38
|
+
}: {
|
|
39
|
+
onClose: () => void
|
|
40
|
+
model: LinearMafDisplayModel
|
|
41
|
+
selectionCoords?: {
|
|
42
|
+
dragStartX: number
|
|
43
|
+
dragEndX: number
|
|
44
|
+
}
|
|
45
|
+
}) {
|
|
46
|
+
const [showAllLetters, setShowAllLetters] = useState(true)
|
|
47
|
+
const { classes } = useStyles()
|
|
48
|
+
const { sequence, loading, error } = useSequences({
|
|
49
|
+
model,
|
|
50
|
+
selectionCoords,
|
|
51
|
+
showAllLetters,
|
|
52
|
+
})
|
|
53
|
+
const sequenceTooLarge = sequence ? sequence.length > 1_000_000 : false
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Dialog open onClose={onClose} title="Subsequence Data" maxWidth="xl">
|
|
57
|
+
<DialogContent>
|
|
58
|
+
<div
|
|
59
|
+
style={{
|
|
60
|
+
display: 'flex',
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
marginBottom: '16px',
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<ToggleButtonGroup
|
|
66
|
+
value={showAllLetters}
|
|
67
|
+
exclusive
|
|
68
|
+
size="small"
|
|
69
|
+
onChange={(_event, newDisplayMode) => {
|
|
70
|
+
if (newDisplayMode !== null) {
|
|
71
|
+
setShowAllLetters(newDisplayMode)
|
|
72
|
+
}
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<ToggleButton value={true}>Show All Letters</ToggleButton>
|
|
76
|
+
<ToggleButton value={false}>Show Only Differences</ToggleButton>
|
|
77
|
+
</ToggleButtonGroup>
|
|
78
|
+
<div style={{ flexGrow: 1 }} />
|
|
79
|
+
<Button
|
|
80
|
+
variant="contained"
|
|
81
|
+
color="primary"
|
|
82
|
+
disabled={loading || !sequence}
|
|
83
|
+
onClick={() => {
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
85
|
+
;(async () => {
|
|
86
|
+
try {
|
|
87
|
+
await navigator.clipboard.writeText(sequence)
|
|
88
|
+
getSession(model).notify(
|
|
89
|
+
'Sequence copied to clipboard',
|
|
90
|
+
'info',
|
|
91
|
+
)
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error(e)
|
|
94
|
+
getSession(model).notifyError(`${e}`, e)
|
|
95
|
+
}
|
|
96
|
+
})()
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
Copy to Clipboard
|
|
100
|
+
</Button>
|
|
101
|
+
<Button
|
|
102
|
+
variant="contained"
|
|
103
|
+
color="secondary"
|
|
104
|
+
disabled={loading || !sequence}
|
|
105
|
+
onClick={() => {
|
|
106
|
+
try {
|
|
107
|
+
const url = URL.createObjectURL(
|
|
108
|
+
new Blob([sequence], { type: 'text/plain' }),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
// Create a temporary anchor element
|
|
112
|
+
const a = document.createElement('a')
|
|
113
|
+
a.href = url
|
|
114
|
+
a.download = 'sequence.fasta'
|
|
115
|
+
|
|
116
|
+
// Trigger the download
|
|
117
|
+
document.body.append(a)
|
|
118
|
+
a.click()
|
|
119
|
+
|
|
120
|
+
// Clean up
|
|
121
|
+
a.remove()
|
|
122
|
+
URL.revokeObjectURL(url)
|
|
123
|
+
getSession(model).notify('Sequence downloaded', 'info')
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.error(e)
|
|
126
|
+
getSession(model).notifyError(`${e}`, e)
|
|
127
|
+
}
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
Download
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
{error ? (
|
|
135
|
+
<ErrorMessage error={error} />
|
|
136
|
+
) : (
|
|
137
|
+
<>
|
|
138
|
+
{loading ? <LoadingEllipses /> : null}
|
|
139
|
+
<TextField
|
|
140
|
+
variant="outlined"
|
|
141
|
+
multiline
|
|
142
|
+
minRows={5}
|
|
143
|
+
maxRows={10}
|
|
144
|
+
disabled={sequenceTooLarge}
|
|
145
|
+
className={classes.dialogContent}
|
|
146
|
+
fullWidth
|
|
147
|
+
value={
|
|
148
|
+
loading
|
|
149
|
+
? 'Loading...'
|
|
150
|
+
: sequenceTooLarge
|
|
151
|
+
? 'Reference sequence too large to display, use the download FASTA button'
|
|
152
|
+
: sequence
|
|
153
|
+
}
|
|
154
|
+
slotProps={{
|
|
155
|
+
input: {
|
|
156
|
+
readOnly: true,
|
|
157
|
+
classes: {
|
|
158
|
+
input: classes.textAreaInput,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
}}
|
|
162
|
+
/>
|
|
163
|
+
</>
|
|
164
|
+
)}
|
|
165
|
+
</DialogContent>
|
|
166
|
+
<DialogActions>
|
|
167
|
+
<Button color="primary" variant="outlined" onClick={onClose}>
|
|
168
|
+
Close
|
|
169
|
+
</Button>
|
|
170
|
+
</DialogActions>
|
|
171
|
+
</Dialog>
|
|
172
|
+
)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
export default GetSequenceDialog
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Menu } from '@jbrowse/core/ui'
|
|
4
|
+
import { getContainingView, getEnv } from '@jbrowse/core/util'
|
|
5
|
+
import { useTheme } from '@mui/material'
|
|
6
|
+
import { observer } from 'mobx-react'
|
|
7
|
+
|
|
8
|
+
import Crosshairs from './Crosshairs'
|
|
9
|
+
import SequenceDialog from './GetSequenceDialog/GetSequenceDialog'
|
|
10
|
+
import MAFTooltip from './MAFTooltip'
|
|
11
|
+
import YScaleBars from './Sidebar/YScaleBars'
|
|
12
|
+
|
|
13
|
+
import type { LinearMafDisplayModel } from '../stateModel'
|
|
14
|
+
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
15
|
+
|
|
16
|
+
const LinearMafDisplay = observer(function (props: {
|
|
17
|
+
model: LinearMafDisplayModel
|
|
18
|
+
}) {
|
|
19
|
+
const { model } = props
|
|
20
|
+
const { pluginManager } = getEnv(model)
|
|
21
|
+
const { rowHeight, height, scrollTop, samples: sources } = model
|
|
22
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
23
|
+
const theme = useTheme()
|
|
24
|
+
|
|
25
|
+
const LinearGenomePlugin = pluginManager.getPlugin(
|
|
26
|
+
'LinearGenomeViewPlugin',
|
|
27
|
+
) as import('@jbrowse/plugin-linear-genome-view').default
|
|
28
|
+
const { BaseLinearDisplayComponent } = LinearGenomePlugin.exports
|
|
29
|
+
|
|
30
|
+
const [mouseY, setMouseY] = useState<number>()
|
|
31
|
+
const [mouseX, setMouseX] = useState<number>()
|
|
32
|
+
const [isDragging, setIsDragging] = useState(false)
|
|
33
|
+
const [dragStartX, setDragStartX] = useState<number>()
|
|
34
|
+
const [dragEndX, setDragEndX] = useState<number>()
|
|
35
|
+
const [showSelectionBox, setShowSelectionBox] = useState(false)
|
|
36
|
+
const [contextCoord, setContextCoord] = useState<{
|
|
37
|
+
coord: [number, number]
|
|
38
|
+
dragStartX: number
|
|
39
|
+
dragEndX: number
|
|
40
|
+
}>()
|
|
41
|
+
const [showSequenceDialog, setShowSequenceDialog] = useState(false)
|
|
42
|
+
const [selectionCoords, setSelectionCoords] = useState<
|
|
43
|
+
| {
|
|
44
|
+
dragStartX: number
|
|
45
|
+
dragEndX: number
|
|
46
|
+
}
|
|
47
|
+
| undefined
|
|
48
|
+
>()
|
|
49
|
+
const { width } = getContainingView(model) as LinearGenomeViewModel
|
|
50
|
+
|
|
51
|
+
const handleMouseDown = (event: React.MouseEvent) => {
|
|
52
|
+
const rect = ref.current?.getBoundingClientRect()
|
|
53
|
+
const left = rect?.left || 0
|
|
54
|
+
const clientX = event.clientX - left
|
|
55
|
+
|
|
56
|
+
// Clear the previous selection box when starting a new drag
|
|
57
|
+
setShowSelectionBox(false)
|
|
58
|
+
setIsDragging(true)
|
|
59
|
+
setDragStartX(clientX)
|
|
60
|
+
setDragEndX(clientX)
|
|
61
|
+
event.stopPropagation()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const handleMouseMove = (event: React.MouseEvent) => {
|
|
65
|
+
const rect = ref.current?.getBoundingClientRect()
|
|
66
|
+
const top = rect?.top || 0
|
|
67
|
+
const left = rect?.left || 0
|
|
68
|
+
const clientX = event.clientX - left
|
|
69
|
+
const clientY = event.clientY - top
|
|
70
|
+
|
|
71
|
+
setMouseY(clientY)
|
|
72
|
+
setMouseX(clientX)
|
|
73
|
+
|
|
74
|
+
if (isDragging) {
|
|
75
|
+
setDragEndX(clientX)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const handleMouseUp = (event: React.MouseEvent) => {
|
|
80
|
+
if (isDragging && dragStartX !== undefined && dragEndX !== undefined) {
|
|
81
|
+
// Calculate the drag distance
|
|
82
|
+
const dragDistanceX = Math.abs(dragEndX - dragStartX)
|
|
83
|
+
|
|
84
|
+
// Only show context menu if the drag distance is at least 2 pixels in either direction
|
|
85
|
+
if (dragDistanceX >= 2) {
|
|
86
|
+
setContextCoord({
|
|
87
|
+
coord: [event.clientX, event.clientY],
|
|
88
|
+
dragEndX: event.clientX,
|
|
89
|
+
dragStartX: dragStartX,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Set showSelectionBox to true to keep the selection visible
|
|
93
|
+
setShowSelectionBox(true)
|
|
94
|
+
} else {
|
|
95
|
+
// For very small drags (less than 2px), don't show selection box or context menu
|
|
96
|
+
clearSelectionBox()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Only set isDragging to false, but keep the coordinates
|
|
101
|
+
setIsDragging(false)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Function to clear the selection box
|
|
105
|
+
const clearSelectionBox = () => {
|
|
106
|
+
setShowSelectionBox(false)
|
|
107
|
+
setDragStartX(undefined)
|
|
108
|
+
setDragEndX(undefined)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Add keydown event handler to clear selection box when Escape key is pressed
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
114
|
+
if (event.key === 'Escape' && showSelectionBox) {
|
|
115
|
+
clearSelectionBox()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add click handler to clear selection box when clicking outside of it
|
|
120
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
121
|
+
if (
|
|
122
|
+
ref.current &&
|
|
123
|
+
!ref.current.contains(event.target as Node) &&
|
|
124
|
+
showSelectionBox
|
|
125
|
+
) {
|
|
126
|
+
clearSelectionBox()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
131
|
+
document.addEventListener('click', handleClickOutside)
|
|
132
|
+
|
|
133
|
+
return () => {
|
|
134
|
+
document.removeEventListener('keydown', handleKeyDown)
|
|
135
|
+
document.removeEventListener('click', handleClickOutside)
|
|
136
|
+
}
|
|
137
|
+
}, [showSelectionBox, clearSelectionBox])
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
ref={ref}
|
|
142
|
+
onMouseDown={handleMouseDown}
|
|
143
|
+
onMouseMove={handleMouseMove}
|
|
144
|
+
onMouseUp={handleMouseUp}
|
|
145
|
+
onDoubleClick={() => {
|
|
146
|
+
// Clear selection box on double click
|
|
147
|
+
if (showSelectionBox) {
|
|
148
|
+
clearSelectionBox()
|
|
149
|
+
}
|
|
150
|
+
}}
|
|
151
|
+
onMouseLeave={() => {
|
|
152
|
+
setMouseY(undefined)
|
|
153
|
+
setMouseX(undefined)
|
|
154
|
+
setIsDragging(false)
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
<BaseLinearDisplayComponent {...props} />
|
|
158
|
+
<YScaleBars model={model} />
|
|
159
|
+
{mouseY && mouseX && sources && !contextCoord && !showSequenceDialog ? (
|
|
160
|
+
<div style={{ position: 'relative' }}>
|
|
161
|
+
<Crosshairs
|
|
162
|
+
width={width}
|
|
163
|
+
height={height}
|
|
164
|
+
scrollTop={scrollTop}
|
|
165
|
+
mouseX={mouseX}
|
|
166
|
+
mouseY={mouseY}
|
|
167
|
+
/>
|
|
168
|
+
<MAFTooltip
|
|
169
|
+
model={model}
|
|
170
|
+
mouseX={mouseX}
|
|
171
|
+
mouseY={mouseY}
|
|
172
|
+
origMouseX={dragStartX}
|
|
173
|
+
rowHeight={rowHeight}
|
|
174
|
+
sources={sources}
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
) : null}
|
|
178
|
+
{(isDragging || showSelectionBox) &&
|
|
179
|
+
dragStartX !== undefined &&
|
|
180
|
+
dragEndX !== undefined ? (
|
|
181
|
+
<div
|
|
182
|
+
style={{
|
|
183
|
+
position: 'absolute',
|
|
184
|
+
left: Math.min(dragStartX, dragEndX),
|
|
185
|
+
top: 0,
|
|
186
|
+
width: Math.abs(dragEndX - dragStartX),
|
|
187
|
+
height,
|
|
188
|
+
backgroundColor: 'rgba(0, 0, 255, 0.2)',
|
|
189
|
+
border: '1px solid rgba(0, 0, 255, 0.5)',
|
|
190
|
+
pointerEvents: 'none',
|
|
191
|
+
}}
|
|
192
|
+
/>
|
|
193
|
+
) : null}
|
|
194
|
+
<Menu
|
|
195
|
+
open={Boolean(contextCoord)}
|
|
196
|
+
onMenuItemClick={(_, callback) => {
|
|
197
|
+
callback()
|
|
198
|
+
setContextCoord(undefined)
|
|
199
|
+
}}
|
|
200
|
+
onClose={() => {
|
|
201
|
+
setContextCoord(undefined)
|
|
202
|
+
}}
|
|
203
|
+
slotProps={{
|
|
204
|
+
transition: {
|
|
205
|
+
onExit: () => {
|
|
206
|
+
setContextCoord(undefined)
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
}}
|
|
210
|
+
anchorReference="anchorPosition"
|
|
211
|
+
anchorPosition={
|
|
212
|
+
contextCoord
|
|
213
|
+
? { top: contextCoord.coord[1], left: contextCoord.coord[0] }
|
|
214
|
+
: undefined
|
|
215
|
+
}
|
|
216
|
+
style={{
|
|
217
|
+
zIndex: theme.zIndex.tooltip,
|
|
218
|
+
}}
|
|
219
|
+
menuItems={[
|
|
220
|
+
{
|
|
221
|
+
label: 'View subsequence',
|
|
222
|
+
onClick: () => {
|
|
223
|
+
if (!contextCoord) {
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Store the selection coordinates for the SequenceDialog to use
|
|
228
|
+
setSelectionCoords({
|
|
229
|
+
dragStartX: contextCoord.dragStartX,
|
|
230
|
+
dragEndX: contextCoord.dragEndX,
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// Show the dialog
|
|
234
|
+
setShowSequenceDialog(true)
|
|
235
|
+
|
|
236
|
+
// Close the context menu
|
|
237
|
+
setContextCoord(undefined)
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
]}
|
|
241
|
+
/>
|
|
242
|
+
|
|
243
|
+
{showSequenceDialog ? (
|
|
244
|
+
<SequenceDialog
|
|
245
|
+
model={model}
|
|
246
|
+
selectionCoords={selectionCoords}
|
|
247
|
+
onClose={() => {
|
|
248
|
+
setShowSequenceDialog(false)
|
|
249
|
+
setSelectionCoords(undefined)
|
|
250
|
+
}}
|
|
251
|
+
/>
|
|
252
|
+
) : null}
|
|
253
|
+
</div>
|
|
254
|
+
)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
export default LinearMafDisplay
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { SanitizedHTML } from '@jbrowse/core/ui'
|
|
4
|
+
import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
|
|
5
|
+
import {
|
|
6
|
+
getBpDisplayStr,
|
|
7
|
+
getContainingView,
|
|
8
|
+
toLocale,
|
|
9
|
+
} from '@jbrowse/core/util'
|
|
10
|
+
import { observer } from 'mobx-react'
|
|
11
|
+
|
|
12
|
+
import type { LinearMafDisplayModel } from '../stateModel'
|
|
13
|
+
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
14
|
+
|
|
15
|
+
interface MAFTooltipProps {
|
|
16
|
+
mouseY: number
|
|
17
|
+
mouseX: number
|
|
18
|
+
rowHeight: number
|
|
19
|
+
sources: Record<string, any>[]
|
|
20
|
+
model: LinearMafDisplayModel
|
|
21
|
+
origMouseX?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const MAFTooltip = observer(function ({
|
|
25
|
+
model,
|
|
26
|
+
mouseY,
|
|
27
|
+
mouseX,
|
|
28
|
+
origMouseX,
|
|
29
|
+
rowHeight,
|
|
30
|
+
sources,
|
|
31
|
+
}: MAFTooltipProps) {
|
|
32
|
+
const view = getContainingView(model) as LinearGenomeViewModel
|
|
33
|
+
const ret = Object.entries(sources[Math.floor(mouseY / rowHeight)] || {})
|
|
34
|
+
.filter(([key]) => key !== 'color' && key !== 'id')
|
|
35
|
+
.map(([key, value]) => `${key}:${value}`)
|
|
36
|
+
.join('\n')
|
|
37
|
+
const p1 = origMouseX ? view.pxToBp(origMouseX) : undefined
|
|
38
|
+
const p2 = view.pxToBp(mouseX)
|
|
39
|
+
return ret ? (
|
|
40
|
+
<BaseTooltip>
|
|
41
|
+
<SanitizedHTML
|
|
42
|
+
html={[
|
|
43
|
+
ret,
|
|
44
|
+
...(p1
|
|
45
|
+
? [
|
|
46
|
+
`Start: ${p1.refName}:${toLocale(p1.coord)}`,
|
|
47
|
+
`End: ${p2.refName}:${toLocale(p2.coord)}`,
|
|
48
|
+
`Length: ${getBpDisplayStr(Math.abs(p1.coord - p2.coord))}`,
|
|
49
|
+
]
|
|
50
|
+
: [`${p2.refName}:${toLocale(p2.coord)}`]),
|
|
51
|
+
]
|
|
52
|
+
.filter(f => !!f)
|
|
53
|
+
.join('<br/>')}
|
|
54
|
+
/>
|
|
55
|
+
</BaseTooltip>
|
|
56
|
+
) : null
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export default MAFTooltip
|