react-msaview 8.2.0 → 8.4.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 (255) hide show
  1. package/bundle/index.js +77 -300
  2. package/bundle/index.js.map +4 -4
  3. package/dist/blockFasta.d.ts +6 -0
  4. package/dist/blockFasta.js +12 -0
  5. package/dist/blockFasta.js.map +1 -0
  6. package/dist/colorSchemes.d.ts +7 -0
  7. package/dist/colorSchemes.js +15 -0
  8. package/dist/colorSchemes.js.map +1 -1
  9. package/dist/columnCounts.d.ts +4 -3
  10. package/dist/columnCounts.js +11 -6
  11. package/dist/columnCounts.js.map +1 -1
  12. package/dist/components/CopyButton.d.ts +1 -1
  13. package/dist/components/CopyButton.js +1 -1
  14. package/dist/components/CopyButton.js.map +1 -1
  15. package/dist/components/DragHandle.js +14 -2
  16. package/dist/components/DragHandle.js.map +1 -1
  17. package/dist/components/Loading.js +10 -11
  18. package/dist/components/Loading.js.map +1 -1
  19. package/dist/components/MSAView.js +15 -9
  20. package/dist/components/MSAView.js.map +1 -1
  21. package/dist/components/MSAViewer.d.ts +39 -7
  22. package/dist/components/MSAViewer.js +72 -22
  23. package/dist/components/MSAViewer.js.map +1 -1
  24. package/dist/components/MetadataTable.d.ts +5 -0
  25. package/dist/components/MetadataTable.js +29 -0
  26. package/dist/components/MetadataTable.js.map +1 -0
  27. package/dist/components/SequenceTextArea.d.ts +11 -0
  28. package/dist/components/SequenceTextArea.js +40 -8
  29. package/dist/components/SequenceTextArea.js.map +1 -1
  30. package/dist/components/StatusMessage.d.ts +12 -0
  31. package/dist/components/StatusMessage.js +43 -0
  32. package/dist/components/StatusMessage.js.map +1 -0
  33. package/dist/components/Track.js +13 -4
  34. package/dist/components/Track.js.map +1 -1
  35. package/dist/components/VerticalScrollbar.js +6 -1
  36. package/dist/components/VerticalScrollbar.js.map +1 -1
  37. package/dist/components/dialogs/ExportSVGDialog.d.ts +3 -2
  38. package/dist/components/dialogs/ExportSVGDialog.js +4 -2
  39. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  40. package/dist/components/dialogs/FeatureDialog.js +15 -10
  41. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  42. package/dist/components/dialogs/MetadataDialog.js +5 -6
  43. package/dist/components/dialogs/MetadataDialog.js.map +1 -1
  44. package/dist/components/getVisibleLeaves.d.ts +1 -1
  45. package/dist/components/header/ColorSchemeMenu.d.ts +6 -0
  46. package/dist/components/header/ColorSchemeMenu.js +23 -5
  47. package/dist/components/header/ColorSchemeMenu.js.map +1 -1
  48. package/dist/components/header/FileMenu.js +9 -0
  49. package/dist/components/header/FileMenu.js.map +1 -1
  50. package/dist/components/header/GappynessSlider.js +15 -3
  51. package/dist/components/header/GappynessSlider.js.map +1 -1
  52. package/dist/components/header/{HeaderStatusArea.d.ts → GoToBox.d.ts} +2 -2
  53. package/dist/components/header/GoToBox.js +21 -0
  54. package/dist/components/header/GoToBox.js.map +1 -0
  55. package/dist/components/header/Header.js +8 -7
  56. package/dist/components/header/Header.js.map +1 -1
  57. package/dist/components/header/SelectionMenu.d.ts +12 -0
  58. package/dist/components/header/SelectionMenu.js +72 -0
  59. package/dist/components/header/SelectionMenu.js.map +1 -0
  60. package/dist/components/header/goTo.d.ts +20 -0
  61. package/dist/components/header/goTo.js +69 -0
  62. package/dist/components/header/goTo.js.map +1 -0
  63. package/dist/components/header/settingsMenuItems.js +57 -1
  64. package/dist/components/header/settingsMenuItems.js.map +1 -1
  65. package/dist/components/import/ImportForm.js +10 -4
  66. package/dist/components/import/ImportForm.js.map +1 -1
  67. package/dist/components/import/ImportFormExamples.js +19 -27
  68. package/dist/components/import/ImportFormExamples.js.map +1 -1
  69. package/dist/components/minimap/Minimap.js +6 -1
  70. package/dist/components/minimap/Minimap.js.map +1 -1
  71. package/dist/components/msa/AnnotationLegend.js +11 -5
  72. package/dist/components/msa/AnnotationLegend.js.map +1 -1
  73. package/dist/components/msa/MSACanvas.js +17 -1
  74. package/dist/components/msa/MSACanvas.js.map +1 -1
  75. package/dist/components/msa/MSACanvasBlock.js +6 -6
  76. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  77. package/dist/components/msa/msaKeys.d.ts +5 -0
  78. package/dist/components/msa/msaKeys.js +41 -0
  79. package/dist/components/msa/msaKeys.js.map +1 -0
  80. package/dist/components/msa/msaRaster.js +1 -0
  81. package/dist/components/msa/msaRaster.js.map +1 -1
  82. package/dist/components/msa/renderHighlights.d.ts +14 -3
  83. package/dist/components/msa/renderHighlights.js +29 -4
  84. package/dist/components/msa/renderHighlights.js.map +1 -1
  85. package/dist/components/msa/tileColor.js +9 -8
  86. package/dist/components/msa/tileColor.js.map +1 -1
  87. package/dist/components/msa/useMsaBlockMouse.d.ts +2 -4
  88. package/dist/components/msa/useMsaBlockMouse.js +34 -14
  89. package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
  90. package/dist/components/overlayColors.d.ts +2 -0
  91. package/dist/components/overlayColors.js +3 -0
  92. package/dist/components/overlayColors.js.map +1 -1
  93. package/dist/components/rowpanels/RowPanelBlock.js +10 -14
  94. package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
  95. package/dist/components/selectionDrag.d.ts +17 -0
  96. package/dist/components/selectionDrag.js +30 -0
  97. package/dist/components/selectionDrag.js.map +1 -0
  98. package/dist/components/tracks/drawTracks.d.ts +1 -0
  99. package/dist/components/tracks/drawTracks.js +14 -2
  100. package/dist/components/tracks/drawTracks.js.map +1 -1
  101. package/dist/components/tracks/useTrackHover.d.ts +4 -6
  102. package/dist/components/tracks/useTrackHover.js +30 -10
  103. package/dist/components/tracks/useTrackHover.js.map +1 -1
  104. package/dist/components/tree/ScaleBarSVG.d.ts +14 -0
  105. package/dist/components/tree/ScaleBarSVG.js +11 -0
  106. package/dist/components/tree/ScaleBarSVG.js.map +1 -0
  107. package/dist/components/tree/TreeCanvas.js +8 -26
  108. package/dist/components/tree/TreeCanvas.js.map +1 -1
  109. package/dist/components/tree/TreeCanvasBlock.js +11 -20
  110. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  111. package/dist/components/tree/{TreeBranchMenu.d.ts → TreeMenu.d.ts} +5 -4
  112. package/dist/components/tree/TreeMenu.js +48 -0
  113. package/dist/components/tree/TreeMenu.js.map +1 -0
  114. package/dist/components/tree/TreeRuler.js +3 -3
  115. package/dist/components/tree/TreeRuler.js.map +1 -1
  116. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js +9 -8
  117. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js.map +1 -1
  118. package/dist/components/tree/renderTreeCanvas.js +19 -9
  119. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  120. package/dist/components/tree/renderTreeMouseover.d.ts +5 -0
  121. package/dist/components/tree/renderTreeMouseover.js +20 -0
  122. package/dist/components/tree/renderTreeMouseover.js.map +1 -0
  123. package/dist/components/tree/useTreeHover.d.ts +3 -8
  124. package/dist/components/tree/useTreeHover.js +8 -10
  125. package/dist/components/tree/useTreeHover.js.map +1 -1
  126. package/dist/components/useHoverAnchor.d.ts +19 -0
  127. package/dist/components/useHoverAnchor.js +18 -0
  128. package/dist/components/useHoverAnchor.js.map +1 -0
  129. package/dist/constants.d.ts +3 -0
  130. package/dist/constants.js +12 -3
  131. package/dist/constants.js.map +1 -1
  132. package/dist/element.d.ts +7 -3
  133. package/dist/element.js +32 -4
  134. package/dist/element.js.map +1 -1
  135. package/dist/expandSpec.js +3 -3
  136. package/dist/expandSpec.js.map +1 -1
  137. package/dist/exportFileName.d.ts +3 -3
  138. package/dist/exportFileName.js +4 -4
  139. package/dist/exportFileName.js.map +1 -1
  140. package/dist/featuresLayer.d.ts +8 -0
  141. package/dist/featuresLayer.js +42 -0
  142. package/dist/featuresLayer.js.map +1 -0
  143. package/dist/hierarchy.d.ts +51 -0
  144. package/dist/hierarchy.js +125 -0
  145. package/dist/hierarchy.js.map +1 -1
  146. package/dist/index.d.ts +2 -2
  147. package/dist/model/DataModel.d.ts +1 -1
  148. package/dist/model/DataModel.js +3 -3
  149. package/dist/model/DataModel.js.map +1 -1
  150. package/dist/model/msaModel.d.ts +14 -1
  151. package/dist/model/msaModel.js +17 -1
  152. package/dist/model/msaModel.js.map +1 -1
  153. package/dist/model/treeModel.d.ts +12 -0
  154. package/dist/model/treeModel.js +14 -1
  155. package/dist/model/treeModel.js.map +1 -1
  156. package/dist/model.d.ts +285 -53
  157. package/dist/model.js +693 -231
  158. package/dist/model.js.map +1 -1
  159. package/dist/renderToSvg.js +2 -4
  160. package/dist/renderToSvg.js.map +1 -1
  161. package/dist/rerootTree.d.ts +33 -0
  162. package/dist/rerootTree.js +144 -0
  163. package/dist/rerootTree.js.map +1 -0
  164. package/dist/svgTestUtil.d.ts +172 -26
  165. package/dist/types.d.ts +75 -12
  166. package/dist/useMsaSvgFigure.d.ts +2 -1
  167. package/dist/useMsaSvgFigure.js +11 -4
  168. package/dist/useMsaSvgFigure.js.map +1 -1
  169. package/dist/version.d.ts +1 -1
  170. package/dist/version.js +1 -1
  171. package/dist/writeNewick.d.ts +8 -0
  172. package/dist/writeNewick.js +34 -0
  173. package/dist/writeNewick.js.map +1 -0
  174. package/package.json +3 -3
  175. package/src/blockFasta.ts +18 -0
  176. package/src/colorSchemes.ts +20 -0
  177. package/src/columnCounts.ts +14 -6
  178. package/src/components/CopyButton.tsx +2 -2
  179. package/src/components/DragHandle.tsx +21 -1
  180. package/src/components/Loading.tsx +14 -30
  181. package/src/components/MSAView.tsx +16 -9
  182. package/src/components/MSAViewer.tsx +121 -26
  183. package/src/components/MetadataTable.tsx +55 -0
  184. package/src/components/SequenceTextArea.tsx +54 -8
  185. package/src/components/StatusMessage.tsx +70 -0
  186. package/src/components/Track.tsx +21 -4
  187. package/src/components/VerticalScrollbar.tsx +7 -0
  188. package/src/components/dialogs/ExportSVGDialog.tsx +5 -2
  189. package/src/components/dialogs/FeatureDialog.tsx +59 -38
  190. package/src/components/dialogs/MetadataDialog.tsx +9 -8
  191. package/src/components/header/ColorSchemeMenu.tsx +31 -12
  192. package/src/components/header/FileMenu.tsx +9 -0
  193. package/src/components/header/GappynessSlider.tsx +17 -4
  194. package/src/components/header/GoToBox.tsx +50 -0
  195. package/src/components/header/Header.tsx +11 -8
  196. package/src/components/header/SelectionMenu.tsx +117 -0
  197. package/src/components/header/goTo.ts +75 -0
  198. package/src/components/header/settingsMenuItems.ts +66 -0
  199. package/src/components/import/ImportForm.tsx +14 -7
  200. package/src/components/import/ImportFormExamples.tsx +20 -28
  201. package/src/components/minimap/Minimap.tsx +7 -0
  202. package/src/components/msa/AnnotationLegend.tsx +12 -6
  203. package/src/components/msa/MSACanvas.tsx +19 -1
  204. package/src/components/msa/MSACanvasBlock.tsx +9 -11
  205. package/src/components/msa/msaKeys.ts +45 -0
  206. package/src/components/msa/msaRaster.ts +1 -0
  207. package/src/components/msa/renderHighlights.ts +46 -4
  208. package/src/components/msa/tileColor.ts +9 -8
  209. package/src/components/msa/useMsaBlockMouse.ts +44 -31
  210. package/src/components/overlayColors.ts +4 -0
  211. package/src/components/rowpanels/RowPanelBlock.tsx +16 -23
  212. package/src/components/selectionDrag.ts +42 -0
  213. package/src/components/tracks/drawTracks.ts +15 -2
  214. package/src/components/tracks/useTrackHover.ts +32 -10
  215. package/src/components/tree/ScaleBarSVG.tsx +30 -0
  216. package/src/components/tree/TreeCanvas.tsx +9 -40
  217. package/src/components/tree/TreeCanvasBlock.tsx +19 -39
  218. package/src/components/tree/TreeMenu.tsx +129 -0
  219. package/src/components/tree/TreeRuler.tsx +7 -13
  220. package/src/components/tree/dialogs/TreeNodeInfoDialog.tsx +14 -16
  221. package/src/components/tree/renderTreeCanvas.ts +20 -8
  222. package/src/components/tree/renderTreeMouseover.ts +41 -0
  223. package/src/components/tree/useTreeHover.ts +8 -17
  224. package/src/components/useHoverAnchor.ts +31 -0
  225. package/src/constants.ts +14 -3
  226. package/src/element.ts +45 -5
  227. package/src/expandSpec.ts +3 -3
  228. package/src/exportFileName.ts +4 -3
  229. package/src/featuresLayer.ts +48 -0
  230. package/src/hierarchy.ts +151 -4
  231. package/src/index.ts +8 -1
  232. package/src/model/DataModel.ts +3 -3
  233. package/src/model/msaModel.ts +19 -1
  234. package/src/model/treeModel.ts +22 -0
  235. package/src/model.ts +841 -272
  236. package/src/renderToSvg.tsx +6 -9
  237. package/src/rerootTree.ts +172 -0
  238. package/src/types.ts +92 -12
  239. package/src/useMsaSvgFigure.ts +11 -4
  240. package/src/version.ts +1 -1
  241. package/src/writeNewick.ts +38 -0
  242. package/dist/components/PortalTooltip.d.ts +0 -11
  243. package/dist/components/PortalTooltip.js +0 -29
  244. package/dist/components/PortalTooltip.js.map +0 -1
  245. package/dist/components/header/HeaderStatusArea.js +0 -21
  246. package/dist/components/header/HeaderStatusArea.js.map +0 -1
  247. package/dist/components/tree/TreeBranchMenu.js +0 -29
  248. package/dist/components/tree/TreeBranchMenu.js.map +0 -1
  249. package/dist/components/tree/TreeNodeMenu.d.ts +0 -13
  250. package/dist/components/tree/TreeNodeMenu.js +0 -41
  251. package/dist/components/tree/TreeNodeMenu.js.map +0 -1
  252. package/src/components/PortalTooltip.tsx +0 -47
  253. package/src/components/header/HeaderStatusArea.tsx +0 -32
  254. package/src/components/tree/TreeBranchMenu.tsx +0 -68
  255. package/src/components/tree/TreeNodeMenu.tsx +0 -95
