react-msaview 5.4.1 → 5.6.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 (180) hide show
  1. package/LICENSE +21 -0
  2. package/bundle/index.js +487 -149
  3. package/bundle/index.js.map +7 -1
  4. package/dist/colorSchemes.js +11 -7
  5. package/dist/colorSchemes.js.map +1 -1
  6. package/dist/components/ConservationTrack.js +20 -26
  7. package/dist/components/ConservationTrack.js.map +1 -1
  8. package/dist/components/MSAViewer.d.ts +13 -1
  9. package/dist/components/MSAViewer.js +13 -7
  10. package/dist/components/MSAViewer.js.map +1 -1
  11. package/dist/components/SequenceTextArea.js +1 -1
  12. package/dist/components/SequenceTextArea.js.map +1 -1
  13. package/dist/components/TextTrack.js +20 -27
  14. package/dist/components/TextTrack.js.map +1 -1
  15. package/dist/components/Track.js +9 -6
  16. package/dist/components/Track.js.map +1 -1
  17. package/dist/components/dialogs/FeatureDialog.js +27 -23
  18. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  19. package/dist/components/dialogs/TrackInfoDialog.js +1 -1
  20. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
  21. package/dist/components/header/ColorSchemeMenu.js +1 -1
  22. package/dist/components/header/ColorSchemeMenu.js.map +1 -1
  23. package/dist/components/header/FileMenu.js +1 -16
  24. package/dist/components/header/FileMenu.js.map +1 -1
  25. package/dist/components/header/Header.js +6 -5
  26. package/dist/components/header/Header.js.map +1 -1
  27. package/dist/components/header/HeaderInfoArea.js +1 -1
  28. package/dist/components/header/HeaderInfoArea.js.map +1 -1
  29. package/dist/components/header/HeaderStatusArea.js +1 -1
  30. package/dist/components/header/HeaderStatusArea.js.map +1 -1
  31. package/dist/components/header/MSASettingsMenu.js +1 -1
  32. package/dist/components/header/MSASettingsMenu.js.map +1 -1
  33. package/dist/components/header/MultiAlignmentSelector.js +0 -0
  34. package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
  35. package/dist/components/header/TreeSettingsMenu.js +1 -1
  36. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  37. package/dist/components/header/ZoomControls.js +14 -12
  38. package/dist/components/header/ZoomControls.js.map +1 -1
  39. package/dist/components/header/ZoomMenu.js +1 -10
  40. package/dist/components/header/ZoomMenu.js.map +1 -1
  41. package/dist/components/header/settingsMenuItems.js +9 -1
  42. package/dist/components/header/settingsMenuItems.js.map +1 -1
  43. package/dist/components/{header/HeaderMenu.d.ts → msa/ColumnStats.d.ts} +3 -3
  44. package/dist/components/msa/ColumnStats.js +42 -0
  45. package/dist/components/msa/ColumnStats.js.map +1 -0
  46. package/dist/components/msa/DomainLegend.d.ts +6 -0
  47. package/dist/components/msa/DomainLegend.js +41 -0
  48. package/dist/components/msa/DomainLegend.js.map +1 -0
  49. package/dist/components/msa/MSACanvasBlock.js +41 -29
  50. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  51. package/dist/components/msa/MSAMouseoverCanvas.js +6 -12
  52. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  53. package/dist/components/msa/MSAPanel.js +3 -1
  54. package/dist/components/msa/MSAPanel.js.map +1 -1
  55. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +60 -14
  56. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  57. package/dist/components/msa/renderMSABlock.js +3 -3
  58. package/dist/components/msa/renderMSABlock.js.map +1 -1
  59. package/dist/components/msa/renderMSAMouseover.js +33 -6
  60. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  61. package/dist/components/overlayColors.d.ts +3 -0
  62. package/dist/components/overlayColors.js +11 -0
  63. package/dist/components/overlayColors.js.map +1 -0
  64. package/dist/components/renderCtx.d.ts +1 -1
  65. package/dist/components/tracks/renderTracksSvg.d.ts +6 -3
  66. package/dist/components/tracks/renderTracksSvg.js +20 -11
  67. package/dist/components/tracks/renderTracksSvg.js.map +1 -1
  68. package/dist/components/tree/TreeCanvas.js +31 -38
  69. package/dist/components/tree/TreeCanvas.js.map +1 -1
  70. package/dist/components/tree/TreeCanvasBlock.js +12 -47
  71. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  72. package/dist/components/tree/clickMap.d.ts +24 -0
  73. package/dist/components/tree/clickMap.js +33 -0
  74. package/dist/components/tree/clickMap.js.map +1 -0
  75. package/dist/components/tree/renderTreeCanvas.d.ts +1 -21
  76. package/dist/components/tree/renderTreeCanvas.js +8 -7
  77. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  78. package/dist/constants.d.ts +4 -0
  79. package/dist/constants.js +17 -0
  80. package/dist/constants.js.map +1 -1
  81. package/dist/createPaletteMap.d.ts +5 -2
  82. package/dist/createPaletteMap.js +15 -5
  83. package/dist/createPaletteMap.js.map +1 -1
  84. package/dist/fetchUtils.d.ts +2 -1
  85. package/dist/fetchUtils.js +3 -2
  86. package/dist/fetchUtils.js.map +1 -1
  87. package/dist/index.d.ts +1 -1
  88. package/dist/launchInterProScan.d.ts +2 -0
  89. package/dist/launchInterProScan.js.map +1 -1
  90. package/dist/model/msaModel.d.ts +25 -2
  91. package/dist/model/msaModel.js +15 -3
  92. package/dist/model/msaModel.js.map +1 -1
  93. package/dist/model/treeModel.d.ts +46 -5
  94. package/dist/model/treeModel.js +20 -7
  95. package/dist/model/treeModel.js.map +1 -1
  96. package/dist/model.d.ts +641 -133
  97. package/dist/model.js +191 -77
  98. package/dist/model.js.map +1 -1
  99. package/dist/propertyConservation.d.ts +7 -0
  100. package/dist/propertyConservation.js +66 -0
  101. package/dist/propertyConservation.js.map +1 -0
  102. package/dist/renderToSvg.js +104 -74
  103. package/dist/renderToSvg.js.map +1 -1
  104. package/dist/types.d.ts +4 -1
  105. package/dist/{webpack.js → umd.js} +1 -1
  106. package/dist/umd.js.map +1 -0
  107. package/dist/useCanvasAutorun.d.ts +2 -0
  108. package/dist/useCanvasAutorun.js +23 -0
  109. package/dist/useCanvasAutorun.js.map +1 -0
  110. package/dist/version.d.ts +1 -1
  111. package/dist/version.js +1 -1
  112. package/package.json +7 -8
  113. package/src/colorSchemes.docs.test.ts +55 -0
  114. package/src/colorSchemes.ts +12 -7
  115. package/src/columnStats.test.ts +94 -0
  116. package/src/components/ConservationTrack.tsx +17 -15
  117. package/src/components/MSAViewer.tsx +42 -18
  118. package/src/components/SequenceTextArea.tsx +1 -1
  119. package/src/components/TextTrack.tsx +7 -11
  120. package/src/components/Track.tsx +9 -6
  121. package/src/components/dialogs/FeatureDialog.tsx +62 -48
  122. package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
  123. package/src/components/header/ColorSchemeMenu.tsx +1 -0
  124. package/src/components/header/FileMenu.tsx +1 -15
  125. package/src/components/header/Header.tsx +11 -8
  126. package/src/components/header/HeaderInfoArea.tsx +1 -1
  127. package/src/components/header/HeaderStatusArea.tsx +1 -1
  128. package/src/components/header/MSASettingsMenu.tsx +1 -0
  129. package/src/components/header/MultiAlignmentSelector.tsx +0 -0
  130. package/src/components/header/TreeSettingsMenu.tsx +1 -0
  131. package/src/components/header/ZoomControls.tsx +24 -17
  132. package/src/components/header/ZoomMenu.tsx +1 -10
  133. package/src/components/header/settingsMenuItems.ts +9 -1
  134. package/src/components/msa/ColumnStats.tsx +54 -0
  135. package/src/components/msa/DomainLegend.tsx +78 -0
  136. package/src/components/msa/MSACanvasBlock.tsx +46 -25
  137. package/src/components/msa/MSAMouseoverCanvas.tsx +10 -13
  138. package/src/components/msa/MSAPanel.tsx +2 -0
  139. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +75 -17
  140. package/src/components/msa/renderMSABlock.ts +3 -3
  141. package/src/components/msa/renderMSAMouseover.ts +34 -6
  142. package/src/components/overlayColors.ts +12 -0
  143. package/src/components/renderCtx.ts +1 -0
  144. package/src/components/tracks/renderTracksSvg.ts +25 -11
  145. package/src/components/tree/TreeCanvas.tsx +47 -54
  146. package/src/components/tree/TreeCanvasBlock.tsx +10 -68
  147. package/src/components/tree/clickMap.ts +55 -0
  148. package/src/components/tree/renderTreeCanvas.ts +10 -34
  149. package/src/constants.ts +19 -0
  150. package/src/createPaletteMap.ts +18 -5
  151. package/src/fetchUtils.ts +6 -3
  152. package/src/index.ts +1 -1
  153. package/src/launchInterProScan.ts +3 -1
  154. package/src/model/msaModel.ts +21 -3
  155. package/src/model/treeModel.ts +21 -7
  156. package/src/model.ts +221 -119
  157. package/src/propertyConservation.test.ts +47 -0
  158. package/src/propertyConservation.ts +73 -0
  159. package/src/renderDomainLegendSvg.test.tsx +134 -0
  160. package/src/renderToSvg.tsx +210 -147
  161. package/src/segmentOverlay.test.ts +70 -0
  162. package/src/stripDefaultSnapshot.test.ts +54 -0
  163. package/src/types.ts +7 -1
  164. package/src/useCanvasAutorun.ts +28 -0
  165. package/src/version.ts +1 -1
  166. package/bundle/index.js.LICENSE.txt +0 -100
  167. package/dist/components/dialogs/SettingsDialog.d.ts +0 -7
  168. package/dist/components/dialogs/SettingsDialog.js +0 -100
  169. package/dist/components/dialogs/SettingsDialog.js.map +0 -1
  170. package/dist/components/header/HeaderMenu.js +0 -27
  171. package/dist/components/header/HeaderMenu.js.map +0 -1
  172. package/dist/components/util.d.ts +0 -1
  173. package/dist/components/util.js +0 -7
  174. package/dist/components/util.js.map +0 -1
  175. package/dist/webpack.js.map +0 -1
  176. package/src/components/dialogs/SettingsDialog.tsx +0 -234
  177. package/src/components/header/HeaderMenu.tsx +0 -38
  178. package/src/components/util.ts +0 -9
  179. /package/dist/{webpack.d.ts → umd.d.ts} +0 -0
  180. /package/src/{webpack.ts → umd.ts} +0 -0
