react-msaview 4.4.6 → 4.5.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/bundle/index.js +9 -9
- package/bundle/index.js.LICENSE.txt +8 -8
- package/bundle/index.js.map +1 -1
- package/dist/colorSchemes.d.ts +0 -6
- package/dist/colorSchemes.js +1 -119
- package/dist/colorSchemes.js.map +1 -1
- package/dist/components/ConservationTrack.d.ts +8 -0
- package/dist/components/ConservationTrack.js +54 -0
- package/dist/components/ConservationTrack.js.map +1 -0
- package/dist/components/Loading.js +14 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +36 -0
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/SequenceTextArea.js +3 -2
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/TextTrack.d.ts +3 -3
- package/dist/components/TextTrack.js +4 -1
- package/dist/components/TextTrack.js.map +1 -1
- package/dist/components/Track.js +21 -8
- package/dist/components/Track.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +19 -3
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/header/GappynessSlider.d.ts +6 -0
- package/dist/components/header/GappynessSlider.js +19 -0
- package/dist/components/header/GappynessSlider.js.map +1 -0
- package/dist/components/header/Header.js +3 -1
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderMenu.js +30 -14
- package/dist/components/header/HeaderMenu.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.js +4 -3
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +56 -42
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +53 -10
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/tracks/renderTracksSvg.d.ts +29 -0
- package/dist/components/tracks/renderTracksSvg.js +83 -0
- package/dist/components/tracks/renderTracksSvg.js.map +1 -0
- package/dist/components/tree/TreeNodeMenu.js +2 -2
- package/dist/components/tree/TreeNodeMenu.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +1 -1
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +22 -0
- package/dist/constants.js +26 -0
- package/dist/constants.js.map +1 -0
- package/dist/layout.js.map +1 -1
- package/dist/model/msaModel.js +3 -2
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.js +9 -8
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +256 -15
- package/dist/model.js +408 -128
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.d.ts +1 -0
- package/dist/neighborJoining.js +839 -0
- package/dist/neighborJoining.js.map +1 -0
- package/dist/neighborJoining.test.d.ts +1 -0
- package/dist/neighborJoining.test.js +110 -0
- package/dist/neighborJoining.test.js.map +1 -0
- package/dist/parsers/A3mMSA.d.ts +43 -0
- package/dist/parsers/A3mMSA.js +277 -0
- package/dist/parsers/A3mMSA.js.map +1 -0
- package/dist/parsers/A3mMSA.test.d.ts +1 -0
- package/dist/parsers/A3mMSA.test.js +138 -0
- package/dist/parsers/A3mMSA.test.js.map +1 -0
- package/dist/parsers/ClustalMSA.d.ts +4 -4
- package/dist/parsers/ClustalMSA.js +3 -1
- package/dist/parsers/ClustalMSA.js.map +1 -1
- package/dist/parsers/FastaMSA.js +17 -16
- package/dist/parsers/FastaMSA.js.map +1 -1
- package/dist/renderToSvg.d.ts +1 -0
- package/dist/renderToSvg.js +48 -18
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.js +2 -0
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/types.d.ts +2 -3
- package/dist/util.js +17 -9
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -6
- package/src/colorSchemes.ts +1 -179
- package/src/components/ConservationTrack.tsx +104 -0
- package/src/components/Loading.tsx +44 -2
- package/src/components/MSAView.tsx +68 -0
- package/src/components/SequenceTextArea.tsx +3 -2
- package/src/components/TextTrack.tsx +7 -4
- package/src/components/Track.tsx +25 -9
- package/src/components/dialogs/ExportSVGDialog.tsx +25 -1
- package/src/components/header/GappynessSlider.tsx +35 -0
- package/src/components/header/Header.tsx +3 -1
- package/src/components/header/HeaderMenu.tsx +36 -15
- package/src/components/minimap/MinimapSVG.tsx +6 -3
- package/src/components/msa/MSACanvasBlock.tsx +66 -48
- package/src/components/msa/renderMSABlock.ts +82 -22
- package/src/components/tracks/renderTracksSvg.ts +157 -0
- package/src/components/tree/TreeNodeMenu.tsx +2 -2
- package/src/components/tree/renderTreeCanvas.ts +1 -1
- package/src/constants.ts +27 -0
- package/src/layout.ts +1 -6
- package/src/model/msaModel.ts +4 -2
- package/src/model/treeModel.ts +19 -8
- package/src/model.ts +496 -140
- package/src/neighborJoining.test.ts +129 -0
- package/src/neighborJoining.ts +885 -0
- package/src/parsers/A3mMSA.test.ts +164 -0
- package/src/parsers/A3mMSA.ts +321 -0
- package/src/parsers/ClustalMSA.ts +7 -5
- package/src/parsers/FastaMSA.ts +17 -17
- package/src/renderToSvg.tsx +105 -26
- package/src/rowCoordinateCalculations.ts +2 -0
- package/src/types.ts +2 -4
- package/src/util.ts +21 -8
- package/src/version.ts +1 -1
- package/dist/components/dialogs/TracklistDialog.d.ts +0 -7
- package/dist/components/dialogs/TracklistDialog.js +0 -23
- package/dist/components/dialogs/TracklistDialog.js.map +0 -1
- package/src/components/dialogs/TracklistDialog.tsx +0 -73
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { observer } from 'mobx-react'
|
|
4
|
+
|
|
5
|
+
import type { MsaViewModel } from '../model'
|
|
6
|
+
import type { BasicTrack } from '../types'
|
|
7
|
+
|
|
8
|
+
const ConservationBlock = observer(function ({
|
|
9
|
+
model,
|
|
10
|
+
offsetX,
|
|
11
|
+
trackHeight,
|
|
12
|
+
}: {
|
|
13
|
+
model: MsaViewModel
|
|
14
|
+
offsetX: number
|
|
15
|
+
trackHeight: number
|
|
16
|
+
}) {
|
|
17
|
+
const { blockSize, scrollX, colWidth, highResScaleFactor, conservation } =
|
|
18
|
+
model
|
|
19
|
+
|
|
20
|
+
const ref = useRef<HTMLCanvasElement>(null)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!ref.current) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ctx = ref.current.getContext('2d')
|
|
28
|
+
if (!ctx) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ctx.resetTransform()
|
|
33
|
+
ctx.scale(highResScaleFactor, highResScaleFactor)
|
|
34
|
+
ctx.clearRect(0, 0, blockSize, trackHeight)
|
|
35
|
+
ctx.translate(-offsetX, 0)
|
|
36
|
+
|
|
37
|
+
const xStart = Math.max(0, Math.floor(offsetX / colWidth))
|
|
38
|
+
const xEnd = Math.max(0, Math.ceil((offsetX + blockSize) / colWidth))
|
|
39
|
+
|
|
40
|
+
for (let i = xStart; i < xEnd && i < conservation.length; i++) {
|
|
41
|
+
const value = conservation[i]!
|
|
42
|
+
const barHeight = value * trackHeight
|
|
43
|
+
const x = i * colWidth
|
|
44
|
+
|
|
45
|
+
const hue = value * 120
|
|
46
|
+
ctx.fillStyle = `hsl(${hue}, 70%, 50%)`
|
|
47
|
+
ctx.fillRect(x, trackHeight - barHeight, colWidth, barHeight)
|
|
48
|
+
}
|
|
49
|
+
}, [
|
|
50
|
+
blockSize,
|
|
51
|
+
colWidth,
|
|
52
|
+
trackHeight,
|
|
53
|
+
offsetX,
|
|
54
|
+
highResScaleFactor,
|
|
55
|
+
conservation,
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<canvas
|
|
60
|
+
ref={ref}
|
|
61
|
+
height={trackHeight * highResScaleFactor}
|
|
62
|
+
width={blockSize * highResScaleFactor}
|
|
63
|
+
style={{
|
|
64
|
+
position: 'absolute',
|
|
65
|
+
left: scrollX + offsetX,
|
|
66
|
+
width: blockSize,
|
|
67
|
+
height: trackHeight,
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const ConservationTrack = observer(function ({
|
|
74
|
+
model,
|
|
75
|
+
track,
|
|
76
|
+
}: {
|
|
77
|
+
model: MsaViewModel
|
|
78
|
+
track: BasicTrack
|
|
79
|
+
}) {
|
|
80
|
+
const { blocksX, msaAreaWidth } = model
|
|
81
|
+
const trackHeight = track.model.height
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
style={{
|
|
86
|
+
position: 'relative',
|
|
87
|
+
height: trackHeight,
|
|
88
|
+
width: msaAreaWidth,
|
|
89
|
+
overflow: 'hidden',
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{blocksX.map(bx => (
|
|
93
|
+
<ConservationBlock
|
|
94
|
+
key={bx}
|
|
95
|
+
model={model}
|
|
96
|
+
offsetX={bx}
|
|
97
|
+
trackHeight={trackHeight}
|
|
98
|
+
/>
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
export default ConservationTrack
|
|
@@ -10,6 +10,45 @@ import ImportForm from './import/ImportForm'
|
|
|
10
10
|
|
|
11
11
|
import type { MsaViewModel } from '../model'
|
|
12
12
|
|
|
13
|
+
function LoadingSpinner() {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 20 }}
|
|
17
|
+
>
|
|
18
|
+
<svg
|
|
19
|
+
width="24"
|
|
20
|
+
height="24"
|
|
21
|
+
viewBox="0 0 24 24"
|
|
22
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
23
|
+
>
|
|
24
|
+
<style>
|
|
25
|
+
{`@keyframes spinner { to { transform: rotate(360deg); } }`}
|
|
26
|
+
</style>
|
|
27
|
+
<circle
|
|
28
|
+
cx="12"
|
|
29
|
+
cy="12"
|
|
30
|
+
r="10"
|
|
31
|
+
stroke="#ccc"
|
|
32
|
+
strokeWidth="3"
|
|
33
|
+
fill="none"
|
|
34
|
+
/>
|
|
35
|
+
<path
|
|
36
|
+
d="M12 2a10 10 0 0 1 10 10"
|
|
37
|
+
stroke="#1976d2"
|
|
38
|
+
strokeWidth="3"
|
|
39
|
+
fill="none"
|
|
40
|
+
strokeLinecap="round"
|
|
41
|
+
style={{
|
|
42
|
+
animation: 'spinner 1s linear infinite',
|
|
43
|
+
transformOrigin: 'center',
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
</svg>
|
|
47
|
+
<Typography variant="h6">Loading...</Typography>
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
13
52
|
const Reset = observer(function ({
|
|
14
53
|
model,
|
|
15
54
|
error,
|
|
@@ -34,7 +73,8 @@ const Reset = observer(function ({
|
|
|
34
73
|
})
|
|
35
74
|
|
|
36
75
|
const Loading = observer(function ({ model }: { model: MsaViewModel }) {
|
|
37
|
-
const { isLoading, dataInitialized } = model
|
|
76
|
+
const { isLoading, dataInitialized, msaFilehandle, treeFilehandle } = model
|
|
77
|
+
const hasPendingFilehandle = !!(msaFilehandle || treeFilehandle)
|
|
38
78
|
|
|
39
79
|
return (
|
|
40
80
|
<div>
|
|
@@ -43,10 +83,12 @@ const Loading = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
43
83
|
>
|
|
44
84
|
{dataInitialized ? (
|
|
45
85
|
isLoading ? (
|
|
46
|
-
<
|
|
86
|
+
<LoadingSpinner />
|
|
47
87
|
) : (
|
|
48
88
|
<MSAView model={model} />
|
|
49
89
|
)
|
|
90
|
+
) : hasPendingFilehandle || isLoading ? (
|
|
91
|
+
<LoadingSpinner />
|
|
50
92
|
) : (
|
|
51
93
|
<ImportForm model={model} />
|
|
52
94
|
)}
|
|
@@ -3,6 +3,7 @@ import React, { Suspense } from 'react'
|
|
|
3
3
|
import { observer } from 'mobx-react'
|
|
4
4
|
|
|
5
5
|
import { HorizontalResizeHandle, VerticalResizeHandle } from './ResizeHandles'
|
|
6
|
+
import Track from './Track'
|
|
6
7
|
import VerticalScrollbar from './VerticalScrollbar'
|
|
7
8
|
import Header from './header/Header'
|
|
8
9
|
import Minimap from './minimap/Minimap'
|
|
@@ -22,6 +23,72 @@ const TopArea = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
22
23
|
)
|
|
23
24
|
})
|
|
24
25
|
|
|
26
|
+
const TrackColumnIndicator = observer(function ({
|
|
27
|
+
model,
|
|
28
|
+
}: {
|
|
29
|
+
model: MsaViewModel
|
|
30
|
+
}) {
|
|
31
|
+
const {
|
|
32
|
+
mouseCol,
|
|
33
|
+
mouseClickCol,
|
|
34
|
+
colWidth,
|
|
35
|
+
scrollX,
|
|
36
|
+
treeAreaWidth,
|
|
37
|
+
resizeHandleWidth,
|
|
38
|
+
totalTrackAreaHeight,
|
|
39
|
+
} = model
|
|
40
|
+
|
|
41
|
+
const left = treeAreaWidth + resizeHandleWidth
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
{mouseCol !== undefined ? (
|
|
46
|
+
<div
|
|
47
|
+
style={{
|
|
48
|
+
position: 'absolute',
|
|
49
|
+
left: left + mouseCol * colWidth + scrollX,
|
|
50
|
+
top: 0,
|
|
51
|
+
width: colWidth,
|
|
52
|
+
height: totalTrackAreaHeight,
|
|
53
|
+
backgroundColor: 'rgba(0,0,0,0.15)',
|
|
54
|
+
pointerEvents: 'none',
|
|
55
|
+
zIndex: 100,
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
) : null}
|
|
59
|
+
{mouseClickCol !== undefined ? (
|
|
60
|
+
<div
|
|
61
|
+
style={{
|
|
62
|
+
position: 'absolute',
|
|
63
|
+
left: left + mouseClickCol * colWidth + scrollX,
|
|
64
|
+
top: 0,
|
|
65
|
+
width: colWidth,
|
|
66
|
+
height: totalTrackAreaHeight,
|
|
67
|
+
backgroundColor: 'rgba(128,128,0,0.2)',
|
|
68
|
+
pointerEvents: 'none',
|
|
69
|
+
zIndex: 100,
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
) : null}
|
|
73
|
+
</>
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const TrackArea = observer(function ({ model }: { model: MsaViewModel }) {
|
|
78
|
+
const { turnedOnTracks } = model
|
|
79
|
+
if (turnedOnTracks.length === 0) {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
return (
|
|
83
|
+
<div style={{ position: 'relative' }}>
|
|
84
|
+
<TrackColumnIndicator model={model} />
|
|
85
|
+
{turnedOnTracks.map(track => (
|
|
86
|
+
<Track key={track.model.id} model={model} track={track} />
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
25
92
|
const MainArea = observer(function ({ model }: { model: MsaViewModel }) {
|
|
26
93
|
const { showVerticalScrollbar } = model
|
|
27
94
|
|
|
@@ -39,6 +106,7 @@ const View = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
39
106
|
return (
|
|
40
107
|
<div style={{ position: 'relative' }}>
|
|
41
108
|
<TopArea model={model} />
|
|
109
|
+
<TrackArea model={model} />
|
|
42
110
|
<MainArea model={model} />
|
|
43
111
|
</div>
|
|
44
112
|
)
|
|
@@ -24,10 +24,11 @@ export default function SequenceTextArea({ str }: { str: [string, string][] }) {
|
|
|
24
24
|
const [showGaps, setShowGaps] = useState(false)
|
|
25
25
|
const [showEmpty, setShowEmpty] = useState(false)
|
|
26
26
|
|
|
27
|
+
const removeGaps = (s: string) => s.replaceAll('-', '').replaceAll('.', '')
|
|
27
28
|
const disp = str
|
|
28
|
-
.map(([s1, s2]) => [s1, showGaps ? s2 : s2
|
|
29
|
+
.map(([s1, s2]) => [s1, showGaps ? s2 : removeGaps(s2)] as const)
|
|
29
30
|
.filter(f => (showEmpty ? true : !!f[1]))
|
|
30
|
-
.map(([s1, s2]) => `>${s1}\n${showGaps ? s2 : s2
|
|
31
|
+
.map(([s1, s2]) => `>${s1}\n${showGaps ? s2 : removeGaps(s2)}`)
|
|
31
32
|
.join('\n')
|
|
32
33
|
return (
|
|
33
34
|
<>
|
|
@@ -6,14 +6,14 @@ import { observer } from 'mobx-react'
|
|
|
6
6
|
import { colorContrast } from '../util'
|
|
7
7
|
|
|
8
8
|
import type { MsaViewModel } from '../model'
|
|
9
|
-
import type {
|
|
9
|
+
import type { BasicTrack } from '../types'
|
|
10
10
|
|
|
11
11
|
const AnnotationBlock = observer(function ({
|
|
12
12
|
track,
|
|
13
13
|
model,
|
|
14
14
|
offsetX,
|
|
15
15
|
}: {
|
|
16
|
-
track:
|
|
16
|
+
track: BasicTrack
|
|
17
17
|
model: MsaViewModel
|
|
18
18
|
offsetX: number
|
|
19
19
|
}) {
|
|
@@ -58,7 +58,7 @@ const AnnotationBlock = observer(function ({
|
|
|
58
58
|
|
|
59
59
|
const xStart = Math.max(0, Math.floor(offsetX / colWidth))
|
|
60
60
|
const xEnd = Math.max(0, Math.ceil((offsetX + blockSize) / colWidth))
|
|
61
|
-
const str = data
|
|
61
|
+
const str = data?.slice(xStart, xEnd)
|
|
62
62
|
for (let i = 0; str && i < str.length; i++) {
|
|
63
63
|
const letter = str[i]!
|
|
64
64
|
const color = colorScheme[letter.toUpperCase()]
|
|
@@ -102,10 +102,13 @@ const AnnotationTrack = observer(function ({
|
|
|
102
102
|
track,
|
|
103
103
|
model,
|
|
104
104
|
}: {
|
|
105
|
-
track:
|
|
105
|
+
track: BasicTrack
|
|
106
106
|
model: MsaViewModel
|
|
107
107
|
}) {
|
|
108
108
|
const { blocksX, msaAreaWidth, rowHeight } = model
|
|
109
|
+
if (!track.model.data) {
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
109
112
|
return (
|
|
110
113
|
<div
|
|
111
114
|
style={{
|
package/src/components/Track.tsx
CHANGED
|
@@ -3,7 +3,6 @@ import React, { lazy, useEffect, useRef, useState } from 'react'
|
|
|
3
3
|
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
|
|
4
4
|
import { IconButton, Menu, MenuItem } from '@mui/material'
|
|
5
5
|
import { observer } from 'mobx-react'
|
|
6
|
-
import normalizeWheel from 'normalize-wheel'
|
|
7
6
|
import { makeStyles } from 'tss-react/mui'
|
|
8
7
|
|
|
9
8
|
import type { MsaViewModel } from '../model'
|
|
@@ -28,8 +27,7 @@ export const TrackLabel = observer(function ({
|
|
|
28
27
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>()
|
|
29
28
|
const { drawLabels, rowHeight, treeAreaWidth: width } = model
|
|
30
29
|
const {
|
|
31
|
-
height,
|
|
32
|
-
model: { name },
|
|
30
|
+
model: { name, height },
|
|
33
31
|
} = track
|
|
34
32
|
const { classes } = useStyles()
|
|
35
33
|
const trackLabelHeight = Math.max(8, rowHeight - 8)
|
|
@@ -101,7 +99,7 @@ const Track = observer(function ({
|
|
|
101
99
|
|
|
102
100
|
track: any
|
|
103
101
|
}) {
|
|
104
|
-
const { resizeHandleWidth } = model
|
|
102
|
+
const { resizeHandleWidth, colWidth, scrollX, numColumns } = model
|
|
105
103
|
const {
|
|
106
104
|
model: { height, error },
|
|
107
105
|
} = track
|
|
@@ -113,9 +111,8 @@ const Track = observer(function ({
|
|
|
113
111
|
if (!curr) {
|
|
114
112
|
return
|
|
115
113
|
}
|
|
116
|
-
function onWheel(
|
|
117
|
-
|
|
118
|
-
deltaX.current += event.pixelX
|
|
114
|
+
function onWheel(event: WheelEvent) {
|
|
115
|
+
deltaX.current += event.deltaX
|
|
119
116
|
|
|
120
117
|
if (!scheduled.current) {
|
|
121
118
|
scheduled.current = true
|
|
@@ -125,18 +122,37 @@ const Track = observer(function ({
|
|
|
125
122
|
scheduled.current = false
|
|
126
123
|
})
|
|
127
124
|
}
|
|
128
|
-
|
|
125
|
+
event.preventDefault()
|
|
129
126
|
}
|
|
130
127
|
curr.addEventListener('wheel', onWheel)
|
|
131
128
|
return () => {
|
|
132
129
|
curr.removeEventListener('wheel', onWheel)
|
|
133
130
|
}
|
|
134
131
|
}, [model])
|
|
132
|
+
|
|
135
133
|
return (
|
|
136
134
|
<div key={track.id} style={{ display: 'flex', height }}>
|
|
137
135
|
<TrackLabel model={model} track={track} />
|
|
138
136
|
<div style={{ width: resizeHandleWidth, flexShrink: 0 }} />
|
|
139
|
-
<div
|
|
137
|
+
<div
|
|
138
|
+
ref={ref}
|
|
139
|
+
onMouseMove={event => {
|
|
140
|
+
if (!ref.current) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
const { left } = ref.current.getBoundingClientRect()
|
|
144
|
+
const mouseX = event.clientX - left - scrollX
|
|
145
|
+
const col = Math.floor(mouseX / colWidth)
|
|
146
|
+
if (col >= 0 && col < numColumns) {
|
|
147
|
+
model.setMousePos(col, undefined)
|
|
148
|
+
} else {
|
|
149
|
+
model.setMousePos(undefined, undefined)
|
|
150
|
+
}
|
|
151
|
+
}}
|
|
152
|
+
onMouseLeave={() => {
|
|
153
|
+
model.setMousePos(undefined, undefined)
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
140
156
|
{error ? (
|
|
141
157
|
<div style={{ color: 'red', fontSize: 10 }}>{`${error}`}</div>
|
|
142
158
|
) : (
|
|
@@ -2,6 +2,7 @@ import React, { useState } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { Dialog, ErrorMessage } from '@jbrowse/core/ui'
|
|
4
4
|
import {
|
|
5
|
+
Alert,
|
|
5
6
|
Button,
|
|
6
7
|
DialogActions,
|
|
7
8
|
DialogContent,
|
|
@@ -26,9 +27,16 @@ export default function ExportSVGDialog({
|
|
|
26
27
|
onClose: () => void
|
|
27
28
|
}) {
|
|
28
29
|
const [includeMinimap, setIncludeMinimap] = useState(true)
|
|
30
|
+
const [includeTracks, setIncludeTracks] = useState(true)
|
|
29
31
|
const [exportType, setExportType] = useState('viewport')
|
|
30
32
|
const [error, setError] = useState<unknown>()
|
|
31
33
|
const theme = useTheme()
|
|
34
|
+
const { totalWidth, totalHeight, treeAreaWidth, turnedOnTracks } = model
|
|
35
|
+
const hasTracks = turnedOnTracks.length > 0
|
|
36
|
+
const entireWidth = totalWidth + treeAreaWidth
|
|
37
|
+
const entireHeight = totalHeight
|
|
38
|
+
const isLargeExport =
|
|
39
|
+
exportType === 'entire' && (entireWidth > 10000 || entireHeight > 10000)
|
|
32
40
|
return (
|
|
33
41
|
<Dialog
|
|
34
42
|
onClose={() => {
|
|
@@ -48,6 +56,15 @@ export default function ExportSVGDialog({
|
|
|
48
56
|
setIncludeMinimap(!includeMinimap)
|
|
49
57
|
}}
|
|
50
58
|
/>
|
|
59
|
+
{hasTracks ? (
|
|
60
|
+
<Checkbox2
|
|
61
|
+
label="Include tracks?"
|
|
62
|
+
checked={includeTracks}
|
|
63
|
+
onChange={() => {
|
|
64
|
+
setIncludeTracks(!includeTracks)
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
) : null}
|
|
51
68
|
<div>
|
|
52
69
|
<FormControl>
|
|
53
70
|
<FormLabel>Export type</FormLabel>
|
|
@@ -70,6 +87,12 @@ export default function ExportSVGDialog({
|
|
|
70
87
|
</RadioGroup>
|
|
71
88
|
</FormControl>
|
|
72
89
|
</div>
|
|
90
|
+
{isLargeExport ? (
|
|
91
|
+
<Alert severity="warning" style={{ marginTop: 8 }}>
|
|
92
|
+
The entire MSA is very large ({Math.round(entireWidth)}x
|
|
93
|
+
{Math.round(entireHeight)} pixels). Export may be slow or fail.
|
|
94
|
+
</Alert>
|
|
95
|
+
) : null}
|
|
73
96
|
</DialogContent>
|
|
74
97
|
<DialogActions>
|
|
75
98
|
<Button
|
|
@@ -83,13 +106,14 @@ export default function ExportSVGDialog({
|
|
|
83
106
|
theme,
|
|
84
107
|
includeMinimap:
|
|
85
108
|
exportType === 'entire' ? false : includeMinimap,
|
|
109
|
+
includeTracks: hasTracks && includeTracks,
|
|
86
110
|
exportType,
|
|
87
111
|
})
|
|
112
|
+
onClose()
|
|
88
113
|
} catch (e) {
|
|
89
114
|
console.error(e)
|
|
90
115
|
setError(e)
|
|
91
116
|
}
|
|
92
|
-
onClose()
|
|
93
117
|
})()
|
|
94
118
|
}}
|
|
95
119
|
>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { Slider, Typography } from '@mui/material'
|
|
4
|
+
import { observer } from 'mobx-react'
|
|
5
|
+
|
|
6
|
+
import type { MsaViewModel } from '../../model'
|
|
7
|
+
|
|
8
|
+
const GappynessSlider = observer(function GappynessSlider({
|
|
9
|
+
model,
|
|
10
|
+
}: {
|
|
11
|
+
model: MsaViewModel
|
|
12
|
+
}) {
|
|
13
|
+
const { hideGaps, allowedGappyness } = model
|
|
14
|
+
if (!hideGaps) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
return (
|
|
18
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
19
|
+
<Typography style={{ whiteSpace: 'nowrap' }}>
|
|
20
|
+
Hide columns w/ >{allowedGappyness}% gaps
|
|
21
|
+
</Typography>
|
|
22
|
+
<Slider
|
|
23
|
+
style={{ width: 100 }}
|
|
24
|
+
min={1}
|
|
25
|
+
max={100}
|
|
26
|
+
value={allowedGappyness}
|
|
27
|
+
onChange={(_, val) => {
|
|
28
|
+
model.setAllowedGappyness(val)
|
|
29
|
+
}}
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export default GappynessSlider
|
|
@@ -5,6 +5,7 @@ import Help from '@mui/icons-material/Help'
|
|
|
5
5
|
import { IconButton } from '@mui/material'
|
|
6
6
|
import { observer } from 'mobx-react'
|
|
7
7
|
|
|
8
|
+
import GappynessSlider from './GappynessSlider'
|
|
8
9
|
import HeaderInfoArea from './HeaderInfoArea'
|
|
9
10
|
import HeaderMenu from './HeaderMenu'
|
|
10
11
|
import HeaderStatusArea from './HeaderStatusArea'
|
|
@@ -30,7 +31,8 @@ const Header = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
30
31
|
<ZoomControls model={model} />
|
|
31
32
|
{model.showZoomStar ? <ZoomStar model={model} /> : null}
|
|
32
33
|
<ZoomMenu model={model} />
|
|
33
|
-
<
|
|
34
|
+
<GappynessSlider model={model} />
|
|
35
|
+
<div style={{ paddingLeft: 20, margin: 'auto' }}>
|
|
34
36
|
<MultiAlignmentSelector model={model} />
|
|
35
37
|
</div>
|
|
36
38
|
<HeaderInfoArea model={model} />
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React, { lazy } from 'react'
|
|
2
2
|
|
|
3
3
|
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
|
|
4
|
+
import AccountTree from '@mui/icons-material/AccountTree'
|
|
4
5
|
import Assignment from '@mui/icons-material/Assignment'
|
|
5
6
|
import FilterAlt from '@mui/icons-material/FilterAlt'
|
|
6
7
|
import FolderOpen from '@mui/icons-material/FolderOpen'
|
|
7
|
-
import List from '@mui/icons-material/List'
|
|
8
8
|
import MoreVert from '@mui/icons-material/Menu'
|
|
9
9
|
import PhotoCamera from '@mui/icons-material/PhotoCamera'
|
|
10
10
|
import Search from '@mui/icons-material/Search'
|
|
@@ -16,7 +16,6 @@ import type { MsaViewModel } from '../../model'
|
|
|
16
16
|
|
|
17
17
|
// lazies
|
|
18
18
|
const MetadataDialog = lazy(() => import('../dialogs/MetadataDialog'))
|
|
19
|
-
const TracklistDialog = lazy(() => import('../dialogs/TracklistDialog'))
|
|
20
19
|
const ExportSVGDialog = lazy(() => import('../dialogs/ExportSVGDialog'))
|
|
21
20
|
const FeatureFilterDialog = lazy(() => import('../dialogs/FeatureDialog'))
|
|
22
21
|
const SettingsDialog = lazy(() => import('../dialogs/SettingsDialog'))
|
|
@@ -26,7 +25,14 @@ const UserProvidedDomainsDialog = lazy(
|
|
|
26
25
|
const InterProScanDialog = lazy(() => import('../dialogs/InterProScanDialog'))
|
|
27
26
|
|
|
28
27
|
const HeaderMenu = observer(({ model }: { model: MsaViewModel }) => {
|
|
29
|
-
const {
|
|
28
|
+
const {
|
|
29
|
+
showDomains,
|
|
30
|
+
actuallyShowDomains,
|
|
31
|
+
subFeatureRows,
|
|
32
|
+
noDomains,
|
|
33
|
+
tracks,
|
|
34
|
+
turnedOffTracks,
|
|
35
|
+
} = model
|
|
30
36
|
return (
|
|
31
37
|
<CascadingMenuButton
|
|
32
38
|
menuItems={[
|
|
@@ -63,19 +69,18 @@ const HeaderMenu = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
63
69
|
},
|
|
64
70
|
},
|
|
65
71
|
{
|
|
66
|
-
label: '
|
|
67
|
-
icon:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
},
|
|
72
|
+
label: 'Tracks',
|
|
73
|
+
icon: Visibility,
|
|
74
|
+
type: 'subMenu',
|
|
75
|
+
subMenu: tracks.map(track => ({
|
|
76
|
+
label: track.model.name,
|
|
77
|
+
type: 'checkbox' as const,
|
|
78
|
+
checked: !turnedOffTracks.has(track.model.id),
|
|
79
|
+
onClick: () => {
|
|
80
|
+
model.toggleTrack(track.model.id)
|
|
81
|
+
},
|
|
82
|
+
})),
|
|
77
83
|
},
|
|
78
|
-
|
|
79
84
|
{
|
|
80
85
|
label: 'Export SVG',
|
|
81
86
|
icon: PhotoCamera,
|
|
@@ -89,6 +94,22 @@ const HeaderMenu = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
89
94
|
])
|
|
90
95
|
},
|
|
91
96
|
},
|
|
97
|
+
...(model.rows.length >= 2
|
|
98
|
+
? [
|
|
99
|
+
{
|
|
100
|
+
label: 'Calculate neighbor joining tree (BLOSUM62)',
|
|
101
|
+
icon: AccountTree,
|
|
102
|
+
onClick: () => {
|
|
103
|
+
try {
|
|
104
|
+
model.calculateNeighborJoiningTreeFromMSA()
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.error('Failed to calculate NJ tree:', e)
|
|
107
|
+
model.setError(e)
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
: []),
|
|
92
113
|
{
|
|
93
114
|
label: 'Features/protein domains',
|
|
94
115
|
type: 'subMenu',
|
|
@@ -21,7 +21,8 @@ const MinimapSVG = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
21
21
|
const right = left + W
|
|
22
22
|
const s = left * unit
|
|
23
23
|
const e = right * unit
|
|
24
|
-
const
|
|
24
|
+
const fillColor = 'rgb(66, 119, 127)'
|
|
25
|
+
const fillOpacity = 0.3
|
|
25
26
|
|
|
26
27
|
return (
|
|
27
28
|
<>
|
|
@@ -38,12 +39,14 @@ const MinimapSVG = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
38
39
|
y={0}
|
|
39
40
|
width={e - s}
|
|
40
41
|
height={BAR_HEIGHT}
|
|
41
|
-
fill={
|
|
42
|
+
fill={fillColor}
|
|
43
|
+
fillOpacity={fillOpacity}
|
|
42
44
|
stroke="#555"
|
|
43
45
|
/>
|
|
44
46
|
<g transform={`translate(0 ${BAR_HEIGHT})`}>
|
|
45
47
|
<polygon
|
|
46
|
-
fill={
|
|
48
|
+
fill={fillColor}
|
|
49
|
+
fillOpacity={fillOpacity}
|
|
47
50
|
points={[
|
|
48
51
|
[e, 0],
|
|
49
52
|
[s, 0],
|