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.
Files changed (129) hide show
  1. package/bundle/index.js +9 -9
  2. package/bundle/index.js.LICENSE.txt +8 -8
  3. package/bundle/index.js.map +1 -1
  4. package/dist/colorSchemes.d.ts +0 -6
  5. package/dist/colorSchemes.js +1 -119
  6. package/dist/colorSchemes.js.map +1 -1
  7. package/dist/components/ConservationTrack.d.ts +8 -0
  8. package/dist/components/ConservationTrack.js +54 -0
  9. package/dist/components/ConservationTrack.js.map +1 -0
  10. package/dist/components/Loading.js +14 -2
  11. package/dist/components/Loading.js.map +1 -1
  12. package/dist/components/MSAView.js +36 -0
  13. package/dist/components/MSAView.js.map +1 -1
  14. package/dist/components/SequenceTextArea.js +3 -2
  15. package/dist/components/SequenceTextArea.js.map +1 -1
  16. package/dist/components/TextTrack.d.ts +3 -3
  17. package/dist/components/TextTrack.js +4 -1
  18. package/dist/components/TextTrack.js.map +1 -1
  19. package/dist/components/Track.js +21 -8
  20. package/dist/components/Track.js.map +1 -1
  21. package/dist/components/dialogs/ExportSVGDialog.js +19 -3
  22. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  23. package/dist/components/header/GappynessSlider.d.ts +6 -0
  24. package/dist/components/header/GappynessSlider.js +19 -0
  25. package/dist/components/header/GappynessSlider.js.map +1 -0
  26. package/dist/components/header/Header.js +3 -1
  27. package/dist/components/header/Header.js.map +1 -1
  28. package/dist/components/header/HeaderMenu.js +30 -14
  29. package/dist/components/header/HeaderMenu.js.map +1 -1
  30. package/dist/components/minimap/MinimapSVG.js +4 -3
  31. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  32. package/dist/components/msa/MSACanvasBlock.js +56 -42
  33. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  34. package/dist/components/msa/renderMSABlock.js +53 -10
  35. package/dist/components/msa/renderMSABlock.js.map +1 -1
  36. package/dist/components/tracks/renderTracksSvg.d.ts +29 -0
  37. package/dist/components/tracks/renderTracksSvg.js +83 -0
  38. package/dist/components/tracks/renderTracksSvg.js.map +1 -0
  39. package/dist/components/tree/TreeCanvasBlock.js +1 -1
  40. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  41. package/dist/components/tree/TreeNodeMenu.js +2 -2
  42. package/dist/components/tree/TreeNodeMenu.js.map +1 -1
  43. package/dist/components/tree/renderTreeCanvas.js +1 -1
  44. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  45. package/dist/constants.d.ts +22 -0
  46. package/dist/constants.js +26 -0
  47. package/dist/constants.js.map +1 -0
  48. package/dist/layout.js.map +1 -1
  49. package/dist/model/msaModel.js +3 -2
  50. package/dist/model/msaModel.js.map +1 -1
  51. package/dist/model/treeModel.js +9 -8
  52. package/dist/model/treeModel.js.map +1 -1
  53. package/dist/model.d.ts +256 -15
  54. package/dist/model.js +408 -128
  55. package/dist/model.js.map +1 -1
  56. package/dist/neighborJoining.d.ts +1 -0
  57. package/dist/neighborJoining.js +839 -0
  58. package/dist/neighborJoining.js.map +1 -0
  59. package/dist/neighborJoining.test.d.ts +1 -0
  60. package/dist/neighborJoining.test.js +110 -0
  61. package/dist/neighborJoining.test.js.map +1 -0
  62. package/dist/parsers/A3mMSA.d.ts +43 -0
  63. package/dist/parsers/A3mMSA.js +277 -0
  64. package/dist/parsers/A3mMSA.js.map +1 -0
  65. package/dist/parsers/A3mMSA.test.d.ts +1 -0
  66. package/dist/parsers/A3mMSA.test.js +138 -0
  67. package/dist/parsers/A3mMSA.test.js.map +1 -0
  68. package/dist/parsers/ClustalMSA.d.ts +4 -4
  69. package/dist/parsers/ClustalMSA.js +3 -1
  70. package/dist/parsers/ClustalMSA.js.map +1 -1
  71. package/dist/parsers/FastaMSA.js +17 -16
  72. package/dist/parsers/FastaMSA.js.map +1 -1
  73. package/dist/renderToSvg.d.ts +1 -0
  74. package/dist/renderToSvg.js +48 -18
  75. package/dist/renderToSvg.js.map +1 -1
  76. package/dist/rowCoordinateCalculations.js +3 -5
  77. package/dist/rowCoordinateCalculations.js.map +1 -1
  78. package/dist/rowCoordinateCalculations.test.js +14 -2
  79. package/dist/rowCoordinateCalculations.test.js.map +1 -1
  80. package/dist/seqCoordToRowSpecificGlobalCoord.js +9 -5
  81. package/dist/seqCoordToRowSpecificGlobalCoord.js.map +1 -1
  82. package/dist/seqCoordToRowSpecificGlobalCoord.test.js +6 -6
  83. package/dist/types.d.ts +2 -3
  84. package/dist/util.js +17 -9
  85. package/dist/util.js.map +1 -1
  86. package/dist/version.d.ts +1 -1
  87. package/dist/version.js +1 -1
  88. package/package.json +6 -6
  89. package/src/colorSchemes.ts +1 -179
  90. package/src/components/ConservationTrack.tsx +104 -0
  91. package/src/components/Loading.tsx +44 -2
  92. package/src/components/MSAView.tsx +68 -0
  93. package/src/components/SequenceTextArea.tsx +3 -2
  94. package/src/components/TextTrack.tsx +7 -4
  95. package/src/components/Track.tsx +25 -9
  96. package/src/components/dialogs/ExportSVGDialog.tsx +25 -1
  97. package/src/components/header/GappynessSlider.tsx +35 -0
  98. package/src/components/header/Header.tsx +3 -1
  99. package/src/components/header/HeaderMenu.tsx +36 -15
  100. package/src/components/minimap/MinimapSVG.tsx +6 -3
  101. package/src/components/msa/MSACanvasBlock.tsx +66 -48
  102. package/src/components/msa/renderMSABlock.ts +82 -22
  103. package/src/components/tracks/renderTracksSvg.ts +157 -0
  104. package/src/components/tree/TreeCanvasBlock.tsx +1 -1
  105. package/src/components/tree/TreeNodeMenu.tsx +2 -2
  106. package/src/components/tree/renderTreeCanvas.ts +1 -1
  107. package/src/constants.ts +27 -0
  108. package/src/layout.ts +1 -6
  109. package/src/model/msaModel.ts +4 -2
  110. package/src/model/treeModel.ts +19 -8
  111. package/src/model.ts +496 -140
  112. package/src/neighborJoining.test.ts +129 -0
  113. package/src/neighborJoining.ts +885 -0
  114. package/src/parsers/A3mMSA.test.ts +164 -0
  115. package/src/parsers/A3mMSA.ts +321 -0
  116. package/src/parsers/ClustalMSA.ts +7 -5
  117. package/src/parsers/FastaMSA.ts +17 -17
  118. package/src/renderToSvg.tsx +105 -26
  119. package/src/rowCoordinateCalculations.test.ts +15 -2
  120. package/src/rowCoordinateCalculations.ts +3 -5
  121. package/src/seqCoordToRowSpecificGlobalCoord.test.ts +6 -6
  122. package/src/seqCoordToRowSpecificGlobalCoord.ts +9 -4
  123. package/src/types.ts +2 -4
  124. package/src/util.ts +21 -8
  125. package/src/version.ts +1 -1
  126. package/dist/components/dialogs/TracklistDialog.d.ts +0 -7
  127. package/dist/components/dialogs/TracklistDialog.js +0 -23
  128. package/dist/components/dialogs/TracklistDialog.js.map +0 -1
  129. 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
