react-msaview 4.4.5 → 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/TreeCanvasBlock.js +1 -1
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- 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 +3 -5
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/rowCoordinateCalculations.test.js +14 -2
- package/dist/rowCoordinateCalculations.test.js.map +1 -1
- package/dist/seqCoordToRowSpecificGlobalCoord.js +9 -5
- package/dist/seqCoordToRowSpecificGlobalCoord.js.map +1 -1
- package/dist/seqCoordToRowSpecificGlobalCoord.test.js +6 -6
- 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/TreeCanvasBlock.tsx +1 -1
- 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.test.ts +15 -2
- package/src/rowCoordinateCalculations.ts +3 -5
- package/src/seqCoordToRowSpecificGlobalCoord.test.ts +6 -6
- package/src/seqCoordToRowSpecificGlobalCoord.ts +9 -4
- 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
|
@@ -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],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useRef } from 'react'
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
|
|
3
|
+
import { BaseTooltip } from '@jbrowse/core/ui'
|
|
3
4
|
import { useTheme } from '@mui/material'
|
|
4
5
|
import { autorun } from 'mobx'
|
|
5
6
|
import { observer } from 'mobx-react'
|
|
@@ -79,55 +80,72 @@ const MSACanvasBlock = observer(function ({
|
|
|
79
80
|
contrastScheme,
|
|
80
81
|
])
|
|
81
82
|
|
|
83
|
+
const [mousePosition, setMousePosition] = useState<{ x: number; y: number }>()
|
|
84
|
+
const { hoveredInsertion } = model
|
|
85
|
+
|
|
82
86
|
return (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
<>
|
|
88
|
+
<canvas
|
|
89
|
+
ref={ref}
|
|
90
|
+
onMouseMove={event => {
|
|
91
|
+
if (!ref.current) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
setMousePosition({ x: event.clientX, y: event.clientY })
|
|
95
|
+
const { left, top } = ref.current.getBoundingClientRect()
|
|
96
|
+
const mouseX = event.clientX - left + offsetX
|
|
97
|
+
const mouseY = event.clientY - top + offsetY
|
|
98
|
+
const x = Math.floor(mouseX / colWidth)
|
|
99
|
+
const y = Math.floor(mouseY / rowHeight)
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
101
|
+
// Only set mouse position if within valid MSA bounds
|
|
102
|
+
if (x >= 0 && x < model.numColumns && y >= 0 && y < model.numRows) {
|
|
103
|
+
model.setMousePos(x, y)
|
|
104
|
+
} else {
|
|
105
|
+
// Clear mouse position when outside bounds
|
|
106
|
+
model.setMousePos(undefined, undefined)
|
|
107
|
+
}
|
|
108
|
+
}}
|
|
109
|
+
onClick={event => {
|
|
110
|
+
if (!ref.current) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
const { left, top } = ref.current.getBoundingClientRect()
|
|
114
|
+
const mouseX = event.clientX - left + offsetX
|
|
115
|
+
const mouseY = event.clientY - top + offsetY
|
|
116
|
+
const x = Math.floor(mouseX / colWidth)
|
|
117
|
+
const y = Math.floor(mouseY / rowHeight)
|
|
118
|
+
if (x === mouseClickCol && y === mouseClickRow) {
|
|
119
|
+
model.setMouseClickPos(undefined, undefined)
|
|
120
|
+
} else {
|
|
121
|
+
model.setMouseClickPos(x, y)
|
|
122
|
+
}
|
|
123
|
+
}}
|
|
124
|
+
onMouseLeave={() => {
|
|
125
|
+
model.setMousePos()
|
|
126
|
+
setMousePosition(undefined)
|
|
127
|
+
}}
|
|
128
|
+
width={blockSize * highResScaleFactor}
|
|
129
|
+
height={blockSize * highResScaleFactor}
|
|
130
|
+
style={{
|
|
131
|
+
position: 'absolute',
|
|
132
|
+
top: scrollY + offsetY,
|
|
133
|
+
left: scrollX + offsetX,
|
|
134
|
+
width: blockSize,
|
|
135
|
+
height: blockSize,
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
{hoveredInsertion && mousePosition ? (
|
|
139
|
+
<BaseTooltip
|
|
140
|
+
clientPoint={{ x: mousePosition.x, y: mousePosition.y + 15 }}
|
|
141
|
+
>
|
|
142
|
+
Insertion ({hoveredInsertion.letters.length}bp):{' '}
|
|
143
|
+
{hoveredInsertion.letters.length > 20
|
|
144
|
+
? `${hoveredInsertion.letters.slice(0, 20)}...`
|
|
145
|
+
: hoveredInsertion.letters}
|
|
146
|
+
</BaseTooltip>
|
|
147
|
+
) : null}
|
|
148
|
+
</>
|
|
131
149
|
)
|
|
132
150
|
})
|
|
133
151
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { getClustalXColor, getPercentIdentityColor } from '../../colorSchemes'
|
|
2
|
-
|
|
3
1
|
import type { MsaViewModel } from '../../model'
|
|
4
2
|
import type { NodeWithIdsAndLength } from '../../types'
|
|
5
3
|
import type { Theme } from '@mui/material'
|
|
@@ -57,7 +55,6 @@ export function renderMSABlock({
|
|
|
57
55
|
ctx,
|
|
58
56
|
theme,
|
|
59
57
|
offsetX,
|
|
60
|
-
offsetY,
|
|
61
58
|
xStart,
|
|
62
59
|
xEnd,
|
|
63
60
|
visibleLeaves,
|
|
@@ -73,6 +70,13 @@ export function renderMSABlock({
|
|
|
73
70
|
xEnd,
|
|
74
71
|
visibleLeaves,
|
|
75
72
|
})
|
|
73
|
+
drawInsertionIndicators({
|
|
74
|
+
model,
|
|
75
|
+
ctx,
|
|
76
|
+
xStart,
|
|
77
|
+
xEnd,
|
|
78
|
+
visibleLeaves,
|
|
79
|
+
})
|
|
76
80
|
ctx.resetTransform()
|
|
77
81
|
}
|
|
78
82
|
|
|
@@ -88,7 +92,6 @@ function drawTiles({
|
|
|
88
92
|
model: MsaViewModel
|
|
89
93
|
offsetX: number
|
|
90
94
|
theme: Theme
|
|
91
|
-
offsetY: number
|
|
92
95
|
ctx: CanvasRenderingContext2D
|
|
93
96
|
visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
|
|
94
97
|
xStart: number
|
|
@@ -127,25 +130,12 @@ function drawTiles({
|
|
|
127
130
|
const r1 = colorSchemeName === 'clustalx_protein_dynamic'
|
|
128
131
|
const r2 = colorSchemeName === 'percent_identity_dynamic'
|
|
129
132
|
const color = r1
|
|
130
|
-
?
|
|
131
|
-
// use model.colStats dot notation here: delay use of colStats
|
|
132
|
-
// until absolutely needed
|
|
133
|
-
model.colStats[xStart + i]!,
|
|
134
|
-
model.colStatsSums[xStart + i]!,
|
|
135
|
-
model,
|
|
136
|
-
name,
|
|
137
|
-
xStart + i,
|
|
138
|
-
)
|
|
133
|
+
? model.colClustalX[xStart + i]![letter]
|
|
139
134
|
: r2
|
|
140
|
-
?
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
model.colStatsSums[xStart + i]!,
|
|
145
|
-
model,
|
|
146
|
-
name,
|
|
147
|
-
xStart + i,
|
|
148
|
-
)
|
|
135
|
+
? (() => {
|
|
136
|
+
const consensus = model.colConsensus[xStart + i]!
|
|
137
|
+
return letter === consensus.letter ? consensus.color : undefined
|
|
138
|
+
})()
|
|
149
139
|
: colorScheme[letter.toUpperCase()]
|
|
150
140
|
if (bgColor || r1 || r2) {
|
|
151
141
|
// Use a very light background for matching positions in relative mode
|
|
@@ -237,3 +227,73 @@ function drawText({
|
|
|
237
227
|
}
|
|
238
228
|
}
|
|
239
229
|
}
|
|
230
|
+
|
|
231
|
+
function drawInsertionIndicators({
|
|
232
|
+
model,
|
|
233
|
+
ctx,
|
|
234
|
+
visibleLeaves,
|
|
235
|
+
xStart,
|
|
236
|
+
xEnd,
|
|
237
|
+
}: {
|
|
238
|
+
model: MsaViewModel
|
|
239
|
+
ctx: CanvasRenderingContext2D
|
|
240
|
+
visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
|
|
241
|
+
xStart: number
|
|
242
|
+
xEnd: number
|
|
243
|
+
}) {
|
|
244
|
+
const { bgColor, hideGapsEffective } = model
|
|
245
|
+
if (!hideGapsEffective) {
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
ctx.lineWidth = 1
|
|
250
|
+
ctx.strokeStyle = '#f0f'
|
|
251
|
+
drawZigZag({ visibleLeaves, xStart, ctx, model, xEnd, offset: 0 })
|
|
252
|
+
ctx.strokeStyle = !bgColor ? '#000' : '#fff'
|
|
253
|
+
drawZigZag({ visibleLeaves, xStart, ctx, model, xEnd, offset: -1 })
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function drawZigZag({
|
|
257
|
+
model,
|
|
258
|
+
ctx,
|
|
259
|
+
visibleLeaves,
|
|
260
|
+
xStart,
|
|
261
|
+
xEnd,
|
|
262
|
+
offset,
|
|
263
|
+
}: {
|
|
264
|
+
model: MsaViewModel
|
|
265
|
+
ctx: CanvasRenderingContext2D
|
|
266
|
+
visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
|
|
267
|
+
xStart: number
|
|
268
|
+
xEnd: number
|
|
269
|
+
offset: number
|
|
270
|
+
}) {
|
|
271
|
+
const zigSize = 1
|
|
272
|
+
const { colWidth, rowHeight, insertionPositions } = model
|
|
273
|
+
for (const node of visibleLeaves) {
|
|
274
|
+
const { name } = node.data
|
|
275
|
+
const insertions = insertionPositions.get(name)
|
|
276
|
+
if (insertions) {
|
|
277
|
+
const y = node.x!
|
|
278
|
+
for (const { pos } of insertions) {
|
|
279
|
+
if (pos >= xStart && pos < xEnd) {
|
|
280
|
+
const x = pos * colWidth
|
|
281
|
+
const top = y - rowHeight
|
|
282
|
+
const bottom = y
|
|
283
|
+
ctx.beginPath()
|
|
284
|
+
ctx.moveTo(x + offset, top + offset)
|
|
285
|
+
let currentY = top
|
|
286
|
+
let goRight = true
|
|
287
|
+
while (currentY < bottom) {
|
|
288
|
+
const nextY = Math.min(currentY + zigSize * 2, bottom)
|
|
289
|
+
const nextX = goRight ? x + zigSize : x - zigSize
|
|
290
|
+
ctx.lineTo(nextX + offset, nextY + offset)
|
|
291
|
+
currentY = nextY
|
|
292
|
+
goRight = !goRight
|
|
293
|
+
}
|
|
294
|
+
ctx.stroke()
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { MsaViewModel } from '../../model'
|
|
2
|
+
import type { BasicTrack } from '../../types'
|
|
3
|
+
|
|
4
|
+
export function renderConservationTrack({
|
|
5
|
+
model,
|
|
6
|
+
ctx,
|
|
7
|
+
offsetX,
|
|
8
|
+
offsetY,
|
|
9
|
+
trackHeight,
|
|
10
|
+
blockSizeXOverride,
|
|
11
|
+
highResScaleFactorOverride,
|
|
12
|
+
}: {
|
|
13
|
+
model: MsaViewModel
|
|
14
|
+
ctx: CanvasRenderingContext2D
|
|
15
|
+
offsetX: number
|
|
16
|
+
offsetY: number
|
|
17
|
+
trackHeight: number
|
|
18
|
+
blockSizeXOverride?: number
|
|
19
|
+
highResScaleFactorOverride?: number
|
|
20
|
+
}) {
|
|
21
|
+
const { blockSize, colWidth, highResScaleFactor, conservation } = model
|
|
22
|
+
const bx = blockSizeXOverride ?? blockSize
|
|
23
|
+
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
24
|
+
|
|
25
|
+
ctx.resetTransform()
|
|
26
|
+
ctx.scale(k, k)
|
|
27
|
+
ctx.translate(-offsetX, offsetY)
|
|
28
|
+
|
|
29
|
+
const xStart = Math.max(0, Math.floor(offsetX / colWidth))
|
|
30
|
+
const xEnd = Math.max(0, Math.ceil((offsetX + bx) / colWidth))
|
|
31
|
+
|
|
32
|
+
for (let i = xStart; i < xEnd && i < conservation.length; i++) {
|
|
33
|
+
const value = conservation[i]!
|
|
34
|
+
const barHeight = value * trackHeight
|
|
35
|
+
const x = i * colWidth
|
|
36
|
+
|
|
37
|
+
const hue = value * 120
|
|
38
|
+
ctx.fillStyle = `hsl(${hue}, 70%, 50%)`
|
|
39
|
+
ctx.fillRect(x, trackHeight - barHeight, colWidth, barHeight)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
ctx.resetTransform()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function renderTextTrack({
|
|
46
|
+
model,
|
|
47
|
+
ctx,
|
|
48
|
+
track,
|
|
49
|
+
offsetX,
|
|
50
|
+
offsetY,
|
|
51
|
+
contrastScheme,
|
|
52
|
+
blockSizeXOverride,
|
|
53
|
+
highResScaleFactorOverride,
|
|
54
|
+
}: {
|
|
55
|
+
model: MsaViewModel
|
|
56
|
+
ctx: CanvasRenderingContext2D
|
|
57
|
+
track: BasicTrack
|
|
58
|
+
offsetX: number
|
|
59
|
+
offsetY: number
|
|
60
|
+
contrastScheme: Record<string, string>
|
|
61
|
+
blockSizeXOverride?: number
|
|
62
|
+
highResScaleFactorOverride?: number
|
|
63
|
+
}) {
|
|
64
|
+
const {
|
|
65
|
+
blockSize,
|
|
66
|
+
bgColor,
|
|
67
|
+
colorScheme: modelColorScheme,
|
|
68
|
+
colWidth,
|
|
69
|
+
fontSize,
|
|
70
|
+
rowHeight,
|
|
71
|
+
highResScaleFactor,
|
|
72
|
+
} = model
|
|
73
|
+
|
|
74
|
+
const { customColorScheme, data } = track.model
|
|
75
|
+
const colorScheme = customColorScheme ?? modelColorScheme
|
|
76
|
+
const bx = blockSizeXOverride ?? blockSize
|
|
77
|
+
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
78
|
+
|
|
79
|
+
ctx.resetTransform()
|
|
80
|
+
ctx.scale(k, k)
|
|
81
|
+
ctx.translate(-offsetX, offsetY)
|
|
82
|
+
ctx.textAlign = 'center'
|
|
83
|
+
ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
|
|
84
|
+
|
|
85
|
+
const xStart = Math.max(0, Math.floor(offsetX / colWidth))
|
|
86
|
+
const xEnd = Math.max(0, Math.ceil((offsetX + bx) / colWidth))
|
|
87
|
+
const str = data?.slice(xStart, xEnd)
|
|
88
|
+
|
|
89
|
+
for (let i = 0; str && i < str.length; i++) {
|
|
90
|
+
const letter = str[i]!
|
|
91
|
+
const color = colorScheme[letter.toUpperCase()]
|
|
92
|
+
const x = i * colWidth + offsetX - (offsetX % colWidth)
|
|
93
|
+
|
|
94
|
+
if (bgColor && color) {
|
|
95
|
+
ctx.fillStyle = color
|
|
96
|
+
ctx.fillRect(x, 0, colWidth, rowHeight)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (rowHeight >= 10 && colWidth >= rowHeight / 2) {
|
|
100
|
+
ctx.fillStyle =
|
|
101
|
+
bgColor && color
|
|
102
|
+
? (contrastScheme[letter.toUpperCase()] ?? 'black')
|
|
103
|
+
: 'black'
|
|
104
|
+
ctx.fillText(letter, x + colWidth / 2, rowHeight / 2 + 1)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ctx.resetTransform()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function renderAllTracks({
|
|
112
|
+
model,
|
|
113
|
+
ctx,
|
|
114
|
+
offsetX,
|
|
115
|
+
contrastScheme,
|
|
116
|
+
blockSizeXOverride,
|
|
117
|
+
highResScaleFactorOverride,
|
|
118
|
+
}: {
|
|
119
|
+
model: MsaViewModel
|
|
120
|
+
ctx: CanvasRenderingContext2D
|
|
121
|
+
offsetX: number
|
|
122
|
+
contrastScheme: Record<string, string>
|
|
123
|
+
blockSizeXOverride?: number
|
|
124
|
+
highResScaleFactorOverride?: number
|
|
125
|
+
}) {
|
|
126
|
+
const { turnedOnTracks } = model
|
|
127
|
+
let currentY = 0
|
|
128
|
+
|
|
129
|
+
for (const track of turnedOnTracks) {
|
|
130
|
+
const trackHeight = track.model.height
|
|
131
|
+
|
|
132
|
+
if (track.model.id === 'conservation') {
|
|
133
|
+
renderConservationTrack({
|
|
134
|
+
model,
|
|
135
|
+
ctx,
|
|
136
|
+
offsetX,
|
|
137
|
+
offsetY: currentY,
|
|
138
|
+
trackHeight,
|
|
139
|
+
blockSizeXOverride,
|
|
140
|
+
highResScaleFactorOverride,
|
|
141
|
+
})
|
|
142
|
+
} else {
|
|
143
|
+
renderTextTrack({
|
|
144
|
+
model,
|
|
145
|
+
ctx,
|
|
146
|
+
track,
|
|
147
|
+
offsetX,
|
|
148
|
+
offsetY: currentY,
|
|
149
|
+
contrastScheme,
|
|
150
|
+
blockSizeXOverride,
|
|
151
|
+
highResScaleFactorOverride,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
currentY += trackHeight
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { useTheme } from '@mui/material'
|
|
4
|
+
import Flatbush from 'flatbush'
|
|
4
5
|
import { autorun } from 'mobx'
|
|
5
6
|
import { observer } from 'mobx-react'
|
|
6
|
-
import Flatbush from 'flatbush'
|
|
7
7
|
|
|
8
8
|
import TreeBranchMenu from './TreeBranchMenu'
|
|
9
9
|
import TreeNodeMenu from './TreeNodeMenu'
|
|
@@ -64,9 +64,9 @@ const TreeMenu = observer(function ({
|
|
|
64
64
|
model.toggleCollapsed(node.id)
|
|
65
65
|
} else {
|
|
66
66
|
if (node.id.endsWith('-leafnode')) {
|
|
67
|
-
model.
|
|
67
|
+
model.toggleCollapsedLeaf(node.id)
|
|
68
68
|
} else {
|
|
69
|
-
model.
|
|
69
|
+
model.toggleCollapsedLeaf(`${node.id}-leafnode`)
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
onClose()
|
|
@@ -107,7 +107,7 @@ export function renderNodeBubbles({
|
|
|
107
107
|
// @ts-expect-error
|
|
108
108
|
const { [val]: x, data } = node
|
|
109
109
|
const y = node.x!
|
|
110
|
-
const { id
|
|
110
|
+
const { id, name } = data
|
|
111
111
|
if (
|
|
112
112
|
node.height > 1 &&
|
|
113
113
|
y > offsetY - extendBounds &&
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Main model defaults
|
|
2
|
+
export const defaultRowHeight = 16
|
|
3
|
+
export const defaultColWidth = 12
|
|
4
|
+
export const defaultHeight = 550
|
|
5
|
+
export const defaultScrollX = 0
|
|
6
|
+
export const defaultScrollY = 0
|
|
7
|
+
export const defaultCurrentAlignment = 0
|
|
8
|
+
export const defaultShowDomains = false
|
|
9
|
+
export const defaultHideGaps = true
|
|
10
|
+
export const defaultAllowedGappyness = 100
|
|
11
|
+
export const defaultContrastLettering = true
|
|
12
|
+
export const defaultSubFeatureRows = false
|
|
13
|
+
export const defaultDrawMsaLetters = true
|
|
14
|
+
|
|
15
|
+
// MSA model defaults
|
|
16
|
+
export const defaultBgColor = true
|
|
17
|
+
export const defaultColorSchemeName = 'maeditor'
|
|
18
|
+
|
|
19
|
+
// Tree model defaults
|
|
20
|
+
export const defaultDrawLabels = true
|
|
21
|
+
export const defaultLabelsAlignRight = false
|
|
22
|
+
export const defaultTreeAreaWidth = 400
|
|
23
|
+
export const defaultTreeWidth = 300
|
|
24
|
+
export const defaultTreeWidthMatchesArea = true
|
|
25
|
+
export const defaultShowBranchLen = true
|
|
26
|
+
export const defaultDrawTree = true
|
|
27
|
+
export const defaultDrawNodeBubbles = true
|
package/src/layout.ts
CHANGED
|
@@ -63,12 +63,7 @@ export default class Layout {
|
|
|
63
63
|
return false
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
const results = this.flatbush.search(
|
|
67
|
-
box.minX,
|
|
68
|
-
box.minY,
|
|
69
|
-
box.maxX,
|
|
70
|
-
box.maxY,
|
|
71
|
-
)
|
|
66
|
+
const results = this.flatbush.search(box.minX, box.minY, box.maxX, box.maxY)
|
|
72
67
|
|
|
73
68
|
return results.length > 0
|
|
74
69
|
}
|
package/src/model/msaModel.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { types } from 'mobx-state-tree'
|
|
2
2
|
|
|
3
|
+
import { defaultBgColor, defaultColorSchemeName } from '../constants'
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* #stateModel MSAModel
|
|
5
7
|
*/
|
|
@@ -12,13 +14,13 @@ export function MSAModelF() {
|
|
|
12
14
|
* #property
|
|
13
15
|
* draw MSA tiles with a background color
|
|
14
16
|
*/
|
|
15
|
-
bgColor:
|
|
17
|
+
bgColor: defaultBgColor,
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* #property
|
|
19
21
|
* default color scheme name
|
|
20
22
|
*/
|
|
21
|
-
colorSchemeName:
|
|
23
|
+
colorSchemeName: defaultColorSchemeName,
|
|
22
24
|
})
|
|
23
25
|
.actions(self => ({
|
|
24
26
|
/**
|