@@ -7,14 +7,16 @@ import type { RenderCtx } from '../renderCtx.ts'
7
7
 
8
8
  export function drawConservationBars({
9
9
  ctx,
10
- conservation,
10
+ values,
11
+ color,
11
12
  colWidth,
12
13
  trackHeight,
13
14
  offsetX,
14
15
  blockSize,
15
16
  }: {
16
17
  ctx: RenderCtx
17
- conservation: number[]
18
+ values: number[]
19
+ color: string
18
20
  colWidth: number
19
21
  trackHeight: number
20
22
  offsetX: number
@@ -26,15 +28,23 @@ export function drawConservationBars({
26
28
  colWidth,
27
29
  })
28
30
 
29
- ctx.fillStyle = 'gray'
30
- for (let i = xStart; i < xEnd && i < conservation.length; i++) {
31
- const value = conservation[i]!
31
+ ctx.fillStyle = color
32
+ for (let i = xStart; i < xEnd && i < values.length; i++) {
33
+ const value = values[i]!
32
34
  const barHeight = value * trackHeight
33
35
  const x = i * colWidth
34
36
  ctx.fillRect(x, trackHeight - barHeight, colWidth, barHeight)
35
37
  }
36
38
  }
37
39
 
40
+ // the bar-track values live on model getters (kept off the plain track object so
41
+ // the live canvas autorun stays reactive); select the right one by track id
42
+ export function barTrackValues(model: MsaViewModel, trackId: string) {
43
+ return trackId === 'property-conservation'
44
+ ? model.propertyConservation
45
+ : model.conservation
46
+ }
47
+
38
48
  export function drawTextTrackContent({
39
49
  ctx,
40
50
  data,
@@ -65,13 +75,13 @@ export function drawTextTrackContent({
65
75
 
66
76
  for (let i = 0; str && i < str.length; i++) {
67
77
  const letter = str[i]!
68
- const color = colorScheme[letter.toUpperCase()]
78
+ const upper = letter.toUpperCase()
69
79
  if (bgColor) {
70
80
  const x = (xStart + i) * colWidth
71
- ctx.fillStyle = color || 'white'
81
+ ctx.fillStyle = colorScheme[upper] || 'white'
72
82
  ctx.fillRect(x, 0, colWidth, rowHeight)
73
83
  if (rowHeight >= 10 && colWidth >= rowHeight / 2) {
74
- ctx.fillStyle = contrastScheme[letter.toUpperCase()] || 'black'
84
+ ctx.fillStyle = contrastScheme[upper] || 'black'
75
85
  ctx.fillText(letter, x + colWidth / 2, rowHeight / 2 + 1)
76
86
  }
77
87
  }
@@ -81,6 +91,7 @@ export function drawTextTrackContent({
81
91
  export function renderConservationTrack({
82
92
  model,
83
93
  ctx,
94
+ track,
84
95
  offsetX,
85
96
  offsetY,
86
97
  trackHeight,
@@ -89,13 +100,14 @@ export function renderConservationTrack({
89
100
  }: {
90
101
  model: MsaViewModel
91
102
  ctx: RenderCtx
103
+ track: BasicTrack
92
104
  offsetX: number
93
105
  offsetY: number
94
106
  trackHeight: number
95
107
  blockSizeXOverride?: number
96
108
  highResScaleFactorOverride?: number
97
109
  }) {
98
- const { blockSize, colWidth, highResScaleFactor, conservation } = model
110
+ const { blockSize, colWidth, highResScaleFactor } = model
99
111
  const bx = blockSizeXOverride ?? blockSize
100
112
  const k = highResScaleFactorOverride ?? highResScaleFactor
101
113
 
@@ -105,7 +117,8 @@ export function renderConservationTrack({
105
117
 
106
118
  drawConservationBars({
107
119
  ctx,
108
- conservation,
120
+ values: barTrackValues(model, track.model.id),
121
+ color: track.model.barColor ?? 'gray',
109
122
  colWidth,
110
123
  trackHeight,
111
124
  offsetX,
@@ -191,10 +204,11 @@ export function renderAllTracks({
191
204
  for (const track of turnedOnTracks) {
192
205
  const trackHeight = track.model.height
193
206
 
194
- if (track.model.id === 'conservation') {
207
+ if (track.model.barColor !== undefined) {
195
208
  renderConservationTrack({
196
209
  model,
197
210
  ctx,
211
+ track,
198
212
  offsetX,
199
213
  offsetY: currentY,
200
214
  trackHeight,
@@ -1,21 +1,18 @@
1
- import React, { useCallback, useEffect, useRef } from 'react'
1
+ import React, { useCallback, useRef } from 'react'
2
2
 
3
3
  import { isAlive } from '@jbrowse/mobx-state-tree'
4
- import { autorun } from 'mobx'
5
4
  import { observer } from 'mobx-react'
6
5
 
7
6
  import TreeCanvasBlock from './TreeCanvasBlock.tsx'
8
7
  import { padding } from './renderTreeCanvas.ts'
8
+ import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
9
9
  import { useWheelScroll } from '../../useWheelScroll.ts'
10
+ import { referenceColor, treeHoverColor } from '../overlayColors.ts'
10
11
 
11
12
  import type { MsaViewModel } from '../../model.ts'
12
13
 
13
- const referenceColor = 'rgba(0,128,255,0.3)'
14
- const treeHoverColor = 'rgba(255,165,0,0.2)'
15
-
16
14
  const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
17
15
  const ref = useRef<HTMLDivElement>(null)
18
- const mouseoverRef = useRef<HTMLCanvasElement>(null)
19
16
  const { treeWidth, height, blocksY, treeAreaWidth } = model
20
17
  const onScrollY = useCallback(
21
18
  (d: number) => {
@@ -25,58 +22,54 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
25
22
  )
26
23
  const { onMouseDown, onMouseUp } = useWheelScroll({ ref, onScrollY })
27
24
 
28
- useEffect(() => {
29
- const ctx = mouseoverRef.current?.getContext('2d')
30
- return ctx
31
- ? autorun(() => {
32
- if (isAlive(model)) {
33
- const {
34
- relativeTo,
35
- leaves,
36
- rowHeight,
37
- hoveredTreeNode,
38
- treeAreaWidth: w,
39
- height: h,
40
- scrollY: sy,
41
- mouseOverRowName,
42
- } = model
43
- ctx.resetTransform()
44
- ctx.clearRect(0, 0, w, h)
45
-
46
- const leafByName = new Map(
47
- leaves.map(leaf => [leaf.data.name, leaf]),
48
- )
49
- const fillRow = (name: string) => {
50
- const leaf = leafByName.get(name)
51
- if (leaf) {
52
- ctx.fillRect(0, leaf.x! + sy - rowHeight / 2, w, rowHeight)
53
- }
54
- }
25
+ const mouseoverRef = useCanvasAutorun(
26
+ ctx => {
27
+ if (isAlive(model)) {
28
+ const {
29
+ relativeTo,
30
+ leaves,
31
+ rowHeight,
32
+ hoveredTreeNode,
33
+ treeAreaWidth: w,
34
+ height: h,
35
+ scrollY: sy,
36
+ mouseOverRowName,
37
+ } = model
38
+ ctx.resetTransform()
39
+ ctx.clearRect(0, 0, w, h)
55
40
 
56
- if (relativeTo) {
57
- ctx.fillStyle = referenceColor
58
- fillRow(relativeTo)
59
- }
41
+ const leafByName = new Map(leaves.map(leaf => [leaf.data.name, leaf]))
42
+ const fillRow = (name: string) => {
43
+ const leaf = leafByName.get(name)
44
+ if (leaf) {
45
+ ctx.fillRect(0, leaf.x! + sy - rowHeight / 2, w, rowHeight)
46
+ }
47
+ }
60
48
 
61
- if (hoveredTreeNode) {
62
- ctx.fillStyle = treeHoverColor
63
- for (const descendantName of hoveredTreeNode.descendantNames) {
64
- fillRow(descendantName)
65
- }
66
- }
49
+ if (relativeTo) {
50
+ ctx.fillStyle = referenceColor
51
+ fillRow(relativeTo)
52
+ }
67
53
 
68
- if (
69
- mouseOverRowName &&
70
- mouseOverRowName !== relativeTo &&
71
- !hoveredTreeNode?.descendantNames.includes(mouseOverRowName)
72
- ) {
73
- ctx.fillStyle = treeHoverColor
74
- fillRow(mouseOverRowName)
75
- }
54
+ if (hoveredTreeNode) {
55
+ ctx.fillStyle = treeHoverColor
56
+ for (const descendantName of hoveredTreeNode.descendantNames) {
57
+ fillRow(descendantName)
76
58
  }
77
- })
78
- : undefined
79
- }, [model])
59
+ }
60
+
61
+ if (
62
+ mouseOverRowName &&
63
+ mouseOverRowName !== relativeTo &&
64
+ !hoveredTreeNode?.descendantNames.includes(mouseOverRowName)
65
+ ) {
66
+ ctx.fillStyle = treeHoverColor
67
+ fillRow(mouseOverRowName)
68
+ }
69
+ }
70
+ },
71
+ [model],
72
+ )
80
73
 
81
74
  return (
82
75
  <div
@@ -1,15 +1,16 @@
1
1
  import React, { useEffect, useRef, useState } from 'react'
2
2
 
3
+ import { makeStyles } from '@jbrowse/core/util/tss-react'
3
4
  import { useTheme } from '@mui/material'
4
- import Flatbush from 'flatbush'
5
- import { autorun } from 'mobx'
6
5
  import { observer } from 'mobx-react'
7
- import { makeStyles } from 'tss-react/mui'
8
6
 
9
7
  import TreeBranchMenu from './TreeBranchMenu.tsx'
10
8
  import TreeNodeMenu from './TreeNodeMenu.tsx'
9
+ import { ClickMapIndex } from './clickMap.ts'
11
10
  import { padding, renderTreeCanvas } from './renderTreeCanvas.ts'
11
+ import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
12
12
 
13
+ import type { ClickEntry } from './clickMap.ts'
13
14
  import type { MsaViewModel } from '../../model.ts'
14
15
 
15
16
  const useStyles = makeStyles()(theme => ({
@@ -36,55 +37,6 @@ interface TooltipData {
36
37
  y: number
37
38
  }
38
39
 
39
- interface ClickEntry {
40
- name: string
41
- id: string
42
- branch?: boolean
43
- minX: number
44
- maxX: number
45
- minY: number
46
- maxY: number
47
- }
48
-
49
- class ClickMapIndex {
50
- private flatbush: Flatbush | null = null
51
- private entries: ClickEntry[] = []
52
-
53
- clear() {
54
- this.flatbush = null
55
- this.entries = []
56
- }
57
-
58
- insert(entry: ClickEntry) {
59
- this.entries.push(entry)
60
- }
61
-
62
- finish() {
63
- if (this.entries.length === 0) {
64
- this.flatbush = null
65
- } else {
66
- this.flatbush = new Flatbush(this.entries.length)
67
- for (const entry of this.entries) {
68
- this.flatbush.add(entry.minX, entry.minY, entry.maxX, entry.maxY)
69
- }
70
- this.flatbush.finish()
71
- }
72
- }
73
-
74
- search(box: {
75
- minX: number
76
- maxX: number
77
- minY: number
78
- maxY: number
79
- }): ClickEntry[] {
80
- return (
81
- this.flatbush
82
- ?.search(box.minX, box.minY, box.maxX, box.maxY)
83
- .map(i => this.entries[i]!) ?? []
84
- )
85
- }
86
- }
87
-
88
40
  const TreeCanvasBlock = observer(function ({
89
41
  model,
90
42
  offsetY,
@@ -94,7 +46,6 @@ const TreeCanvasBlock = observer(function ({
94
46
  }) {
95
47
  const { classes } = useStyles()
96
48
  const theme = useTheme()
97
- const ref = useRef<HTMLCanvasElement>(null)
98
49
  const clickMap = useRef(new ClickMapIndex())
99
50
  const mouseoverRef = useRef<HTMLCanvasElement>(null)
100
51
  const [branchMenu, setBranchMenu] = useState<TooltipData>()
@@ -113,20 +64,10 @@ const TreeCanvasBlock = observer(function ({
113
64
  const w2 = width * highResScaleFactor
114
65
  const h2 = height * highResScaleFactor
115
66
 
116
- useEffect(() => {
117
- const ctx = ref.current?.getContext('2d')
118
- if (!ctx) {
119
- return
120
- }
121
-
122
- return autorun(() => {
67
+ const ref = useCanvasAutorun(
68
+ ctx => {
123
69
  ctx.resetTransform()
124
- ctx.clearRect(
125
- 0,
126
- 0,
127
- (treeAreaWidth + padding) * highResScaleFactor,
128
- blockSize * highResScaleFactor,
129
- )
70
+ ctx.clearRect(0, 0, w2, h2)
130
71
  renderTreeCanvas({
131
72
  ctx,
132
73
  model,
@@ -134,8 +75,9 @@ const TreeCanvasBlock = observer(function ({
134
75
  clickMap: clickMap.current,
135
76
  theme,
136
77
  })
137
- })
138
- }, [model, blockSize, highResScaleFactor, treeAreaWidth, offsetY, theme])
78
+ },
79
+ [model, blockSize, highResScaleFactor, treeAreaWidth, offsetY, theme],
80
+ )
139
81
 
140
82
  useEffect(() => {
141
83
  const ctx = mouseoverRef.current?.getContext('2d')
@@ -0,0 +1,55 @@
1
+ import Flatbush from 'flatbush'
2
+
3
+ export interface ClickEntry {
4
+ name: string
5
+ id: string
6
+ branch?: boolean
7
+ minX: number
8
+ maxX: number
9
+ minY: number
10
+ maxY: number
11
+ }
12
+
13
+ interface Box {
14
+ minX: number
15
+ maxX: number
16
+ minY: number
17
+ maxY: number
18
+ }
19
+
20
+ // Spatial index of clickable tree elements (leaf labels, node bubbles,
21
+ // collapsed-clade triangles). Entries are collected during a render pass, then
22
+ // finish() builds the Flatbush index queried by hit-testing.
23
+ export class ClickMapIndex {
24
+ private flatbush: Flatbush | null = null
25
+ private entries: ClickEntry[] = []
26
+
27
+ clear() {
28
+ this.flatbush = null
29
+ this.entries = []
30
+ }
31
+
32
+ insert(entry: ClickEntry) {
33
+ this.entries.push(entry)
34
+ }
35
+
36
+ finish() {
37
+ if (this.entries.length === 0) {
38
+ this.flatbush = null
39
+ } else {
40
+ this.flatbush = new Flatbush(this.entries.length)
41
+ for (const entry of this.entries) {
42
+ this.flatbush.add(entry.minX, entry.minY, entry.maxX, entry.maxY)
43
+ }
44
+ this.flatbush.finish()
45
+ }
46
+ }
47
+
48
+ search(box: Box): ClickEntry[] {
49
+ return (
50
+ this.flatbush
51
+ ?.search(box.minX, box.minY, box.maxX, box.maxY)
52
+ .map(i => this.entries[i]!) ?? []
53
+ )
54
+ }
55
+ }
@@ -5,6 +5,7 @@ import {
5
5
  } from '../../hierarchy.ts'
6
6
  import { setFontSize } from '../../setFontSize.ts'
7
7
 
8
+ import type { ClickMapIndex } from './clickMap.ts'
8
9
  import type { HierarchyNode } from '../../hierarchy.ts'
9
10
  import type { MsaViewModel } from '../../model.ts'
10
11
  import type { RenderCtx } from '../renderCtx.ts'
@@ -16,6 +17,12 @@ const extendBounds = 5
16
17
  const radius = 2.5
17
18
  const d = radius * 2
18
19
 
20
+ // whether a node's row-center y falls within the block being drawn, padded by
21
+ // extendBounds so bubbles/labels/triangles straddling the edge still render
22
+ function inYBlock(y: number, offsetY: number, by: number) {
23
+ return y > offsetY - extendBounds && y < offsetY + by + extendBounds
24
+ }
25
+
19
26
  // Calculate node x-coordinate for both phylogram (with branch lengths) and cladogram (topology only) modes
20
27
  // For cladograms: x = (maxDepthToLeaf - nodeDepthToLeaf) / maxDepthToLeaf * maxWidth
21
28
  // This positions: leaves at maxWidth (rightmost), root at 0 (leftmost), internal nodes proportionally in between
@@ -36,28 +43,6 @@ function getNodeX(
36
43
  return ((maxDepthToLeaf - depthToLeaf) / maxDepthToLeaf) * maxBranchLen
37
44
  }
38
45
 
39
- interface ClickEntry {
40
- name: string
41
- id: string
42
- branch?: boolean
43
- minX: number
44
- maxX: number
45
- minY: number
46
- maxY: number
47
- }
48
-
49
- interface ClickMapIndex {
50
- clear(): void
51
- insert(entry: ClickEntry): void
52
- finish(): void
53
- search(box: {
54
- minX: number
55
- maxX: number
56
- minY: number
57
- maxY: number
58
- }): ClickEntry[]
59
- }
60
-
61
46
  export function renderTree({
62
47
  offsetY,
63
48
  ctx,
@@ -137,8 +122,7 @@ export function renderCollapsedTriangles({
137
122
  if (collapsed.includes(id)) {
138
123
  const apexX = getNodeX(node, showBranchLen, maxBranchLen, maxDepthToLeaf)
139
124
  const y = node.x!
140
- const inBlock =
141
- y > offsetY - extendBounds && y < offsetY + by + extendBounds
125
+ const inBlock = inYBlock(y, offsetY, by)
142
126
  // in cladogram mode the hidden tips align at the right edge, otherwise use
143
127
  // the branch-length extent recorded in the model's hierarchy getter
144
128
  const baseX = showBranchLen
@@ -214,11 +198,7 @@ export function renderNodeBubbles({
214
198
  const { data } = node
215
199
  const y = node.x!
216
200
  const { id, name } = data
217
- if (
218
- node.height >= 1 &&
219
- y > offsetY - extendBounds &&
220
- y < offsetY + by + extendBounds
221
- ) {
201
+ if (node.height >= 1 && inYBlock(y, offsetY, by)) {
222
202
  const isCollapsed = collapsed.includes(id)
223
203
  ctx.strokeStyle = 'black'
224
204
  ctx.fillStyle = isCollapsed ? 'black' : 'white'
@@ -296,11 +276,7 @@ export function renderTreeLabels({
296
276
  // a collapsed clade is drawn as a triangle + tip count; suppress its leaf
297
277
  // label when the "name" is just the auto-generated internal-node id
298
278
  const isAnonymousCollapsed = collapsed.includes(id) && name === id
299
- if (
300
- !isAnonymousCollapsed &&
301
- y > offsetY - extendBounds &&
302
- y < offsetY + by + extendBounds
303
- ) {
279
+ if (!isAnonymousCollapsed && inYBlock(y, offsetY, by)) {
304
280
  // note: +rowHeight/4 matches with -rowHeight/4 in msa
305
281
  const yp = y + fontSize / 4
306
282
  let xp = 0
package/src/constants.ts CHANGED
@@ -9,6 +9,24 @@ export const defaultShowDomains = false
9
9
  export const defaultHideGaps = true
10
10
  export const defaultAllowedGappyness = 100
11
11
  export const defaultSubFeatureRows = false
12
+ // row height for each stacked feature when subFeatureRows is on
13
+ export const subFeatureRowHeight = 4
14
+
15
+ // Feature types that are ordinal *segments* of a single transcript (exons and
16
+ // the like) rather than categorical domains. Their identity is their position,
17
+ // so the overlay alternates two shades to mark boundaries and labels them by
18
+ // number, instead of assigning every one a distinct hue + legend row.
19
+ export const segmentFeatureTypes = new Set([
20
+ 'exon',
21
+ 'CDS',
22
+ 'five_prime_UTR',
23
+ 'three_prime_UTR',
24
+ 'UTR',
25
+ 'intron',
26
+ ])
27
+
28
+ // the two shades alternated across adjacent segments (exons)
29
+ export const segmentShades = ['#9fb6d4', '#d4dcea']
12
30
  export const defaultDrawMsaLetters = true
13
31
  export const defaultScrollZoom = false
14
32
 
@@ -22,6 +40,7 @@ export const maxCellSize = 80
22
40
  // MSA model defaults
23
41
  export const defaultBgColor = true
24
42
  export const defaultColorSchemeName = 'maeditor'
43
+ export const defaultShowColumnStats = true
25
44
 
26
45
  // Tree model defaults
27
46
  export const defaultDrawLabels = true
@@ -1,11 +1,24 @@
1
+ import { colord } from 'colord'
2
+
1
3
  import palettes from './ggplotPalettes.ts'
2
4
 
3
5
  /**
4
- * Creates a map from keys to colors using ggplot-style palettes.
5
- * The palette is selected based on the number of keys.
6
+ * Creates a map from keys to colors. Up to the largest ggplot palette (8 keys)
7
+ * we use the hand-picked ggplot colors; beyond that e.g. a gene-structure
8
+ * overlay with one feature per exon — we generate evenly spaced HSL hues so
9
+ * every key still gets a distinct, stable color instead of running off the end
10
+ * of the palette.
6
11
  */
7
12
  export function createPaletteMap(keys: string[]) {
8
- const k = Math.min(keys.length - 1, palettes.length - 1)
9
- const palette = palettes[k]!
10
- return Object.fromEntries(keys.map((key, i) => [key, palette[i]!]))
13
+ const n = keys.length
14
+ if (n <= palettes.length) {
15
+ const palette = palettes[n - 1]!
16
+ return Object.fromEntries(keys.map((key, i) => [key, palette[i]!]))
17
+ }
18
+ return Object.fromEntries(
19
+ keys.map((key, i) => [
20
+ key,
21
+ colord({ h: (i * 360) / n, s: 60, l: 60 }).toHex(),
22
+ ]),
23
+ )
11
24
  }
package/src/fetchUtils.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { fetchAndMaybeUnzipText } from '@jbrowse/core/util'
1
+ import { fetchAndMaybeUnzipText, statusMessageText } from '@jbrowse/core/util'
2
+
3
+ import type { RpcStatus } from '@jbrowse/core/util'
2
4
 
3
5
  export interface FetchStatus {
4
6
  msg: string
@@ -10,7 +12,7 @@ type Filehandle = Parameters<typeof fetchAndMaybeUnzipText>[0]
10
12
 
11
13
  type ProgressFetcher = (
12
14
  loc: Filehandle,
13
- opts: { signal: AbortSignal; statusCallback: (msg: string) => void },
15
+ opts: { signal: AbortSignal; statusCallback: (status: RpcStatus) => void },
14
16
  ) => Promise<string>
15
17
 
16
18
  /**
@@ -28,7 +30,8 @@ export async function fetchTextWithProgress(
28
30
  try {
29
31
  return await fetcher(loc, {
30
32
  signal: controller.signal,
31
- statusCallback: msg => {
33
+ statusCallback: status => {
34
+ const msg = statusMessageText(status)
32
35
  setStatus(
33
36
  msg
34
37
  ? {
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ export { renderToSvg } from './renderToSvg.tsx'
9
9
  export { default as MSAView } from './components/Loading.tsx'
10
10
  export { default as MSAViewer } from './components/MSAViewer.tsx'
11
11
  export { type MsaViewModel, default as MSAModelF } from './model.ts'
12
- export type { MSAParserType } from 'msa-parsers'
12
+ export type { MSAFormat, MSAParserType } from 'msa-parsers'
13
13
  export type { HierarchyNode } from './hierarchy.ts'
14
14
  export type { InterProScanResults } from './launchInterProScan.ts'
15
15
  export type {
@@ -13,9 +13,11 @@ export interface InterProScanResults {
13
13
  name: string
14
14
  description: string
15
15
  accession: string
16
+ // original GFF feature type (exon, CDS, ...) when sourced from GFF
17
+ featureType?: string
16
18
  }
17
19
  }
18
- locations: { start: number; end: number }[]
20
+ locations: { start: number; end: number; strand?: number }[]
19
21
  }[]
20
22
  xref: { id: string }[]
21
23
  }
@@ -1,6 +1,10 @@
1
1
  import { types } from '@jbrowse/mobx-state-tree'
2
2
 
3
- import { defaultBgColor, defaultColorSchemeName } from '../constants.ts'
3
+ import {
4
+ defaultBgColor,
5
+ defaultColorSchemeName,
6
+ defaultShowColumnStats,
7
+ } from '../constants.ts'
4
8
 
5
9
  import type { MSAFormat } from 'msa-parsers'
6
10
 
@@ -16,13 +20,20 @@ export function MSAModelF() {
16
20
  * #property
17
21
  * draw MSA tiles with a background color
18
22
  */
19
- bgColor: defaultBgColor,
23
+ bgColor: types.stripDefault(types.boolean, defaultBgColor),
20
24
 
21
25
  /**
22
26
  * #property
23
27
  * default color scheme name
24
28
  */
25
- colorSchemeName: defaultColorSchemeName,
29
+ colorSchemeName: types.stripDefault(types.string, defaultColorSchemeName),
30
+
31
+ /**
32
+ * #property
33
+ * show a per-column statistics tooltip (consensus, conservation, gaps,
34
+ * residue distribution) while hovering the alignment
35
+ */
36
+ showColumnStats: types.stripDefault(types.boolean, defaultShowColumnStats),
26
37
 
27
38
  /**
28
39
  * #property
@@ -49,6 +60,13 @@ export function MSAModelF() {
49
60
  self.bgColor = arg
50
61
  },
51
62
 
63
+ /**
64
+ * #action
65
+ */
66
+ setShowColumnStats(arg: boolean) {
67
+ self.showColumnStats = arg
68
+ },
69
+
52
70
  /**
53
71
  * #action
54
72
  * force a specific MSA parser, or pass undefined to auto-detect