- <div style={{ margin: 'auto' }}>
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 { showDomains, actuallyShowDomains, subFeatureRows, noDomains } = model
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: 'Extra tracks',
67
- icon: List,
68
- onClick: () => {
69
- model.queueDialog(onClose => [
70
- TracklistDialog,
71
- {
72
- model,
73
- onClose,
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 fill = 'rgba(66, 119, 127, 0.3)'
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={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={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
- <canvas
84
- ref={ref}
85
- onMouseMove={event => {
86
- if (!ref.current) {
87
- return
88
- }
89
- const { left, top } = ref.current.getBoundingClientRect()
90
- const mouseX = event.clientX - left + offsetX
91
- const mouseY = event.clientY - top + offsetY
92
- const x = Math.floor(mouseX / colWidth)
93
- const y = Math.floor(mouseY / rowHeight)
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
- // Only set mouse position if within valid MSA bounds
96
- if (x >= 0 && x < model.numColumns && y >= 0 && y < model.numRows) {
97
- model.setMousePos(x, y)
98
- } else {
99
- // Clear mouse position when outside bounds
100
- model.setMousePos(undefined, undefined)
101
- }
102
- }}
103
- onClick={event => {
104
- if (!ref.current) {
105
- return
106
- }
107
- const { left, top } = ref.current.getBoundingClientRect()
108
- const mouseX = event.clientX - left + offsetX
109
- const mouseY = event.clientY - top + offsetY
110
- const x = Math.floor(mouseX / colWidth)
111
- const y = Math.floor(mouseY / rowHeight)
112
- if (x === mouseClickCol && y === mouseClickRow) {
113
- model.setMouseClickPos(undefined, undefined)
114
- } else {
115
- model.setMouseClickPos(x, y)
116
- }
117
- }}
118
- onMouseLeave={() => {
119
- model.setMousePos()
120
- }}
121
- width={blockSize * highResScaleFactor}
122
- height={blockSize * highResScaleFactor}
123
- style={{
124
- position: 'absolute',
125
- top: scrollY + offsetY,
126
- left: scrollX + offsetX,
127
- width: blockSize,
128
- height: blockSize,
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
- ? getClustalXColor(
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
- ? getPercentIdentityColor(
141
- // use model.colStats dot notation here: delay use of
142
- // colStats until absolutely needed
143
- model.colStats[xStart + i]!,
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.toggleCollapsed2(node.id)
67
+ model.toggleCollapsedLeaf(node.id)
68
68
  } else {
69
- model.toggleCollapsed2(`${node.id}-leafnode`)
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 = '', name = '' } = data
110
+ const { id, name } = data
111
111
  if (
112
112
  node.height > 1 &&
113
113
  y > offsetY - extendBounds &&
@@ -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
  }
@@ -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: true,
17
+ bgColor: defaultBgColor,
16
18
 
17
19
  /**
18
20
  * #property
19
21
  * default color scheme name
20
22
  */
21
- colorSchemeName: 'maeditor',
23
+ colorSchemeName: defaultColorSchemeName,
22
24
  })
23
25
  .actions(self => ({
24
26
  /**