@@ -159,6 +159,20 @@ export function rulerStep(colWidth: number, minPixels = 55) {
159
159
  return step
160
160
  }
161
161
 
162
+ // The 0-based columns in [xStart, end) to label: column 1, then each column
163
+ // whose 1-based number is a multiple of the step. A step of 2 or more puts the
164
+ // first multiple at least half a step's width past column 1, which clears the
165
+ // two labels.
166
+ export function rulerCols(xStart: number, end: number, colWidth: number) {
167
+ const step = rulerStep(colWidth)
168
+ const cols = step > 1 && xStart <= 0 && end > 0 ? [0] : []
169
+ const first = Math.max(1, Math.ceil((xStart + 1) / step)) * step
170
+ for (let n = first; n <= end; n += step) {
171
+ cols.push(n - 1)
172
+ }
173
+ return cols
174
+ }
175
+
162
176
  const RULER_FONT_SIZE = 10
163
177
  const TICK_HEIGHT = 4
164
178
 
@@ -186,7 +200,6 @@ export function drawColumnRuler({
186
200
  blockWidth: blockSize,
187
201
  colWidth,
188
202
  })
189
- const step = rulerStep(colWidth)
190
203
  const end = Math.min(xEnd, numColumns)
191
204
  setFontSize(ctx, RULER_FONT_SIZE)
