react-msaview 3.1.4 → 3.1.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/bundle/index.js +31 -40
- package/dist/DataModel.d.ts +34 -0
- package/dist/DataModel.js +46 -0
- package/dist/DataModel.js.map +1 -0
- package/dist/SelectedStructuresMixin.d.ts +46 -0
- package/dist/SelectedStructuresMixin.js +52 -0
- package/dist/SelectedStructuresMixin.js.map +1 -0
- package/dist/components/MSAPanel/MSABlock.js +32 -16
- package/dist/components/MSAPanel/MSABlock.js.map +1 -1
- package/dist/components/MSAPanel/renderMSABlock.js +1 -1
- package/dist/components/MSAPanel/renderMSABlock.js.map +1 -1
- package/dist/components/MSAPanel/renderMSAMouseover.js +14 -4
- package/dist/components/MSAPanel/renderMSAMouseover.js.map +1 -1
- package/dist/components/MSAView.js +2 -2
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/TreePanel/TreeNodeMenu.js +65 -55
- package/dist/components/TreePanel/TreeNodeMenu.js.map +1 -1
- package/dist/components/TreePanel/renderTreeCanvas.js +13 -12
- package/dist/components/TreePanel/renderTreeCanvas.js.map +1 -1
- package/dist/components/dialogs/SettingsDialog.js +42 -32
- package/dist/components/dialogs/SettingsDialog.js.map +1 -1
- package/dist/measureTextCanvas.d.ts +1 -1
- package/dist/measureTextCanvas.js +1 -1
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model.d.ts +121 -108
- package/dist/model.js +72 -94
- package/dist/model.js.map +1 -1
- package/dist/parsers/ClustalMSA.d.ts +1 -11
- package/dist/parsers/ClustalMSA.js +1 -15
- package/dist/parsers/ClustalMSA.js.map +1 -1
- package/dist/util.d.ts +0 -1
- package/dist/util.js +0 -7
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -2
- package/src/DataModel.ts +46 -0
- package/src/SelectedStructuresMixin.ts +59 -0
- package/src/components/MSAPanel/MSABlock.tsx +33 -18
- package/src/components/MSAPanel/renderMSABlock.ts +0 -1
- package/src/components/MSAPanel/renderMSAMouseover.ts +16 -3
- package/src/components/MSAView.tsx +3 -3
- package/src/components/TreePanel/TreeNodeMenu.tsx +93 -84
- package/src/components/TreePanel/renderTreeCanvas.ts +19 -9
- package/src/components/dialogs/SettingsDialog.tsx +121 -117
- package/src/measureTextCanvas.ts +1 -1
- package/src/model.ts +88 -110
- package/src/parsers/ClustalMSA.ts +1 -15
- package/src/util.ts +0 -11
- package/src/version.ts +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useMemo } from 'react'
|
|
2
|
-
import { autorun } from 'mobx'
|
|
3
2
|
import { useTheme } from '@mui/material'
|
|
3
|
+
import { autorun } from 'mobx'
|
|
4
4
|
import { observer } from 'mobx-react'
|
|
5
5
|
|
|
6
6
|
// locals
|
|
@@ -24,6 +24,8 @@ const MSABlock = observer(function ({
|
|
|
24
24
|
scrollX,
|
|
25
25
|
colorScheme,
|
|
26
26
|
blockSize,
|
|
27
|
+
mouseClickCol,
|
|
28
|
+
mouseClickRow,
|
|
27
29
|
highResScaleFactor,
|
|
28
30
|
} = model
|
|
29
31
|
const theme = useTheme()
|
|
@@ -36,19 +38,18 @@ const MSABlock = observer(function ({
|
|
|
36
38
|
const ref = useRef<HTMLCanvasElement>(null)
|
|
37
39
|
useEffect(() => {
|
|
38
40
|
const ctx = ref.current?.getContext('2d')
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
})
|
|
41
|
+
return ctx
|
|
42
|
+
? autorun(() => {
|
|
43
|
+
renderMSABlock({
|
|
44
|
+
ctx,
|
|
45
|
+
theme,
|
|
46
|
+
offsetX,
|
|
47
|
+
offsetY,
|
|
48
|
+
contrastScheme,
|
|
49
|
+
model,
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
: undefined
|
|
52
53
|
}, [model, offsetX, offsetY, theme, contrastScheme])
|
|
53
54
|
return (
|
|
54
55
|
<canvas
|
|
@@ -60,10 +61,24 @@ const MSABlock = observer(function ({
|
|
|
60
61
|
const { left, top } = ref.current.getBoundingClientRect()
|
|
61
62
|
const mouseX = event.clientX - left + offsetX
|
|
62
63
|
const mouseY = event.clientY - top + offsetY
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
const x = Math.floor(mouseX / colWidth) + 1
|
|
65
|
+
const y = Math.floor(mouseY / rowHeight)
|
|
66
|
+
model.setMousePos(x, y)
|
|
67
|
+
}}
|
|
68
|
+
onClick={event => {
|
|
69
|
+
if (!ref.current) {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
const { left, top } = ref.current.getBoundingClientRect()
|
|
73
|
+
const mouseX = event.clientX - left + offsetX
|
|
74
|
+
const mouseY = event.clientY - top + offsetY
|
|
75
|
+
const x = Math.floor(mouseX / colWidth) + 1
|
|
76
|
+
const y = Math.floor(mouseY / rowHeight)
|
|
77
|
+
if (x === mouseClickCol && y === mouseClickRow) {
|
|
78
|
+
model.setMouseClickPos(undefined, undefined)
|
|
79
|
+
} else {
|
|
80
|
+
model.setMouseClickPos(x, y)
|
|
81
|
+
}
|
|
67
82
|
}}
|
|
68
83
|
onMouseLeave={() => model.setMousePos()}
|
|
69
84
|
width={blockSize * highResScaleFactor}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { MsaViewModel } from '../../model'
|
|
2
2
|
|
|
3
|
+
const hoverColor = 'rgba(0,0,0,0.15)'
|
|
4
|
+
const highlightColor = 'rgba(128,128,0,0.2)'
|
|
5
|
+
|
|
3
6
|
export function renderMouseover({
|
|
4
7
|
ctx,
|
|
5
8
|
model,
|
|
@@ -18,19 +21,29 @@ export function renderMouseover({
|
|
|
18
21
|
mouseRow,
|
|
19
22
|
// @ts-expect-error
|
|
20
23
|
mouseCol2,
|
|
24
|
+
mouseClickRow,
|
|
25
|
+
mouseClickCol,
|
|
21
26
|
} = model
|
|
22
27
|
ctx.resetTransform()
|
|
23
28
|
ctx.clearRect(0, 0, width, height)
|
|
24
29
|
if (mouseCol !== undefined) {
|
|
25
|
-
ctx.fillStyle =
|
|
30
|
+
ctx.fillStyle = hoverColor
|
|
26
31
|
ctx.fillRect((mouseCol - 1) * colWidth + scrollX, 0, colWidth, height)
|
|
27
32
|
}
|
|
28
33
|
if (mouseRow !== undefined) {
|
|
29
|
-
ctx.fillStyle =
|
|
34
|
+
ctx.fillStyle = hoverColor
|
|
30
35
|
ctx.fillRect(0, mouseRow * rowHeight + scrollY, width, rowHeight)
|
|
31
36
|
}
|
|
37
|
+
if (mouseClickCol !== undefined) {
|
|
38
|
+
ctx.fillStyle = highlightColor
|
|
39
|
+
ctx.fillRect((mouseClickCol - 1) * colWidth + scrollX, 0, colWidth, height)
|
|
40
|
+
}
|
|
41
|
+
if (mouseClickRow !== undefined) {
|
|
42
|
+
ctx.fillStyle = highlightColor
|
|
43
|
+
ctx.fillRect(0, mouseClickRow * rowHeight + scrollY, width, rowHeight)
|
|
44
|
+
}
|
|
32
45
|
if (mouseCol2 !== undefined) {
|
|
33
|
-
ctx.fillStyle =
|
|
46
|
+
ctx.fillStyle = highlightColor
|
|
34
47
|
ctx.fillRect((mouseCol2 - 1) * colWidth + scrollX, 0, colWidth, height)
|
|
35
48
|
}
|
|
36
49
|
}
|
|
@@ -2,11 +2,11 @@ import React, { Suspense } from 'react'
|
|
|
2
2
|
import { observer } from 'mobx-react'
|
|
3
3
|
|
|
4
4
|
// locals
|
|
5
|
+
import { HorizontalResizeHandle, VerticalResizeHandle } from './ResizeHandles'
|
|
6
|
+
import { MsaViewModel } from '../model'
|
|
5
7
|
import TreeRuler from './TreePanel/TreeRuler'
|
|
6
8
|
import Header from './Header'
|
|
7
9
|
import Track from './Track'
|
|
8
|
-
import { HorizontalResizeHandle, VerticalResizeHandle } from './ResizeHandles'
|
|
9
|
-
import { MsaViewModel } from '../model'
|
|
10
10
|
import MSAPanel from './MSAPanel'
|
|
11
11
|
import TreePanel from './TreePanel'
|
|
12
12
|
import Minimap from './Minimap'
|
|
@@ -32,7 +32,7 @@ function MainArea({ model }: { model: MsaViewModel }) {
|
|
|
32
32
|
const View = observer(function ({ model }: { model: MsaViewModel }) {
|
|
33
33
|
const { turnedOnTracks } = model
|
|
34
34
|
return (
|
|
35
|
-
<div style={{
|
|
35
|
+
<div style={{ position: 'relative' }}>
|
|
36
36
|
<TopArea model={model} />
|
|
37
37
|
{turnedOnTracks?.map(track => (
|
|
38
38
|
<Track key={track.model.id} model={model} track={track} />
|
|
@@ -5,6 +5,7 @@ import { observer } from 'mobx-react'
|
|
|
5
5
|
// locals
|
|
6
6
|
import { MsaViewModel } from '../../model'
|
|
7
7
|
|
|
8
|
+
// lazies
|
|
8
9
|
const TreeNodeInfoDialog = lazy(() => import('./dialogs/TreeNodeInfoDialog'))
|
|
9
10
|
|
|
10
11
|
const TreeMenu = observer(function ({
|
|
@@ -16,104 +17,112 @@ const TreeMenu = observer(function ({
|
|
|
16
17
|
model: MsaViewModel
|
|
17
18
|
onClose: () => void
|
|
18
19
|
}) {
|
|
19
|
-
const { structures } = model
|
|
20
|
+
const { selectedStructures, collapsed, collapsed2, structures } = model
|
|
20
21
|
const nodeDetails = node ? model.getRowData(node.name) : undefined
|
|
21
22
|
|
|
22
23
|
return (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
<Menu
|
|
25
|
+
anchorReference="anchorPosition"
|
|
26
|
+
anchorPosition={{
|
|
27
|
+
top: node.y,
|
|
28
|
+
left: node.x,
|
|
29
|
+
}}
|
|
30
|
+
transitionDuration={0}
|
|
31
|
+
keepMounted
|
|
32
|
+
open={Boolean(node)}
|
|
33
|
+
onClose={onClose}
|
|
34
|
+
>
|
|
35
|
+
<MenuItem dense disabled>
|
|
36
|
+
{node.name}
|
|
37
|
+
</MenuItem>
|
|
38
|
+
|
|
39
|
+
<MenuItem
|
|
40
|
+
dense
|
|
41
|
+
onClick={() => {
|
|
42
|
+
model.queueDialog(onClose => [
|
|
43
|
+
TreeNodeInfoDialog,
|
|
44
|
+
{
|
|
45
|
+
info: model.getRowData(node.name),
|
|
46
|
+
model,
|
|
47
|
+
nodeName: node.name,
|
|
48
|
+
onClose,
|
|
49
|
+
},
|
|
50
|
+
])
|
|
51
|
+
onClose()
|
|
29
52
|
}}
|
|
30
|
-
transitionDuration={0}
|
|
31
|
-
keepMounted
|
|
32
|
-
open={Boolean(node)}
|
|
33
|
-
onClose={onClose}
|
|
34
53
|
>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<MenuItem
|
|
57
|
-
dense
|
|
58
|
-
onClick={() => {
|
|
59
|
-
model.hideNode(node.id)
|
|
60
|
-
onClose()
|
|
61
|
-
}}
|
|
62
|
-
>
|
|
63
|
-
Hide node
|
|
64
|
-
</MenuItem>
|
|
65
|
-
|
|
66
|
-
{structures[node.name]?.map(entry => {
|
|
67
|
-
return !model.selectedStructures.some(n => n.id === node.name) ? (
|
|
68
|
-
<MenuItem
|
|
69
|
-
key={JSON.stringify(entry)}
|
|
70
|
-
dense
|
|
71
|
-
onClick={() => {
|
|
72
|
-
model.addStructureToSelection({
|
|
73
|
-
structure: entry,
|
|
74
|
-
id: node.name,
|
|
75
|
-
})
|
|
76
|
-
onClose()
|
|
77
|
-
}}
|
|
78
|
-
>
|
|
79
|
-
Add PDB to selection ({entry.pdb})
|
|
80
|
-
</MenuItem>
|
|
81
|
-
) : (
|
|
82
|
-
<MenuItem
|
|
83
|
-
key={JSON.stringify(entry)}
|
|
84
|
-
dense
|
|
85
|
-
onClick={() => {
|
|
86
|
-
model.removeStructureFromSelection({
|
|
87
|
-
structure: entry,
|
|
88
|
-
id: node.name,
|
|
89
|
-
})
|
|
90
|
-
onClose()
|
|
91
|
-
}}
|
|
92
|
-
>
|
|
93
|
-
Remove PDB from selection ({entry.pdb})
|
|
94
|
-
</MenuItem>
|
|
95
|
-
)
|
|
96
|
-
})}
|
|
54
|
+
More info...
|
|
55
|
+
</MenuItem>
|
|
56
|
+
<MenuItem
|
|
57
|
+
dense
|
|
58
|
+
onClick={() => {
|
|
59
|
+
if (collapsed.includes(node.id)) {
|
|
60
|
+
model.toggleCollapsed(node.id)
|
|
61
|
+
} else {
|
|
62
|
+
if (node.id.endsWith('-leafnode')) {
|
|
63
|
+
model.toggleCollapsed2(`${node.id}`)
|
|
64
|
+
} else {
|
|
65
|
+
model.toggleCollapsed2(`${node.id}-leafnode`)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
onClose()
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
{collapsed.includes(node.id) || collapsed2.includes(node.id)
|
|
72
|
+
? 'Show node'
|
|
73
|
+
: 'Hide node'}
|
|
74
|
+
</MenuItem>
|
|
97
75
|
|
|
98
|
-
|
|
99
|
-
|
|
76
|
+
{structures[node.name]?.map(entry =>
|
|
77
|
+
!selectedStructures.some(n => n.id === node.name) ? (
|
|
78
|
+
<MenuItem
|
|
79
|
+
key={JSON.stringify(entry)}
|
|
80
|
+
dense
|
|
81
|
+
onClick={() => {
|
|
82
|
+
model.addStructureToSelection({
|
|
83
|
+
structure: entry,
|
|
84
|
+
id: node.name,
|
|
85
|
+
})
|
|
86
|
+
onClose()
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
Add PDB to selection ({entry.pdb})
|
|
90
|
+
</MenuItem>
|
|
91
|
+
) : (
|
|
100
92
|
<MenuItem
|
|
93
|
+
key={JSON.stringify(entry)}
|
|
101
94
|
dense
|
|
102
|
-
key={accession}
|
|
103
95
|
onClick={() => {
|
|
104
|
-
model.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
accession,
|
|
96
|
+
model.removeStructureFromSelection({
|
|
97
|
+
structure: entry,
|
|
98
|
+
id: node.name,
|
|
108
99
|
})
|
|
109
100
|
onClose()
|
|
110
101
|
}}
|
|
111
102
|
>
|
|
112
|
-
|
|
103
|
+
Remove PDB from selection ({entry.pdb})
|
|
113
104
|
</MenuItem>
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
105
|
+
),
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{// @ts-expect-error
|
|
109
|
+
nodeDetails?.data.accession?.map(accession => (
|
|
110
|
+
<MenuItem
|
|
111
|
+
dense
|
|
112
|
+
key={accession}
|
|
113
|
+
onClick={() => {
|
|
114
|
+
model.addUniprotTrack({
|
|
115
|
+
// @ts-expect-error
|
|
116
|
+
name: nodeDetails?.data.name,
|
|
117
|
+
accession,
|
|
118
|
+
})
|
|
119
|
+
onClose()
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
Open UniProt track ({accession})
|
|
123
|
+
</MenuItem>
|
|
124
|
+
))}
|
|
125
|
+
</Menu>
|
|
117
126
|
)
|
|
118
127
|
})
|
|
119
128
|
|
|
@@ -71,7 +71,13 @@ export function renderNodeBubbles({
|
|
|
71
71
|
theme: Theme
|
|
72
72
|
blockSizeYOverride?: number
|
|
73
73
|
}) {
|
|
74
|
-
const {
|
|
74
|
+
const {
|
|
75
|
+
hierarchy,
|
|
76
|
+
showBranchLen,
|
|
77
|
+
collapsed,
|
|
78
|
+
blockSize,
|
|
79
|
+
marginLeft: ml,
|
|
80
|
+
} = model
|
|
75
81
|
const by = blockSizeYOverride || blockSize
|
|
76
82
|
for (const node of hierarchy.descendants()) {
|
|
77
83
|
const val = showBranchLen ? 'len' : 'y'
|
|
@@ -84,6 +90,7 @@ export function renderNodeBubbles({
|
|
|
84
90
|
} = node
|
|
85
91
|
const { branchset, id = '', name = '' } = data
|
|
86
92
|
if (
|
|
93
|
+
!id.endsWith('-leafnode') &&
|
|
87
94
|
branchset.length &&
|
|
88
95
|
y > offsetY - extendBounds &&
|
|
89
96
|
y < offsetY + by + extendBounds
|
|
@@ -96,8 +103,8 @@ export function renderNodeBubbles({
|
|
|
96
103
|
ctx.stroke()
|
|
97
104
|
|
|
98
105
|
clickMap?.insert({
|
|
99
|
-
minX: x - radius,
|
|
100
|
-
maxX: x - radius + d,
|
|
106
|
+
minX: x - radius + ml,
|
|
107
|
+
maxX: x - radius + d + ml,
|
|
101
108
|
minY: y - radius,
|
|
102
109
|
maxY: y - radius + d,
|
|
103
110
|
branch: true,
|
|
@@ -133,6 +140,8 @@ export function renderTreeLabels({
|
|
|
133
140
|
drawTree,
|
|
134
141
|
structures,
|
|
135
142
|
treeAreaWidth,
|
|
143
|
+
treeAreaWidthMinusMargin,
|
|
144
|
+
marginLeft: ml,
|
|
136
145
|
noTree,
|
|
137
146
|
} = model
|
|
138
147
|
const by = blockSizeYOverride || blockSize
|
|
@@ -169,8 +178,8 @@ export function renderTreeLabels({
|
|
|
169
178
|
if (!drawTree && !labelsAlignRight) {
|
|
170
179
|
ctx.fillText(displayName, 0, yp)
|
|
171
180
|
clickMap?.insert({
|
|
172
|
-
minX: 0,
|
|
173
|
-
maxX: width,
|
|
181
|
+
minX: 0 + ml,
|
|
182
|
+
maxX: width + ml,
|
|
174
183
|
minY: yp - height,
|
|
175
184
|
maxY: yp,
|
|
176
185
|
name,
|
|
@@ -178,7 +187,7 @@ export function renderTreeLabels({
|
|
|
178
187
|
})
|
|
179
188
|
} else if (labelsAlignRight) {
|
|
180
189
|
const smallPadding = 2
|
|
181
|
-
const offset =
|
|
190
|
+
const offset = treeAreaWidthMinusMargin - smallPadding
|
|
182
191
|
if (drawTree && !noTree) {
|
|
183
192
|
const { width } = ctx.measureText(displayName)
|
|
184
193
|
ctx.moveTo(xp + radius + 2, y)
|
|
@@ -197,8 +206,8 @@ export function renderTreeLabels({
|
|
|
197
206
|
} else {
|
|
198
207
|
ctx.fillText(displayName, xp + d, yp)
|
|
199
208
|
clickMap?.insert({
|
|
200
|
-
minX: xp + d,
|
|
201
|
-
maxX: xp + d + width,
|
|
209
|
+
minX: xp + d + ml,
|
|
210
|
+
maxX: xp + d + width + ml,
|
|
202
211
|
minY: yp - height,
|
|
203
212
|
maxY: yp,
|
|
204
213
|
name,
|
|
@@ -237,6 +246,7 @@ export function renderTreeCanvas({
|
|
|
237
246
|
blockSize,
|
|
238
247
|
fontSize,
|
|
239
248
|
rowHeight,
|
|
249
|
+
marginLeft,
|
|
240
250
|
nref,
|
|
241
251
|
} = model
|
|
242
252
|
const by = blockSizeYOverride || blockSize
|
|
@@ -252,7 +262,7 @@ export function renderTreeCanvas({
|
|
|
252
262
|
nref < 0 ? -Infinity : highResScaleFactorOverride || highResScaleFactor
|
|
253
263
|
ctx.scale(k, k)
|
|
254
264
|
ctx.clearRect(0, 0, treeWidth + padding, by)
|
|
255
|
-
ctx.translate(
|
|
265
|
+
ctx.translate(marginLeft, -offsetY)
|
|
256
266
|
|
|
257
267
|
const font = ctx.font
|
|
258
268
|
ctx.font = font.replace(/\d+px/, `${fontSize}px`)
|