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.
Files changed (50) hide show
  1. package/bundle/index.js +31 -40
  2. package/dist/DataModel.d.ts +34 -0
  3. package/dist/DataModel.js +46 -0
  4. package/dist/DataModel.js.map +1 -0
  5. package/dist/SelectedStructuresMixin.d.ts +46 -0
  6. package/dist/SelectedStructuresMixin.js +52 -0
  7. package/dist/SelectedStructuresMixin.js.map +1 -0
  8. package/dist/components/MSAPanel/MSABlock.js +32 -16
  9. package/dist/components/MSAPanel/MSABlock.js.map +1 -1
  10. package/dist/components/MSAPanel/renderMSABlock.js +1 -1
  11. package/dist/components/MSAPanel/renderMSABlock.js.map +1 -1
  12. package/dist/components/MSAPanel/renderMSAMouseover.js +14 -4
  13. package/dist/components/MSAPanel/renderMSAMouseover.js.map +1 -1
  14. package/dist/components/MSAView.js +2 -2
  15. package/dist/components/MSAView.js.map +1 -1
  16. package/dist/components/TreePanel/TreeNodeMenu.js +65 -55
  17. package/dist/components/TreePanel/TreeNodeMenu.js.map +1 -1
  18. package/dist/components/TreePanel/renderTreeCanvas.js +13 -12
  19. package/dist/components/TreePanel/renderTreeCanvas.js.map +1 -1
  20. package/dist/components/dialogs/SettingsDialog.js +42 -32
  21. package/dist/components/dialogs/SettingsDialog.js.map +1 -1
  22. package/dist/measureTextCanvas.d.ts +1 -1
  23. package/dist/measureTextCanvas.js +1 -1
  24. package/dist/measureTextCanvas.js.map +1 -1
  25. package/dist/model.d.ts +121 -108
  26. package/dist/model.js +72 -94
  27. package/dist/model.js.map +1 -1
  28. package/dist/parsers/ClustalMSA.d.ts +1 -11
  29. package/dist/parsers/ClustalMSA.js +1 -15
  30. package/dist/parsers/ClustalMSA.js.map +1 -1
  31. package/dist/util.d.ts +0 -1
  32. package/dist/util.js +0 -7
  33. package/dist/util.js.map +1 -1
  34. package/dist/version.d.ts +1 -1
  35. package/dist/version.js +1 -1
  36. package/package.json +1 -2
  37. package/src/DataModel.ts +46 -0
  38. package/src/SelectedStructuresMixin.ts +59 -0
  39. package/src/components/MSAPanel/MSABlock.tsx +33 -18
  40. package/src/components/MSAPanel/renderMSABlock.ts +0 -1
  41. package/src/components/MSAPanel/renderMSAMouseover.ts +16 -3
  42. package/src/components/MSAView.tsx +3 -3
  43. package/src/components/TreePanel/TreeNodeMenu.tsx +93 -84
  44. package/src/components/TreePanel/renderTreeCanvas.ts +19 -9
  45. package/src/components/dialogs/SettingsDialog.tsx +121 -117
  46. package/src/measureTextCanvas.ts +1 -1
  47. package/src/model.ts +88 -110
  48. package/src/parsers/ClustalMSA.ts +1 -15
  49. package/src/util.ts +0 -11
  50. 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
- if (!ctx) {
40
- return
41
- }
42
- return autorun(() => {
43
- renderMSABlock({
44
- ctx,
45
- theme,
46
- offsetX,
47
- offsetY,
48
- contrastScheme,
49
- model,
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
- model.setMousePos(
64
- Math.floor(mouseX / colWidth) + 1,
65
- Math.floor(mouseY / rowHeight),
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}
@@ -136,7 +136,6 @@ function drawText({
136
136
  model,
137
137
  offsetX,
138
138
  contrastScheme,
139
- theme,
140
139
  ctx,
141
140
  visibleLeaves,
142
141
  xStart,
@@ -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 = 'rgba(0,0,0,0.15)'
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 = 'rgba(0,0,0,0.15)'
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 = 'rgba(255,255,0,0.2)'
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={{ paddingLeft: 20, position: 'relative' }}>
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
- <Menu
25
- anchorReference="anchorPosition"
26
- anchorPosition={{
27
- top: node.y,
28
- left: node.x,
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
- <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()
52
- }}
53
- >
54
- More info...
55
- </MenuItem>
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
- {// @ts-expect-error
99
- nodeDetails?.data.accession?.map(accession => (
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.addUniprotTrack({
105
- // @ts-expect-error
106
- name: nodeDetails?.data.name,
107
- accession,
96
+ model.removeStructureFromSelection({
97
+ structure: entry,
98
+ id: node.name,
108
99
  })
109
100
  onClose()
110
101
  }}
111
102
  >
112
- Open UniProt track ({accession})
103
+ Remove PDB from selection ({entry.pdb})
113
104
  </MenuItem>
114
- ))}
115
- </Menu>
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 { hierarchy, showBranchLen, collapsed, blockSize } = model
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 = treeAreaWidth - smallPadding
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(0, -offsetY)
265
+ ctx.translate(marginLeft, -offsetY)
256
266
 
257
267
  const font = ctx.font
258
268
  ctx.font = font.replace(/\d+px/, `${fontSize}px`)