192
205
  ctx.textAlign = 'center'
@@ -195,7 +208,7 @@ export function drawColumnRuler({
195
208
  ctx.strokeStyle = textColor
196
209
  ctx.lineWidth = 1
197
210
 
198
- for (let col = Math.ceil(xStart / step) * step; col < end; col += step) {
211
+ for (const col of rulerCols(xStart, end, colWidth)) {
199
212
  const text = label(col)
200
213
  if (text === undefined) {
201
214
  continue
@@ -1,4 +1,5 @@
1
- import { useState } from 'react'
1
+ import { dragSelection } from '../selectionDrag.ts'
2
+ import { useHoverAnchor } from '../useHoverAnchor.ts'
2
3
 
3
4
  import type { MsaViewModel } from '../../model.ts'
4
5
  import type React from 'react'
@@ -6,7 +7,8 @@ import type React from 'react'
6
7
  /**
7
8
  * Translates pointer events over a track's row into the hovered column and a
8
9
  * tooltip anchor. The track shares the alignment's columns, so hovering it sets
9
- * the model's mouse column too and the alignment's hover band follows along.
10
+ * the model's mouse column too and the alignment's hover band follows along. A
11
+ * drag along the row selects the columns it covers across every row.
10
12
  *
11
13
  * `tooltip` off skips the anchor, so a track whose tooltip is turned off still
12
14
  * drives the hover band without re-rendering on every mouse move.
@@ -18,25 +20,45 @@ export function useTrackHover({
18
20
  model: MsaViewModel
19
21
  tooltip: boolean
20
22
  }) {
21
- const [hover, setHover] = useState<{ col: number; x: number; y: number }>()
23
+ const { anchor, hoverAt, clearAnchor } = useHoverAnchor<number>()
22
24
 
23
25
  // the blocks are laid out in column space and scrolled by a transform on
24
26
  // their container, so the pointer's column comes off the untransformed row
27
+ function colAt(clientX: number, left: number) {
28
+ return Math.floor((clientX - left - model.scrollX) / model.colWidth)
29
+ }
30
+
25
31
  function onMouseMove(event: React.MouseEvent, el: HTMLElement) {
26
- const { colWidth, scrollX, numColumns } = model
27
- const { left } = el.getBoundingClientRect()
28
- const col = Math.floor((event.clientX - left - scrollX) / colWidth)
29
- const hit = col >= 0 && col < numColumns
32
+ const col = colAt(event.clientX, el.getBoundingClientRect().left)
33
+ const hit = col >= 0 && col < model.numColumns
30
34
  model.setMousePos(hit ? col : undefined, undefined)
31
35
  if (tooltip) {
32
- setHover(hit ? { col, x: event.clientX, y: event.clientY } : undefined)
36
+ hoverAt(event, hit ? col : undefined)
37
+ }
38
+ }
39
+
40
+ // a press on a track's canvas, which leaves out the resize divider under it.
41
+ // The row takes the focus, so Escape reaches onMsaKey from there too
42
+ function onMouseDown(event: React.MouseEvent, el: HTMLElement) {
43
+ if (event.button === 0 && event.target instanceof HTMLCanvasElement) {
44
+ event.preventDefault()
45
+ el.focus({ preventScroll: true })
46
+ const { left } = el.getBoundingClientRect()
47
+ dragSelection(model, event, ({ clientX }) => ({
48
+ col: colAt(clientX, left),
49
+ }))
33
50
  }
34
51
  }
35
52
 
36
53
  function onMouseLeave() {
37
- setHover(undefined)
54
+ clearAnchor()
38
55
  model.setMousePos(undefined, undefined)
39
56
  }
40
57
 
41
- return { hover: tooltip ? hover : undefined, onMouseMove, onMouseLeave }
58
+ return {
59
+ anchor: tooltip ? anchor : undefined,
60
+ onMouseMove,
61
+ onMouseDown,
62
+ onMouseLeave,
63
+ }
42
64
  }
@@ -0,0 +1,30 @@
1
+ import React from 'react'
2
+
3
+ /**
4
+ * The branch-length scale bar: its label over a bracket `bar.px` wide, both
5
+ * resting on `y`.
6
+ */
7
+ export default function ScaleBarSVG({
8
+ bar,
9
+ marginLeft,
10
+ y,
11
+ color,
12
+ }: {
13
+ bar: { label: string; px: number }
14
+ marginLeft: number
15
+ y: number
16
+ color: string
17
+ }) {
18
+ return (
19
+ <>
20
+ <text x={marginLeft} y={y - 9} fontSize={10} fill={color}>
21
+ {bar.label}
22
+ </text>
23
+ <path
24
+ d={`M${marginLeft} ${y - 7} v6 h${bar.px} v-6`}
25
+ fill="none"
26
+ stroke={color}
27
+ />
28
+ </>
29
+ )
30
+ }
@@ -5,15 +5,15 @@ import { observer } from 'mobx-react'
5
5
 
6
6
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
7
7
  import { useWheelScroll } from '../../useWheelScroll.ts'
8
- import { referenceColor, treeHoverColor } from '../overlayColors.ts'
9
8
  import CladeLabels from './CladeLabels.tsx'
10
9
  import TreeCanvasBlock from './TreeCanvasBlock.tsx'
10
+ import { renderTreeMouseover } from './renderTreeMouseover.ts'
11
11
 
12
12
  import type { MsaViewModel } from '../../model.ts'
13
13
 
14
14
  const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
15
15
  const ref = useRef<HTMLDivElement>(null)
16
- const { height, blocksY, treeAreaWidth, scrollY } = model
16
+ const { height, blocksY, treeAreaWidth, scrollY, highResScaleFactor } = model
17
17
  const onScrollY = useCallback(
18
18
  (d: number) => {
19
19
  model.doScrollY(d)
@@ -22,47 +22,16 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
22
22
  )
23
23
  const { onMouseDown, onMouseUp } = useWheelScroll({ ref, onScrollY })
24
24
 
25
+ const canvasWidth = treeAreaWidth * highResScaleFactor
26
+ const canvasHeight = height * highResScaleFactor
25
27
  const mouseoverRef = useCanvasAutorun({
26
28
  draw: ctx => {
27
29
  if (isAlive(model)) {
28
- const {
29
- rowHeight,
30
- treeAreaWidth: w,
31
- height: h,
32
- scrollY: sy,
33
- mouseRow,
34
- referenceRowIndex,
35
- hoveredRowIndices,
36
- } = model
37
- ctx.resetTransform()
38
- ctx.clearRect(0, 0, w, h)
39
-
40
- // rows are laid out at a fixed pitch, so a row index is all that is
41
- // needed to place its band (leaf.x is rowHeight*(index+0.5))
42
- const fillRow = (index: number) => {
43
- ctx.fillRect(0, index * rowHeight + sy, w, rowHeight)
44
- }
45
-
46
- if (referenceRowIndex !== undefined) {
47
- ctx.fillStyle = referenceColor
48
- fillRow(referenceRowIndex)
49
- }
50
-
51
- ctx.fillStyle = treeHoverColor
52
- for (const index of hoveredRowIndices) {
53
- fillRow(index)
54
- }
55
- if (
56
- mouseRow !== undefined &&
57
- mouseRow !== referenceRowIndex &&
58
- !hoveredRowIndices.includes(mouseRow)
59
- ) {
60
- fillRow(mouseRow)
61
- }
30
+ renderTreeMouseover({ ctx, model })
62
31
  }
63
32
  },
64
- width: treeAreaWidth,
65
- height,
33
+ width: canvasWidth,
34
+ height: canvasHeight,
66
35
  deps: [model],
67
36
  })
68
37
 
@@ -95,8 +64,8 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
95
64
  </div>
96
65
  <canvas
97
66
  ref={mouseoverRef}
98
- width={treeAreaWidth}
99
- height={height}
67
+ width={canvasWidth}
68
+ height={canvasHeight}
100
69
  style={{
101
70
  position: 'absolute',
102
71
  top: 0,
@@ -1,17 +1,17 @@
1
1
  import React, { useState } from 'react'
2
2
 
3
+ import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
3
4
  import { makeStyles } from '@jbrowse/core/util/tss-react'
4
5
  import { useTheme } from '@mui/material'
5
6
  import { observer } from 'mobx-react'
6
7
 
7
8
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
8
- import PortalTooltip from '../PortalTooltip.tsx'
9
- import TreeBranchMenu from './TreeBranchMenu.tsx'
10
- import TreeNodeMenu from './TreeNodeMenu.tsx'
9
+ import TreeMenu from './TreeMenu.tsx'
11
10
  import { renderTreeCanvas } from './renderTreeCanvas.ts'
12
11
  import { useTreeHover } from './useTreeHover.ts'
13
12
 
14
13
  import type { MsaViewModel } from '../../model.ts'
14
+ import type { TreeMenuTarget } from './TreeMenu.tsx'
15
15
 
16
16
  const useStyles = makeStyles()(theme => ({
17
17
  hover: {
@@ -22,13 +22,6 @@ const useStyles = makeStyles()(theme => ({
22
22
  },
23
23
  }))
24
24
 
25
- interface MenuData {
26
- name: string
27
- id: string
28
- x: number
29
- y: number
30
- }
31
-
32
25
  const TreeCanvasBlock = observer(function ({
33
26
  model,
34
27
  offsetY,
@@ -38,10 +31,11 @@ const TreeCanvasBlock = observer(function ({
38
31
  }) {
39
32
  const { classes } = useStyles()
40
33
  const theme = useTheme()
41
- const [branchMenu, setBranchMenu] = useState<MenuData>()
42
- const [nodeMenu, setNodeMenu] = useState<MenuData>()
43
- const { clickMap, hovered, hitTest, onMouseMove, onMouseLeave } =
44
- useTreeHover({ model, offsetY })
34
+ const [menu, setMenu] = useState<TreeMenuTarget>()
35
+ const { clickMap, anchor, hitTest, onMouseMove, onMouseLeave } = useTreeHover(
36
+ { model, offsetY },
37
+ )
38
+ const hovered = anchor?.value
45
39
 
46
40
  const { treeAreaWidth, blockSize, highResScaleFactor } = model
47
41
  // TreePanel clips to treeAreaWidth, so a wider canvas is wasted backing store
@@ -77,22 +71,12 @@ const TreeCanvasBlock = observer(function ({
77
71
 
78
72
  return (
79
73
  <>
80
- {branchMenu ? (
81
- <TreeBranchMenu
82
- node={branchMenu}
83
- model={model}
84
- onClose={() => {
85
- setBranchMenu(undefined)
86
- }}
87
- />
88
- ) : null}
89
-
90
- {nodeMenu ? (
91
- <TreeNodeMenu
92
- node={nodeMenu}
74
+ {menu ? (
75
+ <TreeMenu
76
+ node={menu}
93
77
  model={model}
94
78
  onClose={() => {
95
- setNodeMenu(undefined)
79
+ setMenu(undefined)
96
80
  }}
97
81
  />
98
82
  ) : null}
@@ -111,17 +95,13 @@ const TreeCanvasBlock = observer(function ({
111
95
  onClick={event => {
112
96
  const entry = hitTest(event)
113
97
  if (entry) {
114
- const menu = {
98
+ setMenu({
115
99
  x: event.clientX,
116
100
  y: event.clientY,
117
101
  id: entry.id,
118
102
  name: entry.name,
119
- }
120
- if (entry.branch) {
121
- setBranchMenu(menu)
122
- } else {
123
- setNodeMenu(menu)
124
- }
103
+ leaf: !entry.branch,
104
+ })
125
105
  }
126
106
  }}
127
107
  />
@@ -139,10 +119,10 @@ const TreeCanvasBlock = observer(function ({
139
119
  />
140
120
  ) : null}
141
121
 
142
- {hovered ? (
143
- <PortalTooltip clientX={hovered.clientX} clientY={hovered.clientY}>
144
- {hovered.name}
145
- </PortalTooltip>
122
+ {anchor ? (
123
+ <BaseTooltip clientPoint={anchor.clientPoint}>
124
+ {anchor.value.name}
125
+ </BaseTooltip>
146
126
  ) : null}
147
127
  </>
148
128
  )
@@ -0,0 +1,129 @@
1
+ import React, { lazy } from 'react'
2
+
3
+ import { Menu, MenuItem } from '@mui/material'
4
+ import { observer } from 'mobx-react'
5
+
6
+ import type { MsaViewModel } from '../../model.ts'
7
+
8
+ const TreeNodeInfoDialog = lazy(
9
+ () => import('./dialogs/TreeNodeInfoDialog.tsx'),
10
+ )
11
+
12
+ export interface TreeMenuTarget {
13
+ x: number
14
+ y: number
15
+ name: string
16
+ id: string
17
+ leaf: boolean
18
+ }
19
+
20
+ const TreeMenu = observer(function ({
21
+ node,
22
+ model,
23
+ onClose,
24
+ }: {
25
+ node: TreeMenuTarget
26
+ model: MsaViewModel
27
+ onClose: () => void
28
+ }) {
29
+ const { collapsed, showOnly, relativeTo } = model
30
+ const { name, id, leaf } = node
31
+ const isCollapsed = collapsed.includes(id)
32
+ const isRoot = id === model.tree.id
33
+ const noun = leaf ? 'subtree' : 'this node'
34
+ return (
35
+ <Menu
36
+ anchorReference="anchorPosition"
37
+ anchorPosition={{ left: node.x, top: node.y }}
38
+ transitionDuration={0}
39
+ open
40
+ onClose={onClose}
41
+ >
42
+ <MenuItem dense disabled>
43
+ {name}
44
+ </MenuItem>
45
+ {leaf ? (
46
+ <MenuItem
47
+ dense
48
+ onClick={() => {
49
+ model.queueDialog(onClose => [
50
+ TreeNodeInfoDialog,
51
+ { info: model.getRowData(name), model, nodeName: name, onClose },
52
+ ])
53
+ onClose()
54
+ }}
55
+ >
56
+ More info...
57
+ </MenuItem>
58
+ ) : null}
59
+ <MenuItem
60
+ dense
61
+ onClick={() => {
62
+ model.toggleCollapsed(id)
63
+ onClose()
64
+ }}
65
+ >
66
+ {isCollapsed ? `Expand ${noun}` : `Collapse ${noun}`}
67
+ </MenuItem>
68
+ {leaf ? null : (
69
+ <MenuItem
70
+ dense
71
+ onClick={() => {
72
+ model.setShowOnly(showOnly === id ? undefined : id)
73
+ onClose()
74
+ }}
75
+ >
76
+ {showOnly === id
77
+ ? 'Disable show only this node'
78
+ : 'Show only this node'}
79
+ </MenuItem>
80
+ )}
81
+ {leaf || isCollapsed ? null : (
82
+ <MenuItem
83
+ dense
84
+ onClick={() => {
85
+ model.toggleRotated(id)
86
+ onClose()
87
+ }}
88
+ >
89
+ Rotate this node
90
+ </MenuItem>
91
+ )}
92
+ {isRoot ? null : (
93
+ <MenuItem
94
+ dense
95
+ onClick={() => {
96
+ model.rerootAt(id)
97
+ onClose()
98
+ }}
99
+ >
100
+ Reroot here
101
+ </MenuItem>
102
+ )}
103
+ {leaf ? (
104
+ <MenuItem
105
+ dense
106
+ onClick={() => {
107
+ model.drawRelativeTo(name)
108
+ onClose()
109
+ }}
110
+ >
111
+ Indicate differences from this row
112
+ </MenuItem>
113
+ ) : null}
114
+ {leaf && relativeTo ? (
115
+ <MenuItem
116
+ dense
117
+ onClick={() => {
118
+ model.drawRelativeTo(undefined)
119
+ onClose()
120
+ }}
121
+ >
122
+ Clear reference row
123
+ </MenuItem>
124
+ ) : null}
125
+ </Menu>
126
+ )
127
+ })
128
+
129
+ export default TreeMenu
@@ -4,6 +4,7 @@ import { useTheme } from '@mui/material'
4
4
  import { observer } from 'mobx-react'
5
5
 
6
6
  import { treeScaleBarHeight as h } from '../../constants.ts'
7
+ import ScaleBarSVG from './ScaleBarSVG.tsx'
7
8
 
8
9
  import type { MsaViewModel } from '../../model.ts'
9
10
 
@@ -18,19 +19,12 @@ const TreeRuler = observer(({ model }: { model: MsaViewModel }) => {
18
19
  return (
19
20
  <div style={{ flexShrink: 0, width: treeAreaWidth }}>
20
21
  {bar ? (
21
- <svg
22
- width={treeAreaWidth}
23
- height={h}
24
- style={{ display: 'block' }}
25
- color={theme.palette.text.primary}
26
- >
27
- <text x={marginLeft} y={h - 9} fontSize={10} fill="currentColor">
28
- {bar.label}
29
- </text>
30
- <path
31
- d={`M${marginLeft} ${h - 7} v6 h${bar.px} v-6`}
32
- fill="none"
33
- stroke="currentColor"
22
+ <svg width={treeAreaWidth} height={h} style={{ display: 'block' }}>
23
+ <ScaleBarSVG
24
+ bar={bar}
25
+ marginLeft={marginLeft}
26
+ y={h}
27
+ color={theme.palette.text.primary}
34
28
  />
35
29
  </svg>
36
30
  ) : null}
@@ -1,11 +1,10 @@
1
1
  import React from 'react'
2
2
 
3
- import Attributes from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/Attributes'
4
- import BaseCard from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/BaseCard'
5
3
  import { Dialog } from '@jbrowse/core/ui'
6
- import { DialogContent } from '@mui/material'
4
+ import { DialogContent, Typography } from '@mui/material'
7
5
  import { observer } from 'mobx-react'
8
6
 
7
+ import MetadataTable from '../../MetadataTable.tsx'
9
8
  import SequenceTextArea from '../../SequenceTextArea.tsx'
10
9
 
11
10
  import type { MsaViewModel } from '../../../model.ts'
@@ -34,20 +33,19 @@ const TreeNodeInfoDialog = observer(function ({
34
33
  maxWidth="xl"
35
34
  >
36
35
  <DialogContent>
37
- <BaseCard title="Attributes">
38
- <Attributes attributes={{ nodeName, ...info }} />
39
- </BaseCard>
40
- <BaseCard title="Sequence">
41
- {res ? (
42
- <SequenceTextArea str={[res]} />
43
- ) : (
44
- <div>Sequence not found</div>
45
- )}
46
- </BaseCard>
36
+ <Typography variant="h6">Attributes</Typography>
37
+ <MetadataTable record={{ nodeName, ...info }} />
38
+ <Typography variant="h6">Sequence</Typography>
39
+ {res ? (
40
+ <SequenceTextArea str={[res]} />
41
+ ) : (
42
+ <Typography>Sequence not found</Typography>
43
+ )}
47
44
  {metadata ? (
48
- <BaseCard title="Extra metadata">
49
- <Attributes attributes={metadata} />
50
- </BaseCard>
45
+ <>
46
+ <Typography variant="h6">Extra metadata</Typography>
47
+ <MetadataTable record={metadata} />
48
+ </>
51
49
  ) : null}
52
50
  </DialogContent>
53
51
  </Dialog>
@@ -6,6 +6,7 @@ import {
6
6
  highlightLabelHeight,
7
7
  highlightRowFill,
8
8
  } from '../msa/renderHighlights.ts'
9
+ import { referenceColor } from '../overlayColors.ts'
9
10
  import { bracketBarWidth, bracketGap } from './cladeBrackets.ts'
10
11
 
11
12
  import type { HierarchyNode } from '../../hierarchy.ts'
@@ -64,6 +65,13 @@ function forEachNodeInBlock(
64
65
  }
65
66
  }
66
67
 
68
+ // withId gives an unnamed node its path, node-0-1-1, as its name
69
+ function cladeName(node: HierarchyNode, unnamed: string) {
70
+ const { id, name } = node.data
71
+ const label = name === id ? unnamed : name
72
+ return node.value === undefined ? label : `${label} (${node.value} tips)`
73
+ }
74
+
67
75
  // Calculate node x-coordinate for both phylogram (with branch lengths) and
68
76
  // cladogram (topology only) modes. tipX is the pixel x the tips sit at.
69
77
  // For cladograms: x = (maxDepthToLeaf - nodeDepthToLeaf) / maxDepthToLeaf * tipX
@@ -182,7 +190,7 @@ function renderCollapsedTriangles({
182
190
  const by = blockSizeYOverride ?? blockSize
183
191
  const halfHeight = Math.max(2, rowHeight * 0.42)
184
192
  forEachNodeInBlock(hierarchy, offsetY, by, blockPad(model), node => {
185
- const { id, name } = node.data
193
+ const { id } = node.data
186
194
  if (collapsedSet.has(id)) {
187
195
  const apexX = getNodeX(node, showBranchLen, tipX, maxDepthToLeaf)
188
196
  const y = node.x!
@@ -212,7 +220,6 @@ function renderCollapsedTriangles({
212
220
  // the whole triangle is a click/hover target that opens the
213
221
  // branch menu (Expand this node); the apex bubble's own click entry is
214
222
  // skipped for collapsed nodes so this descriptive label wins
215
- const label = name === id ? 'Collapsed clade' : name
216
223
  clickMap?.insert({
217
224
  minX: apexX + ml,
218
225
  maxX: baseX + ml,
@@ -220,7 +227,7 @@ function renderCollapsedTriangles({
220
227
  maxY: y + halfHeight,
221
228
  branch: true,
222
229
  id,
223
- name: count === undefined ? label : `${label} (${count} tips)`,
230
+ name: cladeName(node, 'Collapsed clade'),
224
231
  })
225
232
  }
226
233
  }
@@ -266,7 +273,7 @@ function renderNodeBubbles({
266
273
  }
267
274
  const { data } = node
268
275
  const y = node.x!
269
- const { id, name } = data
276
+ const { id } = data
270
277
  if (node.height >= 1 && inYBlock(y, offsetY, by, blockPad(model))) {
271
278
  const isCollapsed = collapsedSet.has(id)
272
279
  if (draw) {
@@ -290,7 +297,7 @@ function renderNodeBubbles({
290
297
  maxY: y - radius + d,
291
298
  branch: true,
292
299
  id,
293
- name,
300
+ name: cladeName(node, 'Clade'),
294
301
  })
295
302
  }
296
303
  }
@@ -572,9 +579,9 @@ function renderCladeBrackets({
572
579
  ctx.strokeStyle = theme.palette.text.primary
573
580
  }
574
581
 
575
- // the `rowTint` encoding and the row sets of `highlights`, washed across the
576
- // tree area under the labels, with each set's label in the gutter at its first
577
- // row
582
+ // the `rowTint` encoding, the reference row and the row sets of `highlights`,
583
+ // washed across the tree area under the labels, with each set's label in the
584
+ // gutter at its first row
578
585
  function renderRowHighlights({
579
586
  ctx,
580
587
  model,
@@ -591,6 +598,7 @@ function renderRowHighlights({
591
598
  const {
592
599
  resolvedHighlights,
593
600
  rowTints,
601
+ referenceRowIndex,
594
602
  rowHeight,
595
603
  treeAreaWidth,
596
604
  marginLeft,
@@ -616,6 +624,10 @@ function renderRowHighlights({
616
624
  }
617
625
  }
618
626
  }
627
+ if (referenceRowIndex !== undefined) {
628
+ ctx.fillStyle = referenceColor
629
+ fillRow(referenceRowIndex)
630
+ }
619
631
  for (const { rowIndices, label, color } of resolvedHighlights) {
620
632
  ctx.fillStyle = color ?? highlightRowFill
621
633
  for (const index of rowIndices) {
@@ -0,0 +1,41 @@
1
+ import { treeHoverColor } from '../overlayColors.ts'
2
+
3
+ import type { MsaViewModel } from '../../model.ts'
4
+
5
+ export function renderTreeMouseover({
6
+ ctx,
7
+ model,
8
+ }: {
9
+ ctx: CanvasRenderingContext2D
10
+ model: MsaViewModel
11
+ }) {
12
+ const {
13
+ rowHeight,
14
+ treeAreaWidth: width,
15
+ height,
16
+ scrollY,
17
+ mouseRow,
18
+ referenceRowIndex,
19
+ hoveredRowIndices,
20
+ highResScaleFactor,
21
+ } = model
22
+ ctx.resetTransform()
23
+ ctx.clearRect(0, 0, width * highResScaleFactor, height * highResScaleFactor)
24
+ ctx.scale(highResScaleFactor, highResScaleFactor)
25
+
26
+ const rowBand = (index: number) => {
27
+ ctx.fillRect(0, index * rowHeight + scrollY, width, rowHeight)
28
+ }
29
+
30
+ ctx.fillStyle = treeHoverColor
31
+ for (const index of hoveredRowIndices) {
32
+ rowBand(index)
33
+ }
34
+ if (
35
+ mouseRow !== undefined &&
36
+ mouseRow !== referenceRowIndex &&
37
+ !hoveredRowIndices.includes(mouseRow)
38
+ ) {
39
+ rowBand(mouseRow)
40
+ }
41
+ }